mirror of
https://github.com/immich-app/immich.git
synced 2024-12-20 00:38:24 +02:00
87fea29e32
* first run of getting background sync working in iOS * got background sync calling into flutter * added background task * added necessary sync files * fixed some names and added more implementations * got as far as Hive.initFlutter * brute force got to await Hive.initFlutter * lots of print statements to figure out where execution is failing, and its failing at the root asset bundle in the localization.dart service * first time working, got plugins registered * removed broken cleanup code * refactored * linters * now can pass user settings * background service plugin uses app background processing instead of fetch * renamed backgroundFetch to backgroundProcessing to make it clearer * don't use max delay * adds fetch back in * fixes require charging default values and backup controller page * fixes background fetch * fixes ios not importing photos * guarded path provider ios * lint * adds max tries for heartbeat to work in iOS * fail after seconds * timeout instead of fail after seconds * removes release lock from system stop * restores checkLockReleasedWithHeartbeat to Future<void> * removes max tries from acquire lock * fixes lock timeout with iOS * restored for loop * adds comments, made the AppRefresh task only run while not requiring network or charge * fixed compile issue * now both are registered and added better comments. also added ability for task to cancel itself * added the podfile and pubspec * added backup diagnostics to IOS and removed iOS ignored backup options and fixed network connectivity always required * Added Alex's dev team * styled debug list item, fixed refresh task not set bug, fixed enable / disable background service on platform channel --------- Co-authored-by: Marty Fuhry <marty@fuhry.farm> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
189 lines
7.4 KiB
Swift
189 lines
7.4 KiB
Swift
//
|
|
// BackgroundSyncProcessing.swift
|
|
// Runner
|
|
//
|
|
// Created by Marty Fuhry on 2/6/23.
|
|
//
|
|
// Credit to https://github.com/fluttercommunity/flutter_workmanager/blob/main/ios/Classes/BackgroundWorker.swift
|
|
|
|
import Foundation
|
|
import Flutter
|
|
import BackgroundTasks
|
|
|
|
// The background worker which creates a new Flutter VM, communicates with it
|
|
// to run the backup job, and then finishes execution and calls back to its callback
|
|
// handler
|
|
class BackgroundSyncWorker {
|
|
|
|
// The Flutter engine we create for background execution.
|
|
// This is not the main Flutter engine which shows the UI,
|
|
// this is a brand new isolate created and managed in this code
|
|
// here. It does not share memory with the main
|
|
// Flutter engine which shows the UI.
|
|
// It needs to be started up, registered, and torn down here
|
|
let engine: FlutterEngine? = FlutterEngine(
|
|
name: "BackgroundImmich"
|
|
)
|
|
|
|
// The background message passing channel
|
|
var channel: FlutterMethodChannel?
|
|
|
|
var completionHandler: (UIBackgroundFetchResult) -> Void
|
|
let taskSessionStart = Date()
|
|
|
|
// We need the completion handler to tell the system when we are done running
|
|
init(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
|
|
|
// This is the background message passing channel to be used with the background engine
|
|
// created here in this platform code
|
|
self.channel = FlutterMethodChannel(
|
|
name: "immich/backgroundChannel",
|
|
binaryMessenger: engine!.binaryMessenger
|
|
)
|
|
self.completionHandler = completionHandler
|
|
}
|
|
|
|
// Handles all of the messages from the Flutter VM called into this platform code
|
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
switch call.method {
|
|
case "initialized":
|
|
// Initialize tells us that we can now call into the Flutter VM to tell it to begin the update
|
|
self.channel?.invokeMethod(
|
|
"backgroundProcessing",
|
|
arguments: nil,
|
|
result: { flutterResult in
|
|
|
|
// This is the result we send back to the BGTaskScheduler to let it know whether we'll need more time later or
|
|
// if this execution failed
|
|
let result: UIBackgroundFetchResult = (flutterResult as? Bool ?? false) ? .newData : .failed
|
|
|
|
// Show the task duration
|
|
let taskSessionCompleter = Date()
|
|
let taskDuration = taskSessionCompleter.timeIntervalSince(self.taskSessionStart)
|
|
print("[\(String(describing: self))] \(#function) -> performBackgroundRequest.\(result) (finished in \(taskDuration) seconds)")
|
|
|
|
// Complete the execution
|
|
self.complete(result)
|
|
})
|
|
break
|
|
case "updateNotification":
|
|
// TODO: implement update notification
|
|
result(true)
|
|
break
|
|
case "showError":
|
|
// TODO: implement show error
|
|
result(true)
|
|
break
|
|
case "clearErrorNotifications":
|
|
// TODO: implement clear error notifications
|
|
result(true)
|
|
break
|
|
case "hasContentChanged":
|
|
// This is only called for Android, but we provide an implementation here
|
|
// telling Flutter that we don't have any information about whether the gallery
|
|
// contents have changed or not, so we can just say "no, they've not changed"
|
|
result(false)
|
|
break
|
|
default:
|
|
result(FlutterError())
|
|
self.complete(UIBackgroundFetchResult.failed)
|
|
}
|
|
}
|
|
|
|
// Runs the background sync by starting up a new isolate and handling the calls
|
|
// until it completes
|
|
public func run(maxSeconds: Int?) {
|
|
// We need the callback handle to start up the Flutter VM from the entry point
|
|
let defaults = UserDefaults.standard
|
|
guard let callbackHandle = defaults.value(forKey: "callback_handle") as? Int64 else {
|
|
// Can't find the callback handle, this is fatal
|
|
complete(UIBackgroundFetchResult.failed)
|
|
return
|
|
|
|
}
|
|
|
|
// Use the provided callbackHandle to get the callback function
|
|
guard let callback = FlutterCallbackCache.lookupCallbackInformation(callbackHandle) else {
|
|
// We need this callback or else this is fatal
|
|
complete(UIBackgroundFetchResult.failed)
|
|
return
|
|
}
|
|
|
|
// Sanity check for the engine existing
|
|
if engine == nil {
|
|
complete(UIBackgroundFetchResult.failed)
|
|
return
|
|
}
|
|
|
|
// Run the engine
|
|
let isRunning = engine!.run(
|
|
withEntrypoint: callback.callbackName,
|
|
libraryURI: callback.callbackLibraryPath
|
|
)
|
|
|
|
// If this engine isn't running, this is fatal
|
|
if !isRunning {
|
|
complete(UIBackgroundFetchResult.failed)
|
|
return
|
|
}
|
|
|
|
// If we have a timer, we need to start the timer to cancel ourselves
|
|
// so that we don't run longer than the provided maxSeconds
|
|
// After maxSeconds has elapsed, we will invoke "systemStop"
|
|
if maxSeconds != nil {
|
|
// Schedule a non-repeating timer to run after maxSeconds
|
|
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!),
|
|
repeats: false) { timer in
|
|
// The callback invalidates the timer and stops execution
|
|
timer.invalidate()
|
|
|
|
// If the channel is already deallocated, we don't need to do anything
|
|
if self.channel == nil {
|
|
return
|
|
}
|
|
|
|
// Tell the Flutter VM to stop backing up now
|
|
self.channel?.invokeMethod(
|
|
"systemStop",
|
|
arguments: nil,
|
|
result: nil)
|
|
|
|
// Complete the execution
|
|
self.complete(UIBackgroundFetchResult.newData)
|
|
}
|
|
}
|
|
|
|
// Set the handle function to the channel message handler
|
|
self.channel?.setMethodCallHandler(handle)
|
|
|
|
// Register this to get access to the plugins on the platform channel
|
|
BackgroundServicePlugin.flutterPluginRegistrantCallback?(engine!)
|
|
}
|
|
|
|
// Cancels execution of this task, used by the system's task expiration handler
|
|
// which is called shortly before execution is about to expire
|
|
public func cancel() {
|
|
// If the channel is already deallocated, we don't need to do anything
|
|
if self.channel == nil {
|
|
return
|
|
}
|
|
|
|
// Tell the Flutter VM to stop backing up now
|
|
self.channel?.invokeMethod(
|
|
"systemStop",
|
|
arguments: nil,
|
|
result: nil)
|
|
|
|
// Complete the execution
|
|
self.complete(UIBackgroundFetchResult.newData)
|
|
}
|
|
|
|
// Completes the execution, destroys the engine, and sends a completion to our callback completionHandler
|
|
private func complete(_ fetchResult: UIBackgroundFetchResult) {
|
|
engine?.destroyContext()
|
|
channel = nil
|
|
completionHandler(fetchResult)
|
|
}
|
|
}
|
|
|