@@ -13,6 +13,7 @@ import Queuer
1313import EasyTipView
1414import SwiftUI
1515import RealmSwift
16+ import MoEngageInApps
1617
1718class AppDelegate : UIResponder , UIApplicationDelegate , UNUserNotificationCenterDelegate {
1819 var backgroundSessionCompletionHandler : ( ( ) -> Void ) ?
@@ -25,6 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
2526 return ProcessInfo . processInfo. arguments. contains ( " UI_TESTING " )
2627 }
2728 var notificationSettings : UNNotificationSettings ?
29+ var pushKitToken : String ?
2830
2931 var loginFlowV2Token = " "
3032 var loginFlowV2Endpoint = " "
@@ -46,13 +48,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
4648 @objc var userId : String = " "
4749 @objc var password : String = " "
4850 var timerErrorNetworking : Timer ?
49-
51+ var tipView : EasyTipView ?
52+
53+ var pushSubscriptionTask : Task < Void , Never > ?
54+
5055 func application( _ application: UIApplication , didFinishLaunchingWithOptions launchOptions: [ UIApplication . LaunchOptionsKey : Any ] ? ) -> Bool {
5156 if isUiTestingEnabled {
5257 Task {
5358 await NCAccount ( ) . deleteAllAccounts ( )
5459 }
5560 }
61+
62+ UINavigationBar . appearance ( ) . tintColor = NCBrandColor . shared. customer
63+ UIToolbar . appearance ( ) . tintColor = NCBrandColor . shared. customer
64+
5665 let utilityFileSystem = NCUtilityFileSystem ( )
5766 let utility = NCUtility ( )
5867
@@ -96,6 +105,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
96105
97106 nkLog ( start: " Start session with level \( NCPreferences ( ) . log) " + versionNextcloudiOS)
98107
108+ // if NCBrandOptions.shared.disable_log {
109+ // utilityFileSystem.removeFile(atPath: NextcloudKit.shared.nkCommonInstance.filenamePathLog)
110+ // utilityFileSystem.removeFile(atPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/" + NextcloudKit.shared.nkCommonInstance.filenameLog)
111+ // } else {
112+ // NextcloudKit.shared.setupLog(pathLog: utilityFileSystem.directoryGroup,
113+ // levelLog: NCKeychain().logLevel,
114+ // copyLogToDocumentDirectory: true)
115+ // NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start session with level \(NCKeychain().logLevel) " + versionNextcloudiOS)
116+ // }
117+
99118 // Push Notification & display notification
100119 UNUserNotificationCenter . current ( ) . getNotificationSettings { settings in
101120 self . notificationSettings = settings
@@ -137,7 +156,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
137156 _ = NCNetworking . shared
138157 _ = NCDownloadAction . shared
139158 _ = NCNetworkingProcess . shared
140-
159+ _ = NCTransferProgress . shared
160+ _ = NCActionCenter . shared
161+
162+ NCTransferProgress . shared. setup ( )
163+ NCActionCenter . shared. setup ( )
164+
141165// if account.isEmpty {
142166// if NCBrandOptions.shared.disable_intro {
143167// openLogin(viewController: nil, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
@@ -188,6 +212,247 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
188212 // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
189213 }
190214
215+ // MARK: - Background Task
216+
217+ /*
218+ @discussion Schedule a refresh task request to ask that the system launch your app briefly so that you can download data and keep your app's contents up-to-date. The system will fulfill this request intelligently based on system conditions and app usage.
219+ */
220+ func scheduleAppRefresh( ) {
221+ let request = BGAppRefreshTaskRequest ( identifier: global. refreshTask)
222+
223+ request. earliestBeginDate = Date ( timeIntervalSinceNow: 60 ) // Refresh after 60 seconds.
224+
225+ do {
226+ try BGTaskScheduler . shared. submit ( request)
227+ } catch {
228+ nkLog ( tag: self . global. logTagTask, emoji: . error, message: " Refresh task failed to submit request: \( error) " )
229+ }
230+ }
231+
232+ /*
233+ @discussion Schedule a processing task request to ask that the system launch your app when conditions are favorable for battery life to handle deferrable, longer-running processing, such as syncing, database maintenance, or similar tasks. The system will attempt to fulfill this request to the best of its ability within the next two days as long as the user has used your app within the past week.
234+ */
235+ func scheduleAppProcessing( ) {
236+ let request = BGProcessingTaskRequest ( identifier: global. processingTask)
237+
238+ request. earliestBeginDate = Date ( timeIntervalSinceNow: 5 * 60 ) // Refresh after 5 minutes.
239+ request. requiresNetworkConnectivity = false
240+ request. requiresExternalPower = false
241+
242+ do {
243+ try BGTaskScheduler . shared. submit ( request)
244+ } catch {
245+ nkLog ( tag: self . global. logTagTask, emoji: . error, message: " Processing task failed to submit request: \( error) " )
246+ }
247+ }
248+
249+ func handleAppRefresh( _ task: BGAppRefreshTask ) {
250+ nkLog ( tag: self . global. logTagTask, emoji: . start, message: " Start refresh task " )
251+ guard NCManageDatabase . shared. openRealmBackground ( ) else {
252+ nkLog ( tag: self . global. logTagTask, emoji: . error, message: " Failed to open Realm in background " )
253+ task. setTaskCompleted ( success: false )
254+ return
255+ }
256+
257+ // Schedule next refresh
258+ scheduleAppRefresh ( )
259+
260+ Task {
261+ defer {
262+ task. setTaskCompleted ( success: true )
263+ }
264+
265+ await backgroundSync ( task: task)
266+ }
267+ }
268+
269+ func handleProcessingTask( _ task: BGProcessingTask ) {
270+ nkLog ( tag: self . global. logTagTask, emoji: . start, message: " Start processing task " )
271+ guard NCManageDatabase . shared. openRealmBackground ( ) else {
272+ nkLog ( tag: self . global. logTagTask, emoji: . error, message: " Failed to open Realm in background " )
273+ task. setTaskCompleted ( success: false )
274+ return
275+ }
276+ var expired = false
277+ task. expirationHandler = {
278+ expired = true
279+ }
280+
281+ // Schedule next processing task
282+ scheduleAppProcessing ( )
283+
284+ Task {
285+ defer {
286+ task. setTaskCompleted ( success: true )
287+ }
288+
289+ // If possible, cleaning every week
290+ if NCPreferences ( ) . cleaningWeek ( ) {
291+ // BGTask expiration flag
292+ nkLog ( tag: self . global. logTagBgSync, emoji: . start, message: " Start cleaning week " )
293+ let tblAccounts = await NCManageDatabase . shared. getAllTableAccountAsync ( )
294+ for tblAccount in tblAccounts {
295+ await NCManageDatabase . shared. cleanTablesOcIds ( account: tblAccount. account, userId: tblAccount. userId, urlBase: tblAccount. urlBase)
296+ guard !expired else { return }
297+ }
298+ await NCUtilityFileSystem ( ) . cleanUpAsync ( )
299+
300+ NCPreferences ( ) . setDoneCleaningWeek ( )
301+ nkLog ( tag: self . global. logTagBgSync, emoji: . stop, message: " Stop cleaning week " )
302+ } else {
303+ await backgroundSync ( task: task)
304+ }
305+ }
306+ }
307+
308+ func backgroundSync( task: BGTask ? = nil ) async {
309+ defer {
310+ // Update badge safely at the end of the background sync
311+ Task { @MainActor in
312+ do {
313+ let count = await NCManageDatabase . shared. getMetadatasInWaitingCountAsync ( )
314+ try await UNUserNotificationCenter . current ( ) . setBadgeCount ( count)
315+ } catch { }
316+ }
317+ }
318+
319+ // BGTask expiration flag
320+ var expired = false
321+ task? . expirationHandler = {
322+ expired = true
323+ }
324+
325+ // Discover new items for Auto Upload
326+ let numAutoUpload = await NCAutoUpload . shared. initAutoUpload ( )
327+ nkLog ( tag: self . global. logTagBgSync, emoji: . start, message: " Auto upload found \( numAutoUpload) new items " )
328+ guard !expired else { return }
329+
330+ // Fetch METADATAS
331+ let metadatas = await NCManageDatabase . shared. getMetadataProcess ( )
332+ guard !metadatas. isEmpty, !expired else {
333+ return
334+ }
335+
336+ // Create all pending Auto Upload folders (fail-fast)
337+ let pendingCreateFolders = metadatas. lazy. filter {
338+ $0. status == self . global. metadataStatusWaitCreateFolder &&
339+ $0. sessionSelector == self . global. selectorUploadAutoUpload
340+ }
341+
342+ for metadata in pendingCreateFolders {
343+ guard !expired else { return }
344+
345+ let err = await NCNetworking . shared. createFolderForAutoUpload (
346+ serverUrlFileName: metadata. serverUrlFileName,
347+ account: metadata. account
348+ )
349+ // Fail-fast: abort the whole sync on first failure
350+ if err != . success {
351+ nkLog ( tag: self . global. logTagBgSync, emoji: . error, message: " Create folder ' \( metadata. serverUrlFileName) ' failed: \( err. errorCode) – aborting sync " )
352+ return
353+ }
354+ }
355+
356+ // Capacity computation
357+ let downloading = metadatas. lazy. filter { $0. status == self . global. metadataStatusDownloading } . count
358+ let uploading = metadatas. lazy. filter { $0. status == self . global. metadataStatusUploading } . count
359+ let availableProcess = max ( 0 , NCBrandOptions . shared. numMaximumProcess - ( downloading + uploading) )
360+
361+ // Start Auto Uploads
362+ let metadatasToUpload = Array (
363+ metadatas. lazy. filter {
364+ $0. status == self . global. metadataStatusWaitUpload &&
365+ $0. sessionSelector == self . global. selectorUploadAutoUpload &&
366+ $0. chunk == 0
367+ }
368+ . prefix ( availableProcess)
369+ )
370+
371+ let cameraRoll = NCCameraRoll ( )
372+
373+ for metadata in metadatasToUpload {
374+ guard !expired else { return }
375+
376+ // File exists? skip it
377+ let existsResult = await NCNetworking . shared. fileExists ( serverUrlFileName: metadata. serverUrlFileName, account: metadata. account)
378+ if existsResult == . success {
379+ // File exists → delete from local metadata and skip
380+ await NCManageDatabase . shared. deleteMetadataAsync ( id: metadata. ocId)
381+ continue
382+ } else if existsResult. errorCode == 404 {
383+ // 404 Not Found → directory does not exist
384+ // Proceed
385+ } else {
386+ // Any other error (423 locked, 401 auth, 403 forbidden, 5xx, etc.)
387+ continue
388+ }
389+
390+ // Expand seed into concrete metadatas (e.g., Live Photo pair)
391+ let extracted = await cameraRoll. extractCameraRoll ( from: metadata)
392+ guard !expired else { return }
393+
394+ for metadata in extracted {
395+ // Sequential await keeps ordering and simplifies backpressure
396+ let err = await NCNetworking . shared. uploadFileInBackground ( metadata: metadata. detachedCopy ( ) )
397+ if err == . success {
398+ nkLog ( tag: self . global. logTagBgSync, message: " In queued upload \( metadata. fileName) -> \( metadata. serverUrl) " )
399+ } else {
400+ nkLog ( tag: self . global. logTagBgSync, emoji: . error, message: " Upload failed \( metadata. fileName) -> \( metadata. serverUrl) [ \( err. errorDescription) ] " )
401+ }
402+ guard !expired else { return }
403+ }
404+ }
405+ }
406+
407+ // func handleAppRefreshProcessingTask(taskText: String, completion: @escaping () -> Void = {}) {
408+ // Task {
409+ // var numAutoUpload = 0
410+ // guard let account = NCManageDatabase.shared.getActiveTableAccount()?.account else {
411+ // return
412+ // }
413+ //
414+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) start handle")
415+ //
416+ // // Test every > 1 min
417+ // if Date() > self.taskAutoUploadDate.addingTimeInterval(60) {
418+ // self.taskAutoUploadDate = Date()
419+ // numAutoUpload = await NCAutoUpload.shared.initAutoUpload(account: account)
420+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) auto upload with \(numAutoUpload) uploads")
421+ // } else {
422+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) disabled auto upload")
423+ // }
424+ //
425+ // let results = await NCNetworkingProcess.shared.refreshProcessingTask()
426+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) networking process with download: \(results.counterDownloading) upload: \(results.counterUploading)")
427+ //
428+ // if taskText == "ProcessingTask",
429+ // numAutoUpload == 0,
430+ // results.counterDownloading == 0,
431+ // results.counterUploading == 0,
432+ // let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "offlineDate", ascending: true) {
433+ // for directory: tableDirectory in directories {
434+ // // test only 3 time for day (every 8 h.)
435+ // if let offlineDate = directory.offlineDate, offlineDate.addingTimeInterval(28800) > Date() {
436+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) skip synchronization for \(directory.serverUrl) in date \(offlineDate)")
437+ // continue
438+ // }
439+ // let results = await NCNetworking.shared.synchronization(account: account, serverUrl: directory.serverUrl, add: false)
440+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) end synchronization for \(directory.serverUrl), errorCode: \(results.errorCode), item: \(results.num)")
441+ // }
442+ // }
443+ //
444+ // let counter = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND (session == %@ || session == %@) AND status != %d",
445+ // account,
446+ // NCNetworking.shared.sessionDownloadBackground,
447+ // NCNetworking.shared.sessionUploadBackground,
448+ // NCGlobal.shared.metadataStatusNormal))?.count ?? 0
449+ // UIApplication.shared.applicationIconBadgeNumber = counter
450+ //
451+ // NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) completion handle")
452+ // completion()
453+ // }
454+ // }
455+
191456 // MARK: - Background Networking Session
192457
193458 func application( _ application: UIApplication , handleEventsForBackgroundURLSession identifier: String , completionHandler: @escaping ( ) -> Void ) {
@@ -246,6 +511,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
246511 }
247512 }
248513
514+ func subscribingPushNotification( account: String , urlBase: String , user: String ) {
515+ #if !targetEnvironment(simulator)
516+ NCNetworking . shared. checkPushNotificationServerProxyCertificateUntrusted ( viewController: UIApplication . shared. firstWindow? . rootViewController) { error in
517+ if error == . success {
518+ NCPushNotification . shared. subscribingNextcloudServerPushNotification ( account: account, urlBase: urlBase, user: user, pushKitToken: self . pushKitToken)
519+ }
520+ }
521+ #endif
522+ }
523+
249524 func nextcloudPushNotificationAction( data: [ String : AnyObject ] ) {
250525 let account = data [ " account " ] as? String ?? " unavailable "
251526 let app = data [ " app " ] as? String
@@ -529,6 +804,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
529804 utilityFileSystem. removeTemporaryDirectory ( )
530805
531806 NCPreferences ( ) . removeAll ( )
807+ // NCKeychain().removeAll()
808+ // NCNetworking.shared.removeAllKeyUserDefaultsData(account: nil)
532809
533810 exit ( 0 )
534811 }
0 commit comments