Go to the documentation of this file.
5 #include <QCoreApplication>
6 #include <QRegularExpression>
15 #include "libmythbase/mythversion.h"
36 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
37 #define capturedView capturedRef
40 #define LOC QString("ThemeChooser: ")
41 #define LOC_WARN QString("ThemeChooser, Warning: ")
42 #define LOC_ERR QString("ThemeChooser, Error: ")
53 QString srcFile, QString destDir) :
m_parent(parent),
62 QCoreApplication::postEvent(
m_parent, me);
91 return s1.fileName().toLower() < s2.fileName().toLower();
121 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
141 QStringList themesSeen;
143 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
144 themes.setSorting(QDir::Name | QDir::IgnoreCase);
148 for (
const auto &theme : std::as_const(
m_infoList))
152 themesSeen << theme.fileName();
158 QFileInfoList sharedThemes = themes.entryInfoList();
159 for (
const auto &sharedTheme : std::as_const(sharedThemes))
161 if ((!themesSeen.contains(sharedTheme.fileName())) &&
165 themesSeen << sharedTheme.fileName();
172 static const QRegularExpression trunkver{
"\\Av[0-9]+-pre.*\\z", QRegularExpression::CaseInsensitiveOption};
173 static const QRegularExpression validver{
174 "\\Av[0-9]+.*\\z", QRegularExpression::CaseInsensitiveOption};
176 auto match = validver.match(MythVersion);
177 if (!match.hasMatch())
179 LOG(VB_GENERAL, LOG_ERR, QString(
"Invalid MythTV version %1, will use themes from trunk").arg(MythVersion));
180 MythVersion =
"trunk";
182 match = trunkver.match(MythVersion);
183 if (match.hasMatch())
184 MythVersion =
"trunk";
186 if (MythVersion ==
"trunk")
189 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
193 MythVersion = MYTH_BINARY_VERSION;
196 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
202 static const QRegularExpression subexp{
"v[0-9]+\\.([0-9]+)-*", QRegularExpression::CaseInsensitiveOption};
205 if (match.hasMatch())
208 LOG(VB_GUI, LOG_INFO, QString(
"Loading version %1").arg(match.capturedView(1).toInt()));
209 for (
int idx = match.capturedView(1).toInt(); idx > 0; --idx)
211 subversion = MythVersion +
"." + QString::number(idx);
212 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(subversion));
228 LOG(VB_GENERAL, LOG_INFO, QString(
"Failed to load themes for %1, trying trunk").arg(MythVersion));
229 MythVersion =
"trunk";
232 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to load themes for %1").arg(MythVersion));
239 QStringList &themesSeen,
bool alert_user)
242 remoteThemesFile.append(
"/tmp/themes.zip");
243 QString themeSite = QString(
"%1/%2")
245 "http://themes.mythtv.org/themes/repository"),
247 QString destdir =
GetCacheDir().append(
"/themechooser");
248 QString versiondir = QString(
"%1/%2").arg(destdir,
version);
249 QDir remoteThemesDir(versiondir);
251 int downloadFailures =
255 QFileInfo finfo(remoteThemesFile);
256 if (finfo.lastModified().toUTC() <
259 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 is over 10 minutes old, forcing "
260 "remote theme list download")
261 .arg(remoteThemesFile));
265 if (!remoteThemesDir.exists())
270 else if (downloadFailures < 2)
272 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 does not exist, forcing remote theme "
274 .arg(remoteThemesFile));
280 QFile test(remoteThemesFile);
281 if (test.open(QIODevice::WriteOnly))
285 ShowOkPopup(tr(
"Unable to create '%1'").arg(remoteThemesFile));
291 QString url = themeSite;
292 url.append(
"/themes.zip");
294 ShowOkPopup(tr(
"Unable to remove '%1'").arg(versiondir));
296 if (!dir.mkpath(destdir))
297 ShowOkPopup(tr(
"Unable to create '%1'").arg(destdir));
300 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Downloading '%1' to '%2'").arg(url, remoteThemesFile));
304 if (!result || !
extractZIP(remoteThemesFile, destdir))
306 QFile::remove(remoteThemesFile);
314 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to download '%1'").arg(url));
316 ShowOkPopup(tr(
"Failed to download '%1'").arg(url));
320 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to unzip '%1' to '%2'").arg(remoteThemesFile, destdir));
323 .arg(remoteThemesFile, destdir));
327 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Unzipped '%1' to '%2'").arg(remoteThemesFile, destdir));
331 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
332 themes.setSorting(QDir::Name | QDir::IgnoreCase);
335 (remoteThemesDir.exists()))
339 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 and %2 exist, using cached remote themes list").arg(remoteThemesFile, remoteThemesDir.absolutePath()));
341 QString themesPath = remoteThemesDir.absolutePath();
342 themes.setPath(themesPath);
344 QFileInfoList downloadableThemes = themes.entryInfoList();
345 for (
const auto &dtheme : std::as_const(downloadableThemes))
347 QString dirName = dtheme.fileName();
349 QString remoteDir = themeSite;
350 remoteDir.append(
"/").append(dirName);
351 QString localDir = themes.absolutePath();
352 localDir.append(
"/").append(dirName);
354 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
356 if (themesSeen.contains(dirName))
367 if ((rmtMaj > locMaj) ||
368 ((rmtMaj == locMaj) &&
373 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));
380 remoteDir.append(
"/").append(finfo.fileName()),
381 localDir.append(
"/").append(finfo.fileName()),
385 else if ((rmtMaj == locMaj) &&
388 LOG(VB_GUI, LOG_DEBUG,
LOC + QString(
"'%1' up to date (%2.%3)").arg(
themeName).arg(locMaj).arg(locMin));
401 themesSeen << dirName;
407 remoteDir.append(
"/").append(finfo.fileName()),
408 localDir.append(
"/").append(finfo.fileName()),
426 for (
const auto &theme : std::as_const(
m_infoList))
435 QString buttonText = QString(
"%1 %2.%3")
453 themeinfo->
ToMap(infomap);
455 item->
SetData(QVariant::fromValue(themeinfo));
460 thumbnail = thumbnail.append(
".thumb.jpg");
464 curThemeInfo = themeinfo;
478 QFile test(testFile);
479 if (test.open(QIODevice::WriteOnly))
483 ShowOkPopup(tr(
"Error creating test file, %1 themes directory is "
491 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
496 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
498 themeinfo =
new ThemeInfo(theme.filePath());
521 QString label = tr(
"Theme Chooser Menu");
524 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
585 [[maybe_unused]]
int result)
598 for (
int i = 0; i < actions.size() && !handled; ++i)
600 const QString&
action = actions[i];
605 else if (
action ==
"DELETE")
607 else if ((
action ==
"ESCAPE") &&
671 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
691 if (!
info->GetDownloadURL().isEmpty())
694 QFile test(testFile);
695 if (test.open(QIODevice::WriteOnly))
699 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
706 LOG(VB_FILE, LOG_INFO, QString(
"Download url is %1").arg(
downloadURL));
708 QString baseName = qfile.fileName();
712 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
715 .split(
";", QString::SkipEmptyParts);
719 .split(
";", Qt::SkipEmptyParts);
723 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"Theme download URL overridden from %1 to %2.").arg(origURL,
downloadURL));
733 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
753 QFileInfo preview(
info->GetPreviewPath());
755 info->ToMap(infomap);
759 if (preview.exists())
773 if (preview.exists())
792 if (
info->GetDownloadURL().isEmpty())
813 progressBar->
SetUsed(bytesReceived);
825 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
826 QStringList tokens = me->
Message().split(
" ", QString::SkipEmptyParts);
828 QStringList tokens = me->Message().split(
" ", Qt::SkipEmptyParts);
831 if (tokens.isEmpty())
834 if (tokens[0] ==
"DOWNLOAD_FILE")
836 QStringList
args = me->ExtraDataList();
838 (tokens.size() != 2) ||
843 if (tokens[1] ==
"UPDATE")
847 else if (tokens[1] ==
"FINISHED")
849 bool remoteFileIsLocal =
false;
850 int fileSize =
args[2].toInt();
851 int errorCode =
args[4].toInt();
861 LOG(VB_FILE, LOG_INFO, QString(
"Download done MBE %1 %2").arg(errorCode).arg(fileSize));
862 if ((errorCode == 0) &&
868 file.setFile(localFile);
872 remoteFileIsLocal =
true;
888 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
896 LOG(VB_FILE, LOG_INFO, QString(
"Download done MFE %1 %2").arg(errorCode).arg(fileSize));
898 if ((errorCode == 0) &&
902 auto *extractThread =
906 extractThread,
"ThemeExtract");
908 if (!remoteFileIsLocal)
917 ShowOkPopup(tr(
"ERROR downloading theme package from frontend."));
922 else if ((me->Message() ==
"THEME_INSTALLED") &&
928 QStringList
args = me->ExtraDataList();
930 if (!
args.isEmpty() && !
args[0].isEmpty())
931 QFile::remove(
args[0]);
933 QString
event = QString(
"THEME_INSTALLED PATH %1")
941 auto *me2 =
new MythEvent(
"THEME_RELOAD");
942 qApp->postEvent(
this, me2);
944 else if ((me->Message() ==
"THEME_RELOAD") &&
964 ShowOkPopup(tr(
"Error, unable to find current theme."));
970 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
972 .arg(
info->GetName()));
984 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
992 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
993 QFileInfoList list = dir.entryInfoList();
995 for (
const auto &fi : std::as_const(list))
997 if (fi.isFile() && !fi.isSymLink())
999 if (!QFile::remove(fi.absoluteFilePath()))
1002 else if (fi.isDir() && !fi.isSymLink())
1009 return dir.rmdir(dirname);
1025 version = MYTH_BINARY_VERSION;
1029 static const QRegularExpression subexp{
"v[0-9]+\\.([0-9]+)-*", QRegularExpression::CaseInsensitiveOption};
1031 if (match.hasMatch())
1033 for (
int idx = match.capturedView(1).toInt(); idx > 0; --idx)
1041 "remotethemes/themes.zip",
1048 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1050 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1055 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1075 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1082 QStringList::iterator Iversion;
1088 QString remoteThemeDir =
1091 QString(
"remotethemes/%1/%2")
1096 QString infoXML = remoteThemeDir;
1097 infoXML.append(
"/themeinfo.xml");
1099 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'").arg(infoXML));
1106 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1107 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1109 LOG(VB_GENERAL, LOG_ERR,
1110 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1111 "Unable to create ThemeInfo for %1")
1114 remoteTheme =
nullptr;
1123 LOG(VB_GENERAL, LOG_ERR,
1124 "ThemeUpdateChecker::checkForUpdate(): "
1125 "Unable to create ThemeInfo for current theme");
1127 localTheme =
nullptr;
1134 int rmtMaj = remoteTheme->GetMajorVersion();
1135 int rmtMin = remoteTheme->GetMinorVersion();
1138 remoteTheme =
nullptr;
1140 if ((rmtMaj > locMaj) ||
1141 ((rmtMaj == locMaj) &&
1145 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName()).arg(rmtMaj).arg(rmtMin);
1151 (currentLocation ==
"mainmenu"))
1156 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1161 QString message = tr(
"Version %1 of the %2 theme is now "
1162 "available in the Theme Chooser. "
1163 "The currently installed version "
QString GetThemesParentDir(void)
void LoadInBackground(const QString &message="")
void SetReturnEvent(QObject *retobject, const QString &resultid)
bool extractZIP(QString zipFile, const QString &outDir)
void SetBusyPopupMessage(const QString &message)
QMap< QString, ThemeInfo * > m_themeNameInfos
QString GetMasterHostName(void)
QString m_lastKnownThemeVersion
View and select installed themes.
MythUIButtonList * m_themes
static const QRegularExpression kVersionDateRE
static const Type kMythEventMessage
Image widget, displays a single image or multiple images in sequence.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
QString GetAspect() const
static bool Exists(const QString &url, struct stat *fileinfo)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
void JumpTo(const QString &Destination, bool Pop=true)
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
This class is used as a container for messages.
void customEvent(QEvent *e) override
const char * GetMythSourceVersion()
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
int GetMinorVersion() const
MythDialogBox * m_popupMenu
@ dsDownloadingOnFrontend
void refreshDownloadableThemes(void)
bool m_refreshDownloadableThemes
void OpenBusyPopup(const QString &message="")
Create a group of widgets.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
void Closed(QString, int)
Screen in which all other widgets are contained and rendered.
QString GetCacheDir(void)
Returns the base directory for all cached files.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
void popupClosed(const QString &which, int result)
void Reset(void) override
Reset the image back to the default defined in the theme.
void updateProgressBar(int bytesReceived, int bytesTotal)
QString GetDownloadURL() const
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
const QString & Message() const
QStringList m_mythVersions
MythUIType * GetFocusWidget(void) const
QHash< QString, QString > InfoMap
void addListener(QObject *listener)
Add a listener to the observable.
ThemeChooser(MythScreenStack *parent, const QString &name="ThemeChooser")
Creates a new ThemeChooser Screen.
void ResetBusyPopup(void)
bool LoadVersion(const QString &version, QStringList &themesSeen, bool alert_user)
void ReloadInBackground(void)
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
bool Create(void) override
void SendSystemEvent(const QString &msg)
bool SetFocusWidget(MythUIType *widget=nullptr)
void AddButton(const QString &title)
Basic menu dialog, message and a list of options.
ThemeInfo * loadThemeInfo(const QFileInfo &theme)
bool Create(void) override
ThemeInfo * m_downloadTheme
void BuildFocusList(void)
virtual void SetTextFromMap(const InfoMap &infoMap)
void checkForUpdate(void)
void Load(void) override
Load data which will ultimately be displayed on-screen or used to determine what appears on-screen (S...
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
QString GetPreviewPath() const
QString GetFirstDir(bool appendSlash=false) const
static bool DeleteFile(const QString &url)
QString GetCurrentLocation(bool FullPath=false, bool MainStackOnly=true)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
int GetNumSetting(const QString &key, int defaultval=0)
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
const char * GetMythSourcePath()
bool GetBoolSetting(const QString &key, bool defaultval=false)
~ThemeUpdateChecker(void) override
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
void toggleFullscreenPreview(void)
All purpose text widget, displays a text string.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool m_fullPreviewShowing
DownloadState m_downloadState
QMap< QString, QString > m_themeStatuses
QMap< QString, ThemeInfo * > m_themeFileNameInfos
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
virtual void SetText(const QString &text)
MythUIText * m_fullScreenName
int GetMajorVersion() const
MythUIImage * m_fullScreenPreview
MythMainWindow * GetMythMainWindow(void)
static void toggleThemeUpdateNotifications(void)
MythUIStateType * m_fullPreviewStateType
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
MythScreenStack * GetStack(const QString &Stackname)
QString RemoteDownloadFile(const QString &url, const QString &storageGroup, const QString &filename)
QString GetHostName(void)
void SaveSetting(const QString &key, int newValue)
static bool downloadURL(const QString &url, QByteArray *buffer, QString &finalURL)
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
MythUIHelper * GetMythUI()
static MThreadPool * globalInstance(void)
void Init(void) override
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
QString GetDirectoryName() const
void ToMap(InfoMap &infoMap) const
void removeListener(QObject *listener)
Remove a listener to the observable.
static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
void CloseBusyPopup(void)
This widget is used for grouping other widgets for display when a particular named state is called....
void start(QRunnable *runnable, const QString &debugName, int priority=0)
bool DisplayState(const QString &name)
bool removeThemeDir(const QString &dirname)
QString GetSetting(const QString &key, const QString &defaultval="")
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
void itemChanged(MythUIButtonListItem *item)