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'; class BackgroundSyncManager { Cancelable? _syncTask; Cancelable? _deviceAlbumSyncTask; Cancelable? _hashTask; Completer? _localSyncMutex; Completer? _remoteSyncMutex; Completer? _hashMutex; BackgroundSyncManager(); Future _withMutex( Completer? Function() getMutex, void Function(Completer?) setMutex, Future Function() operation, ) async { while (getMutex() != null) { await getMutex()!.future; } final mutex = Completer(); setMutex(mutex); try { final result = await operation(); return result; } finally { setMutex(null); mutex.complete(); } } Future _withLocalSyncMutex(Future Function() operation) { return _withMutex( () => _localSyncMutex, (mutex) => _localSyncMutex = mutex, operation, ); } Future _withRemoteSyncMutex(Future Function() operation) { return _withMutex( () => _remoteSyncMutex, (mutex) => _remoteSyncMutex = mutex, operation, ); } Future _withHashMutex(Future Function() operation) { return _withMutex( () => _hashMutex, (mutex) => _hashMutex = mutex, operation, ); } Future cancel() { final futures = []; if (_syncTask != null) { futures.add(_syncTask!.future); } _syncTask?.cancel(); _syncTask = 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}) { return _withLocalSyncMutex(() async { if (_deviceAlbumSyncTask != null) { return _deviceAlbumSyncTask!.future; } // 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; }); }); } // No need to cancel the task, as it can also be run when the user logs out Future hashAssets() { return _withHashMutex(() async { if (_hashTask != null) { return _hashTask!.future; } _hashTask = runInIsolateGentle( computation: (ref) => ref.read(hashServiceProvider).hashAssets(), ); return _hashTask!.whenComplete(() { _hashTask = null; }); }); } Future syncRemote() { return _withRemoteSyncMutex(() async { if (_syncTask != null) { return _syncTask!.future; } _syncTask = runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).sync(), ); return _syncTask!.whenComplete(() { _syncTask = null; }); }); } }