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 : qAsConst(
m_infoList))
152 themesSeen << theme.fileName();
158 QFileInfoList sharedThemes = themes.entryInfoList();
159 for (
const auto &sharedTheme : qAsConst(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 =
253 if (QFile::exists(remoteThemesFile))
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);
334 if ((QFile::exists(remoteThemesFile)) &&
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 : qAsConst(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 : qAsConst(
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 QString
action = actions[i];
605 else if (
action ==
"DELETE")
607 else if ((
action ==
"ESCAPE") &&
669 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
689 if (!info->GetDownloadURL().isEmpty())
692 QFile test(testFile);
693 if (test.open(QIODevice::WriteOnly))
697 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
704 LOG(VB_FILE, LOG_INFO, QString(
"Download url is %1").arg(
downloadURL));
706 QString baseName = qfile.fileName();
710 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
713 .split(
";", QString::SkipEmptyParts);
717 .split(
";", Qt::SkipEmptyParts);
721 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"Theme download URL overridden from %1 to %2.").arg(origURL,
downloadURL));
724 OpenBusyPopup(tr(
"Downloading %1 Theme").arg(info->GetName()));
731 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
751 QFileInfo preview(info->GetPreviewPath());
753 info->ToMap(infomap);
757 if (preview.exists())
769 if (preview.exists())
786 if (info->GetDownloadURL().isEmpty())
807 progressBar->
SetUsed(bytesReceived);
819 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
820 QStringList tokens = me->
Message().split(
" ", QString::SkipEmptyParts);
822 QStringList tokens = me->Message().split(
" ", Qt::SkipEmptyParts);
825 if (tokens.isEmpty())
828 if (tokens[0] ==
"DOWNLOAD_FILE")
830 QStringList
args = me->ExtraDataList();
832 (tokens.size() != 2) ||
837 if (tokens[1] ==
"UPDATE")
841 else if (tokens[1] ==
"FINISHED")
843 bool remoteFileIsLocal =
false;
844 int fileSize =
args[2].toInt();
845 int errorCode =
args[4].toInt();
855 LOG(VB_FILE, LOG_INFO, QString(
"Download done MBE %1 %2").arg(errorCode).arg(fileSize));
856 if ((errorCode == 0) &&
862 file.setFile(localFile);
866 remoteFileIsLocal =
true;
882 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
890 LOG(VB_FILE, LOG_INFO, QString(
"Download done MFE %1 %2").arg(errorCode).arg(fileSize));
892 if ((errorCode == 0) &&
896 auto *extractThread =
900 extractThread,
"ThemeExtract");
902 if (!remoteFileIsLocal)
911 ShowOkPopup(tr(
"ERROR downloading theme package from frontend."));
916 else if ((me->Message() ==
"THEME_INSTALLED") &&
922 QStringList
args = me->ExtraDataList();
924 if (!
args.isEmpty() && !
args[0].isEmpty())
925 QFile::remove(
args[0]);
927 QString
event = QString(
"THEME_INSTALLED PATH %1")
935 auto *me2 =
new MythEvent(
"THEME_RELOAD");
936 qApp->postEvent(
this, me2);
938 else if ((me->Message() ==
"THEME_RELOAD") &&
958 ShowOkPopup(tr(
"Error, unable to find current theme."));
964 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
966 .arg(info->GetName()));
978 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
986 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
987 QFileInfoList list = dir.entryInfoList();
989 for (
const auto &fi : qAsConst(list))
991 if (fi.isFile() && !fi.isSymLink())
993 if (!QFile::remove(fi.absoluteFilePath()))
996 else if (fi.isDir() && !fi.isSymLink())
1003 return dir.rmdir(dirname);
1019 version = MYTH_BINARY_VERSION;
1023 static const QRegularExpression subexp{
"v[0-9]+\\.([0-9]+)-*", QRegularExpression::CaseInsensitiveOption};
1025 if (match.hasMatch())
1027 for (
int idx = match.capturedView(1).toInt(); idx > 0; --idx)
1035 "remotethemes/themes.zip",
1042 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1044 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1049 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1069 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1076 QStringList::iterator Iversion;
1082 QString remoteThemeDir =
1085 QString(
"remotethemes/%1/%2")
1090 QString infoXML = remoteThemeDir;
1091 infoXML.append(
"/themeinfo.xml");
1093 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'").arg(infoXML));
1100 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1101 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1103 LOG(VB_GENERAL, LOG_ERR,
1104 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1105 "Unable to create ThemeInfo for %1")
1108 remoteTheme =
nullptr;
1117 LOG(VB_GENERAL, LOG_ERR,
1118 "ThemeUpdateChecker::checkForUpdate(): "
1119 "Unable to create ThemeInfo for current theme");
1121 localTheme =
nullptr;
1128 int rmtMaj = remoteTheme->GetMajorVersion();
1129 int rmtMin = remoteTheme->GetMinorVersion();
1132 remoteTheme =
nullptr;
1134 if ((rmtMaj > locMaj) ||
1135 ((rmtMaj == locMaj) &&
1139 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName()).arg(rmtMaj).arg(rmtMin);
1145 (currentLocation ==
"mainmenu"))
1150 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1155 QString message = tr(
"Version %1 of the %2 theme is now "
1156 "available in the Theme Chooser. "
1157 "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
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.
@ dsDownloadingOnFrontend
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)