From 05cd30ac06b67ebc594773fc7e7ccf110fc336a3 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Mon, 25 Apr 2022 19:33:17 -0400 Subject: 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. --- launcher/CMakeLists.txt | 5 +- launcher/ui/MainWindow.cpp | 13 +- launcher/ui/pages/global/LauncherPage.cpp | 37 +++-- launcher/updater/ExternalUpdater.h | 87 ++++++++++ launcher/updater/MacSparkleUpdater.h | 126 +++++++++++++++ launcher/updater/MacSparkleUpdater.mm | 222 ++++++++++++++++++++++++++ launcher/updater/UpdateChecker.cpp | 146 +++++++++-------- launcher/updater/UpdateChecker.h | 22 +-- launcher/updater/macsparkle/SparkleUpdater.h | 124 -------------- launcher/updater/macsparkle/SparkleUpdater.mm | 205 ------------------------ 10 files changed, 561 insertions(+), 426 deletions(-) create mode 100644 launcher/updater/ExternalUpdater.h create mode 100644 launcher/updater/MacSparkleUpdater.h create mode 100644 launcher/updater/MacSparkleUpdater.mm delete mode 100644 launcher/updater/macsparkle/SparkleUpdater.h delete mode 100644 launcher/updater/macsparkle/SparkleUpdater.mm diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dc10c38e..51f3dc36 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -162,11 +162,12 @@ set(UPDATE_SOURCES updater/UpdateChecker.cpp updater/DownloadTask.h updater/DownloadTask.cpp + updater/ExternalUpdater.h ) set(MAC_UPDATE_SOURCES - updater/macsparkle/SparkleUpdater.h - updater/macsparkle/SparkleUpdater.mm + updater/MacSparkleUpdater.h + updater/MacSparkleUpdater.mm ) add_unit_test(UpdateChecker diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 12761da1..951fcccf 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1028,12 +1028,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); } -#ifdef Q_OS_MAC - connect(APPLICATION->updateChecker()->getSparkleUpdater(), - &SparkleUpdater::canCheckForUpdatesChanged, - this, - &MainWindow::updatesAllowedChanged); -#endif + if (APPLICATION->updateChecker()->getExternalUpdater()) + { + connect(APPLICATION->updateChecker()->getExternalUpdater(), + &ExternalUpdater::canCheckForUpdatesChanged, + this, + &MainWindow::updatesAllowedChanged); + } } setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 51284a8e..b244b039 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -90,6 +90,13 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch { APPLICATION->updateChecker()->updateChanList(false); } + + if (APPLICATION->updateChecker()->getExternalUpdater()) + { + ui->updateChannelComboBox->setVisible(false); + ui->updateChannelDescLabel->setVisible(false); + ui->updateChannelLabel->setVisible(false); + } } else { @@ -261,11 +268,16 @@ void LauncherPage::applySettings() auto s = APPLICATION->settings(); // Updates -#ifdef Q_OS_MAC - APPLICATION->updateChecker()->getSparkleUpdater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked()); -#else - s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); -#endif + if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater()) + { + APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates( + ui->autoUpdateCheckBox->isChecked()); + } + else + { + s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); + } + s->set("UpdateChannel", m_currentUpdateChannel); auto original = s->get("IconTheme").toString(); //FIXME: make generic @@ -347,11 +359,16 @@ void LauncherPage::loadSettings() { auto s = APPLICATION->settings(); // Updates -#ifdef Q_OS_MAC - ui->autoUpdateCheckBox->setChecked(APPLICATION->updateChecker()->getSparkleUpdater()->getAutomaticallyChecksForUpdates()); -#else - ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); -#endif + if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater()) + { + ui->autoUpdateCheckBox->setChecked( + APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates()); + } + else + { + ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); + } + m_currentUpdateChannel = s->get("UpdateChannel").toString(); //FIXME: make generic auto theme = s->get("IconTheme").toString(); diff --git a/launcher/updater/ExternalUpdater.h b/launcher/updater/ExternalUpdater.h new file mode 100644 index 00000000..a053e081 --- /dev/null +++ b/launcher/updater/ExternalUpdater.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Kenneth Chew + * + * 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 . + */ + +#ifndef LAUNCHER_EXTERNALUPDATER_H +#define LAUNCHER_EXTERNALUPDATER_H + +#include + +/*! + * 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 isn’t 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 diff --git a/launcher/updater/MacSparkleUpdater.h b/launcher/updater/MacSparkleUpdater.h new file mode 100644 index 00000000..d50dbd68 --- /dev/null +++ b/launcher/updater/MacSparkleUpdater.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Kenneth Chew + * + * 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 . + */ + +#ifndef LAUNCHER_MACSPARKLEUPDATER_H +#define LAUNCHER_MACSPARKLEUPDATER_H + +#include +#include +#include "ExternalUpdater.h" + +/*! + * An implementation for the updater on macOS that uses the Sparkle framework. + */ +class MacSparkleUpdater : public ExternalUpdater +{ + Q_OBJECT + +public: + /*! + * Start the Sparkle updater, which automatically checks for updates if necessary. + */ + MacSparkleUpdater(); + ~MacSparkleUpdater() override; + + /*! + * Check for updates manually, showing the user a progress bar and an alert if no updates are found. + */ + void checkForUpdates() override; + + /*! + * Indicates whether or not to check for updates automatically. + */ + bool getAutomaticallyChecksForUpdates() override; + + /*! + * Indicates the current automatic update check interval in seconds. + */ + double getUpdateCheckInterval() override; + + /*! + * Indicates the set of Sparkle channels the updater is allowed to find new updates from. + */ + QSet 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. + * + * As per Sparkle documentation, "By default, Sparkle asks users on second launch for permission if they want + * automatic update checks enabled and sets this property based on their response. If SUEnableAutomaticChecks is + * set in the Info.plist, this permission request is not performed however. + * + * Setting this property will persist in the host bundle’s user defaults. Only set this property if you need + * dynamic behavior (e.g. user preferences). + * + * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow + * reverting this property without kicking off a schedule change immediately." + */ + void setAutomaticallyChecksForUpdates(bool check) override; + + /*! + * Set the current automatic update check interval in seconds. + * + * As per Sparkle documentation, "Setting this property will persist in the host bundle’s user defaults. For this + * reason, only set this property if you need dynamic behavior (eg user preferences). Otherwise prefer to set + * SUScheduledCheckInterval directly in your Info.plist. + * + * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow + * reverting this property without kicking off a schedule change immediately." + */ + void setUpdateCheckInterval(double seconds) override; + + /*! + * Clears all allowed Sparkle channels, returning to the default updater channel behavior. + */ + void clearAllowedChannels(); + + /*! + * Set a single Sparkle channel the updater is allowed to find new updates from. + * + * Items in the default channel can always be found, regardless of this setting. If an empty string is passed, + * return to the default behavior. + */ + void setAllowedChannel(const QString& channel); + + /*! + * Set a set of Sparkle channels the updater is allowed to find new updates from. + * + * Items in the default channel can always be found, regardless of this setting. If an empty set is passed, + * return to the default behavior. + */ + void setAllowedChannels(const QSet& channels); + + /*! + * Set whether or not beta updates should be checked for in addition to regular releases. + */ + void setBetaAllowed(bool allowed) override; + +private: + class Private; + + Private *priv; + + void loadChannelsFromSettings(); +}; + +#endif //LAUNCHER_MACSPARKLEUPDATER_H diff --git a/launcher/updater/MacSparkleUpdater.mm b/launcher/updater/MacSparkleUpdater.mm new file mode 100644 index 00000000..63f5469b --- /dev/null +++ b/launcher/updater/MacSparkleUpdater.mm @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Kenneth Chew + * + * 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 . + */ + +#include "MacSparkleUpdater.h" + +#include "Application.h" + +#include +#include + +@interface UpdaterObserver : NSObject + +@property(nonatomic, readonly) SPUUpdater* updater; + +/// A callback to run when the state of `canCheckForUpdates` for the `updater` changes. +@property(nonatomic, copy) void (^callback) (bool); + +- (id)initWithUpdater:(SPUUpdater*)updater; + +@end + +@implementation UpdaterObserver + +- (id)initWithUpdater:(SPUUpdater*)updater +{ + self = [super init]; + _updater = updater; + [self addObserver:self forKeyPath:@"updater.canCheckForUpdates" options:NSKeyValueObservingOptionNew context:nil]; + + return self; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + if ([keyPath isEqualToString:@"updater.canCheckForUpdates"]) + { + bool canCheck = [change[NSKeyValueChangeNewKey] boolValue]; + self.callback(canCheck); + } +} + +@end + + +@interface UpdaterDelegate : NSObject + +@property(nonatomic, copy) NSSet *allowedChannels; + +@end + +@implementation UpdaterDelegate + +- (NSSet *)allowedChannelsForUpdater:(SPUUpdater *)updater +{ + return _allowedChannels; +} + +@end + + +class MacSparkleUpdater::Private +{ +public: + SPUStandardUpdaterController *updaterController; + UpdaterObserver *updaterObserver; + UpdaterDelegate *updaterDelegate; + NSAutoreleasePool *autoReleasePool; +}; + +MacSparkleUpdater::MacSparkleUpdater() +{ + priv = new MacSparkleUpdater::Private(); + + // Enable Cocoa's memory management. + NSApplicationLoad(); + priv->autoReleasePool = [[NSAutoreleasePool alloc] init]; + + // Delegate is used for setting/getting allowed update channels. + priv->updaterDelegate = [[UpdaterDelegate alloc] init]; + + // Controller is the interface for actually doing the updates. + priv->updaterController = [[SPUStandardUpdaterController alloc] initWithStartingUpdater:true + updaterDelegate:priv->updaterDelegate + userDriverDelegate:nil]; + + priv->updaterObserver = [[UpdaterObserver alloc] initWithUpdater:priv->updaterController.updater]; + // Use KVO to run a callback that emits a Qt signal when `canCheckForUpdates` changes, so the UI can respond accordingly. + priv->updaterObserver.callback = ^(bool canCheck) { + emit canCheckForUpdatesChanged(canCheck); + }; + + loadChannelsFromSettings(); +} + +MacSparkleUpdater::~MacSparkleUpdater() +{ + [priv->updaterObserver removeObserver:priv->updaterObserver forKeyPath:@"updater.canCheckForUpdates"]; + + [priv->updaterController release]; + [priv->updaterObserver release]; + [priv->updaterDelegate release]; + [priv->autoReleasePool release]; + delete priv; +} + +void MacSparkleUpdater::checkForUpdates() +{ + [priv->updaterController checkForUpdates:nil]; +} + +bool MacSparkleUpdater::getAutomaticallyChecksForUpdates() +{ + return priv->updaterController.updater.automaticallyChecksForUpdates; +} + +double MacSparkleUpdater::getUpdateCheckInterval() +{ + return priv->updaterController.updater.updateCheckInterval; +} + +QSet MacSparkleUpdater::getAllowedChannels() +{ + // Convert NSSet -> QSet + __block QSet channels; + [priv->updaterDelegate.allowedChannels enumerateObjectsUsingBlock:^(NSString *channel, BOOL *stop) + { + channels.insert(QString::fromNSString(channel)); + }]; + return channels; +} + +bool MacSparkleUpdater::getBetaAllowed() +{ + return getAllowedChannels().contains("beta"); +} + +void MacSparkleUpdater::setAutomaticallyChecksForUpdates(bool check) +{ + priv->updaterController.updater.automaticallyChecksForUpdates = check ? YES : NO; // make clang-tidy happy +} + +void MacSparkleUpdater::setUpdateCheckInterval(double seconds) +{ + priv->updaterController.updater.updateCheckInterval = seconds; +} + +void MacSparkleUpdater::clearAllowedChannels() +{ + priv->updaterDelegate.allowedChannels = [NSSet set]; + APPLICATION->settings()->set("UpdateChannel", ""); +} + +void MacSparkleUpdater::setAllowedChannel(const QString &channel) +{ + if (channel.isEmpty()) + { + clearAllowedChannels(); + return; + } + + NSSet *nsChannels = [NSSet setWithObject:channel.toNSString()]; + priv->updaterDelegate.allowedChannels = nsChannels; + APPLICATION->settings()->set("UpdateChannel", channel); +} + +void MacSparkleUpdater::setAllowedChannels(const QSet &channels) +{ + if (channels.isEmpty()) + { + clearAllowedChannels(); + return; + } + + QString channelsConfig = ""; + // Convert QSet -> NSSet + NSMutableSet *nsChannels = [NSMutableSet setWithCapacity:channels.count()]; + foreach (const QString channel, channels) + { + [nsChannels addObject:channel.toNSString()]; + channelsConfig += channel + " "; + } + + priv->updaterDelegate.allowedChannels = nsChannels; + APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed()); +} + +void MacSparkleUpdater::setBetaAllowed(bool allowed) +{ + if (allowed) + { + setAllowedChannel("beta"); + } + else + { + clearAllowedChannels(); + } +} + +void MacSparkleUpdater::loadChannelsFromSettings() +{ + QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" "); + auto channels = QSet::fromList(channelList); + setAllowedChannels(channels); +} diff --git a/launcher/updater/UpdateChecker.cpp b/launcher/updater/UpdateChecker.cpp index ad159841..fa6e5a97 100644 --- a/launcher/updater/UpdateChecker.cpp +++ b/launcher/updater/UpdateChecker.cpp @@ -24,7 +24,6 @@ #define CHANLIST_FORMAT 0 #include "BuildConfig.h" -#include "sys.h" UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel, int currentBuild) { @@ -32,6 +31,10 @@ UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QStr m_channelUrl = channelUrl; m_currentChannel = currentChannel; m_currentBuild = currentBuild; + +#ifdef Q_OS_MAC + m_externalUpdater = new MacSparkleUpdater(); +#endif } QList UpdateChecker::getChannelList() const @@ -44,92 +47,95 @@ bool UpdateChecker::hasChannels() const return !m_channels.isEmpty(); } -#ifdef Q_OS_MAC -SparkleUpdater* UpdateChecker::getSparkleUpdater() +ExternalUpdater* UpdateChecker::getExternalUpdater() { - 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 - m_sparkleUpdater->setAllowedChannel(updateChannel); - if (notifyNoUpdate) + if (m_externalUpdater) { - qDebug() << "Checking for updates."; - m_sparkleUpdater->checkForUpdates(); + m_externalUpdater->setBetaAllowed(updateChannel == "beta"); + if (notifyNoUpdate) + { + qDebug() << "Checking for updates."; + m_externalUpdater->checkForUpdates(); + } else + { + // The updater library already handles automatic update checks. + return; + } } else { - // Sparkle already handles automatic update checks. - return; - } -#else - qDebug() << "Checking for updates."; - - // If the channel list hasn't loaded yet, load it and defer checking for updates until - // later. - if (!m_chanListLoaded) - { - qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring update check."; - m_checkUpdateWaiting = true; - m_deferredUpdateChannel = updateChannel; - updateChanList(notifyNoUpdate); - return; - } - - if (m_updateChecking) - { - qDebug() << "Ignoring update check request. Already checking for updates."; - return; - } - - // Find the desired channel within the channel list and get its repo URL. If if cannot be - // found, error. - QString stableUrl; - m_newRepoUrl = ""; - for (ChannelListEntry entry : m_channels) - { - qDebug() << "channelEntry = " << entry.id; - if(entry.id == "stable") { - stableUrl = entry.url; + qDebug() << "Checking for updates."; + // If the channel list hasn't loaded yet, load it and defer checking for updates until + // later. + if (!m_chanListLoaded) + { + qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring update check."; + m_checkUpdateWaiting = true; + m_deferredUpdateChannel = updateChannel; + updateChanList(notifyNoUpdate); + return; } - if (entry.id == updateChannel) { - m_newRepoUrl = entry.url; - qDebug() << "is intended update channel: " << entry.id; + + if (m_updateChecking) + { + qDebug() << "Ignoring update check request. Already checking for updates."; + return; } - if (entry.id == m_currentChannel) { - m_currentRepoUrl = entry.url; - qDebug() << "is current update channel: " << entry.id; + + // Find the desired channel within the channel list and get its repo URL. If if cannot be + // found, error. + QString stableUrl; + m_newRepoUrl = ""; + for (ChannelListEntry entry: m_channels) + { + qDebug() << "channelEntry = " << entry.id; + if (entry.id == "stable") + { + stableUrl = entry.url; + } + if (entry.id == updateChannel) + { + m_newRepoUrl = entry.url; + qDebug() << "is intended update channel: " << entry.id; + } + if (entry.id == m_currentChannel) + { + m_currentRepoUrl = entry.url; + qDebug() << "is current update channel: " << entry.id; + } } - } - qDebug() << "m_repoUrl = " << m_newRepoUrl; + qDebug() << "m_repoUrl = " << m_newRepoUrl; - if (m_newRepoUrl.isEmpty()) { - qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl; - m_newRepoUrl = stableUrl; - } + if (m_newRepoUrl.isEmpty()) + { + qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl; + m_newRepoUrl = stableUrl; + } - // If nothing applies, error - if (m_newRepoUrl.isEmpty()) - { - qCritical() << "failed to select any update repository for: " << updateChannel; - emit updateCheckFailed(); - return; - } + // If nothing applies, error + if (m_newRepoUrl.isEmpty()) + { + qCritical() << "failed to select any update repository for: " << updateChannel; + emit updateCheckFailed(); + return; + } - m_updateChecking = true; + m_updateChecking = true; - QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); + QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); - indexJob = new NetJob("GoUpdate Repository Index", m_network); - indexJob->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); - connect(indexJob.get(), &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); - connect(indexJob.get(), &NetJob::failed, this, &UpdateChecker::updateCheckFailed); - indexJob->start(); -#endif + indexJob = new NetJob("GoUpdate Repository Index", m_network); + indexJob->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); + connect(indexJob.get(), &NetJob::succeeded, [this, notifyNoUpdate]() { updateCheckFinished(notifyNoUpdate); }); + connect(indexJob.get(), &NetJob::failed, this, &UpdateChecker::updateCheckFailed); + indexJob->start(); + } } void UpdateChecker::updateCheckFinished(bool notifyNoUpdate) diff --git a/launcher/updater/UpdateChecker.h b/launcher/updater/UpdateChecker.h index c569e49a..94e4312b 100644 --- a/launcher/updater/UpdateChecker.h +++ b/launcher/updater/UpdateChecker.h @@ -17,9 +17,10 @@ #include "net/NetJob.h" #include "GoUpdate.h" +#include "ExternalUpdater.h" #ifdef Q_OS_MAC -#include "updater/macsparkle/SparkleUpdater.h" +#include "MacSparkleUpdater.h" #endif class UpdateChecker : public QObject @@ -28,7 +29,7 @@ class UpdateChecker : public QObject public: UpdateChecker(shared_qobject_ptr 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). @@ -58,12 +59,10 @@ public: */ 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(); -#endif + ExternalUpdater *getExternalUpdater(); signals: //! 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; -#ifdef Q_OS_MAC - SparkleUpdater *m_sparkleUpdater = new SparkleUpdater(); -#endif + /*! + * If not a nullptr, then the updater here will be used instead of the old updater that uses GoUpdate when + * 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; }; diff --git a/launcher/updater/macsparkle/SparkleUpdater.h b/launcher/updater/macsparkle/SparkleUpdater.h deleted file mode 100644 index 9768d960..00000000 --- a/launcher/updater/macsparkle/SparkleUpdater.h +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Kenneth Chew - * - * 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 . - */ - -#ifndef LAUNCHER_SPARKLEUPDATER_H -#define LAUNCHER_SPARKLEUPDATER_H - -#include -#include - -class SparkleUpdater : public QObject -{ - Q_OBJECT - -public: - /*! - * Start the Sparkle updater, which automatically checks for updates if necessary. - */ - SparkleUpdater(); - ~SparkleUpdater(); - - /*! - * Check for updates manually, showing the user a progress bar and an alert if no updates are found. - */ - void checkForUpdates(); - - /*! - * Indicates whether or not to check for updates automatically. - */ - bool getAutomaticallyChecksForUpdates(); - - /*! - * Indicates the current automatic update check interval in seconds. - */ - double getUpdateCheckInterval(); - - /*! - * Indicates the set of Sparkle channels the updater is allowed to find new updates from. - */ - QSet getAllowedChannels(); - - /*! - * Set whether or not to check for updates automatically. - * - * As per Sparkle documentation, "By default, Sparkle asks users on second launch for permission if they want - * automatic update checks enabled and sets this property based on their response. If SUEnableAutomaticChecks is - * set in the Info.plist, this permission request is not performed however. - * - * Setting this property will persist in the host bundle’s user defaults. Only set this property if you need - * dynamic behavior (e.g. user preferences). - * - * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." - */ - void setAutomaticallyChecksForUpdates(bool check); - - /*! - * Set the current automatic update check interval in seconds. - * - * As per Sparkle documentation, "Setting this property will persist in the host bundle’s user defaults. For this - * reason, only set this property if you need dynamic behavior (eg user preferences). Otherwise prefer to set - * SUScheduledCheckInterval directly in your Info.plist. - * - * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." - */ - void setUpdateCheckInterval(double seconds); - - /*! - * Clears all allowed Sparkle channels, returning to the default updater channel behavior. - */ - void clearAllowedChannels(); - - /*! - * Set a single Sparkle channel the updater is allowed to find new updates from. - * - * Items in the default channel can always be found, regardless of this setting. If an empty string is passed, - * return to the default behavior. - */ - void setAllowedChannel(const QString& channel); - - /*! - * Set a set of Sparkle channels the updater is allowed to find new updates from. - * - * Items in the default channel can always be found, regardless of this setting. If an empty set is passed, - * return to the default behavior. - */ - void setAllowedChannels(const QSet& channels); - -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 isn’t 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); - -private: - class Private; - - Private* priv; - - void loadChannelsFromSettings(); -}; - -#endif //LAUNCHER_SPARKLEUPDATER_H diff --git a/launcher/updater/macsparkle/SparkleUpdater.mm b/launcher/updater/macsparkle/SparkleUpdater.mm deleted file mode 100644 index ad7b83c8..00000000 --- a/launcher/updater/macsparkle/SparkleUpdater.mm +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Kenneth Chew - * - * 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 . - */ - -#include "SparkleUpdater.h" - -#include "Application.h" - -#include -#include - -@interface UpdaterObserver : NSObject - -@property(nonatomic, readonly) SPUUpdater* updater; - -/// A callback to run when the state of `canCheckForUpdates` for the `updater` changes. -@property(nonatomic, copy) void (^callback) (bool); - -- (id)initWithUpdater:(SPUUpdater*)updater; - -@end - -@implementation UpdaterObserver - -- (id)initWithUpdater:(SPUUpdater*)updater -{ - self = [super init]; - _updater = updater; - [self addObserver:self forKeyPath:@"updater.canCheckForUpdates" options:NSKeyValueObservingOptionNew context:nil]; - - return self; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ - if ([keyPath isEqualToString:@"updater.canCheckForUpdates"]) - { - bool canCheck = [change[NSKeyValueChangeNewKey] boolValue]; - self.callback(canCheck); - } -} - -@end - - -@interface UpdaterDelegate : NSObject - -@property(nonatomic, copy) NSSet *allowedChannels; - -@end - -@implementation UpdaterDelegate - -- (NSSet *)allowedChannelsForUpdater:(SPUUpdater *)updater -{ - return _allowedChannels; -} - -@end - - -class SparkleUpdater::Private -{ -public: - SPUStandardUpdaterController *updaterController; - UpdaterObserver *updaterObserver; - UpdaterDelegate *updaterDelegate; - NSAutoreleasePool *autoReleasePool; -}; - -SparkleUpdater::SparkleUpdater() -{ - priv = new SparkleUpdater::Private(); - - // Enable Cocoa's memory management. - NSApplicationLoad(); - priv->autoReleasePool = [[NSAutoreleasePool alloc] init]; - - // Delegate is used for setting/getting allowed update channels. - priv->updaterDelegate = [[UpdaterDelegate alloc] init]; - - // Controller is the interface for actually doing the updates. - priv->updaterController = [[SPUStandardUpdaterController alloc] initWithStartingUpdater:true - updaterDelegate:priv->updaterDelegate - userDriverDelegate:nil]; - - priv->updaterObserver = [[UpdaterObserver alloc] initWithUpdater:priv->updaterController.updater]; - // Use KVO to run a callback that emits a Qt signal when `canCheckForUpdates` changes, so the UI can respond accordingly. - priv->updaterObserver.callback = ^(bool canCheck) { - emit canCheckForUpdatesChanged(canCheck); - }; - - loadChannelsFromSettings(); -} - -SparkleUpdater::~SparkleUpdater() -{ - [priv->updaterObserver removeObserver:priv->updaterObserver forKeyPath:@"updater.canCheckForUpdates"]; - - [priv->updaterController release]; - [priv->updaterObserver release]; - [priv->updaterDelegate release]; - [priv->autoReleasePool release]; - delete priv; -} - -void SparkleUpdater::checkForUpdates() -{ - [priv->updaterController checkForUpdates:nil]; -} - -bool SparkleUpdater::getAutomaticallyChecksForUpdates() -{ - return priv->updaterController.updater.automaticallyChecksForUpdates; -} - -double SparkleUpdater::getUpdateCheckInterval() -{ - return priv->updaterController.updater.updateCheckInterval; -} - -QSet SparkleUpdater::getAllowedChannels() -{ - // Convert NSSet -> QSet - __block QSet channels; - [priv->updaterDelegate.allowedChannels enumerateObjectsUsingBlock:^(NSString *channel, BOOL *stop) - { - channels.insert(QString::fromNSString(channel)); - }]; - return channels; -} - -void SparkleUpdater::setAutomaticallyChecksForUpdates(bool check) -{ - priv->updaterController.updater.automaticallyChecksForUpdates = check ? YES : NO; // make clang-tidy happy -} - -void SparkleUpdater::setUpdateCheckInterval(double seconds) -{ - priv->updaterController.updater.updateCheckInterval = seconds; -} - -void SparkleUpdater::clearAllowedChannels() -{ - priv->updaterDelegate.allowedChannels = [NSSet set]; - APPLICATION->settings()->set("UpdateChannel", ""); -} - -void SparkleUpdater::setAllowedChannel(const QString &channel) -{ - if (channel.isEmpty()) - { - clearAllowedChannels(); - return; - } - - NSSet *nsChannels = [NSSet setWithObject:channel.toNSString()]; - priv->updaterDelegate.allowedChannels = nsChannels; - APPLICATION->settings()->set("UpdateChannel", channel); -} - -void SparkleUpdater::setAllowedChannels(const QSet &channels) -{ - if (channels.isEmpty()) - { - clearAllowedChannels(); - return; - } - - QString channelsConfig = ""; - // Convert QSet -> NSSet - NSMutableSet *nsChannels = [NSMutableSet setWithCapacity:channels.count()]; - foreach (const QString channel, channels) - { - [nsChannels addObject:channel.toNSString()]; - channelsConfig += channel + " "; - } - - priv->updaterDelegate.allowedChannels = nsChannels; - APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed()); -} - -void SparkleUpdater::loadChannelsFromSettings() -{ - QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" "); - auto channels = QSet::fromList(channelList); - setAllowedChannels(channels); -} -- cgit