Browse Source

Merge pull request #14274 from Chocobo1/updater

Improve behavior when using ProgramUpdater class
adaptive-webui-19844
Mike Tzou 4 years ago committed by GitHub
parent
commit
dc39b9e643
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 93
      src/gui/mainwindow.cpp
  2. 19
      src/gui/mainwindow.h
  3. 106
      src/gui/programupdater.cpp
  4. 20
      src/gui/programupdater.h

93
src/gui/mainwindow.cpp

@ -90,7 +90,6 @@
#include "statsdialog.h" #include "statsdialog.h"
#include "statusbar.h" #include "statusbar.h"
#include "torrentcreatordialog.h" #include "torrentcreatordialog.h"
#include "transferlistfilterswidget.h" #include "transferlistfilterswidget.h"
#include "transferlistmodel.h" #include "transferlistmodel.h"
#include "transferlistwidget.h" #include "transferlistwidget.h"
@ -105,6 +104,8 @@
#include "programupdater.h" #include "programupdater.h"
#endif #endif
using namespace std::chrono_literals;
namespace namespace
{ {
#define SETTINGS_KEY(name) "GUI/" name #define SETTINGS_KEY(name) "GUI/" name
@ -148,9 +149,6 @@ MainWindow::MainWindow(QWidget *parent)
, m_posInitialized(false) , m_posInitialized(false)
, m_forceExit(false) , m_forceExit(false)
, m_unlockDlgShowing(false) , m_unlockDlgShowing(false)
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
, m_wasUpdateCheckEnabled(false)
#endif
, m_hasPython(false) , m_hasPython(false)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@ -317,11 +315,11 @@ MainWindow::MainWindow(QWidget *parent)
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds); connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
m_programUpdateTimer = new QTimer(this); connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
m_programUpdateTimer->setInterval(60 * 60 * 1000);
m_programUpdateTimer->setSingleShot(true); // trigger an early check on startup
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate); if (pref->isUpdateCheckEnabled())
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate); checkProgramUpdate(false);
#else #else
m_ui->actionCheckForUpdates->setVisible(false); m_ui->actionCheckForUpdates->setVisible(false);
#endif #endif
@ -804,6 +802,7 @@ void MainWindow::cleanup()
m_preventTimer->stop(); m_preventTimer->stop();
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
if (m_programUpdateTimer)
m_programUpdateTimer->stop(); m_programUpdateTimer->stop();
#endif #endif
@ -1583,15 +1582,21 @@ void MainWindow::loadPreferences(const bool configureSession)
m_propertiesWidget->reloadPreferences(); m_propertiesWidget->reloadPreferences();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) if (pref->isUpdateCheckEnabled())
{
if (!m_programUpdateTimer)
{ {
m_wasUpdateCheckEnabled = true; m_programUpdateTimer = new QTimer(this);
checkProgramUpdate(); 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; delete m_programUpdateTimer;
m_programUpdateTimer->stop(); m_programUpdateTimer = nullptr;
} }
#endif #endif
@ -1897,15 +1902,21 @@ void MainWindow::on_actionDownloadFromURL_triggered()
} }
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #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->setEnabled(true);
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates")); m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program 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.") + "<br/>" const QString msg {tr("A new version is available.") + "<br/>"
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>" + tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
@ -1915,39 +1926,32 @@ void MainWindow::handleUpdateCheckFinished(const bool updateAvailable, const QSt
msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setAttribute(Qt::WA_ShowWithoutActivating); msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
msgBox->setDefaultButton(QMessageBox::Yes); 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) if (msgBox->buttonRole(button) == QMessageBox::YesRole)
{ {
// The user want to update, let's download the update
auto *updater = dynamic_cast<ProgramUpdater *>(signalSender);
updater->updateProgram(); updater->updateProgram();
} }
else
{
if (Preferences::instance()->isUpdateCheckEnabled())
m_programUpdateTimer->start();
}
signalSender->deleteLater();
}); });
connect(msgBox, &QDialog::finished, this, cleanup);
msgBox->open(); msgBox->open();
} }
else if (invokedByUser) else
{
if (invokedByUser)
{ {
auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent") auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent")
, tr("No updates available.\nYou are already using the latest version.") , tr("No updates available.\nYou are already using the latest version.")
, QMessageBox::Ok, this}; , QMessageBox::Ok, this};
msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_DeleteOnClose);
connect(msgBox, &QDialog::finished, this, [this, signalSender](const int) connect(msgBox, &QDialog::finished, this, cleanup);
{
if (Preferences::instance()->isUpdateCheckEnabled())
m_programUpdateTimer->start();
signalSender->deleteLater();
});
msgBox->open(); msgBox->open();
} }
else
{
cleanup();
}
}
} }
#endif #endif
@ -2106,18 +2110,23 @@ QIcon MainWindow::getSystrayIcon() const
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
#if defined(Q_OS_WIN) || defined(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->setEnabled(false);
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates...")); m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background")); m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction *>(sender());
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser); auto *updater = new ProgramUpdater(this);
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished); connect(updater, &ProgramUpdater::updateCheckFinished
, this, [this, invokedByUser, updater]()
{
handleUpdateCheckFinished(updater, invokedByUser);
});
updater->checkForUpdates(); updater->checkForUpdates();
} }
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

19
src/gui/mainwindow.h

@ -47,6 +47,7 @@ class ExecutionLogWidget;
class LineEdit; class LineEdit;
class OptionsDialog; class OptionsDialog;
class PowerManagement; class PowerManagement;
class ProgramUpdater;
class PropertiesWidget; class PropertiesWidget;
class RSSWidget; class RSSWidget;
class SearchWidget; class SearchWidget;
@ -134,9 +135,6 @@ private slots:
void finishedTorrent(BitTorrent::Torrent *const torrent) const; void finishedTorrent(BitTorrent::Torrent *const torrent) const;
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent); void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
void optionsSaved(); void optionsSaved();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void handleUpdateCheckFinished(bool updateAvailable, const QString &newVersion, bool invokedByUser);
#endif
void toggleAlternativeSpeeds(); void toggleAlternativeSpeeds();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -177,9 +175,7 @@ private slots:
void on_actionLock_triggered(); void on_actionLock_triggered();
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences // Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
void updatePowerManagementState(); void updatePowerManagementState();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void checkProgramUpdate();
#endif
void toolbarMenuRequested(const QPoint &point); void toolbarMenuRequested(const QPoint &point);
void toolbarIconsOnly(); void toolbarIconsOnly();
void toolbarTextOnly(); void toolbarTextOnly();
@ -252,10 +248,13 @@ private:
// Power Management // Power Management
PowerManagement *m_pwr; PowerManagement *m_pwr;
QTimer *m_preventTimer; QTimer *m_preventTimer;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
QTimer *m_programUpdateTimer;
bool m_wasUpdateCheckEnabled;
#endif
bool m_hasPython; bool m_hasPython;
QMenu *m_toolbarMenu; 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
}; };

106
src/gui/programupdater.cpp

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Mike Tzou (Chocobo1)
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -36,7 +37,6 @@
#include <QDebug> #include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringList>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@ -44,23 +44,38 @@
#endif #endif
#include "base/net/downloadmanager.h" #include "base/net/downloadmanager.h"
#include "base/utils/version.h"
#include "base/version.h" #include "base/version.h"
namespace namespace
{ {
const QString RSS_URL {QStringLiteral("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml")}; bool isVersionMoreRecent(const QString &remoteVersion)
{
using Version = Utils::Version<int, 4, 3>;
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);
ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser) }
: QObject(parent) catch (const std::runtime_error &)
, m_invokedByUser(invokedByUser)
{ {
return false;
}
}
} }
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, // Don't change this User-Agent. In case our updater goes haywire,
// the filehost can identify it and contact us. // the filehost can identify it and contact us.
Net::DownloadManager::instance()->download( Net::DownloadManager::instance()->download(
@ -68,32 +83,43 @@ void ProgramUpdater::checkForUpdates()
, this, &ProgramUpdater::rssDownloadFinished); , this, &ProgramUpdater::rssDownloadFinished);
} }
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) QString ProgramUpdater::getNewVersion() const
{ {
return m_newVersion;
}
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
if (result.status != Net::DownloadStatus::Success) if (result.status != Net::DownloadStatus::Success)
{ {
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString; qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished(false, QString(), m_invokedByUser); emit updateCheckFinished();
return; return;
} }
qDebug("Finished downloading the new qBittorrent updates RSS"); 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 #ifdef Q_OS_MACOS
const QString OS_TYPE {"Mac OS X"}; const QString OS_TYPE {"Mac OS X"};
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
const QString OS_TYPE const QString OS_TYPE {(::IsWindows7OrGreater()
{(::IsWindows7OrGreater()
&& QSysInfo::currentCpuArchitecture().endsWith("64")) && QSysInfo::currentCpuArchitecture().endsWith("64"))
? "Windows x64" : "Windows"}; ? "Windows x64" : "Windows"};
#endif #endif
QString version;
QXmlStreamReader xml(result.data);
bool inItem = false; bool inItem = false;
QString version;
QString updateLink; QString updateLink;
QString type; QString type;
QXmlStreamReader xml(result.data);
while (!xml.atEnd()) while (!xml.atEnd())
{ {
@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{ {
qDebug("Detected version is %s", qUtf8Printable(version)); qDebug("Detected version is %s", qUtf8Printable(version));
if (isVersionMoreRecent(version)) if (isVersionMoreRecent(version))
m_updateUrl = updateLink; {
m_newVersion = version;
m_updateURL = updateLink;
}
} }
break; 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()); return QDesktopServices::openUrl(m_updateURL);
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<QStringRef> remoteParts = remoteVersion.splitRef('.');
const QVector<QStringRef> 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 {};
}
} }

20
src/gui/programupdater.h

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Mike Tzou (Chocobo1)
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -29,32 +30,33 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QString>
#include <QUrl>
namespace Net namespace Net
{ {
struct DownloadResult; struct DownloadResult;
} }
class ProgramUpdater : public QObject class ProgramUpdater final : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(ProgramUpdater) Q_DISABLE_COPY(ProgramUpdater)
public: public:
explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false); using QObject::QObject;
void checkForUpdates(); void checkForUpdates() const;
void updateProgram(); QString getNewVersion() const;
bool updateProgram() const;
signals: signals:
void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser); void updateCheckFinished();
private slots: private slots:
void rssDownloadFinished(const Net::DownloadResult &result); void rssDownloadFinished(const Net::DownloadResult &result);
private: private:
bool isVersionMoreRecent(const QString &remoteVersion) const; QString m_newVersion;
QUrl m_updateURL;
QString m_updateUrl;
bool m_invokedByUser;
}; };

Loading…
Cancel
Save