diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 29c15bd915..18f07dc021 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -176,7 +176,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } Future _handleBackup({bool processBulk = true}) async { - if (!_isBackupEnabled) { + if (!_isBackupEnabled || _isCleanedUp) { _logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine"); return; } @@ -205,30 +205,27 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } Future _syncAssets({Duration? hashTimeout}) async { - final futures = >[]; + await _ref.read(backgroundSyncProvider).syncLocal(); + if (_isCleanedUp) { + return; + } - final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async { - if (_isCleanedUp) { - return; - } + await _ref.read(backgroundSyncProvider).syncRemote(); + if (_isCleanedUp) { + return; + } - var hashFuture = _ref.read(backgroundSyncProvider).hashAssets(); - if (hashTimeout != null) { - hashFuture = hashFuture.timeout( - hashTimeout, - onTimeout: () { - // Consume cancellation errors as we want to continue processing - }, - ); - } + var hashFuture = _ref.read(backgroundSyncProvider).hashAssets(); + if (hashTimeout != null) { + hashFuture = hashFuture.timeout( + hashTimeout, + onTimeout: () { + // Consume cancellation errors as we want to continue processing + }, + ); + } - return hashFuture; - }); - - futures.add(localSyncFuture); - futures.add(_ref.read(backgroundSyncProvider).syncRemote()); - - await Future.wait(futures); + await hashFuture; } } diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index 37e52e6c16..2c445f3aca 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -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/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:logging/logging.dart'; final syncLinkedAlbumServiceProvider = Provider( (ref) => SyncLinkedAlbumService( @@ -19,7 +20,9 @@ class SyncLinkedAlbumService { final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; - const SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository); + SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository); + + final _log = Logger("SyncLinkedAlbumService"); Future syncLinkedAlbums(String userId) async { final selectedAlbums = await _localAlbumRepository.getBackupAlbums(); @@ -48,8 +51,12 @@ class SyncLinkedAlbumService { } Future manageLinkedAlbums(List localAlbums, String ownerId) async { - for (final album in localAlbums) { - await _processLocalAlbum(album, ownerId); + try { + for (final album in localAlbums) { + await _processLocalAlbum(album, ownerId); + } + } catch (error, stackTrace) { + _log.severe("Error managing linked albums", error, stackTrace); } } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index c64d6fe80f..0bedae4242 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -4,6 +4,7 @@ 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/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/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -47,11 +48,23 @@ class SplashScreenPageState extends ConsumerState { if (accessToken != null && serverUrl != null && endpoint != null) { final infoProvider = ref.read(serverInfoProvider.notifier); final wsProvider = ref.read(websocketProvider.notifier); + final backgroundManager = ref.read(backgroundSyncProvider); + ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( - (a) { + (_) async { try { wsProvider.connect(); 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) { log.severe('Failed establishing connection to the server: $e'); } diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart index 8b68a8b9c0..b60fe1ddc1 100644 --- a/mobile/lib/pages/common/tab_shell.page.dart +++ b/mobile/lib/pages/common/tab_shell.page.dart @@ -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/utils/event_stream.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/infrastructure/album.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/tab.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/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/migration.dart'; @RoutePage() class TabShellPage extends ConsumerStatefulWidget { @@ -32,28 +26,6 @@ class TabShellPage extends ConsumerStatefulWidget { } class _TabShellPageState extends ConsumerState { - @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 Widget build(BuildContext context) { final isScreenLandscape = context.orientation == Orientation.landscape; diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index cfe10a472d..eb6def9353 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -148,19 +148,21 @@ class AppLifeCycleNotifier extends StateNotifier { try { // Run operations sequentially with state checks and error handling for each - _safeRun(backgroundManager.syncLocal(), "syncLocal"); - _safeRun(backgroundManager.hashAssets(), "hashAssets"); - _safeRun(backgroundManager.syncRemote(), "syncRemote").then((_) { - if (isAlbumLinkedSyncEnable) { - _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); - } - }); + await _safeRun(backgroundManager.syncLocal(), "syncLocal"); + await _safeRun(backgroundManager.syncRemote(), "syncRemote"); + await _safeRun(backgroundManager.hashAssets(), "hashAssets"); + if (isAlbumLinkedSyncEnable) { + await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); + } // Handle backup resume only if still active if (isEnableBackup) { final currentUser = _ref.read(currentUserProvider); 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) { diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index c21f2979d9..c5fb5edc0f 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -5,7 +5,6 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:drift/drift.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/store.model.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/user.entity.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:isar/isar.dart'; -import 'package:logging/logging.dart'; // ignore: import_rule_photo_manager import 'package:photo_manager/photo_manager.dart'; @@ -310,25 +304,6 @@ class _DeviceAsset { const _DeviceAsset({required this.assetId, this.hash, this.dateTime}); } -Future> 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 resetDriftDatabase(Drift drift) async { // https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94 final database = drift.attachedDatabase; diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index ab78536a92..7b5d31544a 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -10,9 +10,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:fluttertoast/fluttertoast.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/extensions/build_context_extensions.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/gallery_permission.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'; } + Future 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 { TextInput.finishAutofillContext(); @@ -178,7 +192,7 @@ class LoginForm extends HookConsumerWidget { final isBeta = Store.isBetaTimelineEnabled; if (isBeta) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - + handleSyncFlow(); context.replaceRoute(const TabShellRoute()); return; } @@ -276,6 +290,7 @@ class LoginForm extends HookConsumerWidget { } if (isBeta) { await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); + handleSyncFlow(); context.replaceRoute(const TabShellRoute()); return; }