Go to the documentation of this file.
9 #include <QCoreApplication>
38 using namespace std::chrono_literals;
40 #define LOC QString("ThemeChooser: ")
41 #define LOC_WARN QString("ThemeChooser, Warning: ")
42 #define LOC_ERR QString("ThemeChooser, Error: ")
51 QString srcFile, QString destDir) :
53 m_srcFile(std::move(srcFile)),
54 m_destDir(std::move(destDir)) {}
60 auto *me =
new MythEvent(
"THEME_INSTALLED", QStringList(m_srcFile));
61 QCoreApplication::postEvent(m_parent, me);
76 const QString &name) :
92 return s1.fileName().toLower() < s2.fileName().toLower();
124 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
144 QStringList themesSeen;
146 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
147 themes.setSorting(QDir::Name | QDir::IgnoreCase);
151 for (
const auto & theme : qAsConst(
m_infoList))
155 themesSeen << theme.fileName();
161 QFileInfoList sharedThemes = themes.entryInfoList();
162 for (
const auto & sharedTheme : qAsConst(sharedThemes))
164 if ((!themesSeen.contains(sharedTheme.fileName())) &&
168 themesSeen << sharedTheme.fileName();
175 QRegExp trunkver(
"v[0-9]+-pre.*",Qt::CaseInsensitive);
176 QRegExp validver(
"v[0-9]+.*",Qt::CaseInsensitive);
178 if (!validver.exactMatch(MythVersion))
180 LOG(VB_GENERAL, LOG_ERR, QString(
"Invalid MythTV version %1, will use themes from trunk").
arg(MythVersion));
181 MythVersion =
"trunk";
183 if (trunkver.exactMatch(MythVersion))
184 MythVersion =
"trunk";
186 if (MythVersion ==
"trunk")
189 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").
arg(MythVersion));
196 MythVersion.replace(QRegExp(
"\\.[0-9]{8,}.*"),
"");
197 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").
arg(MythVersion));
202 QRegExp subexp(
"v[0-9]+\\.([0-9]+)-*");
208 for (
int idx = subexp.cap(1).toInt(); idx > 0; --idx)
210 subversion = MythVersion +
"." + QString::number(idx);
211 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").
arg(subversion));
223 QStringList &themesSeen,
bool alert_user)
226 remoteThemesFile.append(
"/tmp/themes.zip");
227 QString themeSite = QString(
"%1/%2")
229 "http://themes.mythtv.org/themes/repository")).arg(
version);
230 QString destdir =
GetCacheDir().append(
"/themechooser/");
231 QString versiondir = QString(
"%1/%2").arg(destdir).arg(
version);
232 QDir remoteThemesDir(versiondir);
234 int downloadFailures =
236 if (QFile::exists(remoteThemesFile))
238 QFileInfo finfo(remoteThemesFile);
239 if (finfo.lastModified().toUTC() <
242 LOG(VB_GUI, LOG_INFO,
LOC +
243 QString(
"%1 is over 10 minutes old, forcing "
244 "remote theme list download").
arg(remoteThemesFile));
248 if (!remoteThemesDir.exists())
251 else if (downloadFailures < 2)
253 LOG(VB_GUI, LOG_INFO,
LOC +
254 QString(
"%1 does not exist, forcing remote theme "
255 "list download").
arg(remoteThemesFile));
261 QFile test(remoteThemesFile);
262 if (test.open(QIODevice::WriteOnly))
272 QString url = themeSite;
273 url.append(
"/themes.zip");
277 if (!
dir.mkpath(destdir))
281 LOG(VB_GUI, LOG_INFO,
LOC +
282 QString(
"Downloading '%1' to '%2'").
arg(url).
arg(remoteThemesFile));
286 if (!result || !
extractZIP(remoteThemesFile, destdir))
288 QFile::remove(remoteThemesFile);
296 LOG(VB_GUI, LOG_ERR,
LOC +
297 QString(
"Failed to download '%1'").
arg(url));
303 LOG(VB_GUI, LOG_ERR,
LOC +
304 QString(
"Failed to unzip '%1' to '%2'")
305 .
arg(remoteThemesFile).
arg(destdir));
308 .
arg(remoteThemesFile).
arg(destdir));
313 LOG(VB_GUI, LOG_INFO,
LOC +
314 QString(
"Unzipped '%1' to '%2'")
315 .
arg(remoteThemesFile)
321 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
322 themes.setSorting(QDir::Name | QDir::IgnoreCase);
324 if ((QFile::exists(remoteThemesFile)) &&
325 (remoteThemesDir.exists()))
329 LOG(VB_GUI, LOG_INFO,
LOC +
330 QString(
"%1 and %2 exist, using cached remote themes list")
331 .
arg(remoteThemesFile).
arg(remoteThemesDir.absolutePath()));
333 QString themesPath = remoteThemesDir.absolutePath();
334 themes.setPath(themesPath);
336 QFileInfoList downloadableThemes = themes.entryInfoList();
337 for (
const auto & dtheme : qAsConst(downloadableThemes))
339 QString dirName = dtheme.fileName();
341 QString remoteDir = themeSite;
342 remoteDir.append(
"/").append(dirName);
343 QString localDir = themes.absolutePath();
344 localDir.append(
"/").append(dirName);
346 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
348 if (themesSeen.contains(dirName))
359 if ((rmtMaj > locMaj) ||
360 ((rmtMaj == locMaj) &&
365 LOG(VB_GUI, LOG_DEBUG,
LOC +
366 QString(
"'%1' old version %2.%3, new version %4.%5")
368 .
arg(rmtMaj).
arg(rmtMin));
375 remoteDir.append(
"/").append(finfo.fileName()),
376 localDir.append(
"/").append(finfo.fileName()),
380 else if ((rmtMaj == locMaj) &&
383 LOG(VB_GUI, LOG_DEBUG,
LOC +
384 QString(
"'%1' up to date (%2.%3)")
392 LOG(VB_GUI, LOG_DEBUG,
LOC +
393 QString(
"'%1' (%2.%3) available")
402 themesSeen << dirName;
408 remoteDir.append(
"/").append(finfo.fileName()),
409 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 "
490 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
495 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
497 themeinfo =
new ThemeInfo(theme.filePath());
520 QString label = tr(
"Theme Chooser Menu");
523 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
599 for (
int i = 0; i < actions.size() && !handled; ++i)
601 QString
action = actions[i];
606 else if (
action ==
"DELETE")
608 else if ((
action ==
"ESCAPE") &&
670 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
690 if (!info->GetDownloadURL().isEmpty())
693 QFile test(testFile);
694 if (test.open(QIODevice::WriteOnly))
698 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
705 QString
baseName = qfile.fileName();
709 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
712 .split(
";", QString::SkipEmptyParts);
716 .split(
";", Qt::SkipEmptyParts);
720 LOG(VB_FILE, LOG_WARNING,
LOC +
721 QString(
"Theme download URL overridden from %1 to %2.")
752 QFileInfo preview(info->GetPreviewPath());
754 info->ToMap(infomap);
758 if (preview.exists())
770 if (preview.exists())
787 if (info->GetDownloadURL().isEmpty())
808 progressBar->
SetUsed(bytesReceived);
820 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
821 QStringList tokens = me->
Message().split(
" ", QString::SkipEmptyParts);
823 QStringList tokens = me->Message().split(
" ", Qt::SkipEmptyParts);
826 if (tokens.isEmpty())
829 if (tokens[0] ==
"DOWNLOAD_FILE")
831 QStringList
args = me->ExtraDataList();
833 (tokens.size() != 2) ||
838 if (tokens[1] ==
"UPDATE")
842 else if (tokens[1] ==
"FINISHED")
844 bool remoteFileIsLocal =
false;
845 int fileSize =
args[2].toInt();
846 int errorCode =
args[4].toInt();
856 if ((errorCode == 0) &&
862 file.setFile(localFile);
866 remoteFileIsLocal =
true;
882 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
890 if ((errorCode == 0) &&
894 auto *extractThread =
898 extractThread,
"ThemeExtract");
900 if (!remoteFileIsLocal)
909 ShowOkPopup(tr(
"ERROR downloading theme package from master backend."));
914 else if ((me->Message() ==
"THEME_INSTALLED") &&
920 QStringList
args = me->ExtraDataList();
921 QFile::remove(
args[0]);
923 QString
event = QString(
"THEME_INSTALLED PATH %1")
931 auto *me2 =
new MythEvent(
"THEME_RELOAD");
932 qApp->postEvent(
this, me2);
934 else if ((me->Message() ==
"THEME_RELOAD") &&
954 ShowOkPopup(tr(
"Error, unable to find current theme."));
960 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
961 "be deleted.").
arg(info->GetName()));
973 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
981 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
982 QFileInfoList list =
dir.entryInfoList();
984 for (
const auto & fi : qAsConst(list))
986 if (fi.isFile() && !fi.isSymLink())
988 if (!QFile::remove(fi.absoluteFilePath()))
991 else if (fi.isDir() && !fi.isSymLink())
998 return dir.rmdir(dirname);
1004 m_updateTimer(new QTimer(this))
1016 version.replace(QRegExp(
"\\.[0-9]{8,}.*"),
"");
1019 QRegExp subexp(
"v[0-9]+.[0-9]+.([0-9]+)-*");
1023 for (
int idx = subexp.cap(1).toInt(); idx > 0; --idx)
1031 "remotethemes/themes.zip",
1038 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1040 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1045 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1065 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1072 QStringList::iterator Iversion;
1078 QString remoteThemeDir =
1081 QString(
"remotethemes/%1/%2")
1086 QString infoXML = remoteThemeDir;
1087 infoXML.append(
"/themeinfo.xml");
1089 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'")
1097 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1098 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1100 LOG(VB_GENERAL, LOG_ERR,
1101 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1102 "Unable to create ThemeInfo for %1")
1105 remoteTheme =
nullptr;
1114 LOG(VB_GENERAL, LOG_ERR,
1115 "ThemeUpdateChecker::checkForUpdate(): "
1116 "Unable to create ThemeInfo for current theme");
1118 localTheme =
nullptr;
1125 int rmtMaj = remoteTheme->GetMajorVersion();
1126 int rmtMin = remoteTheme->GetMinorVersion();
1129 remoteTheme =
nullptr;
1131 if ((rmtMaj > locMaj) ||
1132 ((rmtMaj == locMaj) &&
1136 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName())
1137 .arg(rmtMaj).arg(rmtMin);
1140 (
"ThemeUpdateStatus");
1145 (currentLocation ==
"mainmenu"))
1148 .arg(locMaj).arg(locMin);
1149 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)
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
arg(title).arg(filename).arg(doDelete))
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)