fix: restore Sparkle automatic update checks (#1597)
This commit is contained in:
parent
f585461868
commit
e15825826f
5 changed files with 112 additions and 17 deletions
|
|
@ -146,8 +146,16 @@
|
|||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SUAutomaticallyUpdate</key>
|
||||
<false/>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<true/>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://github.com/manaflow-ai/cmux/releases/latest/download/appcast.xml</string>
|
||||
<key>SUScheduledCheckInterval</key>
|
||||
<integer>86400</integer>
|
||||
<key>SUSendProfileInfo</key>
|
||||
<false/>
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>$(SPARKLE_PUBLIC_KEY)</string>
|
||||
</dict>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,47 @@ import Cocoa
|
|||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum UpdateSettings {
|
||||
static let automaticChecksKey = "SUEnableAutomaticChecks"
|
||||
static let automaticallyUpdateKey = "SUAutomaticallyUpdate"
|
||||
static let scheduledCheckIntervalKey = "SUScheduledCheckInterval"
|
||||
static let sendProfileInfoKey = "SUSendProfileInfo"
|
||||
static let migrationKey = "cmux.sparkle.automaticChecksMigration.v1"
|
||||
static let scheduledCheckInterval: TimeInterval = 60 * 60 * 24
|
||||
|
||||
static func apply(to defaults: UserDefaults) {
|
||||
defaults.register(defaults: [
|
||||
automaticChecksKey: true,
|
||||
automaticallyUpdateKey: false,
|
||||
scheduledCheckIntervalKey: scheduledCheckInterval,
|
||||
sendProfileInfoKey: false,
|
||||
])
|
||||
|
||||
guard !defaults.bool(forKey: migrationKey) else { return }
|
||||
|
||||
// Repair older installs that may have ended up with automatic checks disabled
|
||||
// before the updater defaults were embedded in Info.plist.
|
||||
defaults.set(true, forKey: automaticChecksKey)
|
||||
|
||||
if let interval = defaults.object(forKey: scheduledCheckIntervalKey) as? NSNumber {
|
||||
if interval.doubleValue <= 0 {
|
||||
defaults.set(scheduledCheckInterval, forKey: scheduledCheckIntervalKey)
|
||||
}
|
||||
} else {
|
||||
defaults.set(scheduledCheckInterval, forKey: scheduledCheckIntervalKey)
|
||||
}
|
||||
|
||||
if defaults.object(forKey: automaticallyUpdateKey) == nil {
|
||||
defaults.set(false, forKey: automaticallyUpdateKey)
|
||||
}
|
||||
if defaults.object(forKey: sendProfileInfoKey) == nil {
|
||||
defaults.set(false, forKey: sendProfileInfoKey)
|
||||
}
|
||||
|
||||
defaults.set(true, forKey: migrationKey)
|
||||
}
|
||||
}
|
||||
|
||||
/// Controller for managing Sparkle updates in cmux.
|
||||
class UpdateController {
|
||||
private(set) var updater: SPUUpdater
|
||||
|
|
@ -27,13 +68,8 @@ class UpdateController {
|
|||
}
|
||||
|
||||
init() {
|
||||
// cmux checks for updates in the background, but keeps automatic download and
|
||||
// profile submission disabled so all install intent stays user-driven.
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.register(defaults: [
|
||||
"SUSendProfileInfo": false,
|
||||
"SUAutomaticallyUpdate": false,
|
||||
])
|
||||
UpdateSettings.apply(to: defaults)
|
||||
|
||||
let hostBundle = Bundle.main
|
||||
self.userDriver = UpdateDriver(viewModel: .init(), hostBundle: hostBundle)
|
||||
|
|
@ -63,19 +99,22 @@ class UpdateController {
|
|||
// delegate now suppresses Sparkle's permission UI entirely.
|
||||
if ProcessInfo.processInfo.environment["CMUX_UI_TEST_RESET_SPARKLE_PERMISSION"] == "1" {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.removeObject(forKey: "SUEnableAutomaticChecks")
|
||||
defaults.removeObject(forKey: "SUSendProfileInfo")
|
||||
defaults.removeObject(forKey: "SUAutomaticallyUpdate")
|
||||
defaults.removeObject(forKey: UpdateSettings.automaticChecksKey)
|
||||
defaults.removeObject(forKey: UpdateSettings.automaticallyUpdateKey)
|
||||
defaults.removeObject(forKey: UpdateSettings.scheduledCheckIntervalKey)
|
||||
defaults.removeObject(forKey: UpdateSettings.sendProfileInfoKey)
|
||||
defaults.removeObject(forKey: UpdateSettings.migrationKey)
|
||||
defaults.synchronize()
|
||||
UpdateLogStore.shared.append("reset sparkle permission defaults (ui test)")
|
||||
}
|
||||
#endif
|
||||
do {
|
||||
updater.automaticallyChecksForUpdates = true
|
||||
updater.automaticallyDownloadsUpdates = false
|
||||
updater.sendsSystemProfile = false
|
||||
try updater.start()
|
||||
didStartUpdater = true
|
||||
let interval = Int(updater.updateCheckInterval.rounded())
|
||||
UpdateLogStore.shared.append(
|
||||
"updater started (autoChecks=\(updater.automaticallyChecksForUpdates), interval=\(interval)s, autoDownloads=\(updater.automaticallyDownloadsUpdates))"
|
||||
)
|
||||
} catch {
|
||||
userDriver.viewModel.state = .error(.init(
|
||||
error: error,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,15 @@ extension UpdateDriver: SPUUpdaterDelegate {
|
|||
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: infoFeedURL)
|
||||
UpdateLogStore.shared.append("update channel: \(resolved.isNightly ? "nightly" : "stable")")
|
||||
recordFeedURLString(resolved.url, usedFallback: resolved.usedFallback)
|
||||
return infoFeedURL
|
||||
return resolved.url
|
||||
}
|
||||
|
||||
func updater(_ updater: SPUUpdater, willScheduleUpdateCheckAfterDelay delay: TimeInterval) {
|
||||
UpdateLogStore.shared.append("next update check scheduled in \(Int(delay.rounded()))s")
|
||||
}
|
||||
|
||||
func updaterWillNotScheduleUpdateCheck(_ updater: SPUUpdater) {
|
||||
UpdateLogStore.shared.append("automatic update checks disabled; no scheduled check")
|
||||
}
|
||||
|
||||
/// Called when an update is scheduled to install silently,
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ class UpdateDriver: NSObject, SPUUserDriver {
|
|||
return
|
||||
}
|
||||
#endif
|
||||
// Never show Sparkle's permission UI. cmux relies on its in-app update pill instead,
|
||||
// and defaults to manual update checks unless explicitly enabled elsewhere.
|
||||
UpdateLogStore.shared.append("auto-deny update permission (no UI)")
|
||||
// Never show Sparkle's permission UI. cmux always enables scheduled checks and keeps
|
||||
// automatic downloads disabled so installs remain user-driven.
|
||||
UpdateLogStore.shared.append("auto-allow update permission (no UI)")
|
||||
DispatchQueue.main.async {
|
||||
reply(SUUpdatePermissionResponse(automaticUpdateChecks: false, sendSystemProfile: false))
|
||||
reply(SUUpdatePermissionResponse(automaticUpdateChecks: true, sendSystemProfile: false))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5190,6 +5190,46 @@ final class UpdateChannelSettingsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
final class UpdateSettingsTests: XCTestCase {
|
||||
func testApplyEnablesAutomaticChecksAndDailySchedule() {
|
||||
let defaults = makeDefaults()
|
||||
UpdateSettings.apply(to: defaults)
|
||||
|
||||
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
|
||||
XCTAssertEqual(defaults.double(forKey: UpdateSettings.scheduledCheckIntervalKey), UpdateSettings.scheduledCheckInterval)
|
||||
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.automaticallyUpdateKey))
|
||||
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.sendProfileInfoKey))
|
||||
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.migrationKey))
|
||||
}
|
||||
|
||||
func testApplyRepairsLegacyDisabledAutomaticChecksOnce() {
|
||||
let defaults = makeDefaults()
|
||||
defaults.set(false, forKey: UpdateSettings.automaticChecksKey)
|
||||
defaults.set(0, forKey: UpdateSettings.scheduledCheckIntervalKey)
|
||||
defaults.set(true, forKey: UpdateSettings.automaticallyUpdateKey)
|
||||
|
||||
UpdateSettings.apply(to: defaults)
|
||||
|
||||
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
|
||||
XCTAssertEqual(defaults.double(forKey: UpdateSettings.scheduledCheckIntervalKey), UpdateSettings.scheduledCheckInterval)
|
||||
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticallyUpdateKey))
|
||||
|
||||
defaults.set(false, forKey: UpdateSettings.automaticChecksKey)
|
||||
UpdateSettings.apply(to: defaults)
|
||||
|
||||
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
|
||||
}
|
||||
|
||||
private func makeDefaults() -> UserDefaults {
|
||||
let suiteName = "UpdateSettingsTests.\(UUID().uuidString)"
|
||||
guard let defaults = UserDefaults(suiteName: suiteName) else {
|
||||
fatalError("Failed to create isolated UserDefaults suite")
|
||||
}
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
|
||||
final class SidebarRemoteErrorCopySupportTests: XCTestCase {
|
||||
func testMenuLabelIsNilWhenThereAreNoErrors() {
|
||||
XCTAssertNil(SidebarRemoteErrorCopySupport.menuLabel(for: []))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue