@@ -44,7 +44,6 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
4444 ExtensionDelegate . singleton = self
4545
4646 BackgroundRefreshLogger . info ( " Application did finish launching " )
47- scheduleBackgroundRefresh ( )
4847 AppMessageService . singleton. keepAwakePhoneApp ( )
4948 }
5049
@@ -67,11 +66,6 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
6766 MainController . mainViewModel. refreshData ( forceRefresh: true , moveToLatestValue: false )
6867 }
6968
70- // nightscout data message
71- WatchMessageService . singleton. onMessage { [ weak self] ( message: NightscoutDataMessage ) in
72- self ? . onNightscoutDataReceivedFromPhoneApp ( message. nightscoutData)
73- }
74-
7569 // user defaults sync message
7670 WatchMessageService . singleton. onMessage { ( message: UserDefaultSyncMessage ) in
7771
@@ -127,300 +121,6 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
127121 AppState . isUIActive = false
128122
129123 print ( " Application will resign active. " )
130- scheduleBackgroundRefresh ( )
131124 AppMessageService . singleton. keepAwakePhoneApp ( )
132125 }
133-
134- public func handle( _ backgroundTasks: Set < WKRefreshBackgroundTask > ) {
135-
136- for task in backgroundTasks {
137-
138- // crash solving trick: acces the task user info to avoid a rare, but weird crash.. (https://forums.developer.apple.com/thread/96504 and https://stackoverflow.com/questions/46464660/wkrefreshbackgroundtask-cleanupstorage-error-attempting-to-reach-file)
139- userInfoAccess = task. userInfo
140-
141- if let watchConnectivityBackgroundTask = task as? WKWatchConnectivityRefreshBackgroundTask {
142- handleWatchConnectivityBackgroundTask ( watchConnectivityBackgroundTask)
143- } else if let snapshotTask = task as? WKSnapshotRefreshBackgroundTask {
144- handleSnapshotTask ( snapshotTask)
145- } else if let sessionTask = task as? WKURLSessionRefreshBackgroundTask {
146- handleURLSessionTask ( sessionTask)
147- } else if let refreshTask = task as? WKApplicationRefreshBackgroundTask , WKApplication . shared ( ) . applicationState == . background {
148- handleRefreshTask ( refreshTask)
149- } else {
150- // not handled!
151- task. setTaskCompletedWithSnapshot ( false )
152- }
153- }
154- }
155- }
156-
157- extension ExtensionDelegate {
158-
159- // MARK:- Background update methods
160-
161- func handleWatchConnectivityBackgroundTask ( _ watchConnectivityBackgroundTask: WKWatchConnectivityRefreshBackgroundTask ) {
162-
163- BackgroundRefreshLogger . info ( " WKWatchConnectivityRefreshBackgroundTask received " )
164- watchConnectivityBackgroundTask. setTaskCompletedWithSnapshot ( false )
165- }
166-
167- func handleSnapshotTask( _ snapshotTask : WKSnapshotRefreshBackgroundTask ) {
168-
169- BackgroundRefreshLogger . info ( " WKSnapshotRefreshBackgroundTask received " )
170-
171- // update user interface with current nightscout data (or error)
172- let currentNightscoutData = NightscoutCacheService . singleton. getCurrentNightscoutData ( )
173- MainController . mainViewModel. pushBackgroundData ( newNightscoutData: currentNightscoutData)
174-
175- snapshotTask. setTaskCompleted ( restoredDefaultState: true , estimatedSnapshotExpiration: Date . distantFuture, userInfo: nil )
176- }
177-
178- func handleRefreshTask( _ task : WKRefreshBackgroundTask ) {
179-
180- BackgroundRefreshLogger . info ( " WKApplicationRefreshBackgroundTask received " )
181- BackgroundRefreshLogger . backgroundRefreshes += 1
182-
183- scheduleURLSessionIfNeeded ( )
184-
185- // schedule the next background refresh
186- BackgroundRefreshScheduler . instance. schedule ( )
187-
188- // Ask to refresh all complications
189- WidgetCenter . shared. reloadAllTimelines ( )
190-
191- task. setTaskCompletedWithSnapshot ( false )
192- }
193-
194- func handleURLSessionTask( _ sessionTask: WKURLSessionRefreshBackgroundTask ) {
195-
196- BackgroundRefreshLogger . info ( " WKURLSessionRefreshBackgroundTask received " )
197-
198- let backgroundConfigObject = URLSessionConfiguration . background ( withIdentifier: sessionTask. sessionIdentifier)
199- let backgroundSession = URLSession ( configuration: backgroundConfigObject, delegate: self , delegateQueue: nil )
200- print ( " Rejoining session " , backgroundSession)
201-
202- // keep the session background task, it will be ended later... (https://stackoverflow.com/questions/41156386/wkurlsessionrefreshbackgroundtask-isnt-called-when-attempting-to-do-background)
203- self . pendingBackgroundURLTask = sessionTask
204- }
205-
206- @discardableResult
207- func onNightscoutDataReceivedFromPhoneApp( _ nightscoutData: NightscoutData ) -> Bool {
208-
209- BackgroundRefreshLogger . phoneUpdates += 1
210-
211- guard !nightscoutData. isOlderThanXMinutes ( 60 ) else {
212- BackgroundRefreshLogger . info ( " 📱Rejected nightscout data (>1hr old!) " )
213- return false
214- }
215-
216- let updateResult = updateNightscoutData ( nightscoutData, updateComplication: true ) // always update complication!
217- BackgroundRefreshLogger . nightscoutDataReceived ( nightscoutData, updateResult: updateResult, updateSource: . phoneApp)
218- switch updateResult {
219- case . updateDataIsOld:
220- BackgroundRefreshLogger . phoneUpdatesWithOldData += 1
221- case . updateDataAlreadyExists:
222- BackgroundRefreshLogger . phoneUpdatesWithSameData += 1
223- case . updated:
224- BackgroundRefreshLogger . phoneUpdatesWithNewData += 1
225- }
226-
227- return updateResult != . updateDataIsOld
228- }
229-
230- // MARK:- Internals
231-
232- fileprivate func scheduleBackgroundRefresh( ) {
233- BackgroundRefreshScheduler . instance. schedule ( )
234- }
235-
236- enum UpdateSource {
237-
238- // the update was initiated by phone app
239- case phoneApp
240-
241- // the update was initiated by watch (background URL session)
242- case urlSession
243- }
244-
245- enum UpdateResult {
246-
247- // update succeeded
248- case updated
249-
250- // update data already exists (is the current nightscout data) - no need to update!
251- case updateDataAlreadyExists
252-
253- // update data is older than current nightscout data
254- case updateDataIsOld
255- }
256-
257- fileprivate func updateNightscoutData( _ newNightscoutData: NightscoutData , updateComplication: Bool = true ) -> UpdateResult {
258-
259- // synchronize the background update to prevent concurrent modifications
260- objc_sync_enter ( self )
261-
262- defer {
263- objc_sync_exit ( self )
264- }
265-
266- // check the data that already exists on the watch... maybe is newer that the received data
267- let currentNightscoutData = NightscoutCacheService . singleton. getCurrentNightscoutData ( )
268- if currentNightscoutData. time. doubleValue > newNightscoutData. time. doubleValue {
269-
270- // Old data was received from remote (phone app or URL session)! This can happen because:
271- // 1. if receiving data from phone app: the watch can have newer data than the phone app (phone app background fetch is once in 5 minutes) or because the delivery is not instantaneous and ... and the watch can update its data in between (when the app enters foreground)
272- // 2. if receiving data from a URL session: the session can complete later, when there are resources available on the watch to execute it... so there is a posibility than the watch app update itself till then
273- print ( " Received older nightscout data than current watch nightscout data! " )
274- return . updateDataIsOld
275-
276- } else if currentNightscoutData. time. doubleValue == newNightscoutData. time. doubleValue {
277-
278- // already have this data...
279- return . updateDataAlreadyExists
280- }
281-
282- print ( " Nightscout data was received from remote (phone app or URL session)! " )
283- NightscoutCacheService . singleton. updateCurrentNightscoutData ( newNightscoutData: newNightscoutData)
284- // if #available(watchOSApplicationExtension 3.0, *) {
285- // scheduleSnapshotRefresh()
286- // }
287-
288- return . updated
289- }
290-
291- func synced( _ lock: Any , closure: ( ) -> ( ) ) {
292- objc_sync_enter ( lock)
293- closure ( )
294- objc_sync_exit ( lock)
295- }
296-
297- func scheduleURLSessionIfNeeded( ) {
298-
299- // let currentNightscoutData = NightscoutCacheService.singleton.getCurrentNightscoutData()
300- // guard currentNightscoutData.isOlderThan5Minutes() else {
301- // BackgroundRefreshLogger.info("Recent nightscout data, skipping URL session!")
302- // return
303- // }
304-
305- if self . backgroundSession != nil {
306-
307- if let sessionStartTime = self . sessionStartTime, Calendar . current. date ( byAdding: . minute, value: BackgroundRefreshSettings . urlSessionTaskTimeout, to: sessionStartTime) ! > Date ( ) {
308-
309- // URL session running.. we'll let it do its work!
310- BackgroundRefreshLogger . info ( " URL session already exists, cannot start a new one! " )
311- return
312- } else {
313-
314- // timeout reached for URL session, we'll start a new one!
315- BackgroundRefreshLogger . info ( " URL session timeout exceeded, finishing current and starting a new one! " )
316- completePendingURLSessionTask ( )
317- }
318- }
319-
320- guard let ( backgroundSession, downloadTask) = scheduleURLSession ( ) else {
321- BackgroundRefreshLogger . info ( " URL session cannot be created, probably base uri is not configured! " )
322- return
323- }
324-
325- self . sessionStartTime = Date ( )
326- self . backgroundSession = backgroundSession
327- self . downloadTask = downloadTask
328- BackgroundRefreshLogger . backgroundURLSessions += 1
329- BackgroundRefreshLogger . info ( " URL session started " )
330- }
331- }
332-
333- extension ExtensionDelegate : URLSessionDownloadDelegate {
334-
335- func urlSession( _ session: URLSession , downloadTask: URLSessionDownloadTask , didFinishDownloadingTo location: URL ) {
336- print ( " Background download was finished. " )
337-
338- // reset the session error
339- self . sessionError = nil
340-
341- let nightscoutData = NSData ( contentsOf: location as URL )
342-
343- // extract data on main thead
344- DispatchQueue . main. async { [ unowned self] in
345-
346- if nightscoutData == nil {
347- return
348- }
349-
350- NightscoutService . singleton. extractApiV2PropertiesData ( data: nightscoutData! as Data , { [ unowned self] result in
351-
352- switch result {
353- case . error( let error) :
354- self . sessionError = error
355-
356- case . data( let newNightscoutData) :
357- self . sessionError = nil
358-
359- let updateResult = self . updateNightscoutData ( newNightscoutData)
360- BackgroundRefreshLogger . nightscoutDataReceived ( newNightscoutData, updateResult: updateResult, updateSource: . urlSession)
361- switch updateResult {
362- case . updateDataIsOld:
363- BackgroundRefreshLogger . backgroundURLSessionUpdatesWithOldData += 1
364- BackgroundRefreshLogger . info ( " URL session data: OLD " )
365- case . updateDataAlreadyExists:
366- BackgroundRefreshLogger . backgroundURLSessionUpdatesWithSameData += 1
367- BackgroundRefreshLogger . info ( " URL session data: EXISTING " )
368- case . updated:
369- BackgroundRefreshLogger . backgroundURLSessionUpdatesWithNewData += 1
370- BackgroundRefreshLogger . info ( " URL session data: NEW " )
371- }
372- }
373- } )
374- }
375-
376- completePendingURLSessionTask ( )
377- }
378-
379- func urlSession( _ session: URLSession , task: URLSessionTask , didCompleteWithError error: Error ? ) {
380- print ( " Background url session completed with error: \( String ( describing: error) ) " )
381- if let error = error {
382- BackgroundRefreshLogger . info ( " URL session did complete with error: \( error) " )
383- completePendingURLSessionTask ( )
384- }
385-
386- // keep the session error (if any!)
387- self . sessionError = error
388- }
389-
390- func urlSessionDidFinishEvents( forBackgroundURLSession session: URLSession ) {
391- BackgroundRefreshLogger . info ( " URL session did finish events " )
392- // completePendingURLSessionTask()
393- }
394-
395- fileprivate func completePendingURLSessionTask( ) {
396-
397- self . backgroundSession? . invalidateAndCancel ( )
398- self . backgroundSession = nil
399- self . downloadTask = nil
400- self . sessionStartTime = nil
401- ( self . pendingBackgroundURLTask as? WKRefreshBackgroundTask ) ? . setTaskCompletedWithSnapshot ( false )
402- self . pendingBackgroundURLTask = nil
403-
404- BackgroundRefreshLogger . info ( " URL session COMPLETED " )
405- }
406-
407- func scheduleURLSession( ) -> ( URLSession , URLSessionDownloadTask ) ? {
408-
409- let baseUri = UserDefaultsRepository . baseUri. value
410- if baseUri == " " {
411- return nil
412- }
413-
414- let backgroundConfigObject = URLSessionConfiguration . background ( withIdentifier: NSUUID ( ) . uuidString)
415- backgroundConfigObject. sessionSendsLaunchEvents = true
416- // backgroundConfigObject.timeoutIntervalForRequest = 15 // 15 seconds timeout for request (after 15 seconds, the task is finished and a crash occurs, so... we have to stop it somehow!)
417- // backgroundConfigObject.timeoutIntervalForResource = 15 // the same for retry interval (no retries!)
418- let backgroundSession = URLSession ( configuration: backgroundConfigObject, delegate: self , delegateQueue: nil )
419-
420- let downloadURL = URL ( string: baseUri + " /pebble " ) !
421- let downloadTask = backgroundSession. downloadTask ( with: downloadURL)
422- downloadTask. resume ( )
423-
424- return ( backgroundSession, downloadTask)
425- }
426126}
0 commit comments