Go to the documentation of this file.
9 #include <QCoreApplication>
10 #include <QRegularExpression>
38 #if QT_VERSION < QT_VERSION_CHECK(5,15,2)
39 #define capturedView capturedRef
42 #define LOC QString("ThemeChooser: ")
43 #define LOC_WARN QString("ThemeChooser, Warning: ")
44 #define LOC_ERR QString("ThemeChooser, Error: ")
53 QString srcFile, QString destDir) :
63 QCoreApplication::postEvent(
m_parent, me);
78 const QString &name) :
94 return s1.fileName().toLower() < s2.fileName().toLower();
126 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
146 QStringList themesSeen;
148 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
149 themes.setSorting(QDir::Name | QDir::IgnoreCase);
153 for (
const auto & theme : qAsConst(
m_infoList))
157 themesSeen << theme.fileName();
163 QFileInfoList sharedThemes = themes.entryInfoList();
164 for (
const auto & sharedTheme : qAsConst(sharedThemes))
166 if ((!themesSeen.contains(sharedTheme.fileName())) &&
170 themesSeen << sharedTheme.fileName();
177 static const QRegularExpression trunkver
178 {
"\\Av[0-9]+-pre.*\\z", QRegularExpression::CaseInsensitiveOption };
179 static const QRegularExpression validver {
180 "\\Av[0-9]+.*\\z", QRegularExpression::CaseInsensitiveOption };
182 auto match = validver.match(MythVersion);
183 if (!match.hasMatch())
185 LOG(VB_GENERAL, LOG_ERR, QString(
"Invalid MythTV version %1, will use themes from trunk").arg(MythVersion));
186 MythVersion =
"trunk";
188 match = trunkver.match(MythVersion);
189 if (match.hasMatch())
190 MythVersion =
"trunk";
192 if (MythVersion ==
"trunk")
195 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
202 MythVersion.remove(QRegularExpression(
"\\.[0-9]{8,}.*"));
203 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
208 static const QRegularExpression subexp
209 {
"v[0-9]+\\.([0-9]+)-*", QRegularExpression::CaseInsensitiveOption };
212 if (match.hasMatch())
215 for (
int idx = match.capturedView(1).toInt(); idx > 0; --idx)
217 subversion = MythVersion +
"." + QString::number(idx);
218 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(subversion));
230 QStringList &themesSeen,
bool alert_user)
233 remoteThemesFile.append(
"/tmp/themes.zip");
234 QString themeSite = QString(
"%1/%2")
236 "http://themes.mythtv.org/themes/repository")).arg(
version);
237 QString destdir =
GetCacheDir().append(
"/themechooser/");
238 QString versiondir = QString(
"%1/%2").arg(destdir).arg(
version);
239 QDir remoteThemesDir(versiondir);
241 int downloadFailures =
243 if (QFile::exists(remoteThemesFile))
245 QFileInfo finfo(remoteThemesFile);
246 if (finfo.lastModified().toUTC() <
249 LOG(VB_GUI, LOG_INFO,
LOC +
250 QString(
"%1 is over 10 minutes old, forcing "
251 "remote theme list download").arg(remoteThemesFile));
255 if (!remoteThemesDir.exists())
258 else if (downloadFailures < 2)
260 LOG(VB_GUI, LOG_INFO,
LOC +
261 QString(
"%1 does not exist, forcing remote theme "
262 "list download").arg(remoteThemesFile));
268 QFile test(remoteThemesFile);
269 if (test.open(QIODevice::WriteOnly))
273 ShowOkPopup(tr(
"Unable to create '%1'").arg(remoteThemesFile));
279 QString url = themeSite;
280 url.append(
"/themes.zip");
282 ShowOkPopup(tr(
"Unable to remove '%1'").arg(versiondir));
284 if (!dir.mkpath(destdir))
285 ShowOkPopup(tr(
"Unable to create '%1'").arg(destdir));
288 LOG(VB_GUI, LOG_INFO,
LOC +
289 QString(
"Downloading '%1' to '%2'").arg(url).arg(remoteThemesFile));
293 if (!result || !
extractZIP(remoteThemesFile, destdir))
295 QFile::remove(remoteThemesFile);
303 LOG(VB_GUI, LOG_ERR,
LOC +
304 QString(
"Failed to download '%1'").arg(url));
306 ShowOkPopup(tr(
"Failed to download '%1'").arg(url));
310 LOG(VB_GUI, LOG_ERR,
LOC +
311 QString(
"Failed to unzip '%1' to '%2'")
312 .arg(remoteThemesFile).arg(destdir));
315 .arg(remoteThemesFile).arg(destdir));
320 LOG(VB_GUI, LOG_INFO,
LOC +
321 QString(
"Unzipped '%1' to '%2'")
322 .arg(remoteThemesFile)
328 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
329 themes.setSorting(QDir::Name | QDir::IgnoreCase);
331 if ((QFile::exists(remoteThemesFile)) &&
332 (remoteThemesDir.exists()))
336 LOG(VB_GUI, LOG_INFO,
LOC +
337 QString(
"%1 and %2 exist, using cached remote themes list")
338 .arg(remoteThemesFile).arg(remoteThemesDir.absolutePath()));
340 QString themesPath = remoteThemesDir.absolutePath();
341 themes.setPath(themesPath);
343 QFileInfoList downloadableThemes = themes.entryInfoList();
344 for (
const auto & dtheme : qAsConst(downloadableThemes))
346 QString dirName = dtheme.fileName();
348 QString remoteDir = themeSite;
349 remoteDir.append(
"/").append(dirName);
350 QString localDir = themes.absolutePath();
351 localDir.append(
"/").append(dirName);
353 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
355 if (themesSeen.contains(dirName))
366 if ((rmtMaj > locMaj) ||
367 ((rmtMaj == locMaj) &&
372 LOG(VB_GUI, LOG_DEBUG,
LOC +
373 QString(
"'%1' old version %2.%3, new version %4.%5")
375 .arg(rmtMaj).arg(rmtMin));
382 remoteDir.append(
"/").append(finfo.fileName()),
383 localDir.append(
"/").append(finfo.fileName()),
387 else if ((rmtMaj == locMaj) &&
390 LOG(VB_GUI, LOG_DEBUG,
LOC +
391 QString(
"'%1' up to date (%2.%3)")
392 .arg(
themeName).arg(locMaj).arg(locMin));
399 LOG(VB_GUI, LOG_DEBUG,
LOC +
400 QString(
"'%1' (%2.%3) available")
409 themesSeen << dirName;
415 remoteDir.append(
"/").append(finfo.fileName()),
416 localDir.append(
"/").append(finfo.fileName()),
433 for (
const auto & theme : qAsConst(
m_infoList))
442 QString buttonText = QString(
"%1 %2.%3")
460 themeinfo->
ToMap(infomap);
462 item->
SetData(QVariant::fromValue(themeinfo));
467 thumbnail = thumbnail.append(
".thumb.jpg");
471 curThemeInfo = themeinfo;
485 QFile test(testFile);
486 if (test.open(QIODevice::WriteOnly))
490 ShowOkPopup(tr(
"Error creating test file, %1 themes directory is "
497 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
502 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
504 themeinfo =
new ThemeInfo(theme.filePath());
527 QString label = tr(
"Theme Chooser Menu");
530 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
606 for (
int i = 0; i < actions.size() && !handled; ++i)
608 QString
action = actions[i];
613 else if (
action ==
"DELETE")
615 else if ((
action ==
"ESCAPE") &&
677 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
697 if (!info->GetDownloadURL().isEmpty())
700 QFile test(testFile);
701 if (test.open(QIODevice::WriteOnly))
705 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
712 QString baseName = qfile.fileName();
716 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
719 .split(
";", QString::SkipEmptyParts);
723 .split(
";", Qt::SkipEmptyParts);
727 LOG(VB_FILE, LOG_WARNING,
LOC +
728 QString(
"Theme download URL overridden from %1 to %2.")
732 OpenBusyPopup(tr(
"Downloading %1 Theme").arg(info->GetName()));
739 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
759 QFileInfo preview(info->GetPreviewPath());
761 info->ToMap(infomap);
765 if (preview.exists())
777 if (preview.exists())
794 if (info->GetDownloadURL().isEmpty())
815 progressBar->
SetUsed(bytesReceived);
827 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
828 QStringList tokens = me->
Message().split(
" ", QString::SkipEmptyParts);
830 QStringList tokens = me->Message().split(
" ", Qt::SkipEmptyParts);
833 if (tokens.isEmpty())
836 if (tokens[0] ==
"DOWNLOAD_FILE")
838 QStringList
args = me->ExtraDataList();
840 (tokens.size() != 2) ||
845 if (tokens[1] ==
"UPDATE")
849 else if (tokens[1] ==
"FINISHED")
851 bool remoteFileIsLocal =
false;
852 int fileSize =
args[2].toInt();
853 int errorCode =
args[4].toInt();
863 if ((errorCode == 0) &&
869 file.setFile(localFile);
873 remoteFileIsLocal =
true;
889 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
897 if ((errorCode == 0) &&
901 auto *extractThread =
905 extractThread,
"ThemeExtract");
907 if (!remoteFileIsLocal)
916 ShowOkPopup(tr(
"ERROR downloading theme package from master backend."));
921 else if ((me->Message() ==
"THEME_INSTALLED") &&
927 QStringList
args = me->ExtraDataList();
928 QFile::remove(
args[0]);
930 QString
event = QString(
"THEME_INSTALLED PATH %1")
938 auto *me2 =
new MythEvent(
"THEME_RELOAD");
939 qApp->postEvent(
this, me2);
941 else if ((me->Message() ==
"THEME_RELOAD") &&
961 ShowOkPopup(tr(
"Error, unable to find current theme."));
967 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
968 "be deleted.").arg(info->GetName()));
980 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
988 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
989 QFileInfoList list = dir.entryInfoList();
991 for (
const auto & fi : qAsConst(list))
993 if (fi.isFile() && !fi.isSymLink())
995 if (!QFile::remove(fi.absoluteFilePath()))
998 else if (fi.isDir() && !fi.isSymLink())
1005 return dir.rmdir(dirname);
1011 m_updateTimer(new QTimer(this))
1023 version.remove(QRegularExpression(
"\\.[0-9]{8,}.*"));
1026 static const QRegularExpression subexp
1027 {
"v[0-9]+\\.([0-9]+)-*", QRegularExpression::CaseInsensitiveOption };
1029 if (match.hasMatch())
1031 for (
int idx = match.capturedView(1).toInt(); idx > 0; --idx)
1039 "remotethemes/themes.zip",
1046 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1048 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1053 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1073 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1080 QStringList::iterator Iversion;
1086 QString remoteThemeDir =
1089 QString(
"remotethemes/%1/%2")
1094 QString infoXML = remoteThemeDir;
1095 infoXML.append(
"/themeinfo.xml");
1097 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'")
1105 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1106 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1108 LOG(VB_GENERAL, LOG_ERR,
1109 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1110 "Unable to create ThemeInfo for %1")
1113 remoteTheme =
nullptr;
1122 LOG(VB_GENERAL, LOG_ERR,
1123 "ThemeUpdateChecker::checkForUpdate(): "
1124 "Unable to create ThemeInfo for current theme");
1126 localTheme =
nullptr;
1133 int rmtMaj = remoteTheme->GetMajorVersion();
1134 int rmtMin = remoteTheme->GetMinorVersion();
1137 remoteTheme =
nullptr;
1139 if ((rmtMaj > locMaj) ||
1140 ((rmtMaj == locMaj) &&
1144 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName())
1145 .arg(rmtMaj).arg(rmtMin);
1148 (
"ThemeUpdateStatus");
1153 (currentLocation ==
"mainmenu"))
1156 .arg(locMaj).arg(locMin);
1157 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1163 QString message = tr(
"Version %1 of the %2 theme is now "
1164 "available in the Theme Chooser. "
1165 "The currently installed version "
QString GetThemesParentDir(void)
void LoadInBackground(const QString &message="")
void SetReturnEvent(QObject *retobject, const QString &resultid)
static Type MythEventMessage
void SetBusyPopupMessage(const QString &message)
QMap< QString, ThemeInfo * > m_themeNameInfos
QString GetMasterHostName(void)
QString m_lastKnownThemeVersion
View and select installed themes.
MythUIButtonList * m_themes
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
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)
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)
const char * GetMythSourcePath()
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
bool GetBoolSetting(const QString &key, bool defaultval=false)
~ThemeUpdateChecker(void) override
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
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
void LoadVersion(const QString &version, QStringList &themesSeen, bool alert_user)
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)
const char * GetMythSourceVersion()
QString GetHostName(void)
bool extractZIP(const QString &zipFile, const QString &outDir)
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 RemoteDownloadFile(const QString &url, const QString &storageGroup, const QString &filename)
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)