5#include <QCoreApplication>
6#include <QRegularExpression>
15#include "libmythbase/mythversion.h"
34#define LOC QString("ThemeChooser: ")
35#define LOC_WARN QString("ThemeChooser, Warning: ")
36#define LOC_ERR QString("ThemeChooser, Error: ")
47 QString srcFile, QString destDir) :
m_parent(parent),
56 QCoreApplication::postEvent(
m_parent, me);
85 return s1.fileName().toLower() < s2.fileName().toLower();
115 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Cannot load screen 'themechooser'");
135 QStringList themesSeen;
137 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
138 themes.setSorting(QDir::Name | QDir::IgnoreCase);
142 for (
const auto &theme : std::as_const(
m_infoList))
146 themesSeen << theme.fileName();
152 QFileInfoList sharedThemes = themes.entryInfoList();
153 for (
const auto &sharedTheme : std::as_const(sharedThemes))
155 if ((!themesSeen.contains(sharedTheme.fileName())) &&
159 themesSeen << sharedTheme.fileName();
166 bool devel {
false };
169 if (!parsed || devel)
172 LOG(VB_GENERAL, LOG_ERR,
173 QString(
"Invalid MythTV version %1, will use themes from devel")
175 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for devel"));
180 QString MythVersion { QString::number(major) };
181 LOG(VB_GUI, LOG_INFO, QString(
"Loading themes for %1").arg(MythVersion));
184 for (
int idx =
minor ; idx > 0; idx--)
187 subversion = MythVersion +
"." + QString::number(idx);
188 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))
259 ShowOkPopup(tr(
"Unable to create '%1'").arg(remoteThemesFile));
265 QString url = themeSite;
266 url.append(
"/themes.zip");
268 ShowOkPopup(tr(
"Unable to remove '%1'").arg(versiondir));
270 if (!dir.mkpath(destdir))
271 ShowOkPopup(tr(
"Unable to create '%1'").arg(destdir));
274 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Downloading '%1' to '%2'").arg(url, remoteThemesFile));
278 if (!result || !
extractZIP(remoteThemesFile, destdir))
280 QFile::remove(remoteThemesFile);
288 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to download '%1'").arg(url));
290 ShowOkPopup(tr(
"Failed to download '%1'").arg(url));
294 LOG(VB_GUI, LOG_ERR,
LOC + QString(
"Failed to unzip '%1' to '%2'").arg(remoteThemesFile, destdir));
297 .arg(remoteThemesFile, destdir));
301 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"Unzipped '%1' to '%2'").arg(remoteThemesFile, destdir));
305 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
306 themes.setSorting(QDir::Name | QDir::IgnoreCase);
309 (remoteThemesDir.exists()))
313 LOG(VB_GUI, LOG_INFO,
LOC + QString(
"%1 and %2 exist, using cached remote themes list").arg(remoteThemesFile, remoteThemesDir.absolutePath()));
315 QString themesPath = remoteThemesDir.absolutePath();
316 themes.setPath(themesPath);
318 QFileInfoList downloadableThemes = themes.entryInfoList();
319 for (
const auto &dtheme : std::as_const(downloadableThemes))
321 QString dirName = dtheme.fileName();
323 QString remoteDir = themeSite;
324 remoteDir.append(
"/").append(dirName);
325 QString localDir = themes.absolutePath();
326 localDir.append(
"/").append(dirName);
328 ThemeInfo remoteTheme(dtheme.absoluteFilePath());
330 if (themesSeen.contains(dirName))
341 if ((rmtMaj > locMaj) ||
342 ((rmtMaj == locMaj) &&
347 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));
354 remoteDir.append(
"/").append(finfo.fileName()),
355 localDir.append(
"/").append(finfo.fileName()),
359 else if ((rmtMaj == locMaj) &&
362 LOG(VB_GUI, LOG_DEBUG,
LOC + QString(
"'%1' up to date (%2.%3)").arg(
themeName).arg(locMaj).arg(locMin));
375 themesSeen << dirName;
381 remoteDir.append(
"/").append(finfo.fileName()),
382 localDir.append(
"/").append(finfo.fileName()),
400 for (
const auto &theme : std::as_const(
m_infoList))
409 QString buttonText = QString(
"%1 %2.%3")
427 themeinfo->
ToMap(infomap);
429 item->
SetData(QVariant::fromValue(themeinfo));
434 thumbnail = thumbnail.append(
".thumb.jpg");
438 curThemeInfo = themeinfo;
452 QFile test(testFile);
453 if (test.open(QIODevice::WriteOnly))
459 ShowOkPopup(tr(
"Error creating test file, %1 themes directory is "
467 if (theme.fileName() ==
"default" || theme.fileName() ==
"default-wide")
472 themeinfo =
new ThemeInfo(theme.absoluteFilePath());
474 themeinfo =
new ThemeInfo(theme.filePath());
497 QString label = tr(
"Theme Chooser Menu");
500 new MythDialogBox(label, popupStack,
"themechoosermenupopup");
563 [[maybe_unused]]
int result)
576 for (
int i = 0; i < actions.size() && !handled; ++i)
578 const QString&
action = actions[i];
585 else if (
action ==
"DELETE")
589 else if ((
action ==
"ESCAPE") &&
653 LOG(VB_GUI, LOG_INFO,
LOC +
"Forcing remote theme list refresh");
673 if (!
info->GetDownloadURL().isEmpty())
676 QFile test(testFile);
677 if (test.open(QIODevice::WriteOnly))
683 ShowOkPopup(tr(
"Unable to install theme, %1 themes directory is "
690 LOG(VB_FILE, LOG_INFO, QString(
"Download url is %1").arg(
downloadURL));
692 QString baseName = qfile.fileName();
698 .split(
";", Qt::SkipEmptyParts);
701 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"Theme download URL overridden from %1 to %2.").arg(origURL,
downloadURL));
711 QString localFile =
GetConfDir() +
"/tmp/" + baseName;
731 QFileInfo preview(
info->GetPreviewPath());
733 info->ToMap(infomap);
737 if (preview.exists())
751 if (preview.exists())
770 if (
info->GetDownloadURL().isEmpty())
791 progressBar->
SetUsed(bytesReceived);
803 QStringList tokens = me->
Message().split(
" ", Qt::SkipEmptyParts);
804 if (tokens.isEmpty())
807 if (tokens[0] ==
"DOWNLOAD_FILE")
809 QStringList
args = me->ExtraDataList();
811 (tokens.size() != 2) ||
816 if (tokens[1] ==
"UPDATE")
820 else if (tokens[1] ==
"FINISHED")
822 bool remoteFileIsLocal =
false;
823 int fileSize =
args[2].toInt();
824 int errorCode =
args[4].toInt();
834 LOG(VB_FILE, LOG_INFO, QString(
"Download done MBE %1 %2").arg(errorCode).arg(fileSize));
835 if ((errorCode == 0) &&
841 file.setFile(localFile);
845 remoteFileIsLocal =
true;
861 ShowOkPopup(tr(
"ERROR downloading theme package on master backend."));
869 LOG(VB_FILE, LOG_INFO, QString(
"Download done MFE %1 %2").arg(errorCode).arg(fileSize));
871 if ((errorCode == 0) &&
875 auto *extractThread =
879 extractThread,
"ThemeExtract");
881 if (!remoteFileIsLocal)
890 ShowOkPopup(tr(
"ERROR downloading theme package from frontend."));
895 else if ((me->Message() ==
"THEME_INSTALLED") &&
901 QStringList
args = me->ExtraDataList();
903 if (!
args.isEmpty() && !
args[0].isEmpty())
904 QFile::remove(
args[0]);
906 QString
event = QString(
"THEME_INSTALLED PATH %1")
914 auto *me2 =
new MythEvent(
"THEME_RELOAD");
915 qApp->postEvent(
this, me2);
917 else if ((me->Message() ==
"THEME_RELOAD") &&
937 ShowOkPopup(tr(
"Error, unable to find current theme."));
943 ShowOkPopup(tr(
"%1 is not a user-installed theme and can not "
945 .arg(
info->GetName()));
957 (!dirname.startsWith(
GetMythUI()->GetThemeCacheDir())))
965 dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
966 QFileInfoList list = dir.entryInfoList();
968 for (
const auto &fi : std::as_const(list))
970 if (fi.isFile() && !fi.isSymLink())
972 if (!QFile::remove(fi.absoluteFilePath()))
975 else if (fi.isDir() && !fi.isSymLink())
982 return dir.rmdir(dirname);
991 bool devel {
false };
994 if (!parsed || devel)
1000 for (
int i =
minor ; i > 0; i--)
1007 "remotethemes/themes.zip",
1014 if (qEnvironmentVariableIsSet(
"MYTHTV_DEBUGMDM"))
1016 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every minute");
1021 LOG(VB_GENERAL, LOG_INFO,
"Checking for theme updates every hour");
1041 if (
GetMythUI()->GetCurrentLocation(
false,
true) !=
"mainmenu")
1048 QStringList::iterator Iversion;
1054 QString remoteThemeDir =
1057 QString(
"remotethemes/%1/%2")
1062 QString infoXML = remoteThemeDir;
1063 infoXML.append(
"/themeinfo.xml");
1065 LOG(VB_GUI, LOG_INFO, QString(
"ThemeUpdateChecker Loading '%1'").arg(infoXML));
1072 auto *remoteTheme =
new ThemeInfo(remoteThemeDir);
1073 if (!remoteTheme || remoteTheme->GetType() &
THEME_UNKN)
1075 LOG(VB_GENERAL, LOG_ERR,
1076 QString(
"ThemeUpdateChecker::checkForUpdate(): "
1077 "Unable to create ThemeInfo for %1")
1080 remoteTheme =
nullptr;
1089 LOG(VB_GENERAL, LOG_ERR,
1090 "ThemeUpdateChecker::checkForUpdate(): "
1091 "Unable to create ThemeInfo for current theme");
1093 localTheme =
nullptr;
1100 int rmtMaj = remoteTheme->GetMajorVersion();
1101 int rmtMin = remoteTheme->GetMinorVersion();
1104 remoteTheme =
nullptr;
1106 if ((rmtMaj > locMaj) ||
1107 ((rmtMaj == locMaj) &&
1111 QString(
"%1-%2.%3").arg(
GetMythUI()->GetThemeName()).arg(rmtMaj).arg(rmtMin);
1117 (currentLocation ==
"mainmenu"))
1122 m_newVersion = QString(
"%1.%2").arg(rmtMaj).arg(rmtMin);
1127 QString message = tr(
"Version %1 of the %2 theme is now "
1128 "available in the Theme Chooser. "
1129 "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)