summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm
diff options
context:
space:
mode:
Diffstat (limited to 'archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm')
-rw-r--r--archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm264
1 files changed, 264 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm b/archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm
new file mode 100644
index 0000000000..82de1074ee
--- /dev/null
+++ b/archived/projt-launcher/launcher/updater/MacSparkleUpdater.mm
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * === Upstream License Block (Do Not Modify) ==============================
+ *
+ *
+ *
+ *
+ * Prism Launcher - 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ *
+ * ======================================================================== */
+
+#include "MacSparkleUpdater.h"
+
+#include "Application.h"
+#include "BuildConfig.h"
+
+#include <Cocoa/Cocoa.h>
+#include <Sparkle/Sparkle.h>
+
+@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<NSKeyValueChangeKey, id>*)change
+ context:(void*)context {
+ if ([keyPath isEqualToString:@"updater.canCheckForUpdates"]) {
+ bool canCheck = [change[NSKeyValueChangeNewKey] boolValue];
+ self.callback(canCheck);
+ }
+}
+
+@end
+
+@interface UpdaterDelegate : NSObject <SPUUpdaterDelegate>
+
+@property(nonatomic, copy) NSSet<NSString*>* allowedChannels;
+
+@end
+
+@implementation UpdaterDelegate
+
+- (NSSet<NSString*>*)allowedChannelsForUpdater:(SPUUpdater*)updater {
+ return _allowedChannels;
+}
+
+@end
+
+class MacSparkleUpdater::Private {
+ public:
+ SPUStandardUpdaterController* updaterController;
+ UpdaterObserver* updaterObserver;
+ UpdaterDelegate* updaterDelegate;
+ NSAutoreleasePool* autoReleasePool;
+ QString lineChannel;
+ QString migrationChannel;
+};
+
+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);
+ };
+
+ auto extractLineChannel = [](QString version) -> QString {
+ version = version.trimmed();
+ if (version.startsWith(QLatin1Char('v'), Qt::CaseInsensitive)) {
+ version.remove(0, 1);
+ }
+ while (version.endsWith(QLatin1Char('.'))) {
+ version.chop(1);
+ }
+
+ if (version.contains(QLatin1Char('-'))) {
+ return version.section(QLatin1Char('-'), 0, 0);
+ }
+
+ const auto parts = version.split(QLatin1Char('.'), Qt::KeepEmptyParts);
+ if (parts.size() >= 3) {
+ return QString("%1.%2.%3").arg(parts.at(0), parts.at(1), parts.at(2));
+ }
+ return QString();
+ };
+
+ priv->lineChannel = extractLineChannel(BuildConfig.printableVersionString());
+ if (!priv->lineChannel.isEmpty()) {
+ const auto parts = priv->lineChannel.split(QLatin1Char('.'), Qt::KeepEmptyParts);
+ if (parts.size() == 3) {
+ bool ok = false;
+ const auto z = parts.at(2).toInt(&ok);
+ if (ok) {
+ priv->migrationChannel = QString("%1.%2.%3").arg(parts.at(0), parts.at(1)).arg(z + 1);
+ }
+ }
+
+ QSet<QString> channels{priv->lineChannel};
+ if (!priv->migrationChannel.isEmpty()) {
+ channels.insert(priv->migrationChannel);
+ }
+ setAllowedChannels(channels);
+ }
+}
+
+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<QString> MacSparkleUpdater::getAllowedChannels() {
+ // Convert NSSet<NSString> -> QSet<QString>
+ __block QSet<QString> 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];
+}
+
+void MacSparkleUpdater::setAllowedChannel(const QString& channel) {
+ if (channel.isEmpty()) {
+ clearAllowedChannels();
+ return;
+ }
+
+ NSSet<NSString*>* nsChannels = [NSSet setWithObject:channel.toNSString()];
+ priv->updaterDelegate.allowedChannels = nsChannels;
+}
+
+void MacSparkleUpdater::setAllowedChannels(const QSet<QString>& channels) {
+ if (channels.isEmpty()) {
+ clearAllowedChannels();
+ return;
+ }
+
+ QString channelsConfig = "";
+ // Convert QSet<QString> -> NSSet<NSString>
+ NSMutableSet<NSString*>* nsChannels = [NSMutableSet setWithCapacity:channels.count()];
+ for (const QString& channel : channels) {
+ [nsChannels addObject:channel.toNSString()];
+ channelsConfig += channel + " ";
+ }
+
+ priv->updaterDelegate.allowedChannels = nsChannels;
+}
+
+void MacSparkleUpdater::setBetaAllowed(bool allowed) {
+ if (priv->lineChannel.isEmpty()) {
+ if (allowed) {
+ setAllowedChannel("beta");
+ } else {
+ clearAllowedChannels();
+ }
+ return;
+ }
+
+ QSet<QString> channels{priv->lineChannel};
+ if (!priv->migrationChannel.isEmpty()) {
+ channels.insert(priv->migrationChannel);
+ }
+ if (allowed) {
+ channels.insert("beta");
+ }
+ setAllowedChannels(channels);
+}