chore: making order of background tasks better (#21928)

* chore: making order of background tasks better

* chore: prevent action not running when returning from backup screen too soon after toggle backup
This commit is contained in:
Alex 2025-09-15 10:07:41 -05:00 committed by GitHub
parent 5c06ec5e0b
commit 77340075f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 38 deletions

View file

@ -3,6 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
@ -34,21 +36,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
} }
Future<void> startBackup() async {
final currentUser = ref.read(currentUserProvider);
if (currentUser == null) {
return;
}
await ref.read(backgroundSyncProvider).syncRemote();
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
await ref.read(driftBackupProvider.notifier).startBackup(currentUser.id);
}
Future<void> stopBackup() async {
await ref.read(driftBackupProvider.notifier).cancel();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedAlbum = ref final selectedAlbum = ref
@ -56,6 +43,24 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
.where((album) => album.backupSelection == BackupSelection.selected) .where((album) => album.backupSelection == BackupSelection.selected)
.toList(); .toList();
final backupNotifier = ref.read(driftBackupProvider.notifier);
final backgroundManager = ref.read(backgroundSyncProvider);
Future<void> startBackup() async {
final currentUser = Store.tryGet(StoreKey.currentUser);
if (currentUser == null) {
return;
}
await backgroundManager.syncRemote();
await backupNotifier.getBackupStatus(currentUser.id);
await backupNotifier.startBackup(currentUser.id);
}
Future<void> stopBackup() async {
await backupNotifier.cancel();
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,

View file

@ -6,6 +6,7 @@ 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/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/backup/drift_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';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
@ -22,6 +23,7 @@ class SplashScreenPage extends StatefulHookConsumerWidget {
class SplashScreenPageState extends ConsumerState<SplashScreenPage> { class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
final log = Logger("SplashScreenPage"); final log = Logger("SplashScreenPage");
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -49,6 +51,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
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); final backgroundManager = ref.read(backgroundSyncProvider);
final backupProvider = ref.read(driftBackupProvider.notifier);
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
(_) async { (_) async {
@ -57,13 +60,17 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
infoProvider.getServerInfo(); infoProvider.getServerInfo();
if (Store.isBetaTimelineEnabled) { if (Store.isBetaTimelineEnabled) {
await backgroundManager.syncLocal(); await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]);
await backgroundManager.syncRemote(); await Future.wait([
await backgroundManager.hashAssets(); backgroundManager.hashAssets().then((_) {
} _resumeBackup(backupProvider);
}),
_resumeBackup(backupProvider),
]);
if (Store.get(StoreKey.syncAlbums, false)) { if (Store.get(StoreKey.syncAlbums, false)) {
await backgroundManager.syncLinkedAlbum(); 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');
@ -106,6 +113,17 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
} }
} }
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
final isEnableBackup = Store.get(StoreKey.enableBackup, false);
if (isEnableBackup) {
final currentUser = Store.tryGet(StoreKey.currentUser);
if (currentUser != null) {
notifier.handleBackupResume(currentUser.id);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( return const Scaffold(

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
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/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart';
@ -18,7 +19,6 @@ import 'package:immich_mobile/providers/memory.provider.dart';
import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/notification_permission.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
@ -144,32 +144,42 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
final backgroundManager = _ref.read(backgroundSyncProvider); final backgroundManager = _ref.read(backgroundSyncProvider);
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
try { try {
// Run operations sequentially with state checks and error handling for each await Future.wait([
await _safeRun(backgroundManager.syncLocal(), "syncLocal"); _safeRun(backgroundManager.syncLocal(), "syncLocal"),
await _safeRun(backgroundManager.syncRemote(), "syncRemote"); _safeRun(backgroundManager.syncRemote(), "syncRemote"),
await _safeRun(backgroundManager.hashAssets(), "hashAssets"); ]);
await Future.wait([
_safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) {
_resumeBackup();
}),
_resumeBackup(),
]);
if (isAlbumLinkedSyncEnable) { if (isAlbumLinkedSyncEnable) {
await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
} }
// Handle backup resume only if still active
if (isEnableBackup) {
final currentUser = _ref.read(currentUserProvider);
if (currentUser != null) {
await _safeRun(
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
"handleBackupResume",
);
}
}
} catch (e, stackTrace) { } catch (e, stackTrace) {
_log.severe("Error during background sync", e, stackTrace); _log.severe("Error during background sync", e, stackTrace);
} }
} }
Future<void> _resumeBackup() async {
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
if (isEnableBackup) {
final currentUser = Store.tryGet(StoreKey.currentUser);
if (currentUser != null) {
await _safeRun(
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
"handleBackupResume",
);
}
}
}
// Helper method to check if operations should continue // Helper method to check if operations should continue
bool _shouldContinueOperation() { bool _shouldContinueOperation() {
return [AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state) && return [AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state) &&