diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 5983ab8a6..1dc302d0a 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -90,7 +90,6 @@ #include "statsdialog.h" #include "statusbar.h" #include "torrentcreatordialog.h" - #include "transferlistfilterswidget.h" #include "transferlistmodel.h" #include "transferlistwidget.h" @@ -105,6 +104,8 @@ #include "programupdater.h" #endif +using namespace std::chrono_literals; + namespace { #define SETTINGS_KEY(name) "GUI/" name @@ -148,9 +149,6 @@ MainWindow::MainWindow(QWidget *parent) , m_posInitialized(false) , m_forceExit(false) , m_unlockDlgShowing(false) -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - , m_wasUpdateCheckEnabled(false) -#endif , m_hasPython(false) { m_ui->setupUi(this); @@ -317,11 +315,11 @@ MainWindow::MainWindow(QWidget *parent) connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds); #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - m_programUpdateTimer = new QTimer(this); - m_programUpdateTimer->setInterval(60 * 60 * 1000); - m_programUpdateTimer->setSingleShot(true); - connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate); - connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate); + connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); }); + + // trigger an early check on startup + if (pref->isUpdateCheckEnabled()) + checkProgramUpdate(false); #else m_ui->actionCheckForUpdates->setVisible(false); #endif @@ -804,7 +802,8 @@ void MainWindow::cleanup() m_preventTimer->stop(); #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) - m_programUpdateTimer->stop(); + if (m_programUpdateTimer) + m_programUpdateTimer->stop(); #endif // remove all child widgets @@ -1583,15 +1582,21 @@ void MainWindow::loadPreferences(const bool configureSession) m_propertiesWidget->reloadPreferences(); #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) + if (pref->isUpdateCheckEnabled()) { - m_wasUpdateCheckEnabled = true; - checkProgramUpdate(); + if (!m_programUpdateTimer) + { + m_programUpdateTimer = new QTimer(this); + m_programUpdateTimer->setInterval(24h); + m_programUpdateTimer->setSingleShot(true); + connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); }); + m_programUpdateTimer->start(); + } } - else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled) + else { - m_wasUpdateCheckEnabled = false; - m_programUpdateTimer->stop(); + delete m_programUpdateTimer; + m_programUpdateTimer = nullptr; } #endif @@ -1897,15 +1902,21 @@ void MainWindow::on_actionDownloadFromURL_triggered() } #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) -void MainWindow::handleUpdateCheckFinished(const bool updateAvailable, const QString &newVersion, const bool invokedByUser) +void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser) { m_ui->actionCheckForUpdates->setEnabled(true); m_ui->actionCheckForUpdates->setText(tr("&Check for Updates")); m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates")); - QObject *signalSender = sender(); + const auto cleanup = [this, updater]() + { + if (m_programUpdateTimer) + m_programUpdateTimer->start(); + updater->deleteLater(); + }; - if (updateAvailable) + const QString newVersion = updater->getNewVersion(); + if (!newVersion.isEmpty()) { const QString msg {tr("A new version is available.") + "
" + tr("Do you want to download %1?").arg(newVersion) + "

" @@ -1915,38 +1926,31 @@ void MainWindow::handleUpdateCheckFinished(const bool updateAvailable, const QSt msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_ShowWithoutActivating); msgBox->setDefaultButton(QMessageBox::Yes); - connect(msgBox, &QMessageBox::buttonClicked, this, [this, msgBox, signalSender](QAbstractButton *button) + connect(msgBox, &QMessageBox::buttonClicked, this, [this, msgBox, updater](QAbstractButton *button) { if (msgBox->buttonRole(button) == QMessageBox::YesRole) { - // The user want to update, let's download the update - auto *updater = dynamic_cast(signalSender); updater->updateProgram(); } - else - { - if (Preferences::instance()->isUpdateCheckEnabled()) - m_programUpdateTimer->start(); - } - - signalSender->deleteLater(); }); + connect(msgBox, &QDialog::finished, this, cleanup); msgBox->open(); } - else if (invokedByUser) + else { - auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent") - , tr("No updates available.\nYou are already using the latest version.") - , QMessageBox::Ok, this}; - msgBox->setAttribute(Qt::WA_DeleteOnClose); - connect(msgBox, &QDialog::finished, this, [this, signalSender](const int) + if (invokedByUser) { - if (Preferences::instance()->isUpdateCheckEnabled()) - m_programUpdateTimer->start(); - - signalSender->deleteLater(); - }); - msgBox->open(); + auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent") + , tr("No updates available.\nYou are already using the latest version.") + , QMessageBox::Ok, this}; + msgBox->setAttribute(Qt::WA_DeleteOnClose); + connect(msgBox, &QDialog::finished, this, cleanup); + msgBox->open(); + } + else + { + cleanup(); + } } } #endif @@ -2106,18 +2110,23 @@ QIcon MainWindow::getSystrayIcon() const #endif // Q_OS_MACOS #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) -void MainWindow::checkProgramUpdate() +void MainWindow::checkProgramUpdate(const bool invokedByUser) { - m_programUpdateTimer->stop(); // If the user had clicked the menu item + if (m_programUpdateTimer) + m_programUpdateTimer->stop(); + m_ui->actionCheckForUpdates->setEnabled(false); m_ui->actionCheckForUpdates->setText(tr("Checking for Updates...")); m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background")); - bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast(sender()); - ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser); - connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished); + + auto *updater = new ProgramUpdater(this); + connect(updater, &ProgramUpdater::updateCheckFinished + , this, [this, invokedByUser, updater]() + { + handleUpdateCheckFinished(updater, invokedByUser); + }); updater->checkForUpdates(); } - #endif #ifdef Q_OS_WIN diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 6f0ee5998..63a8096ba 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -47,6 +47,7 @@ class ExecutionLogWidget; class LineEdit; class OptionsDialog; class PowerManagement; +class ProgramUpdater; class PropertiesWidget; class RSSWidget; class SearchWidget; @@ -134,9 +135,6 @@ private slots: void finishedTorrent(BitTorrent::Torrent *const torrent) const; void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent); void optionsSaved(); -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - void handleUpdateCheckFinished(bool updateAvailable, const QString &newVersion, bool invokedByUser); -#endif void toggleAlternativeSpeeds(); #ifdef Q_OS_WIN @@ -177,9 +175,7 @@ private slots: void on_actionLock_triggered(); // Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences void updatePowerManagementState(); -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - void checkProgramUpdate(); -#endif + void toolbarMenuRequested(const QPoint &point); void toolbarIconsOnly(); void toolbarTextOnly(); @@ -252,10 +248,13 @@ private: // Power Management PowerManagement *m_pwr; QTimer *m_preventTimer; -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - QTimer *m_programUpdateTimer; - bool m_wasUpdateCheckEnabled; -#endif bool m_hasPython; QMenu *m_toolbarMenu; + +#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) + void checkProgramUpdate(bool invokedByUser); + void handleUpdateCheckFinished(ProgramUpdater *updater, bool invokedByUser); + + QTimer *m_programUpdateTimer = nullptr; +#endif }; diff --git a/src/gui/programupdater.cpp b/src/gui/programupdater.cpp index ee18931eb..7301b83ea 100644 --- a/src/gui/programupdater.cpp +++ b/src/gui/programupdater.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021 Mike Tzou (Chocobo1) * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -36,7 +37,6 @@ #include #include #include -#include #include #if defined(Q_OS_WIN) @@ -44,56 +44,82 @@ #endif #include "base/net/downloadmanager.h" +#include "base/utils/version.h" #include "base/version.h" namespace { - const QString RSS_URL {QStringLiteral("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml")}; + bool isVersionMoreRecent(const QString &remoteVersion) + { + using Version = Utils::Version; - QString getStringValue(QXmlStreamReader &xml); + try + { + const Version newVersion {remoteVersion}; + const Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD}; + if (newVersion == currentVersion) + { + const bool isDevVersion = QString::fromLatin1(QBT_VERSION_STATUS).contains( + QRegularExpression(QLatin1String("(alpha|beta|rc)"))); + if (isDevVersion) + return true; + } + return (newVersion > currentVersion); + } + catch (const std::runtime_error &) + { + return false; + } + } } -ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser) - : QObject(parent) - , m_invokedByUser(invokedByUser) -{ -} - -void ProgramUpdater::checkForUpdates() +void ProgramUpdater::checkForUpdates() const { + const auto RSS_URL = QString::fromLatin1("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml"); // Don't change this User-Agent. In case our updater goes haywire, // the filehost can identify it and contact us. Net::DownloadManager::instance()->download( - Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)") - , this, &ProgramUpdater::rssDownloadFinished); + Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)") + , this, &ProgramUpdater::rssDownloadFinished); +} + +QString ProgramUpdater::getNewVersion() const +{ + return m_newVersion; } void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) { - if (result.status != Net::DownloadStatus::Success) { qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString; - emit updateCheckFinished(false, QString(), m_invokedByUser); + emit updateCheckFinished(); return; } qDebug("Finished downloading the new qBittorrent updates RSS"); + const auto getStringValue = [](QXmlStreamReader &xml) -> QString + { + xml.readNext(); + return (xml.isCharacters() && !xml.isWhitespace()) + ? xml.text().toString() + : QString {}; + }; + #ifdef Q_OS_MACOS const QString OS_TYPE {"Mac OS X"}; #elif defined(Q_OS_WIN) - const QString OS_TYPE - {(::IsWindows7OrGreater() - && QSysInfo::currentCpuArchitecture().endsWith("64")) + const QString OS_TYPE {(::IsWindows7OrGreater() + && QSysInfo::currentCpuArchitecture().endsWith("64")) ? "Windows x64" : "Windows"}; #endif - QString version; - QXmlStreamReader xml(result.data); bool inItem = false; + QString version; QString updateLink; QString type; + QXmlStreamReader xml(result.data); while (!xml.atEnd()) { @@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) { qDebug("Detected version is %s", qUtf8Printable(version)); if (isVersionMoreRecent(version)) - m_updateUrl = updateLink; + { + m_newVersion = version; + m_updateURL = updateLink; + } } break; } @@ -134,51 +163,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) } } - emit updateCheckFinished(!m_updateUrl.isEmpty(), version, m_invokedByUser); + emit updateCheckFinished(); } -void ProgramUpdater::updateProgram() +bool ProgramUpdater::updateProgram() const { - Q_ASSERT(!m_updateUrl.isEmpty()); - QDesktopServices::openUrl(m_updateUrl); - return; -} - -bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const -{ - const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION); - if (regVerMatch.hasMatch()) - { - const QString localVersion = regVerMatch.captured(1); - const QVector remoteParts = remoteVersion.splitRef('.'); - const QVector localParts = localVersion.splitRef('.'); - - for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i) - { - if (remoteParts[i].toInt() > localParts[i].toInt()) - return true; - if (remoteParts[i].toInt() < localParts[i].toInt()) - return false; - } - // Compared parts were equal, if remote version is longer, then it's more recent (2.9.2.1 > 2.9.2) - if (remoteParts.size() > localParts.size()) - return true; - // versions are equal, check if the local version is a development release, in which case it is older (2.9.2beta < 2.9.2) - const QRegularExpressionMatch regDevelMatch = QRegularExpression("(alpha|beta|rc)").match(QBT_VERSION); - if (regDevelMatch.hasMatch()) - return true; - } - return false; -} - -namespace -{ - QString getStringValue(QXmlStreamReader &xml) - { - xml.readNext(); - if (xml.isCharacters() && !xml.isWhitespace()) - return xml.text().toString(); - - return {}; - } + return QDesktopServices::openUrl(m_updateURL); } diff --git a/src/gui/programupdater.h b/src/gui/programupdater.h index c843324ab..1a22886a4 100644 --- a/src/gui/programupdater.h +++ b/src/gui/programupdater.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2021 Mike Tzou (Chocobo1) * Copyright (C) 2010 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -29,32 +30,33 @@ #pragma once #include +#include +#include namespace Net { struct DownloadResult; } -class ProgramUpdater : public QObject +class ProgramUpdater final : public QObject { Q_OBJECT Q_DISABLE_COPY(ProgramUpdater) public: - explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false); + using QObject::QObject; - void checkForUpdates(); - void updateProgram(); + void checkForUpdates() const; + QString getNewVersion() const; + bool updateProgram() const; signals: - void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser); + void updateCheckFinished(); private slots: void rssDownloadFinished(const Net::DownloadResult &result); private: - bool isVersionMoreRecent(const QString &remoteVersion) const; - - QString m_updateUrl; - bool m_invokedByUser; + QString m_newVersion; + QUrl m_updateURL; };