diff --git a/README.md b/README.md index 433d1c5..ee570ce 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,16 @@ Preferences expose Sparkle’s built-in toggles for “Automatically check” an > Build settings include `INFOPLIST_KEY_SUFeedURL` and `INFOPLIST_KEY_SUPublicEDKey`. Make sure to fill both before shipping a build so Sparkle knows where to fetch updates and how to verify them. +#### Debugging Sparkle updates + +Launch the shipped app via CLI with `SPARKLE_VERBOSE_LOGGING=1` to mirror Sparkle’s activity in stdout/stderr: + +```bash +SPARKLE_VERBOSE_LOGGING=1 /Applications/iKeyMon.app/Contents/MacOS/iKeyMon +``` + +Sparkle’s installer helper runs in a separate `SUPipedBinary` process. If the installer fails, collect additional details with `log show --process SUPipedBinary --last 5m`. + ### Automated release push If you want `git push origin master` to build/sign/notarize/upload automatically, enable the provided pre-push hook: diff --git a/Sources/ViewModels/SparkleUpdater.swift b/Sources/ViewModels/SparkleUpdater.swift index 48342eb..6ab6269 100644 --- a/Sources/ViewModels/SparkleUpdater.swift +++ b/Sources/ViewModels/SparkleUpdater.swift @@ -1,13 +1,19 @@ import Sparkle import Foundation +import OSLog @MainActor final class SparkleUpdater: NSObject, ObservableObject { let controller: SPUStandardUpdaterController + 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" self.controller = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) super.init() + controller.updater.delegate = self + log("Sparkle updater initialized (verbose=\(verboseLogging)).") } var automaticallyChecksForUpdates: Bool { @@ -21,6 +27,65 @@ final class SparkleUpdater: NSObject, ObservableObject { } 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 ?? item.versionString ?? "unknown" + let build = item.versionString ?? "?" + return "\(short) (build \(build))" + } +} + +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)") + } }