@@ -2,32 +2,37 @@ import Foundation
22import Sparkle
33
44/// Manages Sparkle update checks and lifecycle.
5- final class UpdateManager {
5+ final class UpdateManager : NSObject {
66 static let shared = UpdateManager ( )
77
8- private let updaterController : SPUStandardUpdaterController
9- private var updateAvailabilityHandlers : [ UUID : ( Bool ) -> Void ] = [ : ]
8+ private lazy var updaterController = SPUStandardUpdaterController (
9+ startingUpdater: true ,
10+ updaterDelegate: self ,
11+ userDriverDelegate: nil
12+ )
13+ private let availabilityProbeThrottleInterval : TimeInterval = 15 * 60
14+ private var updateButtonStateHandlers : [ UUID : ( UpdateButtonState ) -> Void ] = [ : ]
1015 private var sparkleNotificationObservers : [ NSObjectProtocol ] = [ ]
16+ private var canCheckForUpdatesObservation : NSKeyValueObservation ?
17+ private var updateCheckCoordinator = UpdateCheckCoordinator ( )
1118 private( set) var hasAvailableUpdate = false
19+ private( set) var canCheckForUpdates = false
1220
13- private init ( ) {
14- updaterController = SPUStandardUpdaterController (
15- startingUpdater: true ,
16- updaterDelegate: nil ,
17- userDriverDelegate: nil
18- )
21+ private override init ( ) {
22+ super. init ( )
1923 registerSparkleObservers ( )
24+ observeCanCheckForUpdates ( )
2025 probeForUpdateAvailability ( )
2126 }
2227
2328 // MARK: - Updates
2429
2530 func checkForUpdates( ) {
2631 if Thread . isMainThread {
27- updaterController . checkForUpdates ( nil )
32+ requestForegroundUpdateCheck ( )
2833 } else {
2934 DispatchQueue . main. async { [ weak self] in
30- self ? . updaterController . checkForUpdates ( nil )
35+ self ? . requestForegroundUpdateCheck ( )
3136 }
3237 }
3338 }
@@ -36,8 +41,19 @@ final class UpdateManager {
3641 let probe = { [ weak self] in
3742 guard let self = self else { return }
3843 let updater = self . updaterController. updater
39- guard !updater. sessionInProgress else { return }
40- updater. checkForUpdateInformation ( )
44+ switch self . updateCheckCoordinator. requestAvailabilityProbe (
45+ now: Date ( ) ,
46+ hasAvailableUpdate: self . hasAvailableUpdate,
47+ sessionInProgress: updater. sessionInProgress,
48+ throttleInterval: self . availabilityProbeThrottleInterval
49+ ) {
50+ case . startNow:
51+ updater. checkForUpdateInformation ( )
52+ case . queued:
53+ break
54+ case . none:
55+ break
56+ }
4157 }
4258
4359 if Thread . isMainThread {
@@ -49,39 +65,30 @@ final class UpdateManager {
4965 }
5066 }
5167
52- func addUpdateAvailabilityHandler ( _ handler: @escaping ( Bool ) -> Void ) -> UUID {
68+ func addUpdateButtonStateHandler ( _ handler: @escaping ( UpdateButtonState ) -> Void ) -> UUID {
5369 let token = UUID ( )
54- updateAvailabilityHandlers [ token] = handler
55- handler ( hasAvailableUpdate )
70+ updateButtonStateHandlers [ token] = handler
71+ handler ( updateButtonState )
5672 return token
5773 }
5874
59- func removeUpdateAvailabilityHandler ( _ token: UUID ) {
60- updateAvailabilityHandlers . removeValue ( forKey: token)
75+ func removeUpdateButtonStateHandler ( _ token: UUID ) {
76+ updateButtonStateHandlers . removeValue ( forKey: token)
6177 }
6278
6379 // MARK: - Private
6480
81+ private var updateButtonState : UpdateButtonState {
82+ UpdateButtonState (
83+ hasAvailableUpdate: hasAvailableUpdate,
84+ canCheckForUpdates: canCheckForUpdates
85+ )
86+ }
87+
6588 private func registerSparkleObservers( ) {
6689 let center = NotificationCenter . default
6790 let updater = updaterController. updater
6891
69- let didFindObserver = center. addObserver (
70- forName: NSNotification . Name. SUUpdaterDidFindValidUpdate,
71- object: updater,
72- queue: . main
73- ) { [ weak self] _ in
74- self ? . setHasAvailableUpdate ( true )
75- }
76-
77- let didNotFindObserver = center. addObserver (
78- forName: NSNotification . Name. SUUpdaterDidNotFindUpdate,
79- object: updater,
80- queue: . main
81- ) { [ weak self] _ in
82- self ? . setHasAvailableUpdate ( false )
83- }
84-
8592 let willRestartObserver = center. addObserver (
8693 forName: NSNotification . Name. SUUpdaterWillRestart,
8794 object: updater,
@@ -90,12 +97,84 @@ final class UpdateManager {
9097 self ? . setHasAvailableUpdate ( false )
9198 }
9299
93- sparkleNotificationObservers = [ didFindObserver, didNotFindObserver, willRestartObserver]
100+ sparkleNotificationObservers = [ willRestartObserver]
101+ }
102+
103+ private func observeCanCheckForUpdates( ) {
104+ canCheckForUpdatesObservation = updaterController. updater. observe (
105+ \. canCheckForUpdates,
106+ options: [ . initial, . new]
107+ ) { [ weak self] updater, _ in
108+ DispatchQueue . main. async {
109+ self ? . setCanCheckForUpdates ( updater. canCheckForUpdates)
110+ }
111+ }
112+ }
113+
114+ private func requestForegroundUpdateCheck( ) {
115+ let updater = updaterController. updater
116+ setCanCheckForUpdates ( updater. canCheckForUpdates, startsPendingCheck: false )
117+
118+ switch updateCheckCoordinator. requestForegroundCheck ( canCheckForUpdates: updater. canCheckForUpdates) {
119+ case . startNow:
120+ updaterController. checkForUpdates ( nil )
121+ case . queued:
122+ break
123+ case . none:
124+ break
125+ }
94126 }
95127
96128 private func setHasAvailableUpdate( _ hasUpdate: Bool ) {
97129 guard hasAvailableUpdate != hasUpdate else { return }
98130 hasAvailableUpdate = hasUpdate
99- updateAvailabilityHandlers. values. forEach { $0 ( hasUpdate) }
131+ notifyUpdateButtonStateHandlers ( )
132+ }
133+
134+ private func setHasAvailableUpdateOnMain( _ hasUpdate: Bool ) {
135+ if Thread . isMainThread {
136+ setHasAvailableUpdate ( hasUpdate)
137+ } else {
138+ DispatchQueue . main. async { [ weak self] in
139+ self ? . setHasAvailableUpdate ( hasUpdate)
140+ }
141+ }
142+ }
143+
144+ private func setCanCheckForUpdates( _ canCheck: Bool , startsPendingCheck: Bool = true ) {
145+ let previousCanCheck = canCheckForUpdates
146+ canCheckForUpdates = canCheck
147+ if previousCanCheck != canCheck {
148+ notifyUpdateButtonStateHandlers ( )
149+ }
150+
151+ guard startsPendingCheck else { return }
152+ switch updateCheckCoordinator. didUpdateCanCheckForUpdates ( canCheck) {
153+ case . startNow:
154+ updaterController. checkForUpdates ( nil )
155+ case . queued:
156+ break
157+ case . none:
158+ break
159+ }
160+ }
161+
162+ private func notifyUpdateButtonStateHandlers( ) {
163+ let state = updateButtonState
164+ updateButtonStateHandlers. values. forEach { $0 ( state) }
165+ }
166+ }
167+
168+ extension UpdateManager : SPUUpdaterDelegate {
169+ func updater( _ updater: SPUUpdater , didFindValidUpdate item: SUAppcastItem ) {
170+ setHasAvailableUpdateOnMain ( true )
171+ }
172+
173+ func updaterDidNotFindUpdate( _ updater: SPUUpdater ) {
174+ setHasAvailableUpdateOnMain ( false )
175+ }
176+
177+ func updaterDidNotFindUpdate( _ updater: SPUUpdater , error: Error ) {
178+ setHasAvailableUpdateOnMain ( false )
100179 }
101180}
0 commit comments