Compare commits

...

3 Commits

Author SHA1 Message Date
Alex
782a089da0 fix: scrubber scroll error when page is not long enough 2025-07-07 23:15:25 -05:00
Alex
d5eff115e8 Merge branch 'main' of github.com:immich-app/immich 2025-07-07 15:44:28 -05:00
Alex
f8ab533acb use mutex 2025-07-05 14:32:46 -05:00
3 changed files with 106 additions and 46 deletions

View File

@@ -9,8 +9,57 @@ class BackgroundSyncManager {
Cancelable<void>? _deviceAlbumSyncTask; Cancelable<void>? _deviceAlbumSyncTask;
Cancelable<void>? _hashTask; Cancelable<void>? _hashTask;
Completer<void>? _localSyncMutex;
Completer<void>? _remoteSyncMutex;
Completer<void>? _hashMutex;
BackgroundSyncManager(); BackgroundSyncManager();
Future<T> _withMutex<T>(
Completer<void>? Function() getMutex,
void Function(Completer<void>?) setMutex,
Future<T> Function() operation,
) async {
while (getMutex() != null) {
await getMutex()!.future;
}
final mutex = Completer<void>();
setMutex(mutex);
try {
final result = await operation();
return result;
} finally {
setMutex(null);
mutex.complete();
}
}
Future<T> _withLocalSyncMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _localSyncMutex,
(mutex) => _localSyncMutex = mutex,
operation,
);
}
Future<T> _withRemoteSyncMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _remoteSyncMutex,
(mutex) => _remoteSyncMutex = mutex,
operation,
);
}
Future<T> _withHashMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _hashMutex,
(mutex) => _hashMutex = mutex,
operation,
);
}
Future<void> cancel() { Future<void> cancel() {
final futures = <Future>[]; final futures = <Future>[];
@@ -25,51 +74,57 @@ class BackgroundSyncManager {
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
Future<void> syncLocal({bool full = false}) { Future<void> syncLocal({bool full = false}) {
if (_deviceAlbumSyncTask != null) { return _withLocalSyncMutex(() async {
return _deviceAlbumSyncTask!.future; if (_deviceAlbumSyncTask != null) {
} return _deviceAlbumSyncTask!.future;
}
// We use a ternary operator to avoid [_deviceAlbumSyncTask] from being // We use a ternary operator to avoid [_deviceAlbumSyncTask] from being
// captured by the closure passed to [runInIsolateGentle]. // captured by the closure passed to [runInIsolateGentle].
_deviceAlbumSyncTask = full _deviceAlbumSyncTask = full
? runInIsolateGentle( ? runInIsolateGentle(
computation: (ref) => computation: (ref) =>
ref.read(localSyncServiceProvider).sync(full: true), ref.read(localSyncServiceProvider).sync(full: true),
) )
: runInIsolateGentle( : runInIsolateGentle(
computation: (ref) => computation: (ref) =>
ref.read(localSyncServiceProvider).sync(full: false), ref.read(localSyncServiceProvider).sync(full: false),
); );
return _deviceAlbumSyncTask!.whenComplete(() { return _deviceAlbumSyncTask!.whenComplete(() {
_deviceAlbumSyncTask = null; _deviceAlbumSyncTask = null;
});
}); });
} }
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
Future<void> hashAssets() { Future<void> hashAssets() {
if (_hashTask != null) { return _withHashMutex(() async {
return _hashTask!.future; if (_hashTask != null) {
} return _hashTask!.future;
}
_hashTask = runInIsolateGentle( _hashTask = runInIsolateGentle(
computation: (ref) => ref.read(hashServiceProvider).hashAssets(), computation: (ref) => ref.read(hashServiceProvider).hashAssets(),
); );
return _hashTask!.whenComplete(() { return _hashTask!.whenComplete(() {
_hashTask = null; _hashTask = null;
});
}); });
} }
Future<void> syncRemote() { Future<void> syncRemote() {
if (_syncTask != null) { return _withRemoteSyncMutex(() async {
return _syncTask!.future; if (_syncTask != null) {
} return _syncTask!.future;
}
_syncTask = runInIsolateGentle( _syncTask = runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).sync(), computation: (ref) => ref.read(syncStreamServiceProvider).sync(),
); );
return _syncTask!.whenComplete(() { return _syncTask!.whenComplete(() {
_syncTask = null; _syncTask = null;
});
}); });
} }
} }

View File

@@ -354,22 +354,24 @@ class ScrubberState extends ConsumerState<Scrubber>
isDragging: _isDragging, isDragging: _isDragging,
), ),
), ),
PositionedDirectional( if (_scrollController.hasClients &&
top: _thumbTopOffset + widget.topPadding, _scrollController.position.maxScrollExtent > 0)
end: 0, PositionedDirectional(
child: RepaintBoundary( top: _thumbTopOffset + widget.topPadding,
child: GestureDetector( end: 0,
onVerticalDragStart: _onDragStart, child: RepaintBoundary(
onVerticalDragUpdate: _onDragUpdate, child: GestureDetector(
onVerticalDragEnd: _onDragEnd, onVerticalDragStart: _onDragStart,
child: _Scrubber( onVerticalDragUpdate: _onDragUpdate,
thumbAnimation: _thumbAnimation, onVerticalDragEnd: _onDragEnd,
labelAnimation: _labelAnimation, child: _Scrubber(
label: label, thumbAnimation: _thumbAnimation,
labelAnimation: _labelAnimation,
label: label,
),
), ),
), ),
), ),
),
], ],
), ),
); );

View File

@@ -73,7 +73,10 @@ class ImmichSliverAppBar extends ConsumerWidget {
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
IconButton( IconButton(
onPressed: () => ref.read(backgroundSyncProvider).syncRemote(), onPressed: () {
ref.read(backgroundSyncProvider).syncLocal(full: true);
ref.read(backgroundSyncProvider).syncRemote();
},
icon: const Icon( icon: const Icon(
Icons.sync, Icons.sync,
), ),