5#include <QCoreApplication>
6#include <QRegularExpression>
15#include "libmythbase/mythversion.h"
34#define LOC QString("ThemeChooser: ")
35#define LOC_WARN QString("ThemeChooser, Warning: ")
36#define LOC_ERR QString("ThemeChooser, Error: ")
47 QString srcFile, QString destDir) :
m_parent(parent),
56 QCoreApplication::postEvent(
m_parent, me);
85 return s1.fileName().toLower() < s2.fileName().toLower();
115 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
135 QStringList themesSeen;
137 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
138 themes.setSorting(QDir::Name | QDir::IgnoreCase);
142 for (
const auto &theme : std::as_const(
m_infoList))
146 themesSeen << theme.fileName();
152 QFileInfoList sharedThemes = themes.entryInfoList();
153 for (
const auto &sharedTheme : std::as_const(sharedThemes))
155 if ((!themesSeen.contains(sharedTheme.fileName())) &&
159 themesSeen << sharedTheme.fileName();
166 bool devel {
false };
169 if (!parsed || devel)
172 LOG(VB_GENERAL, LOG_ERR,
173 QString(
"Invalid MythTV version %1, will use themes from devel")
175 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for devel"));
180 QString MythVersion { QString::number(major) };
181 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
184 for (
int idx =
minor ; idx > 0; idx--)
187 subversion = MythVersion +
"." + QString::number(idx);
188 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(subversion));
199 LOG(VB_GENERAL, LOG_INFO, QString(
"Failed to load themes for %1, trying devel").arg(MythVersion));
202 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to load themes for devel"));
209 QStringList &themesSeen,
bool alert_user)
212 remoteThemesFile.append(
"/tmp/themes.zip");
213 QString themeSite = QString(
"%1/%2")
215 "http://themes.mythtv.org/themes/repository"),
217 QString destdir =
GetCacheDir().append(
"/themechooser");
218 QString versiondir = QString(
"%1/%2").arg(destdir,
version);
219 QDir remoteThemesDir(versiondir);
221 int downloadFailures =
225 QFileInfo finfo(remoteThemesFile);
226 if (finfo.lastModified().toUTC() <
229 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 is over 10 minutes old, forcing "
230 "remote theme list download")
231 .arg(remoteThemesFile));
235 if (!remoteThemesDir.exists())
240 else if (downloadFailures < 2)
242 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 does not exist, forcing remote theme "
244 .arg(remoteThemesFile));
250 QFile test(remoteThemesFile);
251 if (test.open(QIODevice::WriteOnly))
255 ShowOkPopup(tr(
"Unable to create '%1'").arg(remoteThemesFile));
261 QString url = themeSite;
262 url.append(
"/themes.zip");
264 ShowOkPopup(tr(
"Unable to remove '%1'").arg(versiondir));
266 if (!dir.mkpath(destdir))
267 ShowOkPopup(tr(
"Unable to create '%1'").arg(destdir));
270 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Downloading '%1' to '%2'").arg(url, remoteThemesFile));
274 if (!result || !
extractZIP(remoteThemesFile, destdir))
276 QFile::remove(remoteThemesFile);
284 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to download '%1'").arg(url));
286 ShowOkPopup(tr(
"Failed to download '%1'").arg(url));
290 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to unzip '%1' to '%2'").arg(remoteThemesFile, destdir));
293 .arg(remoteThemesFile, destdir));
297 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Unzipped '%1' to '%2'").arg(remoteThemesFile, destdir));
301 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
302 themes.setSorting(QDir::Name | QDir::IgnoreCase);
305 (remoteThemesDir.exists()))
309 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 and %2 exist, using cached remote themes list").arg(remoteThemesFile, remoteThemesDir.absolutePath()));
311 QString themesPath = remoteThemesDir.absolutePath();
312 themes.setPath(themesPath);
314 QFileInfoList downloadableThemes = themes.entryInfoList();
315 for (
const auto &dtheme : std::as_const(downloadableThemes))
317 QString dirName = dtheme.fileName();
319 QString remoteDir = themeSite;
320 remoteDir.append(
"/").append(dirName);
321 QString localDir = themes.absolutePath();
322 localDir.append(
"/").append(dirName);
324 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
326 if (themesSeen.contains(dirName))
337 if ((rmtMaj > locMaj) ||
338 ((rmtMaj == locMaj) &&
343 LOG(VB_GUI, LOG_DEBUG,
LOC + QString(
"'%1' old version %2.%3, new version %4.%5").arg(
themeName).arg(locMaj).arg(locMin).arg(rmtMaj).arg(rmtMin));
350 remoteDir.append(
"/").append(finfo.fileName()),
351 localDir.append(
"/").append(finfo.fileName()),
355 else if ((rmtMaj == locMaj) &&
358 LOG(VB_GUI, LOG_DEBUG,
LOC + QString(
"'%1' up to date (%2.%3)").arg(
themeName).arg(locMaj).arg(locMin));
371 themesSeen << dirName;
377 remoteDir.append(
"/").append(finfo.fileName()),
378 localDir.append(
"/").append(finfo.fileName()),
396 for (
const auto &theme : std::as_const(
m_infoList))
405 QString buttonText = QString(
"%1 %2.%3")
423 themeinfo->
ToMap(infomap);
425 item->
SetData(QVariant::fromValue(themeinfo));
430 thumbnail = thumbnail.append(
".thumb.jpg");
434 curThemeInfo = themeinfo;
448 QFile test(testFile);
449 if (test.open(QIODevice::WriteOnly))
453 ShowOkPopup(tr(
"Error creating test file, %1 themes directory is "
461 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
466 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
468 themeinfo =
new ThemeInfo(theme.filePath());
491 QString label = tr(
"Theme Chooser Menu");
494 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
555 [[maybe_unused]]
int result)
568 for (
int i = 0; i < actions.size() && !handled; ++i)
570 const QString&
action = actions[i];
575 else if (
action ==
"DELETE")
577 else if ((
action ==
"ESCAPE") &&
641 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
661 if (!
info->GetDownloadURL().isEmpty())
664 QFile test(testFile);
665 if (test.open(QIODevice::WriteOnly))
669 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
676 LOG(VB_FILE, LOG_INFO, QString(
"Download url is %1").arg(
downloadURL));
678 QString baseName = qfile.fileName();
684 .split(
";", Qt::SkipEmptyParts);
687 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"Theme download URL overridden from %1 to %2.").arg(origURL,
downloadURL));
697 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
717 QFileInfo preview(
info->GetPreviewPath());
719 info->ToMap(infomap);
723 if (preview.exists())
737 if (preview.exists())
756 if (
info->GetDownloadURL().isEmpty())
777 progressBar->
SetUsed(bytesReceived);
789 QStringList tokens = me->
Message().split(
" ", Qt::SkipEmptyParts);
790 if (tokens.isEmpty())
793 if (tokens[0] ==
"DOWNLOAD_FILE")
795 QStringList
args = me->ExtraDataList();
797 (tokens.size() != 2) ||
802 if (tokens[1] ==
"UPDATE")
806 else if (tokens[1] ==
"FINISHED")
808 bool remoteFileIsLocal =
false;
809 int fileSize =
args[2].toInt();
810 int errorCode =
args[4].toInt();
820 LOG(VB_FILE, LOG_INFO, QString(
"Download done MBE %1 %2").arg(errorCode).arg(fileSize));
821 if ((errorCode == 0) &&
827 file.setFile(localFile);
831 remoteFileIsLocal =
true;
847 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
855 LOG(VB_FILE, LOG_INFO, QString(
"Download done MFE %1 %2").arg(errorCode).arg(fileSize));
857 if ((errorCode == 0) &&
861 auto *extractThread =
865 extractThread,
"ThemeExtract");
867 if (!remoteFileIsLocal)
876 ShowOkPopup(tr(
"ERROR downloading theme package from frontend."));
881 else if ((me->Message() ==
"THEME_INSTALLED") &&
887 QStringList
args = me->ExtraDataList();
889 if (!
args.isEmpty() && !
args[0].isEmpty())
890 QFile::remove(
args[0]);
892 QString
event = QString(
"THEME_INSTALLED PATH %1")
900 auto *me2 =
new MythEvent(
"THEME_RELOAD");
901 qApp->postEvent(
this, me2);
903 else if ((me->Message() ==
"THEME_RELOAD") &&
923 ShowOkPopup(tr(
"Error, unable to find current theme."));
929 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
931 .arg(
info->GetName()));
943 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
951 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
952 QFileInfoList list = dir.entryInfoList();
954 for (
const auto &fi : std::as_const(list))
956 if (fi.isFile() && !fi.isSymLink())
958 if (!QFile::remove(fi.absoluteFilePath()))
961 else if (fi.isDir() && !fi.isSymLink())
968 return dir.rmdir(dirname);
977 bool devel {
false };
980 if (!parsed || devel)
986 for (
int i =
minor ; i > 0; i--)
993 "remotethemes/themes.zip",
1000 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1002 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1007 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1027 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1034 QStringList::iterator Iversion;
1040 QString remoteThemeDir =
1043 QString(
"remotethemes/%1/%2")
1048 QString infoXML = remoteThemeDir;
1049 infoXML.append(
"/themeinfo.xml");
1051 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'").arg(infoXML));
1058 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1059 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1061 LOG(VB_GENERAL, LOG_ERR,
1062 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1063 "Unable to create ThemeInfo for %1")
1066 remoteTheme =
nullptr;
1075 LOG(VB_GENERAL, LOG_ERR,
1076 "ThemeUpdateChecker::checkForUpdate(): "
1077 "Unable to create ThemeInfo for current theme");
1079 localTheme =
nullptr;
1086 int rmtMaj = remoteTheme->GetMajorVersion();
1087 int rmtMin = remoteTheme->GetMinorVersion();
1090 remoteTheme =
nullptr;
1092 if ((rmtMaj > locMaj) ||
1093 ((rmtMaj == locMaj) &&
1097 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName()).arg(rmtMaj).arg(rmtMin);
1103 (currentLocation ==
"mainmenu"))
1108 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1113 QString message = tr(
"Version %1 of the %2 theme is now "
1114 "available in the Theme Chooser. "
1115 "The currently installed version "
static MThreadPool * globalInstance(void)
void start(QRunnable *runnable, const QString &debugName, int priority=0)
QString GetHostName(void)
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
void SendSystemEvent(const QString &msg)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
int GetNumSetting(const QString &key, int defaultval=0)
QString GetMasterHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
Basic menu dialog, message and a list of options.
void AddButton(const QString &title)
void SetReturnEvent(QObject *retobject, const QString &resultid)
void Closed(QString, int)
bool Create(void) override
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
This class is used as a container for messages.
const QString & Message() const
static const Type kMythEventMessage
void JumpTo(const QString &Destination, bool Pop=true)
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void LoadInBackground(const QString &message="")
void OpenBusyPopup(const QString &message="")
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
void SetBusyPopupMessage(const QString &message)
void CloseBusyPopup(void)
void ReloadInBackground(void)
void ResetBusyPopup(void)
virtual void SetTextFromMap(const InfoMap &infoMap)
Create a group of widgets.
Image widget, displays a single image or multiple images in sequence.
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
void Reset(void) override
Reset the image back to the default defined in the theme.
QString GetCurrentLocation(bool FullPath=false, bool MainStackOnly=true)
This widget is used for grouping other widgets for display when a particular named state is called.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
bool DisplayState(const QString &name)
All purpose text widget, displays a text string.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
virtual void SetText(const QString &text)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
static bool DeleteFile(const QString &url)
static bool Exists(const QString &url, struct stat *fileinfo)
QString GetFirstDir(bool appendSlash=false) const
View and select installed themes.
void refreshDownloadableThemes(void)
ThemeInfo * loadThemeInfo(const QFileInfo &theme)
bool removeThemeDir(const QString &dirname)
MythUIImage * m_fullScreenPreview
bool m_fullPreviewShowing
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void itemChanged(MythUIButtonListItem *item)
ThemeInfo * m_downloadTheme
void updateProgressBar(int bytesReceived, int bytesTotal)
void toggleFullscreenPreview(void)
void popupClosed(const QString &which, int result)
MythUIText * m_fullScreenName
MythUIButtonList * m_themes
QMap< QString, QString > m_themeStatuses
void Init(void) override
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
QMap< QString, ThemeInfo * > m_themeNameInfos
bool m_refreshDownloadableThemes
bool Create(void) override
DownloadState m_downloadState
void Load(void) override
Load data which will ultimately be displayed on-screen or used to determine what appears on-screen (S...
bool LoadVersion(const QString &version, QStringList &themesSeen, bool alert_user)
static void toggleThemeUpdateNotifications(void)
QMap< QString, ThemeInfo * > m_themeFileNameInfos
@ dsDownloadingOnFrontend
void customEvent(QEvent *e) override
MythDialogBox * m_popupMenu
ThemeChooser(MythScreenStack *parent, const QString &name="ThemeChooser")
Creates a new ThemeChooser Screen.
MythUIStateType * m_fullPreviewStateType
QString GetPreviewPath() const
QString GetDownloadURL() const
QString GetAspect() const
int GetMinorVersion() const
void ToMap(InfoMap &infoMap) const
int GetMajorVersion() const
QString GetDirectoryName() const
QString m_lastKnownThemeVersion
~ThemeUpdateChecker(void) override
QStringList m_mythVersions
void checkForUpdate(void)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
QString GetThemesParentDir(void)
QString GetCacheDir(void)
Returns the base directory for all cached files.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythMainWindow * GetMythMainWindow(void)
QHash< QString, QString > InfoMap
MythUIHelper * GetMythUI()
const char * GetMythSourceVersion()
bool ParseMythSourceVersion(bool &devel, uint &major, uint &minor, const char *version)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
QString RemoteDownloadFile(const QString &url, const QString &storageGroup, const QString &filename)
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
static const QRegularExpression kVersionDateRE
bool extractZIP(QString zipFile, const QString &outDir)