mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
fix: local sync task never runs on iOS (#21491)
* fix: local sync task never runs on iOS * chore: rename ios register method * refactor from using dart callback to dart entrypoint + more logs * check if file exists before hashing * reschedule local sync task * chore: rename background worker logger * refactor: move file exists check inside repo --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
4f7702c6bf
commit
674faf2e57
12 changed files with 218 additions and 152 deletions
|
|
@ -62,7 +62,7 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
|
|||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerFgHostApi {
|
||||
fun enableSyncWorker()
|
||||
fun enableUploadWorker(callbackHandle: Long)
|
||||
fun enableUploadWorker()
|
||||
fun disableUploadWorker()
|
||||
|
||||
companion object {
|
||||
|
|
@ -93,11 +93,9 @@ interface BackgroundWorkerFgHostApi {
|
|||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val callbackHandleArg = args[0] as Long
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.enableUploadWorker(callbackHandleArg)
|
||||
api.enableUploadWorker()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
|
|
@ -130,6 +128,7 @@ interface BackgroundWorkerFgHostApi {
|
|||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerBgHostApi {
|
||||
fun onInitialized()
|
||||
fun close()
|
||||
|
||||
companion object {
|
||||
/** The codec used by BackgroundWorkerBgHostApi. */
|
||||
|
|
@ -156,6 +155,22 @@ interface BackgroundWorkerBgHostApi {
|
|||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.close()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||
import com.google.common.util.concurrent.SettableFuture
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor.DartCallback
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import io.flutter.embedding.engine.loader.FlutterLoader
|
||||
import io.flutter.view.FlutterCallbackInformation
|
||||
|
||||
private const val TAG = "BackgroundWorker"
|
||||
|
||||
|
|
@ -58,25 +57,6 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
engine = FlutterEngine(ctx)
|
||||
|
||||
// Retrieve the callback handle stored by the main Flutter app
|
||||
// This handle points to the Flutter function that should be executed in the background
|
||||
val callbackHandle =
|
||||
ctx.getSharedPreferences(BackgroundWorkerApiImpl.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getLong(BackgroundWorkerApiImpl.SHARED_PREF_CALLBACK_HANDLE, 0L)
|
||||
|
||||
if (callbackHandle == 0L) {
|
||||
// Without a valid callback handle, we cannot start the Flutter background execution
|
||||
complete(Result.failure())
|
||||
return@ensureInitializationCompleteAsync
|
||||
}
|
||||
|
||||
// Start the Flutter engine with the specified callback as the entry point
|
||||
val callback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle)
|
||||
if (callback == null) {
|
||||
complete(Result.failure())
|
||||
return@ensureInitializationCompleteAsync
|
||||
}
|
||||
|
||||
// Register custom plugins
|
||||
MainActivity.registerPlugins(ctx, engine!!)
|
||||
flutterApi =
|
||||
|
|
@ -86,8 +66,12 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||
api = this
|
||||
)
|
||||
|
||||
engine!!.dartExecutor.executeDartCallback(
|
||||
DartCallback(ctx.assets, loader.findAppBundlePath(), callback)
|
||||
engine!!.dartExecutor.executeDartEntrypoint(
|
||||
DartExecutor.DartEntrypoint(
|
||||
loader.findAppBundlePath(),
|
||||
"package:immich_mobile/domain/services/background_worker.service.dart",
|
||||
"backgroundSyncNativeEntrypoint"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,14 +93,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system has to stop this worker because constraints are
|
||||
* no longer met or the system needs resources for more important tasks
|
||||
* This is also called when the worker has been explicitly cancelled or replaced
|
||||
*/
|
||||
override fun onStopped() {
|
||||
Log.d(TAG, "About to stop BackupWorker")
|
||||
|
||||
override fun close() {
|
||||
if (isComplete) {
|
||||
return
|
||||
}
|
||||
|
|
@ -134,6 +111,16 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||
}, 5000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system has to stop this worker because constraints are
|
||||
* no longer met or the system needs resources for more important tasks
|
||||
* This is also called when the worker has been explicitly cancelled or replaced
|
||||
*/
|
||||
override fun onStopped() {
|
||||
Log.d(TAG, "About to stop BackupWorker")
|
||||
close()
|
||||
}
|
||||
|
||||
private fun handleHostResult(result: kotlin.Result<Unit>) {
|
||||
if (isComplete) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -21,9 +21,8 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
|||
Log.i(TAG, "Scheduled media observer")
|
||||
}
|
||||
|
||||
override fun enableUploadWorker(callbackHandle: Long) {
|
||||
override fun enableUploadWorker() {
|
||||
updateUploadEnabled(ctx, true)
|
||||
updateCallbackHandle(ctx, callbackHandle)
|
||||
Log.i(TAG, "Scheduled background upload tasks")
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +40,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
|||
|
||||
const val SHARED_PREF_NAME = "Immich::Background"
|
||||
const val SHARED_PREF_BACKUP_ENABLED = "Background::backup::enabled"
|
||||
const val SHARED_PREF_CALLBACK_HANDLE = "Background::backup::callbackHandle"
|
||||
|
||||
private fun updateUploadEnabled(context: Context, enabled: Boolean) {
|
||||
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit {
|
||||
|
|
@ -49,12 +47,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateCallbackHandle(context: Context, callbackHandle: Long) {
|
||||
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit {
|
||||
putLong(SHARED_PREF_CALLBACK_HANDLE, callbackHandle)
|
||||
}
|
||||
}
|
||||
|
||||
fun enqueueMediaObserver(ctx: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import UIKit
|
|||
BackgroundServicePlugin.register(with: self.registrar(forPlugin: "BackgroundServicePlugin")!)
|
||||
|
||||
BackgroundServicePlugin.registerBackgroundProcessing()
|
||||
BackgroundWorkerApiImpl.registerBackgroundProcessing()
|
||||
BackgroundWorkerApiImpl.registerBackgroundWorkers()
|
||||
|
||||
BackgroundServicePlugin.setPluginRegistrantCallback { registry in
|
||||
if !registry.hasPlugin("org.cocoapods.path-provider-foundation") {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
|
|||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol BackgroundWorkerFgHostApi {
|
||||
func enableSyncWorker() throws
|
||||
func enableUploadWorker(callbackHandle: Int64) throws
|
||||
func enableUploadWorker() throws
|
||||
func disableUploadWorker() throws
|
||||
}
|
||||
|
||||
|
|
@ -99,11 +99,9 @@ class BackgroundWorkerFgHostApiSetup {
|
|||
}
|
||||
let enableUploadWorkerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
enableUploadWorkerChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let callbackHandleArg = args[0] as! Int64
|
||||
enableUploadWorkerChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
try api.enableUploadWorker(callbackHandle: callbackHandleArg)
|
||||
try api.enableUploadWorker()
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
|
|
@ -130,6 +128,7 @@ class BackgroundWorkerFgHostApiSetup {
|
|||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol BackgroundWorkerBgHostApi {
|
||||
func onInitialized() throws
|
||||
func close() throws
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
|
|
@ -151,6 +150,19 @@ class BackgroundWorkerBgHostApiSetup {
|
|||
} else {
|
||||
onInitializedChannel.setMessageHandler(nil)
|
||||
}
|
||||
let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
closeChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
try api.close()
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
closeChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
||||
|
|
|
|||
|
|
@ -86,28 +86,10 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
|||
* starts the engine, and sets up a timeout timer if specified.
|
||||
*/
|
||||
func run() {
|
||||
// Retrieve the callback handle stored by the main Flutter app
|
||||
// This handle points to the Flutter function that should be executed in the background
|
||||
let callbackHandle = Int64(UserDefaults.standard.string(
|
||||
forKey: BackgroundWorkerApiImpl.backgroundUploadCallbackHandleKey) ?? "0") ?? 0
|
||||
|
||||
if callbackHandle == 0 {
|
||||
// Without a valid callback handle, we cannot start the Flutter background execution
|
||||
complete(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the callback handle to retrieve the actual Flutter callback information
|
||||
guard let callback = FlutterCallbackCache.lookupCallbackInformation(callbackHandle) else {
|
||||
// The callback handle is invalid or the callback was not found
|
||||
complete(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
// Start the Flutter engine with the specified callback as the entry point
|
||||
let isRunning = engine.run(
|
||||
withEntrypoint: callback.callbackName,
|
||||
libraryURI: callback.callbackLibraryPath
|
||||
withEntrypoint: "backgroundSyncNativeEntrypoint",
|
||||
libraryURI: "package:immich_mobile/domain/services/background_worker.service.dart"
|
||||
)
|
||||
|
||||
// Verify that the Flutter engine started successfully
|
||||
|
|
@ -127,7 +109,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
|||
if maxSeconds != nil {
|
||||
// Schedule a timer to cancel the task after the specified timeout period
|
||||
Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!), repeats: false) { _ in
|
||||
self.cancel()
|
||||
self.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +138,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
|||
* Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure
|
||||
* the completion handler is eventually called even if Flutter doesn't respond.
|
||||
*/
|
||||
func cancel() {
|
||||
func close() {
|
||||
if isComplete {
|
||||
return
|
||||
}
|
||||
|
|
@ -182,7 +164,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
|
|||
private func handleHostResult(result: Result<Void, PigeonError>) {
|
||||
switch result {
|
||||
case .success(): self.complete(success: true)
|
||||
case .failure(_): self.cancel()
|
||||
case .failure(_): self.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
|||
print("BackgroundUploadImpl:enableSyncWorker Local Sync worker scheduled")
|
||||
}
|
||||
|
||||
func enableUploadWorker(callbackHandle: Int64) throws {
|
||||
func enableUploadWorker() throws {
|
||||
BackgroundWorkerApiImpl.updateUploadEnabled(true)
|
||||
// Store the callback handle for later use when starting background Flutter isolates
|
||||
BackgroundWorkerApiImpl.updateUploadCallbackHandle(callbackHandle)
|
||||
|
||||
BackgroundWorkerApiImpl.scheduleRefreshUpload()
|
||||
BackgroundWorkerApiImpl.scheduleProcessingUpload()
|
||||
|
|
@ -23,7 +21,6 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
|||
}
|
||||
|
||||
public static let backgroundUploadEnabledKey = "immich:background:backup:enabled"
|
||||
public static let backgroundUploadCallbackHandleKey = "immich:background:backup:callbackHandle"
|
||||
|
||||
private static let localSyncTaskID = "app.alextran.immich.background.localSync"
|
||||
private static let refreshUploadTaskID = "app.alextran.immich.background.refreshUpload"
|
||||
|
|
@ -33,17 +30,13 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
|||
return UserDefaults.standard.set(isEnabled, forKey: BackgroundWorkerApiImpl.backgroundUploadEnabledKey)
|
||||
}
|
||||
|
||||
private static func updateUploadCallbackHandle(_ callbackHandle: Int64) {
|
||||
return UserDefaults.standard.set(String(callbackHandle), forKey: BackgroundWorkerApiImpl.backgroundUploadCallbackHandleKey)
|
||||
}
|
||||
|
||||
private static func cancelUploadTasks() {
|
||||
BackgroundWorkerApiImpl.updateUploadEnabled(false)
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: refreshUploadTaskID);
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: processingUploadTaskID);
|
||||
}
|
||||
|
||||
public static func registerBackgroundProcessing() {
|
||||
public static func registerBackgroundWorkers() {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: processingUploadTaskID, using: nil) { task in
|
||||
if task is BGProcessingTask {
|
||||
|
|
@ -102,9 +95,22 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
|||
}
|
||||
|
||||
private static func handleBackgroundRefresh(task: BGAppRefreshTask, taskType: BackgroundTaskType) {
|
||||
let maxSeconds: Int?
|
||||
|
||||
switch taskType {
|
||||
case .localSync:
|
||||
maxSeconds = 15
|
||||
scheduleLocalSync()
|
||||
case .refreshUpload:
|
||||
maxSeconds = 20
|
||||
scheduleRefreshUpload()
|
||||
// Restrict the refresh task to run only for a maximum of 20 seconds
|
||||
runBackgroundWorker(task: task, taskType: taskType, maxSeconds: 20)
|
||||
case .processingUpload:
|
||||
print("Unexpected background refresh task encountered")
|
||||
return;
|
||||
}
|
||||
|
||||
// Restrict the refresh task to run only for a maximum of (maxSeconds) seconds
|
||||
runBackgroundWorker(task: task, taskType: taskType, maxSeconds: maxSeconds)
|
||||
}
|
||||
|
||||
private static func handleBackgroundProcessing(task: BGProcessingTask) {
|
||||
|
|
@ -134,7 +140,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
|||
|
||||
task.expirationHandler = {
|
||||
DispatchQueue.main.async {
|
||||
backgroundWorker.cancel()
|
||||
backgroundWorker.close()
|
||||
}
|
||||
isSuccess = false
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
|||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
import 'package:immich_mobile/services/localization.service.dart';
|
||||
|
|
@ -31,9 +32,7 @@ class BackgroundWorkerFgService {
|
|||
// TODO: Move this call to native side once old timeline is removed
|
||||
Future<void> enableSyncService() => _foregroundHostApi.enableSyncWorker();
|
||||
|
||||
Future<void> enableUploadService() => _foregroundHostApi.enableUploadWorker(
|
||||
PluginUtilities.getCallbackHandle(_backgroundSyncNativeEntrypoint)!.toRawHandle(),
|
||||
);
|
||||
Future<void> enableUploadService() => _foregroundHostApi.enableUploadWorker();
|
||||
|
||||
Future<void> disableUploadService() => _foregroundHostApi.disableUploadWorker();
|
||||
}
|
||||
|
|
@ -44,7 +43,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
final Drift _drift;
|
||||
final DriftLogger _driftLogger;
|
||||
final BackgroundWorkerBgHostApi _backgroundHostApi;
|
||||
final Logger _logger = Logger('BackgroundUploadBgService');
|
||||
final Logger _logger = Logger('BackgroundWorkerBgService');
|
||||
|
||||
bool _isCleanedUp = false;
|
||||
|
||||
|
|
@ -66,6 +65,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
bool get _isBackupEnabled => _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
|
||||
Future<void> init() async {
|
||||
try {
|
||||
await loadTranslations();
|
||||
HttpSSLOptions.apply(applyNative: false);
|
||||
await _ref.read(authServiceProvider).setOpenApiServiceEndpoint();
|
||||
|
|
@ -83,12 +83,19 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
await FileDownloader().trackTasks();
|
||||
configureFileDownloaderNotifications();
|
||||
|
||||
// Notify the host that the background upload service has been initialized and is ready to use
|
||||
await _backgroundHostApi.onInitialized();
|
||||
await _ref.read(fileMediaRepositoryProvider).enableBackgroundAccess();
|
||||
|
||||
// Notify the host that the background worker service has been initialized and is ready to use
|
||||
_backgroundHostApi.onInitialized();
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to initialize background worker", error, stack);
|
||||
_backgroundHostApi.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLocalSync(int? maxSeconds) async {
|
||||
try {
|
||||
_logger.info('Local background syncing started');
|
||||
final sw = Stopwatch()..start();
|
||||
|
||||
|
|
@ -97,6 +104,11 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
|
||||
sw.stop();
|
||||
_logger.info("Local sync completed in ${sw.elapsed.inSeconds}s");
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to complete local sync", error, stack);
|
||||
} finally {
|
||||
await _cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/* We do the following on Android upload
|
||||
|
|
@ -107,16 +119,20 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
*/
|
||||
@override
|
||||
Future<void> onAndroidUpload() async {
|
||||
try {
|
||||
_logger.info('Android background processing started');
|
||||
final sw = Stopwatch()..start();
|
||||
|
||||
await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6));
|
||||
await _handleBackup(processBulk: false);
|
||||
|
||||
await _cleanup();
|
||||
|
||||
sw.stop();
|
||||
_logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s");
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to complete Android background processing", error, stack);
|
||||
} finally {
|
||||
await _cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/* We do the following on background upload
|
||||
|
|
@ -129,6 +145,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
*/
|
||||
@override
|
||||
Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async {
|
||||
try {
|
||||
_logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s');
|
||||
final sw = Stopwatch()..start();
|
||||
|
||||
|
|
@ -142,16 +159,23 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
await backupFuture;
|
||||
}
|
||||
|
||||
await _cleanup();
|
||||
|
||||
sw.stop();
|
||||
_logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s");
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to complete iOS background upload", error, stack);
|
||||
} finally {
|
||||
await _cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> cancel() async {
|
||||
_logger.warning("Background upload cancelled");
|
||||
_logger.warning("Background worker cancelled");
|
||||
try {
|
||||
await _cleanup();
|
||||
} catch (error, stack) {
|
||||
debugPrint('Failed to cleanup background worker: $error with stack: $stack');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cleanup() async {
|
||||
|
|
@ -159,13 +183,21 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_isCleanedUp = true;
|
||||
_logger.info("Cleaning up background worker");
|
||||
await _ref.read(backgroundSyncProvider).cancel();
|
||||
await _ref.read(backgroundSyncProvider).cancelLocal();
|
||||
if (_isar.isOpen) {
|
||||
await _isar.close();
|
||||
}
|
||||
await _drift.close();
|
||||
await _driftLogger.close();
|
||||
_ref.dispose();
|
||||
debugPrint("Background worker cleaned up");
|
||||
} catch (error, stack) {
|
||||
debugPrint('Failed to cleanup background worker: $error with stack: $stack');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleBackup({bool processBulk = true}) async {
|
||||
|
|
@ -221,8 +253,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||
}
|
||||
}
|
||||
|
||||
/// Native entry invoked from the background worker. If renaming or moving this to a different
|
||||
/// library, make sure to update the entry points and URI in native workers as well
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _backgroundSyncNativeEntrypoint() async {
|
||||
Future<void> backgroundSyncNativeEntrypoint() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
DartPluginRegistrant.ensureInitialized();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ class StorageRepository {
|
|||
file = await entity?.originFile;
|
||||
if (file == null) {
|
||||
log.warning("Cannot get file for asset $assetId");
|
||||
return null;
|
||||
}
|
||||
|
||||
final exists = await file.exists();
|
||||
if (!exists) {
|
||||
log.warning("File for asset $assetId does not exist");
|
||||
return null;
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
log.warning("Error getting file for asset $assetId", error, stackTrace);
|
||||
|
|
@ -34,6 +41,13 @@ class StorageRepository {
|
|||
log.warning(
|
||||
"Cannot get motion file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final exists = await file.exists();
|
||||
if (!exists) {
|
||||
log.warning("Motion file for asset ${asset.id} does not exist");
|
||||
return null;
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
log.warning(
|
||||
|
|
|
|||
|
|
@ -206,14 +206,14 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// needs to be delayed so that EasyLocalization is working
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
ref.read(backgroundServiceProvider).disableService();
|
||||
ref.read(driftBackgroundUploadFgService).enableSyncService();
|
||||
if (ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup)) {
|
||||
ref.read(backgroundServiceProvider).disableService();
|
||||
ref.read(driftBackgroundUploadFgService).enableUploadService();
|
||||
}
|
||||
} else {
|
||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
ref.read(driftBackgroundUploadFgService).disableUploadService();
|
||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
27
mobile/lib/platform/background_worker_api.g.dart
generated
27
mobile/lib/platform/background_worker_api.g.dart
generated
|
|
@ -82,7 +82,7 @@ class BackgroundWorkerFgHostApi {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> enableUploadWorker(int callbackHandle) async {
|
||||
Future<void> enableUploadWorker() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
|
|
@ -90,7 +90,7 @@ class BackgroundWorkerFgHostApi {
|
|||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[callbackHandle]);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
|
|
@ -164,6 +164,29 @@ class BackgroundWorkerBgHostApi {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BackgroundWorkerFlutterApi {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ import 'package:pigeon/pigeon.dart';
|
|||
abstract class BackgroundWorkerFgHostApi {
|
||||
void enableSyncWorker();
|
||||
|
||||
// Enables the background upload service with the given callback handle
|
||||
void enableUploadWorker(int callbackHandle);
|
||||
void enableUploadWorker();
|
||||
|
||||
// Disables the background upload service
|
||||
void disableUploadWorker();
|
||||
|
|
@ -27,6 +26,8 @@ abstract class BackgroundWorkerBgHostApi {
|
|||
// Called from the background flutter engine when it has bootstrapped and established the
|
||||
// required platform channels to notify the native side to start the background upload
|
||||
void onInitialized();
|
||||
|
||||
void close();
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue