import Sparkle import Foundation import OSLog @MainActor final class SparkleUpdater: NSObject, ObservableObject { private lazy var controller: SPUStandardUpdaterController = { SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: self, userDriverDelegate: nil) }() private let logger = Logger(subsystem: "net.24unix.iKeyMon", category: "Sparkle") private let verboseLogging: Bool override init() { self.verboseLogging = ProcessInfo.processInfo.environment["SPARKLE_VERBOSE_LOGGING"] == "1" super.init() _ = controller log("Sparkle updater initialized (verbose=\(verboseLogging)).") } var automaticallyChecksForUpdates: Bool { get { controller.updater.automaticallyChecksForUpdates } set { controller.updater.automaticallyChecksForUpdates = newValue } } var automaticallyDownloadsUpdates: Bool { get { controller.updater.automaticallyDownloadsUpdates } set { controller.updater.automaticallyDownloadsUpdates = newValue } } func checkForUpdates() { log("Manual check for updates triggered.") controller.checkForUpdates(nil) } private func log(_ message: String) { logger.log("\(message, privacy: .public)") if verboseLogging { print("[Sparkle] \(message)") } } private func logError(_ message: String) { logger.error("\(message, privacy: .public)") if verboseLogging { fputs("[Sparkle][error] \(message)\n", stderr) } } private func describe(update item: SUAppcastItem) -> String { let short = item.displayVersionString let build = item.versionString return "\(short) (build \(build))" } } @MainActor extension SparkleUpdater: SPUUpdaterDelegate { func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) { log("Loaded Sparkle appcast containing \(appcast.items.count) item(s).") } func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) { log("Found valid update \(describe(update: item))") } func updaterDidNotFindUpdate(_ updater: SPUUpdater) { log("No updates available.") } func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) { log("Downloading \(describe(update: item)) from \(request.url?.absoluteString ?? "unknown URL")") } func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) { log("Finished downloading \(describe(update: item))") } func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) { logError("Failed to download \(describe(update: item)): \(error.localizedDescription)") } func userDidCancelDownload(_ updater: SPUUpdater) { log("User cancelled Sparkle download.") } func updater(_ updater: SPUUpdater, willInstallUpdate item: SUAppcastItem) { log("Will install update \(describe(update: item))") } func updater(_ updater: SPUUpdater, didAbortWithError error: Error) { logError("Sparkle aborted: \(error.localizedDescription)") } }