00001
00002
00003 #include "themechooser.h"
00004
00005
00006 #include <QCoreApplication>
00007 #include <QRunnable>
00008 #include <QRegExp>
00009
00010
00011 #include "mythcorecontext.h"
00012 #include "mythcoreutil.h"
00013 #include "mthreadpool.h"
00014 #include "remotefile.h"
00015 #include "mythdownloadmanager.h"
00016 #include "programtypes.h"
00017 #include "mythsystemevent.h"
00018 #include "mythmiscutil.h"
00019 #include "mythversion.h"
00020 #include "mythlogging.h"
00021 #include "storagegroup.h"
00022
00023
00024 #include "mythmainwindow.h"
00025 #include "mythuihelper.h"
00026 #include "mythuiprogressbar.h"
00027 #include "mythdialogbox.h"
00028 #include "mythuibuttonlist.h"
00029 #include "mythscreenstack.h"
00030 #include "mythuistatetype.h"
00031
00032 #define LOC QString("ThemeChooser: ")
00033 #define LOC_WARN QString("ThemeChooser, Warning: ")
00034 #define LOC_ERR QString("ThemeChooser, Error: ")
00035
00039 class ThemeExtractThread : public QRunnable
00040 {
00041 public:
00042 ThemeExtractThread(ThemeChooser *parent,
00043 const QString &srcFile, const QString &destDir) :
00044 m_parent(parent),
00045 m_srcFile(srcFile),
00046 m_destDir(destDir)
00047 {
00048 m_srcFile.detach();
00049 m_destDir.detach();
00050 }
00051
00052 void run()
00053 {
00054 extractZIP(m_srcFile, m_destDir);
00055
00056 MythEvent *me =
00057 new MythEvent("THEME_INSTALLED", QStringList(m_srcFile));
00058 QCoreApplication::postEvent(m_parent, me);
00059 }
00060
00061 private:
00062 ThemeChooser *m_parent;
00063 QString m_srcFile;
00064 QString m_destDir;
00065 };
00066
00067
00072 ThemeChooser::ThemeChooser(MythScreenStack *parent,
00073 const QString name) :
00074 MythScreenType(parent, name),
00075 m_fullPreviewShowing(false),
00076 m_fullPreviewStateType(NULL),
00077 m_fullScreenName(NULL),
00078 m_fullScreenPreview(NULL),
00079 m_refreshDownloadableThemes(false),
00080 m_downloadTheme(NULL),
00081 m_downloadState(dsIdle),
00082 m_popupMenu(NULL)
00083 {
00084 gCoreContext->addListener(this);
00085
00086 StorageGroup sgroup("Themes", gCoreContext->GetHostName());
00087 m_userThemeDir = sgroup.GetFirstDir(true);
00088 }
00089
00090 ThemeChooser::~ThemeChooser()
00091 {
00092 gCoreContext->removeListener(this);
00093 }
00094
00095 static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
00096 {
00097 return s1.fileName().toLower() < s2.fileName().toLower();
00098 }
00099
00100
00101 bool ThemeChooser::Create(void)
00102 {
00103
00104 if (!LoadWindowFromXML("settings-ui.xml", "themechooser", this))
00105 return false;
00106
00107 bool err = false;
00108 UIUtilE::Assign(this, m_themes, "themes", &err);
00109
00110 UIUtilW::Assign(this, m_preview, "preview");
00111 UIUtilW::Assign(this, m_fullPreviewStateType, "fullpreviewstate");
00112
00113 if (m_fullPreviewStateType)
00114 {
00115 MythUIGroup *state =
00116 dynamic_cast<MythUIGroup*>
00117 (m_fullPreviewStateType->GetChild("fullscreen"));
00118 if (state)
00119 {
00120 m_fullScreenName =
00121 dynamic_cast<MythUIText*>(state->GetChild("fullscreenname"));
00122 m_fullScreenPreview =
00123 dynamic_cast<MythUIImage*>(state->GetChild("fullscreenpreview"));
00124 }
00125 }
00126
00127 if (err)
00128 {
00129 LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load screen 'themechooser'");
00130 return false;
00131 }
00132
00133 connect(m_themes, SIGNAL(itemClicked(MythUIButtonListItem*)),
00134 this, SLOT(saveAndReload(MythUIButtonListItem*)));
00135 connect(m_themes, SIGNAL(itemSelected(MythUIButtonListItem*)),
00136 this, SLOT(itemChanged(MythUIButtonListItem*)));
00137
00138 BuildFocusList();
00139
00140 LoadInBackground();
00141
00142 return true;
00143 }
00144
00145 void ThemeChooser::Load(void)
00146 {
00147 SetBusyPopupMessage(tr("Loading Installed Themes"));
00148
00149 QString MythVersion = MYTH_SOURCE_PATH;
00150 QStringList themesSeen;
00151 QDir themes(m_userThemeDir);
00152 themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
00153 themes.setSorting(QDir::Name | QDir::IgnoreCase);
00154
00155
00156 if (MythVersion == "master")
00157 MythVersion = "trunk";
00158
00159 if (MythVersion != "trunk")
00160 {
00161 MythVersion = MYTH_BINARY_VERSION;
00162 MythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), "");
00163 }
00164
00165 m_infoList = themes.entryInfoList();
00166
00167 for( QFileInfoList::iterator it = m_infoList.begin();
00168 it != m_infoList.end();
00169 ++it )
00170 {
00171 if (loadThemeInfo(*it))
00172 {
00173 themesSeen << (*it).fileName();
00174 m_themeStatuses[(*it).fileName()] = "default";
00175 }
00176 }
00177
00178 themes.setPath(GetThemesParentDir());
00179 QFileInfoList sharedThemes = themes.entryInfoList();
00180 for( QFileInfoList::iterator it = sharedThemes.begin();
00181 it != sharedThemes.end();
00182 ++it )
00183 {
00184 if ((!themesSeen.contains((*it).fileName())) &&
00185 (loadThemeInfo(*it)))
00186 {
00187 m_infoList << *it;
00188 themesSeen << (*it).fileName();
00189 m_themeStatuses[(*it).fileName()] = "default";
00190 }
00191 }
00192
00193 QString remoteThemesFile = GetConfDir();
00194 remoteThemesFile.append("/tmp/themes.zip");
00195 QString themeSite = QString("%1/%2")
00196 .arg(gCoreContext->GetSetting("ThemeRepositoryURL",
00197 "http://themes.mythtv.org/themes/repository")).arg(MythVersion);
00198 QDir remoteThemesDir(GetMythUI()->GetThemeCacheDir()
00199 .append("/themechooser/").append(MythVersion));
00200
00201 int downloadFailures =
00202 gCoreContext->GetNumSetting("ThemeInfoDownloadFailures", 0);
00203 if (QFile::exists(remoteThemesFile))
00204 {
00205 QFileInfo finfo(remoteThemesFile);
00206 if (finfo.lastModified() < mythCurrentDateTime().addSecs(-600))
00207 {
00208 LOG(VB_GUI, LOG_INFO, LOC +
00209 QString("%1 is over 10 minutes old, forcing "
00210 "remote theme list download").arg(remoteThemesFile));
00211 m_refreshDownloadableThemes = true;
00212 }
00213
00214 if (!remoteThemesDir.exists())
00215 m_refreshDownloadableThemes = true;
00216 }
00217 else if (downloadFailures < 2)
00218 {
00219 LOG(VB_GUI, LOG_INFO, LOC +
00220 QString("%1 does not exist, forcing remote theme "
00221 "list download").arg(remoteThemesFile));
00222 m_refreshDownloadableThemes = true;
00223 }
00224
00225 if (m_refreshDownloadableThemes)
00226 {
00227 SetBusyPopupMessage(tr("Refreshing Downloadable Themes Information"));
00228
00229 QString url = themeSite;
00230 url.append("/themes.zip");
00231 QString destdir = GetMythUI()->GetThemeCacheDir();
00232 destdir.append("/themechooser");
00233 QString versiondir = QString("%1/%2").arg(destdir).arg(MythVersion);
00234 removeThemeDir(versiondir);
00235 QDir dir;
00236 dir.mkpath(destdir);
00237 bool result = GetMythDownloadManager()->download(url, remoteThemesFile);
00238
00239 SetBusyPopupMessage(tr("Extracting Downloadable Themes Information"));
00240
00241 if (!result || !extractZIP(remoteThemesFile, destdir))
00242 {
00243 QFile::remove(remoteThemesFile);
00244
00245 downloadFailures++;
00246 gCoreContext->SaveSetting("ThemeInfoDownloadFailures",
00247 downloadFailures);
00248 }
00249 }
00250
00251 if ((QFile::exists(remoteThemesFile)) &&
00252 (remoteThemesDir.exists()))
00253 {
00254 SetBusyPopupMessage(tr("Loading Downloadable Themes"));
00255
00256 LOG(VB_GUI, LOG_INFO, LOC +
00257 QString("%1 and %2 exist, using cached remote themes list")
00258 .arg(remoteThemesFile).arg(remoteThemesDir.absolutePath()));
00259
00260 QString themesPath = remoteThemesDir.absolutePath();
00261 themes.setPath(themesPath);
00262
00263 QFileInfoList downloadableThemes = themes.entryInfoList();
00264 for( QFileInfoList::iterator it = downloadableThemes.begin();
00265 it != downloadableThemes.end();
00266 ++it )
00267 {
00268 QString dirName = (*it).fileName();
00269 QString themeName = dirName;
00270 QString remoteDir = themeSite;
00271 remoteDir.append("/").append(dirName);
00272 QString localDir = themes.absolutePath();
00273 localDir.append("/").append(dirName);
00274
00275 if (themesSeen.contains(dirName))
00276 {
00277 ThemeInfo remoteTheme((*it).absoluteFilePath());
00278 ThemeInfo *localTheme = m_themeNameInfos[dirName];
00279
00280 themeName = remoteTheme.GetName();
00281
00282 int rmtMaj = remoteTheme.GetMajorVersion();
00283 int rmtMin = remoteTheme.GetMinorVersion();
00284 int locMaj = localTheme->GetMajorVersion();
00285 int locMin = localTheme->GetMinorVersion();
00286
00287 if ((rmtMaj > locMaj) ||
00288 ((rmtMaj == locMaj) &&
00289 (rmtMin > locMin)))
00290 {
00291 if (loadThemeInfo(*it))
00292 {
00293 m_infoList << *it;
00294 m_themeStatuses[themeName] = "updateavailable";
00295
00296 QFileInfo finfo(remoteTheme.GetPreviewPath());
00297 GetMythDownloadManager()->queueDownload(
00298 remoteDir.append("/").append(finfo.fileName()),
00299 localDir.append("/").append(finfo.fileName()),
00300 NULL);
00301 }
00302 }
00303 else if ((rmtMaj == locMaj) &&
00304 (rmtMin == locMin))
00305 {
00306 m_themeStatuses[themeName] = "uptodate";
00307 }
00308 }
00309 else
00310 {
00311 ThemeInfo *remoteTheme = loadThemeInfo(*it);
00312 if (remoteTheme)
00313 {
00314 themeName = remoteTheme->GetName();
00315 themesSeen << dirName;
00316 m_infoList << *it;
00317 m_themeStatuses[themeName] = "updateavailable";
00318
00319 QFileInfo finfo(remoteTheme->GetPreviewPath());
00320 GetMythDownloadManager()->queueDownload(
00321 remoteDir.append("/").append(finfo.fileName()),
00322 localDir.append("/").append(finfo.fileName()),
00323 NULL);
00324 }
00325 }
00326 }
00327 }
00328
00329 ResetBusyPopup();
00330
00331 qSort(m_infoList.begin(), m_infoList.end(), sortThemeNames);
00332 }
00333
00334 void ThemeChooser::Init(void)
00335 {
00336 QString curTheme = gCoreContext->GetSetting("Theme");
00337 ThemeInfo *themeinfo = NULL;
00338 ThemeInfo *curThemeInfo = NULL;
00339 MythUIButtonListItem *item = NULL;
00340
00341 m_themes->Reset();
00342 for( QFileInfoList::iterator it = m_infoList.begin();
00343 it != m_infoList.end();
00344 ++it )
00345 {
00346 QFileInfo &theme = *it;
00347
00348 if (!m_themeFileNameInfos.contains(theme.filePath()))
00349 continue;
00350
00351 themeinfo = m_themeFileNameInfos[theme.filePath()];
00352 if (!themeinfo)
00353 continue;
00354
00355 QString buttonText = QString("%1 %2.%3")
00356 .arg(themeinfo->GetName())
00357 .arg(themeinfo->GetMajorVersion())
00358 .arg(themeinfo->GetMinorVersion());
00359
00360 item = new MythUIButtonListItem(m_themes, buttonText);
00361 if (item)
00362 {
00363 if (themeinfo->GetDownloadURL().isEmpty())
00364 item->DisplayState("local", "themelocation");
00365 else
00366 item->DisplayState("remote", "themelocation");
00367
00368 item->DisplayState(themeinfo->GetAspect(), "aspectstate");
00369
00370 item->DisplayState(m_themeStatuses[themeinfo->GetName()],
00371 "themestatus");
00372 QHash<QString, QString> infomap;
00373 themeinfo->ToMap(infomap);
00374 item->SetTextFromMap(infomap);
00375 item->SetData(qVariantFromValue(themeinfo));
00376
00377 QString thumbnail = themeinfo->GetPreviewPath();
00378 QFileInfo fInfo(thumbnail);
00379
00380 if (!themeinfo->GetDownloadURL().isEmpty())
00381 thumbnail = thumbnail.append(".thumb.jpg");
00382 item->SetImage(thumbnail);
00383
00384 if (curTheme == themeinfo->GetDirectoryName())
00385 curThemeInfo = themeinfo;
00386 }
00387 else
00388 delete item;
00389 }
00390
00391 SetFocusWidget(m_themes);
00392
00393 if (curThemeInfo)
00394 m_themes->SetValueByData(qVariantFromValue(curThemeInfo));
00395
00396 MythUIButtonListItem *current = m_themes->GetItemCurrent();
00397 if (current)
00398 itemChanged(current);
00399 }
00400
00401 ThemeInfo *ThemeChooser::loadThemeInfo(QFileInfo &theme)
00402 {
00403 if (theme.fileName() == "default" || theme.fileName() == "default-wide")
00404 return NULL;
00405
00406 ThemeInfo *themeinfo = NULL;
00407 if (theme.exists())
00408 themeinfo = new ThemeInfo(theme.absoluteFilePath());
00409 else
00410 themeinfo = new ThemeInfo(theme.filePath());
00411
00412 if (!themeinfo)
00413 return NULL;
00414
00415 if (themeinfo->GetName().isEmpty() || !(themeinfo->GetType() & THEME_UI))
00416 {
00417 delete themeinfo;
00418 return NULL;
00419 }
00420
00421 m_themeFileNameInfos[theme.filePath()] = themeinfo;
00422 m_themeNameInfos[theme.fileName()] = themeinfo;
00423
00424 return themeinfo;
00425 }
00426
00427 void ThemeChooser::showPopupMenu(void)
00428 {
00429 if (m_popupMenu)
00430 return;
00431
00432 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00433 QString label = tr("Theme Chooser Menu");
00434
00435 m_popupMenu =
00436 new MythDialogBox(label, popupStack, "themechoosermenupopup");
00437
00438 connect(m_popupMenu, SIGNAL(Closed(QString, int)), SLOT(popupClosed(QString, int)));
00439
00440 if (m_popupMenu->Create())
00441 popupStack->AddScreen(m_popupMenu);
00442 else
00443 {
00444 delete m_popupMenu;
00445 m_popupMenu = NULL;
00446 return;
00447 }
00448
00449 m_popupMenu->SetReturnEvent(this, "popupmenu");
00450
00451 if (m_fullPreviewStateType)
00452 {
00453 if (m_fullPreviewShowing)
00454 m_popupMenu->AddButton(tr("Hide Fullscreen Preview"),
00455 SLOT(toggleFullscreenPreview()));
00456 else
00457 m_popupMenu->AddButton(tr("Show Fullscreen Preview"),
00458 SLOT(toggleFullscreenPreview()));
00459 }
00460
00461 m_popupMenu->AddButton(tr("Refresh Downloadable Themes"),
00462 SLOT(refreshDownloadableThemes()));
00463
00464 MythUIButtonListItem *current = m_themes->GetItemCurrent();
00465 if (current)
00466 {
00467 ThemeInfo *info = qVariantValue<ThemeInfo *>(current->GetData());
00468
00469 if (info)
00470 {
00471 m_popupMenu->AddButton(tr("Select Theme"),
00472 SLOT(saveAndReload()));
00473
00474 if (info->GetPreviewPath().startsWith(m_userThemeDir))
00475 m_popupMenu->AddButton(tr("Delete Theme"),
00476 SLOT(removeTheme()));
00477 }
00478 }
00479
00480 if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
00481 m_popupMenu->AddButton(tr("Disable Theme Update Notifications"),
00482 SLOT(toggleThemeUpdateNotifications()));
00483 else
00484 m_popupMenu->AddButton(tr("Enable Theme Update Notifications"),
00485 SLOT(toggleThemeUpdateNotifications()));
00486 }
00487
00488 void ThemeChooser::popupClosed(QString which, int result)
00489 {
00490 (void)which;
00491 (void)result;
00492
00493 m_popupMenu = NULL;
00494 }
00495
00496 bool ThemeChooser::keyPressEvent(QKeyEvent *event)
00497 {
00498 if (GetFocusWidget()->keyPressEvent(event))
00499 return true;
00500
00501 bool handled = false;
00502 QStringList actions;
00503 handled = GetMythMainWindow()->TranslateKeyPress("Theme Chooser", event, actions);
00504
00505 for (int i = 0; i < actions.size() && !handled; ++i)
00506 {
00507 QString action = actions[i];
00508 handled = true;
00509
00510 if (action == "MENU")
00511 showPopupMenu();
00512 else if (action == "DELETE")
00513 removeTheme();
00514 else if ((action == "ESCAPE") &&
00515 (m_fullPreviewShowing))
00516 {
00517 toggleFullscreenPreview();
00518 }
00519 else
00520 handled = false;
00521 }
00522
00523 if (!handled && MythScreenType::keyPressEvent(event))
00524 handled = true;
00525
00526 return handled;
00527 }
00528
00529 void ThemeChooser::toggleFullscreenPreview(void)
00530 {
00531 if (m_fullPreviewStateType)
00532 {
00533 if (m_fullPreviewShowing)
00534 {
00535 if (m_fullScreenPreview)
00536 m_fullScreenPreview->Reset();
00537
00538 if (m_fullScreenName)
00539 m_fullScreenName->Reset();
00540
00541 m_fullPreviewStateType->Reset();
00542 m_fullPreviewShowing = false;
00543 }
00544 else
00545 {
00546 MythUIButtonListItem *item = m_themes->GetItemCurrent();
00547 ThemeInfo *info = qVariantValue<ThemeInfo*>(item->GetData());
00548 if (info)
00549 {
00550 if (m_fullScreenPreview)
00551 {
00552 m_fullScreenPreview->SetFilename(info->GetPreviewPath());
00553 m_fullScreenPreview->Load();
00554 }
00555
00556 if (m_fullScreenName)
00557 m_fullScreenName->SetText(info->GetName());
00558
00559 m_fullPreviewStateType->DisplayState("fullscreen");
00560 m_fullPreviewShowing = true;
00561 }
00562 }
00563 }
00564 }
00565
00566 void ThemeChooser::toggleThemeUpdateNotifications(void)
00567 {
00568 if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
00569 gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "0", "");
00570 else
00571 gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "1", "");
00572 }
00573
00574 void ThemeChooser::refreshDownloadableThemes(void)
00575 {
00576 LOG(VB_GUI, LOG_INFO, LOC + "Forcing remote theme list refresh");
00577 m_refreshDownloadableThemes = true;
00578 gCoreContext->SaveSetting("ThemeInfoDownloadFailures", 0);
00579 ReloadInBackground();
00580 }
00581
00582 void ThemeChooser::saveAndReload(void)
00583 {
00584 MythUIButtonListItem *current = m_themes->GetItemCurrent();
00585 if (current)
00586 saveAndReload(current);
00587 }
00588
00589 void ThemeChooser::saveAndReload(MythUIButtonListItem *item)
00590 {
00591 ThemeInfo *info = qVariantValue<ThemeInfo *>(item->GetData());
00592
00593 if (!info)
00594 return;
00595
00596 if (!info->GetDownloadURL().isEmpty())
00597 {
00598 QString downloadURL = info->GetDownloadURL();
00599 QFileInfo qfile(downloadURL);
00600 QString baseName = qfile.fileName();
00601
00602 if (!gCoreContext->GetSetting("ThemeDownloadURL").isEmpty())
00603 {
00604 QStringList tokens =
00605 gCoreContext->GetSetting("ThemeDownloadURL")
00606 .split(";", QString::SkipEmptyParts);
00607 QString origURL = downloadURL;
00608 downloadURL.replace(tokens[0], tokens[1]);
00609 LOG(VB_FILE, LOG_WARNING, LOC +
00610 QString("Theme download URL overridden from %1 to %2.")
00611 .arg(origURL).arg(downloadURL));
00612 }
00613
00614 OpenBusyPopup(tr("Downloading %1 Theme").arg(info->GetName()));
00615 m_downloadTheme = info;
00616 #if 0
00617 m_downloadFile = RemoteDownloadFile(downloadURL,
00618 "Temp", baseName);
00619 m_downloadState = dsDownloadingOnBackend;
00620 #else
00621 QString localFile = GetConfDir() + "/tmp/" + baseName;
00622 GetMythDownloadManager()->queueDownload(downloadURL, localFile, this);
00623 m_downloadFile = localFile;
00624 m_downloadState = dsDownloadingOnFrontend;
00625 #endif
00626 }
00627 else
00628 {
00629 gCoreContext->SaveSetting("Theme", info->GetDirectoryName());
00630 GetMythMainWindow()->JumpTo("Reload Theme");
00631 }
00632 }
00633
00634 void ThemeChooser::itemChanged(MythUIButtonListItem *item)
00635 {
00636 ThemeInfo *info = qVariantValue<ThemeInfo*>(item->GetData());
00637
00638 if (!info)
00639 return;
00640
00641 QFileInfo preview(info->GetPreviewPath());
00642 QHash<QString, QString> infomap;
00643 info->ToMap(infomap);
00644 SetTextFromMap(infomap);
00645 if (m_preview)
00646 {
00647 if (preview.exists())
00648 {
00649 m_preview->SetFilename(info->GetPreviewPath());
00650 m_preview->Load();
00651 }
00652 else
00653 m_preview->Reset();
00654 }
00655 if (m_fullPreviewShowing && m_fullPreviewStateType)
00656 {
00657 if (m_fullScreenPreview)
00658 {
00659 if (preview.exists())
00660 {
00661 m_fullScreenPreview->SetFilename(info->GetPreviewPath());
00662 m_fullScreenPreview->Load();
00663 }
00664 else
00665 m_fullScreenPreview->Reset();
00666 }
00667
00668 if (m_fullScreenName)
00669 m_fullScreenName->SetText(info->GetName());
00670 }
00671
00672 MythUIStateType *themeLocation =
00673 dynamic_cast<MythUIStateType*>(GetChild("themelocation"));
00674 if (themeLocation)
00675 {
00676 if (info->GetDownloadURL().isEmpty())
00677 themeLocation->DisplayState("local");
00678 else
00679 themeLocation->DisplayState("remote");
00680 }
00681
00682 MythUIStateType *aspectState =
00683 dynamic_cast<MythUIStateType*>(GetChild("aspectstate"));
00684 if (aspectState)
00685 aspectState->DisplayState(info->GetAspect());
00686 }
00687
00688 void ThemeChooser::updateProgressBar(int bytesReceived,
00689 int bytesTotal)
00690 {
00691 MythUIProgressBar *progressBar =
00692 dynamic_cast<MythUIProgressBar *>(GetChild("downloadprogressbar"));
00693
00694 if (!progressBar)
00695 return;
00696
00697 progressBar->SetUsed(bytesReceived);
00698 progressBar->SetTotal(bytesTotal);
00699 }
00700
00701 void ThemeChooser::customEvent(QEvent *e)
00702 {
00703 if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
00704 {
00705 MythEvent *me = (MythEvent *)e;
00706 QStringList tokens = me->Message().split(" ", QString::SkipEmptyParts);
00707
00708 if (tokens.isEmpty())
00709 return;
00710
00711 if (tokens[0] == "DOWNLOAD_FILE")
00712 {
00713 QStringList args = me->ExtraDataList();
00714 if ((m_downloadState == dsIdle) ||
00715 (tokens.size() != 2) ||
00716 (!m_downloadTheme) ||
00717 (args[1] != m_downloadFile))
00718 return;
00719
00720 if (tokens[1] == "UPDATE")
00721 {
00722 updateProgressBar(args[2].toInt(), args[3].toInt());
00723 }
00724 else if (tokens[1] == "FINISHED")
00725 {
00726 bool remoteFileIsLocal = false;
00727 int fileSize = args[2].toInt();
00728 int errorCode = args[4].toInt();
00729
00730 CloseBusyPopup();
00731
00732 QFileInfo file(m_downloadFile);
00733 if ((m_downloadState == dsDownloadingOnBackend) &&
00734 (m_downloadFile.startsWith("myth://")))
00735 {
00736
00737
00738 if ((errorCode == 0) &&
00739 (fileSize > 0))
00740 {
00741 m_downloadState = dsDownloadingOnFrontend;
00742 QString localFile = GetConfDir() + "/tmp/" +
00743 file.fileName();
00744 file.setFile(localFile);
00745
00746 if (file.exists())
00747 {
00748 remoteFileIsLocal = true;
00749 m_downloadFile = localFile;
00750 }
00751 else
00752 {
00753 GetMythDownloadManager()->queueDownload(
00754 m_downloadFile, localFile, this);
00755 OpenBusyPopup(tr("Copying %1 Theme Package")
00756 .arg(m_downloadTheme->GetName()));
00757 m_downloadFile = localFile;
00758 return;
00759 }
00760 }
00761 else
00762 {
00763 m_downloadState = dsIdle;
00764 ShowOkPopup(tr("ERROR downloading theme package on master backend."));
00765 }
00766 }
00767
00768 if ((m_downloadState == dsDownloadingOnFrontend) &&
00769 (file.exists()))
00770 {
00771
00772 if ((errorCode == 0) &&
00773 (fileSize > 0))
00774 {
00775 m_downloadState = dsExtractingTheme;
00776 ThemeExtractThread *extractThread =
00777 new ThemeExtractThread(this, m_downloadFile,
00778 m_userThemeDir);
00779 MThreadPool::globalInstance()->start(
00780 extractThread, "ThemeExtract");
00781
00782 if (!remoteFileIsLocal)
00783 RemoteFile::DeleteFile(args[0]);
00784
00785 OpenBusyPopup(tr("Installing %1 Theme")
00786 .arg(m_downloadTheme->GetName()));
00787 }
00788 else
00789 {
00790 m_downloadState = dsIdle;
00791 ShowOkPopup(tr("ERROR downloading theme package from master backend."));
00792 }
00793 }
00794 }
00795 }
00796 else if ((me->Message() == "THEME_INSTALLED") &&
00797 (m_downloadTheme) &&
00798 (m_downloadState == dsExtractingTheme))
00799 {
00800 m_downloadState = dsIdle;
00801 CloseBusyPopup();
00802 QStringList args = me->ExtraDataList();
00803 QFile::remove(args[0]);
00804
00805 QString event = QString("THEME_INSTALLED PATH %1")
00806 .arg(m_userThemeDir +
00807 m_downloadTheme->GetDirectoryName());
00808 gCoreContext->SendSystemEvent(event);
00809
00810 gCoreContext->SaveSetting("Theme", m_downloadTheme->GetDirectoryName());
00811 GetMythMainWindow()->JumpTo("Reload Theme");
00812 }
00813 }
00814 }
00815
00816 void ThemeChooser::removeTheme(void)
00817 {
00818 MythUIButtonListItem *current = m_themes->GetItemCurrent();
00819 if (!current)
00820 {
00821 ShowOkPopup(tr("Error, no theme selected."));
00822 return;
00823 }
00824
00825 ThemeInfo *info = qVariantValue<ThemeInfo *>(current->GetData());
00826 if (!info)
00827 {
00828 ShowOkPopup(tr("Error, unable to find current theme."));
00829 return;
00830 }
00831
00832 if (!info->GetPreviewPath().startsWith(m_userThemeDir))
00833 {
00834 ShowOkPopup(tr("%1 is not a user-installed theme and can not "
00835 "be deleted.").arg(info->GetName()));
00836 return;
00837 }
00838
00839 removeThemeDir(m_userThemeDir + info->GetDirectoryName());
00840
00841 ReloadInBackground();
00842 }
00843
00844 void ThemeChooser::removeThemeDir(const QString &dirname)
00845 {
00846 if ((!dirname.startsWith(m_userThemeDir)) &&
00847 (!dirname.startsWith(GetMythUI()->GetThemeCacheDir())))
00848 return;
00849
00850 QDir dir(dirname);
00851
00852 if (!dir.exists())
00853 return;
00854
00855 QFileInfoList list = dir.entryInfoList();
00856 QFileInfoList::const_iterator it = list.begin();
00857 const QFileInfo *fi;
00858
00859 while (it != list.end())
00860 {
00861 fi = &(*it++);
00862 if (fi->fileName() == "." || fi->fileName() == "..")
00863 continue;
00864 if (fi->isFile() && !fi->isSymLink())
00865 {
00866 QFile::remove(fi->absoluteFilePath());
00867 }
00868 else if (fi->isDir() && !fi->isSymLink())
00869 {
00870 removeThemeDir(fi->absoluteFilePath());
00871 }
00872 }
00873
00874 dir.rmdir(dirname);
00875 }
00876
00878
00879 ThemeUpdateChecker::ThemeUpdateChecker() :
00880 m_updateTimer(new QTimer(this))
00881 {
00882 m_mythVersion = MYTH_SOURCE_PATH;
00883
00884
00885 if (m_mythVersion == "master")
00886 m_mythVersion = "trunk";
00887
00888 if (m_mythVersion != "trunk")
00889 {
00890 m_mythVersion = MYTH_BINARY_VERSION;
00891 m_mythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), "");
00892 }
00893
00894 m_infoPackage = gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"),
00895 0,
00896 "remotethemes/themes.zip",
00897 "Temp");
00898
00899 gCoreContext->SaveSetting("ThemeUpdateStatus", "");
00900
00901 connect(m_updateTimer, SIGNAL(timeout()), SLOT(checkForUpdate()));
00902 m_updateTimer->start(60 * 60 * 1000);
00903
00904
00905 QTimer::singleShot(15 * 1000, this, SLOT(checkForUpdate()));
00906 }
00907
00908 ThemeUpdateChecker::~ThemeUpdateChecker()
00909 {
00910 if (m_updateTimer)
00911 {
00912 m_updateTimer->stop();
00913 delete m_updateTimer;
00914 m_updateTimer = NULL;
00915 }
00916 }
00917
00918 void ThemeUpdateChecker::checkForUpdate(void)
00919 {
00920 if (GetMythUI()->GetCurrentLocation(false, true) != "mainmenu")
00921 return;
00922
00923 if (RemoteFile::Exists(m_infoPackage))
00924 {
00925 QString remoteThemeDir =
00926 gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"),
00927 0,
00928 QString("remotethemes/%1/%2")
00929 .arg(m_mythVersion)
00930 .arg(GetMythUI()->GetThemeName()),
00931 "Temp");
00932
00933 QString infoXML = remoteThemeDir;
00934 infoXML.append("/themeinfo.xml");
00935
00936 if (RemoteFile::Exists(infoXML))
00937 {
00938 ThemeInfo *remoteTheme = new ThemeInfo(remoteThemeDir);
00939 if (!remoteTheme)
00940 {
00941 LOG(VB_GENERAL, LOG_ERR,
00942 QString("ThemeUpdateChecker::checkForUpdate(): "
00943 "Unable to create ThemeInfo for %1")
00944 .arg(infoXML));
00945 return;
00946 }
00947
00948 ThemeInfo *localTheme = new ThemeInfo(GetMythUI()->GetThemeDir());
00949 if (!localTheme)
00950 {
00951 LOG(VB_GENERAL, LOG_ERR,
00952 "ThemeUpdateChecker::checkForUpdate(): "
00953 "Unable to create ThemeInfo for current theme");
00954 return;
00955 }
00956
00957 int rmtMaj = remoteTheme->GetMajorVersion();
00958 int rmtMin = remoteTheme->GetMinorVersion();
00959 int locMaj = localTheme->GetMajorVersion();
00960 int locMin = localTheme->GetMinorVersion();
00961
00962 if ((rmtMaj > locMaj) ||
00963 ((rmtMaj == locMaj) &&
00964 (rmtMin > locMin)))
00965 {
00966 m_lastKnownThemeVersion =
00967 QString("%1-%2.%3").arg(GetMythUI()->GetThemeName())
00968 .arg(rmtMaj).arg(rmtMin);
00969
00970 QString status = gCoreContext->GetSetting("ThemeUpdateStatus");
00971 QString currentLocation = GetMythUI()->GetCurrentLocation(false, true);
00972
00973 if ((!status.startsWith(m_lastKnownThemeVersion)) &&
00974 (currentLocation == "mainmenu"))
00975 {
00976 m_currentVersion = QString("%1.%2").arg(locMaj).arg(locMin);
00977 m_newVersion = QString("%1.%2").arg(rmtMaj).arg(rmtMin);
00978
00979 gCoreContext->SaveSetting("ThemeUpdateStatus",
00980 m_lastKnownThemeVersion + " notified");
00981
00982 QString message = tr("Version %1 of the %2 theme is now "
00983 "available in the Theme Chooser. The "
00984 "currently installed version is %3.")
00985 .arg(m_newVersion)
00986 .arg(GetMythUI()->GetThemeName())
00987 .arg(m_currentVersion);
00988
00989 ShowOkPopup(message);
00990 }
00991 }
00992
00993 delete remoteTheme;
00994 delete localTheme;
00995 }
00996 }
00997 }
00998
00999
01000