5#include <QCoreApplication>
6#include <QRegularExpression>
15#include "libmythbase/mythversion.h"
36#define LOC QString("ThemeChooser: ")
37#define LOC_WARN QString("ThemeChooser, Warning: ")
38#define LOC_ERR QString("ThemeChooser, Error: ")
49 QString srcFile, QString destDir) :
m_parent(parent),
58 QCoreApplication::postEvent(
m_parent, me);
87 return s1.fileName().toLower() < s2.fileName().toLower();
117 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
137 QStringList themesSeen;
139 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
140 themes.setSorting(QDir::Name | QDir::IgnoreCase);
144 for (
const auto &theme : std::as_const(
m_infoList))
148 themesSeen << theme.fileName();
154 QFileInfoList sharedThemes = themes.entryInfoList();
155 for (
const auto &sharedTheme : std::as_const(sharedThemes))
157 if ((!themesSeen.contains(sharedTheme.fileName())) &&
161 themesSeen << sharedTheme.fileName();
168 bool devel {
false };
171 if (!parsed || devel)
174 LOG(VB_GENERAL, LOG_ERR,
175 QString(
"Invalid MythTV version %1, will use themes from devel")
177 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for devel"));
182 QString MythVersion { QString::number(major) };
183 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
186 for (
int idx =
minor ; idx > 0; idx--)
189 subversion = MythVersion +
"." + QString::number(idx);
190 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(subversion));
201 LOG(VB_GENERAL, LOG_INFO, QString(
"Failed to load themes for %1, trying devel").arg(MythVersion));
204 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to load themes for devel"));
211 QStringList &themesSeen,
bool alert_user)
214 remoteThemesFile.append(
"/tmp/themes.zip");
215 QString themeSite = QString(
"%1/%2")
217 "http://themes.mythtv.org/themes/repository"),
219 QString destdir =
GetCacheDir().append(
"/themechooser");
220 QString versiondir = QString(
"%1/%2").arg(destdir,
version);
221 QDir remoteThemesDir(versiondir);
223 int downloadFailures =
227 QFileInfo finfo(remoteThemesFile);
228 if (finfo.lastModified().toUTC() <
231 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 is over 10 minutes old, forcing "
232 "remote theme list download")
233 .arg(remoteThemesFile));
237 if (!remoteThemesDir.exists())
242 else if (downloadFailures < 2)
244 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 does not exist, forcing remote theme "
246 .arg(remoteThemesFile));
252 QFile test(remoteThemesFile);
253 if (test.open(QIODevice::WriteOnly))
257 ShowOkPopup(tr(
"Unable to create '%1'").arg(remoteThemesFile));
263 QString url = themeSite;
264 url.append(
"/themes.zip");
266 ShowOkPopup(tr(
"Unable to remove '%1'").arg(versiondir));
268 if (!dir.mkpath(destdir))
269 ShowOkPopup(tr(
"Unable to create '%1'").arg(destdir));
272 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Downloading '%1' to '%2'").arg(url, remoteThemesFile));
276 if (!result || !
extractZIP(remoteThemesFile, destdir))
278 QFile::remove(remoteThemesFile);
286 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to download '%1'").arg(url));
288 ShowOkPopup(tr(
"Failed to download '%1'").arg(url));
292 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to unzip '%1' to '%2'").arg(remoteThemesFile, destdir));
295 .arg(remoteThemesFile, destdir));
299 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Unzipped '%1' to '%2'").arg(remoteThemesFile, destdir));
303 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
304 themes.setSorting(QDir::Name | QDir::IgnoreCase);
307 (remoteThemesDir.exists()))
311 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 and %2 exist, using cached remote themes list").arg(remoteThemesFile, remoteThemesDir.absolutePath()));
313 QString themesPath = remoteThemesDir.absolutePath();
314 themes.setPath(themesPath);
316 QFileInfoList downloadableThemes = themes.entryInfoList();
317 for (
const auto &dtheme : std::as_const(downloadableThemes))
319 QString dirName = dtheme.fileName();
321 QString remoteDir = themeSite;
322 remoteDir.append(
"/").append(dirName);
323 QString localDir = themes.absolutePath();
324 localDir.append(
"/").append(dirName);
326 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
328 if (themesSeen.contains(dirName))
339 if ((rmtMaj > locMaj) ||
340 ((rmtMaj == locMaj) &&
345 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));
352 remoteDir.append(
"/").append(finfo.fileName()),
353 localDir.append(
"/").append(finfo.fileName()),
357 else if ((rmtMaj == locMaj) &&
360 LOG(VB_GUI, LOG_DEBUG,
LOC + QString(
"'%1' up to date (%2.%3)").arg(
themeName).arg(locMaj).arg(locMin));
373 themesSeen << dirName;
379 remoteDir.append(
"/").append(finfo.fileName()),
380 localDir.append(
"/").append(finfo.fileName()),
398 for (
const auto &theme : std::as_const(
m_infoList))
407 QString buttonText = QString(
"%1 %2.%3")
425 themeinfo->
ToMap(infomap);
427 item->
SetData(QVariant::fromValue(themeinfo));
432 thumbnail = thumbnail.append(
".thumb.jpg");
436 curThemeInfo = themeinfo;
450 QFile test(testFile);
451 if (test.open(QIODevice::WriteOnly))
455 ShowOkPopup(tr(
"Error creating test file, %1 themes directory is "
463 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
468 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
470 themeinfo =
new ThemeInfo(theme.filePath());
493 QString label = tr(
"Theme Chooser Menu");
496 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
557 [[maybe_unused]]
int result)
570 for (
int i = 0; i < actions.size() && !handled; ++i)
572 const QString&
action = actions[i];
577 else if (
action ==
"DELETE")
579 else if ((
action ==
"ESCAPE") &&
643 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
663 if (!
info->GetDownloadURL().isEmpty())
666 QFile test(testFile);
667 if (test.open(QIODevice::WriteOnly))
671 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
678 LOG(VB_FILE, LOG_INFO, QString(
"Download url is %1").arg(
downloadURL));
680 QString baseName = qfile.fileName();
686 .split(
";", Qt::SkipEmptyParts);
689 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"Theme download URL overridden from %1 to %2.").arg(origURL,
downloadURL));
699 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
719 QFileInfo preview(
info->GetPreviewPath());
721 info->ToMap(infomap);
725 if (preview.exists())
739 if (preview.exists())
758 if (
info->GetDownloadURL().isEmpty())
779 progressBar->
SetUsed(bytesReceived);
791 QStringList tokens = me->
Message().split(
" ", Qt::SkipEmptyParts);
792 if (tokens.isEmpty())
795 if (tokens[0] ==
"DOWNLOAD_FILE")
797 QStringList
args = me->ExtraDataList();
799 (tokens.size() != 2) ||
804 if (tokens[1] ==
"UPDATE")
808 else if (tokens[1] ==
"FINISHED")
810 bool remoteFileIsLocal =
false;
811 int fileSize =
args[2].toInt();
812 int errorCode =
args[4].toInt();
822 LOG(VB_FILE, LOG_INFO, QString(
"Download done MBE %1 %2").arg(errorCode).arg(fileSize));
823 if ((errorCode == 0) &&
829 file.setFile(localFile);
833 remoteFileIsLocal =
true;
849 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
857 LOG(VB_FILE, LOG_INFO, QString(
"Download done MFE %1 %2").arg(errorCode).arg(fileSize));
859 if ((errorCode == 0) &&
863 auto *extractThread =
867 extractThread,
"ThemeExtract");
869 if (!remoteFileIsLocal)
878 ShowOkPopup(tr(
"ERROR downloading theme package from frontend."));
883 else if ((me->Message() ==
"THEME_INSTALLED") &&
889 QStringList
args = me->ExtraDataList();
891 if (!
args.isEmpty() && !
args[0].isEmpty())
892 QFile::remove(
args[0]);
894 QString
event = QString(
"THEME_INSTALLED PATH %1")
902 auto *me2 =
new MythEvent(
"THEME_RELOAD");
903 qApp->postEvent(
this, me2);
905 else if ((me->Message() ==
"THEME_RELOAD") &&
925 ShowOkPopup(tr(
"Error, unable to find current theme."));
931 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
933 .arg(
info->GetName()));
945 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
953 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
954 QFileInfoList list = dir.entryInfoList();
956 for (
const auto &fi : std::as_const(list))
958 if (fi.isFile() && !fi.isSymLink())
960 if (!QFile::remove(fi.absoluteFilePath()))
963 else if (fi.isDir() && !fi.isSymLink())
970 return dir.rmdir(dirname);
979 bool devel {
false };
982 if (!parsed || devel)
988 for (
int i =
minor ; i > 0; i--)
995 "remotethemes/themes.zip",
1002 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1004 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1009 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1029 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1036 QStringList::iterator Iversion;
1042 QString remoteThemeDir =
1045 QString(
"remotethemes/%1/%2")
1050 QString infoXML = remoteThemeDir;
1051 infoXML.append(
"/themeinfo.xml");
1053 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'").arg(infoXML));
1060 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1061 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1063 LOG(VB_GENERAL, LOG_ERR,
1064 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1065 "Unable to create ThemeInfo for %1")
1068 remoteTheme =
nullptr;
1077 LOG(VB_GENERAL, LOG_ERR,
1078 "ThemeUpdateChecker::checkForUpdate(): "
1079 "Unable to create ThemeInfo for current theme");
1081 localTheme =
nullptr;
1088 int rmtMaj = remoteTheme->GetMajorVersion();
1089 int rmtMin = remoteTheme->GetMinorVersion();
1092 remoteTheme =
nullptr;
1094 if ((rmtMaj > locMaj) ||
1095 ((rmtMaj == locMaj) &&
1099 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName()).arg(rmtMaj).arg(rmtMin);
1105 (currentLocation ==
"mainmenu"))
1110 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1115 QString message = tr(
"Version %1 of the %2 theme is now "
1116 "available in the Theme Chooser. "
1117 "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)