Refactor code, create abstract class ExternalUpdater

(Hopefully) this makes implementing updaters using external libraries easier on other platforms. To implement an updater on a new platform, create a new class that implements the pure virtual methods from `ExternalUpdater` and add code in the `UpdateChecker` initializer to initialize the new class.
This commit is contained in:
Kenneth Chew 2022-04-25 19:33:17 -04:00
parent 34adcec616
commit 05cd30ac06
No known key found for this signature in database
GPG Key ID: F17D3E14A07739DA
8 changed files with 273 additions and 138 deletions

View File

@ -162,11 +162,12 @@ set(UPDATE_SOURCES
updater/UpdateChecker.cpp updater/UpdateChecker.cpp
updater/DownloadTask.h updater/DownloadTask.h
updater/DownloadTask.cpp updater/DownloadTask.cpp
updater/ExternalUpdater.h
) )
set(MAC_UPDATE_SOURCES set(MAC_UPDATE_SOURCES
updater/macsparkle/SparkleUpdater.h updater/MacSparkleUpdater.h
updater/macsparkle/SparkleUpdater.mm updater/MacSparkleUpdater.mm
) )
add_unit_test(UpdateChecker add_unit_test(UpdateChecker

View File

@ -1028,12 +1028,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);
} }
#ifdef Q_OS_MAC if (APPLICATION->updateChecker()->getExternalUpdater())
connect(APPLICATION->updateChecker()->getSparkleUpdater(), {
&SparkleUpdater::canCheckForUpdatesChanged, connect(APPLICATION->updateChecker()->getExternalUpdater(),
&ExternalUpdater::canCheckForUpdatesChanged,
this, this,
&MainWindow::updatesAllowedChanged); &MainWindow::updatesAllowedChanged);
#endif }
} }
setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString());

View File

@ -90,6 +90,13 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
{ {
APPLICATION->updateChecker()->updateChanList(false); APPLICATION->updateChecker()->updateChanList(false);
} }
if (APPLICATION->updateChecker()->getExternalUpdater())
{
ui->updateChannelComboBox->setVisible(false);
ui->updateChannelDescLabel->setVisible(false);
ui->updateChannelLabel->setVisible(false);
}
} }
else else
{ {
@ -261,11 +268,16 @@ void LauncherPage::applySettings()
auto s = APPLICATION->settings(); auto s = APPLICATION->settings();
// Updates // Updates
#ifdef Q_OS_MAC if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
APPLICATION->updateChecker()->getSparkleUpdater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked()); {
#else APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates(
ui->autoUpdateCheckBox->isChecked());
}
else
{
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
#endif }
s->set("UpdateChannel", m_currentUpdateChannel); s->set("UpdateChannel", m_currentUpdateChannel);
auto original = s->get("IconTheme").toString(); auto original = s->get("IconTheme").toString();
//FIXME: make generic //FIXME: make generic
@ -347,11 +359,16 @@ void LauncherPage::loadSettings()
{ {
auto s = APPLICATION->settings(); auto s = APPLICATION->settings();
// Updates // Updates
#ifdef Q_OS_MAC if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
ui->autoUpdateCheckBox->setChecked(APPLICATION->updateChecker()->getSparkleUpdater()->getAutomaticallyChecksForUpdates()); {
#else ui->autoUpdateCheckBox->setChecked(
APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates());
}
else
{
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
#endif }
m_currentUpdateChannel = s->get("UpdateChannel").toString(); m_currentUpdateChannel = s->get("UpdateChannel").toString();
//FIXME: make generic //FIXME: make generic
auto theme = s->get("IconTheme").toString(); auto theme = s->get("IconTheme").toString();

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Kenneth Chew <kenneth.c0@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef LAUNCHER_EXTERNALUPDATER_H
#define LAUNCHER_EXTERNALUPDATER_H
#include <QObject>
/*!
* A base class for an updater that uses an external library.
* This class contains basic functions to control the updater.
*
* To implement the updater on a new platform, create a new class that inherits from this class and
* implement the pure virtual functions.
*
* The initializer of the new class should have the side effect of starting the automatic updater. That is,
* once the class is initialized, the program should automatically check for updates if necessary.
*/
class ExternalUpdater : public QObject
{
Q_OBJECT
public:
/*!
* Check for updates manually, showing the user a progress bar and an alert if no updates are found.
*/
virtual void checkForUpdates() = 0;
/*!
* Indicates whether or not to check for updates automatically.
*/
virtual bool getAutomaticallyChecksForUpdates() = 0;
/*!
* Indicates the current automatic update check interval in seconds.
*/
virtual double getUpdateCheckInterval() = 0;
/*!
* Indicates whether or not beta updates should be checked for in addition to regular releases.
*/
virtual bool getBetaAllowed() = 0;
/*!
* Set whether or not to check for updates automatically.
*/
virtual void setAutomaticallyChecksForUpdates(bool check) = 0;
/*!
* Set the current automatic update check interval in seconds.
*/
virtual void setUpdateCheckInterval(double seconds) = 0;
/*!
* Set whether or not beta updates should be checked for in addition to regular releases.
*/
virtual void setBetaAllowed(bool allowed) = 0;
signals:
/*!
* Emits whenever the user's ability to check for updates changes.
*
* As per Sparkle documentation, "An update check can be made by the user when an update session isnt in progress,
* or when an update or its progress is being shown to the user. A user cannot check for updates when data (such
* as the feed or an update) is still being downloaded automatically in the background.
*
* This property is suitable to use for menu item validation for seeing if checkForUpdates can be invoked."
*/
void canCheckForUpdatesChanged(bool canCheck);
};
#endif //LAUNCHER_EXTERNALUPDATER_H

View File

@ -16,13 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef LAUNCHER_SPARKLEUPDATER_H #ifndef LAUNCHER_MACSPARKLEUPDATER_H
#define LAUNCHER_SPARKLEUPDATER_H #define LAUNCHER_MACSPARKLEUPDATER_H
#include <QObject> #include <QObject>
#include <QSet> #include <QSet>
#include "ExternalUpdater.h"
class SparkleUpdater : public QObject /*!
* An implementation for the updater on macOS that uses the Sparkle framework.
*/
class MacSparkleUpdater : public ExternalUpdater
{ {
Q_OBJECT Q_OBJECT
@ -30,29 +34,34 @@ public:
/*! /*!
* Start the Sparkle updater, which automatically checks for updates if necessary. * Start the Sparkle updater, which automatically checks for updates if necessary.
*/ */
SparkleUpdater(); MacSparkleUpdater();
~SparkleUpdater(); ~MacSparkleUpdater() override;
/*! /*!
* Check for updates manually, showing the user a progress bar and an alert if no updates are found. * Check for updates manually, showing the user a progress bar and an alert if no updates are found.
*/ */
void checkForUpdates(); void checkForUpdates() override;
/*! /*!
* Indicates whether or not to check for updates automatically. * Indicates whether or not to check for updates automatically.
*/ */
bool getAutomaticallyChecksForUpdates(); bool getAutomaticallyChecksForUpdates() override;
/*! /*!
* Indicates the current automatic update check interval in seconds. * Indicates the current automatic update check interval in seconds.
*/ */
double getUpdateCheckInterval(); double getUpdateCheckInterval() override;
/*! /*!
* Indicates the set of Sparkle channels the updater is allowed to find new updates from. * Indicates the set of Sparkle channels the updater is allowed to find new updates from.
*/ */
QSet<QString> getAllowedChannels(); QSet<QString> getAllowedChannels();
/*!
* Indicates whether or not beta updates should be checked for in addition to regular releases.
*/
bool getBetaAllowed() override;
/*! /*!
* Set whether or not to check for updates automatically. * Set whether or not to check for updates automatically.
* *
@ -66,7 +75,7 @@ public:
* The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow * The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow
* reverting this property without kicking off a schedule change immediately." * reverting this property without kicking off a schedule change immediately."
*/ */
void setAutomaticallyChecksForUpdates(bool check); void setAutomaticallyChecksForUpdates(bool check) override;
/*! /*!
* Set the current automatic update check interval in seconds. * Set the current automatic update check interval in seconds.
@ -78,7 +87,7 @@ public:
* The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow * The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow
* reverting this property without kicking off a schedule change immediately." * reverting this property without kicking off a schedule change immediately."
*/ */
void setUpdateCheckInterval(double seconds); void setUpdateCheckInterval(double seconds) override;
/*! /*!
* Clears all allowed Sparkle channels, returning to the default updater channel behavior. * Clears all allowed Sparkle channels, returning to the default updater channel behavior.
@ -101,17 +110,10 @@ public:
*/ */
void setAllowedChannels(const QSet<QString>& channels); void setAllowedChannels(const QSet<QString>& channels);
signals:
/*! /*!
* Emits whenever the user's ability to check for updates changes. * Set whether or not beta updates should be checked for in addition to regular releases.
*
* As per Sparkle documentation, "An update check can be made by the user when an update session isnt in progress,
* or when an update or its progress is being shown to the user. A user cannot check for updates when data (such
* as the feed or an update) is still being downloaded automatically in the background.
*
* This property is suitable to use for menu item validation for seeing if checkForUpdates can be invoked."
*/ */
void canCheckForUpdatesChanged(bool canCheck); void setBetaAllowed(bool allowed) override;
private: private:
class Private; class Private;
@ -121,4 +123,4 @@ private:
void loadChannelsFromSettings(); void loadChannelsFromSettings();
}; };
#endif //LAUNCHER_SPARKLEUPDATER_H #endif //LAUNCHER_MACSPARKLEUPDATER_H

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "SparkleUpdater.h" #include "MacSparkleUpdater.h"
#include "Application.h" #include "Application.h"
@ -76,7 +76,7 @@
@end @end
class SparkleUpdater::Private class MacSparkleUpdater::Private
{ {
public: public:
SPUStandardUpdaterController *updaterController; SPUStandardUpdaterController *updaterController;
@ -85,9 +85,9 @@ public:
NSAutoreleasePool *autoReleasePool; NSAutoreleasePool *autoReleasePool;
}; };
SparkleUpdater::SparkleUpdater() MacSparkleUpdater::MacSparkleUpdater()
{ {
priv = new SparkleUpdater::Private(); priv = new MacSparkleUpdater::Private();
// Enable Cocoa's memory management. // Enable Cocoa's memory management.
NSApplicationLoad(); NSApplicationLoad();
@ -110,7 +110,7 @@ SparkleUpdater::SparkleUpdater()
loadChannelsFromSettings(); loadChannelsFromSettings();
} }
SparkleUpdater::~SparkleUpdater() MacSparkleUpdater::~MacSparkleUpdater()
{ {
[priv->updaterObserver removeObserver:priv->updaterObserver forKeyPath:@"updater.canCheckForUpdates"]; [priv->updaterObserver removeObserver:priv->updaterObserver forKeyPath:@"updater.canCheckForUpdates"];
@ -121,22 +121,22 @@ SparkleUpdater::~SparkleUpdater()
delete priv; delete priv;
} }
void SparkleUpdater::checkForUpdates() void MacSparkleUpdater::checkForUpdates()
{ {
[priv->updaterController checkForUpdates:nil]; [priv->updaterController checkForUpdates:nil];
} }
bool SparkleUpdater::getAutomaticallyChecksForUpdates() bool MacSparkleUpdater::getAutomaticallyChecksForUpdates()
{ {
return priv->updaterController.updater.automaticallyChecksForUpdates; return priv->updaterController.updater.automaticallyChecksForUpdates;
} }
double SparkleUpdater::getUpdateCheckInterval() double MacSparkleUpdater::getUpdateCheckInterval()
{ {
return priv->updaterController.updater.updateCheckInterval; return priv->updaterController.updater.updateCheckInterval;
} }
QSet<QString> SparkleUpdater::getAllowedChannels() QSet<QString> MacSparkleUpdater::getAllowedChannels()
{ {
// Convert NSSet<NSString> -> QSet<QString> // Convert NSSet<NSString> -> QSet<QString>
__block QSet<QString> channels; __block QSet<QString> channels;
@ -147,23 +147,28 @@ QSet<QString> SparkleUpdater::getAllowedChannels()
return channels; return channels;
} }
void SparkleUpdater::setAutomaticallyChecksForUpdates(bool check) bool MacSparkleUpdater::getBetaAllowed()
{
return getAllowedChannels().contains("beta");
}
void MacSparkleUpdater::setAutomaticallyChecksForUpdates(bool check)
{ {
priv->updaterController.updater.automaticallyChecksForUpdates = check ? YES : NO; // make clang-tidy happy priv->updaterController.updater.automaticallyChecksForUpdates = check ? YES : NO; // make clang-tidy happy
} }
void SparkleUpdater::setUpdateCheckInterval(double seconds) void MacSparkleUpdater::setUpdateCheckInterval(double seconds)
{ {
priv->updaterController.updater.updateCheckInterval = seconds; priv->updaterController.updater.updateCheckInterval = seconds;
} }
void SparkleUpdater::clearAllowedChannels() void MacSparkleUpdater::clearAllowedChannels()
{ {
priv->updaterDelegate.allowedChannels = [NSSet set]; priv->updaterDelegate.allowedChannels = [NSSet set];
APPLICATION->settings()->set("UpdateChannel", ""); APPLICATION->settings()->set("UpdateChannel", "");
} }
void SparkleUpdater::setAllowedChannel(const QString &channel) void MacSparkleUpdater::setAllowedChannel(const QString &channel)
{ {
if (channel.isEmpty()) if (channel.isEmpty())
{ {
@ -176,7 +181,7 @@ void SparkleUpdater::setAllowedChannel(const QString &channel)
APPLICATION->settings()->set("UpdateChannel", channel); APPLICATION->settings()->set("UpdateChannel", channel);
} }
void SparkleUpdater::setAllowedChannels(const QSet<QString> &channels) void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
{ {
if (channels.isEmpty()) if (channels.isEmpty())
{ {
@ -197,7 +202,19 @@ void SparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed()); APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed());
} }
void SparkleUpdater::loadChannelsFromSettings() void MacSparkleUpdater::setBetaAllowed(bool allowed)
{
if (allowed)
{
setAllowedChannel("beta");
}
else
{
clearAllowedChannels();
}
}
void MacSparkleUpdater::loadChannelsFromSettings()
{ {
QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" "); QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" ");
auto channels = QSet<QString>::fromList(channelList); auto channels = QSet<QString>::fromList(channelList);

View File

@ -24,7 +24,6 @@
#define CHANLIST_FORMAT 0 #define CHANLIST_FORMAT 0
#include "BuildConfig.h" #include "BuildConfig.h"
#include "sys.h"
UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild) UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild)
{ {
@ -32,6 +31,10 @@ UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QStr
m_channelUrl = channelUrl; m_channelUrl = channelUrl;
m_currentChannel = currentChannel; m_currentChannel = currentChannel;
m_currentBuild = currentBuild; m_currentBuild = currentBuild;
#ifdef Q_OS_MAC
m_externalUpdater = new MacSparkleUpdater();
#endif
} }
QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
@ -44,30 +47,29 @@ bool UpdateChecker::hasChannels() const
return !m_channels.isEmpty(); return !m_channels.isEmpty();
} }
#ifdef Q_OS_MAC ExternalUpdater* UpdateChecker::getExternalUpdater()
SparkleUpdater* UpdateChecker::getSparkleUpdater()
{ {
return m_sparkleUpdater; return m_externalUpdater;
} }
#endif
void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) void UpdateChecker::checkForUpdate(const QString& updateChannel, bool notifyNoUpdate)
{ {
#ifdef Q_OS_MAC if (m_externalUpdater)
m_sparkleUpdater->setAllowedChannel(updateChannel); {
m_externalUpdater->setBetaAllowed(updateChannel == "beta");
if (notifyNoUpdate) if (notifyNoUpdate)
{ {
qDebug() << "Checking for updates."; qDebug() << "Checking for updates.";
m_sparkleUpdater->checkForUpdates(); m_externalUpdater->checkForUpdates();
} else
{
// The updater library already handles automatic update checks.
return;
}
} }
else else
{ {
// Sparkle already handles automatic update checks.
return;
}
#else
qDebug() << "Checking for updates."; qDebug() << "Checking for updates.";
// If the channel list hasn't loaded yet, load it and defer checking for updates until // If the channel list hasn't loaded yet, load it and defer checking for updates until
// later. // later.
if (!m_chanListLoaded) if (!m_chanListLoaded)
@ -92,14 +94,17 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate)
for (ChannelListEntry entry: m_channels) for (ChannelListEntry entry: m_channels)
{ {
qDebug() << "channelEntry = " << entry.id; qDebug() << "channelEntry = " << entry.id;
if(entry.id == "stable") { if (entry.id == "stable")
{
stableUrl = entry.url; stableUrl = entry.url;
} }
if (entry.id == updateChannel) { if (entry.id == updateChannel)
{
m_newRepoUrl = entry.url; m_newRepoUrl = entry.url;
qDebug() << "is intended update channel: " << entry.id; qDebug() << "is intended update channel: " << entry.id;
} }
if (entry.id == m_currentChannel) { if (entry.id == m_currentChannel)
{
m_currentRepoUrl = entry.url; m_currentRepoUrl = entry.url;
qDebug() << "is current update channel: " << entry.id; qDebug() << "is current update channel: " << entry.id;
} }
@ -107,7 +112,8 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate)
qDebug() << "m_repoUrl = " << m_newRepoUrl; qDebug() << "m_repoUrl = " << m_newRepoUrl;
if (m_newRepoUrl.isEmpty()) { if (m_newRepoUrl.isEmpty())
{
qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl; qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl;
m_newRepoUrl = stableUrl; m_newRepoUrl = stableUrl;
} }
@ -129,7 +135,7 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate)
connect(indexJob.get(), &NetJob::succeeded, [this, notifyNoUpdate]() { updateCheckFinished(notifyNoUpdate); }); connect(indexJob.get(), &NetJob::succeeded, [this, notifyNoUpdate]() { updateCheckFinished(notifyNoUpdate); });
connect(indexJob.get(), &NetJob::failed, this, &UpdateChecker::updateCheckFailed); connect(indexJob.get(), &NetJob::failed, this, &UpdateChecker::updateCheckFailed);
indexJob->start(); indexJob->start();
#endif }
} }
void UpdateChecker::updateCheckFinished(bool notifyNoUpdate) void UpdateChecker::updateCheckFinished(bool notifyNoUpdate)

View File

@ -17,9 +17,10 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "GoUpdate.h" #include "GoUpdate.h"
#include "ExternalUpdater.h"
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
#include "updater/macsparkle/SparkleUpdater.h" #include "MacSparkleUpdater.h"
#endif #endif
class UpdateChecker : public QObject class UpdateChecker : public QObject
@ -28,7 +29,7 @@ class UpdateChecker : public QObject
public: public:
UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild); UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild);
void checkForUpdate(QString updateChannel, bool notifyNoUpdate); void checkForUpdate(const QString& updateChannel, bool notifyNoUpdate);
/*! /*!
* Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake).
@ -58,12 +59,10 @@ public:
*/ */
bool hasChannels() const; bool hasChannels() const;
#ifdef Q_OS_MAC
/*! /*!
* Returns a pointer to the Sparkle updater. * Returns a pointer to an object that controls the external updater, or nullptr if an external updater is not used.
*/ */
SparkleUpdater *getSparkleUpdater(); ExternalUpdater *getExternalUpdater();
#endif
signals: signals:
//! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version.
@ -129,8 +128,13 @@ private:
QString m_newRepoUrl; QString m_newRepoUrl;
#ifdef Q_OS_MAC /*!
SparkleUpdater *m_sparkleUpdater = new SparkleUpdater(); * If not a nullptr, then the updater here will be used instead of the old updater that uses GoUpdate when
#endif * checking for updates.
*
* As a result, signals from this class won't be emitted, and most of the functions in this class other
* than checkForUpdate are not useful. Call functions from this external updater object instead.
*/
ExternalUpdater *m_externalUpdater = nullptr;
}; };