Redline: timestamped result for the manual update check, update-state renders, and a duplicated-status fix (MMDB-2697) #27

Merged
kua-agent merged 6 commits from feat/check-updates-feedback-20260905 into main 2026-09-05 07:15:15 +00:00
Showing only changes of commit 7cdc3e7652 - Show all commits
+16 -3
View File
@@ -2,6 +2,7 @@ import AppKit
import CryptoKit import CryptoKit
import Foundation import Foundation
import Security import Security
import ShotdeckCore
/// Built-in updater. Checks an appcast, stages a verified payload, and installs /// Built-in updater. Checks an appcast, stages a verified payload, and installs
/// only when the user clicks the menu row never automatically. /// only when the user clicks the menu row never automatically.
@@ -31,6 +32,10 @@ final class UpdateChecker {
private var repeatingTimer: Timer? private var repeatingTimer: Timer?
private var firstCheckTask: Task<Void, Never>? private var firstCheckTask: Task<Void, Never>?
private var stagingDirectory: URL? private var stagingDirectory: URL?
/// Snapshot-only override for previousVersion; when snapshotUsesPreviousVersionOverride is true,
/// this value (including nil) is returned instead of checking the file system.
var snapshotPreviousVersionOverride: String?
var snapshotUsesPreviousVersionOverride: Bool = false
init() { init() {
let config = URLSessionConfiguration.ephemeral let config = URLSessionConfiguration.ephemeral
@@ -85,7 +90,12 @@ final class UpdateChecker {
guard Self.isNewer(appcast.version, than: Self.currentVersion()) else { guard Self.isNewer(appcast.version, than: Self.currentVersion()) else {
clearOffer() clearOffer()
statusMessage = manual ? "Redline \(Self.currentVersion()) is up to date." : nil if manual {
let timestamp = DubaiTime.checkTime(lastCheckedAt ?? Date())
statusMessage = "Redline \(Self.currentVersion()) is up to date, checked \(timestamp)"
} else {
statusMessage = nil
}
onChecked?() onChecked?()
return return
} }
@@ -93,7 +103,7 @@ final class UpdateChecker {
do { do {
try await downloadAndStage(appcast) try await downloadAndStage(appcast)
availableUpdate = (version: appcast.version, notes: appcast.notes ?? "") availableUpdate = (version: appcast.version, notes: appcast.notes ?? "")
statusMessage = nil statusMessage = manual ? "Update to \(appcast.version) is ready" : nil
} catch UpdateCheckError.checksumMismatch { } catch UpdateCheckError.checksumMismatch {
discardStaging() discardStaging()
availableUpdate = nil availableUpdate = nil
@@ -226,8 +236,11 @@ final class UpdateChecker {
} }
/// The version recorded in `Redline.app.previous`'s Info.plist, or nil when no /// The version recorded in `Redline.app.previous`'s Info.plist, or nil when no
/// rollback copy exists. /// rollback copy exists. Respects the snapshot-only override for panel rendering.
func previousVersion(target: URL = UpdateChecker.defaultInstallTarget) -> String? { func previousVersion(target: URL = UpdateChecker.defaultInstallTarget) -> String? {
if snapshotUsesPreviousVersionOverride {
return snapshotPreviousVersionOverride
}
let previousURL = target.deletingLastPathComponent().appendingPathComponent("Redline.app.previous") let previousURL = target.deletingLastPathComponent().appendingPathComponent("Redline.app.previous")
let plistURL = previousURL.appendingPathComponent("Contents/Info.plist") let plistURL = previousURL.appendingPathComponent("Contents/Info.plist")
guard let plist = NSDictionary(contentsOf: plistURL) as? [String: Any] else { return nil } guard let plist = NSDictionary(contentsOf: plistURL) as? [String: Any] else { return nil }