import 'dart:async'; import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; import 'package:immich_mobile/utils/isolate.dart'; import 'package:worker_manager/worker_manager.dart'; typedef SyncCallback = void Function(); typedef SyncErrorCallback = void Function(String error); class BackgroundSyncManager { final SyncCallback? onRemoteSyncStart; final SyncCallback? onRemoteSyncComplete; final SyncErrorCallback? onRemoteSyncError; final SyncCallback? onLocalSyncStart; final SyncCallback? onLocalSyncComplete; final SyncErrorCallback? onLocalSyncError; final SyncCallback? onHashingStart; final SyncCallback? onHashingComplete; final SyncErrorCallback? onHashingError; Cancelable? _syncTask; Cancelable? _syncWebsocketTask; Cancelable? _deviceAlbumSyncTask; Cancelable? _hashTask; BackgroundSyncManager({ this.onRemoteSyncStart, this.onRemoteSyncComplete, this.onRemoteSyncError, this.onLocalSyncStart, this.onLocalSyncComplete, this.onLocalSyncError, this.onHashingStart, this.onHashingComplete, this.onHashingError, }); Future cancel() { final futures = []; if (_syncTask != null) { futures.add(_syncTask!.future); } _syncTask?.cancel(); _syncTask = null; if (_syncWebsocketTask != null) { futures.add(_syncWebsocketTask!.future); } _syncWebsocketTask?.cancel(); _syncWebsocketTask = null; return Future.wait(futures); } // No need to cancel the task, as it can also be run when the user logs out Future syncLocal({bool full = false}) { if (_deviceAlbumSyncTask != null) { return _deviceAlbumSyncTask!.future; } onLocalSyncStart?.call(); // We use a ternary operator to avoid [_deviceAlbumSyncTask] from being // captured by the closure passed to [runInIsolateGentle]. _deviceAlbumSyncTask = full ? runInIsolateGentle( computation: (ref) => ref.read(localSyncServiceProvider).sync(full: true), ) : runInIsolateGentle( computation: (ref) => ref.read(localSyncServiceProvider).sync(full: false), ); return _deviceAlbumSyncTask!.whenComplete(() { _deviceAlbumSyncTask = null; onLocalSyncComplete?.call(); }).catchError((error) { onLocalSyncError?.call(error.toString()); _deviceAlbumSyncTask = null; }); } // No need to cancel the task, as it can also be run when the user logs out Future hashAssets() { if (_hashTask != null) { return _hashTask!.future; } onHashingStart?.call(); _hashTask = runInIsolateGentle( computation: (ref) => ref.read(hashServiceProvider).hashAssets(), ); return _hashTask!.whenComplete(() { onHashingComplete?.call(); _hashTask = null; }).catchError((error) { onHashingError?.call(error.toString()); _hashTask = null; }); } Future syncRemote() { if (_syncTask != null) { return _syncTask!.future; } onRemoteSyncStart?.call(); _syncTask = runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).sync(), ); return _syncTask!.whenComplete(() { onRemoteSyncComplete?.call(); _syncTask = null; }).catchError((error) { onRemoteSyncError?.call(error.toString()); _syncTask = null; }); } Future syncWebsocketBatch(List batchData) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; } _syncWebsocketTask = _handleWsAssetUploadReadyV1Batch(batchData); return _syncWebsocketTask!.whenComplete(() { _syncWebsocketTask = null; }); } } Cancelable _handleWsAssetUploadReadyV1Batch( List batchData, ) => runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData), );