mirror of
https://github.com/immich-app/immich.git
synced 2024-12-20 00:38:24 +02:00
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)
|
||
|
}
|
||
|
}
|
||
|
|