@@ -7,12 +7,43 @@ import SwiftUI
77@preconcurrency import SushitrainCore
88import BackgroundTasks
99
10+ struct BackgroundSyncRun : Codable , Equatable {
11+ var started : Date
12+ var ended : Date ?
13+ var taskType : BackgroundTaskType ?
14+
15+ var asString : String {
16+ if let ended = self . ended {
17+ if let tt = self . taskType {
18+ return " \( self . started. formatted ( ) ) - \( ended. formatted ( ) ) ( \( tt. localizedTypeName) ) "
19+ }
20+ return " \( self . started. formatted ( ) ) - \( ended. formatted ( ) ) "
21+ }
22+ return self . started. formatted ( )
23+ }
24+ }
25+
26+ enum BackgroundTaskType : String , Codable , Equatable {
27+ case short = " nl.t-shaped.sushitrain.short-background-sync "
28+ case long = " nl.t-shaped.sushitrain.background-sync "
29+ case continued = " nl.t-shaped.sushitrain.background-sync.continued "
30+
31+ var identifier : String {
32+ return self . rawValue
33+ }
34+
35+ var localizedTypeName : String {
36+ switch self {
37+ case . short: String ( localized: " short " )
38+ case . long: String ( localized: " long " )
39+ case . continued: String ( localized: " continued " )
40+ }
41+ }
42+ }
43+
1044#if os(iOS)
1145 @MainActor class BackgroundManager : ObservableObject {
12- private static let longBackgroundSyncID = " nl.t-shaped.sushitrain.background-sync "
13- private static let shortBackgroundSyncID = " nl.t-shaped.sushitrain.short-background-sync "
1446 private static let watchdogNotificationID = " nl.t-shaped.sushitrain.watchdog-notification "
15- private static let continuedBackgroundSyncID = " nl.t-shaped.sushitrain.background-sync.continued "
1647
1748 // Time before the end of allotted background time to start ending the task to prevent forceful expiration by the OS
1849 private static let backgroundTimeReserve : TimeInterval = 5.6
@@ -59,13 +90,13 @@ import BackgroundTasks
5990 // Schedule background synchronization task
6091 // Must start on a specified queue (here we simply use main) to prevent a crash in dispatch_assert_queue
6192 BGTaskScheduler . shared. register (
62- forTaskWithIdentifier: Self . longBackgroundSyncID , using: DispatchQueue . main,
93+ forTaskWithIdentifier: BackgroundTaskType . long . identifier , using: DispatchQueue . main,
6394 launchHandler: self . backgroundLaunchHandler)
6495 BGTaskScheduler . shared. register (
65- forTaskWithIdentifier: Self . shortBackgroundSyncID , using: DispatchQueue . main,
96+ forTaskWithIdentifier: BackgroundTaskType . short . identifier , using: DispatchQueue . main,
6697 launchHandler: self . backgroundLaunchHandler)
6798 BGTaskScheduler . shared. register (
68- forTaskWithIdentifier: Self . continuedBackgroundSyncID , using: DispatchQueue . main,
99+ forTaskWithIdentifier: BackgroundTaskType . continued . identifier , using: DispatchQueue . main,
69100 launchHandler: self . backgroundLaunchHandler)
70101
71102 updateBackgroundRunHistory ( appending: nil )
@@ -77,16 +108,24 @@ import BackgroundTasks
77108 }
78109
79110 @available ( iOS 26 , * ) func startContinuedSync( ) throws {
80- let request = BGContinuedProcessingTaskRequest (
81- identifier: Self . continuedBackgroundSyncID,
82- title: " Synchronize files " ,
83- subtitle: " About to start... " ,
84- )
85- request. strategy = . fail
86- try BGTaskScheduler . shared. submit ( request)
111+ do {
112+ let request = BGContinuedProcessingTaskRequest (
113+ identifier: BackgroundTaskType . continued. identifier,
114+ title: " Synchronize files " ,
115+ subtitle: " About to start... " ,
116+ )
117+ request. strategy = . queue
118+ Log . info ( " Scheduling continued background processing task " )
119+ try BGTaskScheduler . shared. submit ( request)
120+ }
121+ catch {
122+ Log . warn ( " Failed to schedule continued background processing task: \( error. localizedDescription) " )
123+ throw error
124+ }
87125 }
88126
89127 private func backgroundLaunchHandler( _ task: BGTask ) {
128+ Log . info ( " Background launch handler: \( task. identifier) " )
90129 Task { @MainActor in
91130 await self . handleBackgroundSync ( task: task)
92131 }
@@ -102,9 +141,15 @@ import BackgroundTasks
102141 }
103142
104143 private func handleBackgroundSync( task: BGTask ) async {
144+ guard let taskType = BackgroundTaskType ( rawValue: task. identifier) else {
145+ Log . warn ( " invalid background task type identifier= \( task. identifier) " )
146+ return
147+ }
148+
105149 let start = Date . now
106150 self . currentBackgroundTask = task
107- Log . info ( " Start background task at \( start) \( task. identifier) " )
151+ Log . info ( " Start background task at \( start) \( task. identifier) type= \( taskType) " )
152+
108153 DispatchQueue . main. async {
109154 _ = self . scheduleBackgroundSync ( )
110155 }
@@ -144,20 +189,20 @@ import BackgroundTasks
144189
145190 // Start photo back-up if the user has enabled it
146191 var photoBackupTask : Task < ( ) , Error > ? = nil
147- if self . appState. photoBackup. enableBackgroundCopy && task . identifier == Self . longBackgroundSyncID {
192+ if self . appState. photoBackup. enableBackgroundCopy && taskType == . long {
148193 Log . info ( " Start photo backup task " )
149194 photoBackupTask = self . appState. photoBackup. backup ( appState: self . appState, fullExport: false , isInBackground: true )
150195 }
151196
152197 // Start background sync on long and short sync task (if enabled) and continued task
153- if appState . userSettings . longBackgroundSyncEnabled || appState. userSettings. shortBackgroundSyncEnabled
154- || task . identifier == Self . continuedBackgroundSyncID
198+ if ( taskType == . long && appState. userSettings. longBackgroundSyncEnabled )
199+ || ( taskType == . short && appState . userSettings . shortBackgroundSyncEnabled ) || taskType == . continued
155200 {
156201 Log . info (
157202 " Start background sync, time remaining = \( UIApplication . shared. backgroundTimeRemaining) "
158203 )
159204 await self . appState. suspend ( false )
160- currentRun = BackgroundSyncRun ( started: start, ended: nil )
205+ currentRun = BackgroundSyncRun ( started: start, ended: nil , taskType : taskType )
161206 self . lastBackgroundSyncRun = currentRun
162207 if #available( iOS 26 , * ) {
163208 if let cg = task as? BGContinuedProcessingTask {
@@ -179,6 +224,7 @@ import BackgroundTasks
179224
180225 // Run to expiration
181226 task. expirationHandler = {
227+ Log . warn ( " Task expiration handler called identifier= \( task. identifier) " )
182228 Task { @MainActor in
183229 Log . warn (
184230 " Background task expired (this should not happen because our timer should have expired the task first; perhaps iOS changed its mind?) Remaining = \( UIApplication . shared. backgroundTimeRemaining) "
@@ -189,7 +235,7 @@ import BackgroundTasks
189235 }
190236 else {
191237 // We're just doing some photo backupping this time
192- if task . identifier == Self . longBackgroundSyncID {
238+ if taskType == . long {
193239 // When background task expires, end photo back-up
194240 task. expirationHandler = {
195241 Log . warn (
@@ -289,7 +335,7 @@ import BackgroundTasks
289335 var success = true
290336
291337 if appState. userSettings. longBackgroundSyncEnabled {
292- let longRequest = BGProcessingTaskRequest ( identifier: Self . longBackgroundSyncID )
338+ let longRequest = BGProcessingTaskRequest ( identifier: BackgroundTaskType . long . identifier )
293339
294340 // No earlier than within 15 minutes
295341 longRequest. earliestBeginDate = Date ( timeIntervalSinceNow: 15 * 60 )
@@ -309,7 +355,7 @@ import BackgroundTasks
309355 }
310356
311357 if appState. userSettings. shortBackgroundSyncEnabled {
312- let shortRequest = BGAppRefreshTaskRequest ( identifier: Self . shortBackgroundSyncID )
358+ let shortRequest = BGAppRefreshTaskRequest ( identifier: BackgroundTaskType . short . identifier )
313359 // No earlier than within 15 minutes
314360 shortRequest. earliestBeginDate = Date ( timeIntervalSinceNow: 30 * 60 )
315361 Log . info (
0 commit comments