Files
iKeyMon/Sources/ViewModels/SparkleUpdater.swift
Micha b7f5d1a762 chore: remove update logs UI from preferences
Remove the Show/Hide Logs button and logs view from the Updates preferences
tab. Keep the logging infrastructure in SparkleUpdater for diagnostics,
but don't display it in the UI.
2025-12-30 19:27:06 +01:00

128 lines
4.5 KiB
Swift

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
@Published var logMessages: [String] = []
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)")
addLogMessage("[INFO] \(message)")
if verboseLogging {
print("[Sparkle] \(message)")
}
}
private func logError(_ message: String) {
logger.error("\(message, privacy: .public)")
addLogMessage("[ERROR] \(message)")
if verboseLogging {
fputs("[Sparkle][error] \(message)\n", stderr)
}
}
private func addLogMessage(_ message: String) {
let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
let timestampedMessage = "[\(timestamp)] \(message)"
logMessages.append(timestampedMessage)
if logMessages.count > 100 {
logMessages.removeFirst()
}
}
private func describe(update item: SUAppcastItem) -> String {
let short = item.displayVersionString
let build = item.versionString
return "\(short) (build \(build))"
}
}
extension SparkleUpdater: SPUUpdaterDelegate {
nonisolated func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) {
Task { @MainActor in
log("Loaded Sparkle appcast containing \(appcast.items.count) item(s).")
}
}
nonisolated func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Found valid update \(describe(update: item))")
}
}
nonisolated func updaterDidNotFindUpdate(_ updater: SPUUpdater) {
Task { @MainActor in
log("No updates available.")
}
}
nonisolated func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) {
Task { @MainActor in
log("Downloading \(describe(update: item)) from \(request.url?.absoluteString ?? "unknown URL")")
}
}
nonisolated func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Finished downloading \(describe(update: item))")
}
}
nonisolated func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) {
Task { @MainActor in
logError("Failed to download \(describe(update: item)): \(error.localizedDescription)")
}
}
nonisolated func userDidCancelDownload(_ updater: SPUUpdater) {
Task { @MainActor in
log("User cancelled Sparkle download.")
}
}
nonisolated func updater(_ updater: SPUUpdater, willInstallUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Will install update \(describe(update: item))")
}
}
nonisolated func updater(_ updater: SPUUpdater, didAbortWithError error: Error) {
Task { @MainActor in
let errorDescription = error as NSError
let details = "Domain: \(errorDescription.domain), Code: \(errorDescription.code), Description: \(error.localizedDescription)"
logError("Sparkle aborted: \(details)")
if let underlying = errorDescription.userInfo[NSUnderlyingErrorKey] as? NSError {
logError("Underlying error: Domain: \(underlying.domain), Code: \(underlying.code), Description: \(underlying.localizedDescription)")
}
}
}
}