chore: clean up background tasks (#21785)

This commit is contained in:
Alex 2025-09-10 16:17:37 -05:00 committed by GitHub
parent 8529f92ebc
commit 56e5236a39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 69 additions and 88 deletions

View file

@ -176,7 +176,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
Future<void> _handleBackup({bool processBulk = true}) async { Future<void> _handleBackup({bool processBulk = true}) async {
if (!_isBackupEnabled) { if (!_isBackupEnabled || _isCleanedUp) {
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine"); _logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
return; return;
} }
@ -205,30 +205,27 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
Future<void> _syncAssets({Duration? hashTimeout}) async { Future<void> _syncAssets({Duration? hashTimeout}) async {
final futures = <Future<void>>[]; await _ref.read(backgroundSyncProvider).syncLocal();
if (_isCleanedUp) {
return;
}
final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async { await _ref.read(backgroundSyncProvider).syncRemote();
if (_isCleanedUp) { if (_isCleanedUp) {
return; return;
} }
var hashFuture = _ref.read(backgroundSyncProvider).hashAssets(); var hashFuture = _ref.read(backgroundSyncProvider).hashAssets();
if (hashTimeout != null) { if (hashTimeout != null) {
hashFuture = hashFuture.timeout( hashFuture = hashFuture.timeout(
hashTimeout, hashTimeout,
onTimeout: () { onTimeout: () {
// Consume cancellation errors as we want to continue processing // Consume cancellation errors as we want to continue processing
}, },
); );
} }
return hashFuture; await hashFuture;
});
futures.add(localSyncFuture);
futures.add(_ref.read(backgroundSyncProvider).syncRemote());
await Future.wait(futures);
} }
} }

View file

@ -5,6 +5,7 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:logging/logging.dart';
final syncLinkedAlbumServiceProvider = Provider( final syncLinkedAlbumServiceProvider = Provider(
(ref) => SyncLinkedAlbumService( (ref) => SyncLinkedAlbumService(
@ -19,7 +20,9 @@ class SyncLinkedAlbumService {
final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftRemoteAlbumRepository _remoteAlbumRepository;
final DriftAlbumApiRepository _albumApiRepository; final DriftAlbumApiRepository _albumApiRepository;
const SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository); SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
final _log = Logger("SyncLinkedAlbumService");
Future<void> syncLinkedAlbums(String userId) async { Future<void> syncLinkedAlbums(String userId) async {
final selectedAlbums = await _localAlbumRepository.getBackupAlbums(); final selectedAlbums = await _localAlbumRepository.getBackupAlbums();
@ -48,8 +51,12 @@ class SyncLinkedAlbumService {
} }
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async { Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
for (final album in localAlbums) { try {
await _processLocalAlbum(album, ownerId); for (final album in localAlbums) {
await _processLocalAlbum(album, ownerId);
}
} catch (error, stackTrace) {
_log.severe("Error managing linked albums", error, stackTrace);
} }
} }

View file

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart';
@ -47,11 +48,23 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
if (accessToken != null && serverUrl != null && endpoint != null) { if (accessToken != null && serverUrl != null && endpoint != null) {
final infoProvider = ref.read(serverInfoProvider.notifier); final infoProvider = ref.read(serverInfoProvider.notifier);
final wsProvider = ref.read(websocketProvider.notifier); final wsProvider = ref.read(websocketProvider.notifier);
final backgroundManager = ref.read(backgroundSyncProvider);
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
(a) { (_) async {
try { try {
wsProvider.connect(); wsProvider.connect();
infoProvider.getServerInfo(); infoProvider.getServerInfo();
if (Store.isBetaTimelineEnabled) {
await backgroundManager.syncLocal();
await backgroundManager.syncRemote();
await backgroundManager.hashAssets();
}
if (Store.get(StoreKey.syncAlbums, false)) {
await backgroundManager.syncLinkedAlbum();
}
} catch (e) { } catch (e) {
log.severe('Failed establishing connection to the server: $e'); log.severe('Failed establishing connection to the server: $e');
} }

View file

@ -7,8 +7,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
@ -17,11 +15,7 @@ import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.da
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/migration.dart';
@RoutePage() @RoutePage()
class TabShellPage extends ConsumerStatefulWidget { class TabShellPage extends ConsumerStatefulWidget {
@ -32,28 +26,6 @@ class TabShellPage extends ConsumerStatefulWidget {
} }
class _TabShellPageState extends ConsumerState<TabShellPage> { class _TabShellPageState extends ConsumerState<TabShellPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
ref.read(websocketProvider.notifier).connect();
final isEnableBackup = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
await runNewSync(ref, full: true).then((_) async {
if (isEnableBackup) {
final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
await ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
}
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isScreenLandscape = context.orientation == Orientation.landscape; final isScreenLandscape = context.orientation == Orientation.landscape;

View file

@ -148,19 +148,21 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
try { try {
// Run operations sequentially with state checks and error handling for each // Run operations sequentially with state checks and error handling for each
_safeRun(backgroundManager.syncLocal(), "syncLocal"); await _safeRun(backgroundManager.syncLocal(), "syncLocal");
_safeRun(backgroundManager.hashAssets(), "hashAssets"); await _safeRun(backgroundManager.syncRemote(), "syncRemote");
_safeRun(backgroundManager.syncRemote(), "syncRemote").then((_) { await _safeRun(backgroundManager.hashAssets(), "hashAssets");
if (isAlbumLinkedSyncEnable) { if (isAlbumLinkedSyncEnable) {
_safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
} }
});
// Handle backup resume only if still active // Handle backup resume only if still active
if (isEnableBackup) { if (isEnableBackup) {
final currentUser = _ref.read(currentUserProvider); final currentUser = _ref.read(currentUserProvider);
if (currentUser != null) { if (currentUser != null) {
_safeRun(_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id), "handleBackupResume"); await _safeRun(
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
"handleBackupResume",
);
} }
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {

View file

@ -5,7 +5,6 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
@ -23,13 +22,8 @@ import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
// ignore: import_rule_photo_manager // ignore: import_rule_photo_manager
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
@ -310,25 +304,6 @@ class _DeviceAsset {
const _DeviceAsset({required this.assetId, this.hash, this.dateTime}); const _DeviceAsset({required this.assetId, this.hash, this.dateTime});
} }
Future<List<void>> runNewSync(WidgetRef ref, {bool full = false}) {
ref.read(backupProvider.notifier).cancelBackup();
final backgroundManager = ref.read(backgroundSyncProvider);
final isAlbumLinkedSyncEnable = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
return Future.wait([
backgroundManager.syncLocal(full: full).then((_) {
Logger("runNewSync").fine("Hashing assets after syncLocal");
return backgroundManager.hashAssets();
}),
backgroundManager.syncRemote().then((_) {
if (isAlbumLinkedSyncEnable) {
return backgroundManager.syncLinkedAlbum();
}
}),
]);
}
Future<void> resetDriftDatabase(Drift drift) async { Future<void> resetDriftDatabase(Drift drift) async {
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94 // https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
final database = drift.attachedDatabase; final database = drift.attachedDatabase;

View file

@ -10,9 +10,11 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/oauth.provider.dart';
@ -161,6 +163,18 @@ class LoginForm extends HookConsumerWidget {
serverEndpointController.text = 'http://10.1.15.216:2283/api'; serverEndpointController.text = 'http://10.1.15.216:2283/api';
} }
Future<void> handleSyncFlow() async {
final backgroundManager = ref.read(backgroundSyncProvider);
await backgroundManager.syncLocal(full: true);
await backgroundManager.syncRemote();
await backgroundManager.hashAssets();
if (Store.get(StoreKey.syncAlbums, false)) {
await backgroundManager.syncLinkedAlbum();
}
}
login() async { login() async {
TextInput.finishAutofillContext(); TextInput.finishAutofillContext();
@ -178,7 +192,7 @@ class LoginForm extends HookConsumerWidget {
final isBeta = Store.isBetaTimelineEnabled; final isBeta = Store.isBetaTimelineEnabled;
if (isBeta) { if (isBeta) {
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
handleSyncFlow();
context.replaceRoute(const TabShellRoute()); context.replaceRoute(const TabShellRoute());
return; return;
} }
@ -276,6 +290,7 @@ class LoginForm extends HookConsumerWidget {
} }
if (isBeta) { if (isBeta) {
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
handleSyncFlow();
context.replaceRoute(const TabShellRoute()); context.replaceRoute(const TabShellRoute());
return; return;
} }