#if os(macOS) import Foundation /// How an app's updater behaves between the checks a person asks for. /// /// `standard` is Sparkle's own flow: it asks before checking automatically and /// shows its update alert when a scheduled check finds something. `quiet` never /// opens a window for a scheduled check. It downloads in the background, installs /// when the app quits, and publishes `NativeUpdater.pendingUpdate` so the app can /// show a small notice. While the app reports itself busy (`NativeUpdater.isBusy`, /// a call, say) scheduled checks wait until it is idle again, so a download never /// competes with the busy work. A check the person asks for always behaves normally. /// /// Quiet also needs the bundle packaged with `"install": "automatic"` in its /// `mac-updates.json`: that is what turns on automatic checks and downloads /// without Sparkle's first-launch question. See docs/mac-updates.md. public enum NativeUpdatePolicy: Sendable, Equatable { case standard case quiet } /// What a quiet updater has found on a schedule and not yet installed. public enum NativePendingUpdate: Sendable, Equatable { case none /// Found but not downloaded, because the person turned automatic updates off /// or the update needs their attention first. Check for Updates shows it. case available(version: String) /// Downloaded and staged. It installs when the app quits, or at once through /// `NativeUpdater.installPendingUpdate()`. case ready(version: String) } /// Which kind of check Sparkle is about to start, without importing Sparkle. enum NativeUpdateCheck: Sendable { case userInitiated, background, information } /// The quiet policy's decisions, kept apart from Sparkle so that they can be tested /// without a feed, a signed bundle or a network. struct QuietUpdateRules: Sendable, Equatable { private(set) var pending: NativePendingUpdate = .none private(set) var busy = false /// A scheduled check was declined while busy. Sparkle records the declined check /// as done and waits a full interval, so without this the update would wait a /// day for a call that lasted ten minutes. private(set) var deferredCheck = false /// Only scheduled checks wait. A person who chooses Check for Updates mid-call /// asked for it. mutating func mayPerform(_ check: NativeUpdateCheck) -> Bool { guard check == .background, busy else { return true } deferredCheck = true return false } /// Returns true when a check deferred while busy should run now. mutating func setBusy(_ newValue: Bool) -> Bool { busy = newValue guard !newValue, deferredCheck else { return false } deferredCheck = false return true } mutating func staged(version: String) { pending = .ready(version: version) } /// A scheduled update Sparkle would have shown in a window. One already /// downloaded is ready; otherwise the notice offers it. mutating func found(version: String, downloaded: Bool) { pending = downloaded ? .ready(version: version) : .available(version: version) } /// Sparkle's own window has the update now, or the session is over. mutating func clear() { pending = .none } /// Acting on a pending update either relaunches the app or opens Sparkle's /// window, so neither happens while busy. var canInstall: Bool { pending != .none && !busy } } #endif