mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
chore: clean up background tasks (#21785)
This commit is contained in:
parent
8529f92ebc
commit
56e5236a39
7 changed files with 69 additions and 88 deletions
|
|
@ -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,9 +205,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -222,13 +225,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashFuture;
|
await hashFuture;
|
||||||
});
|
|
||||||
|
|
||||||
futures.add(localSyncFuture);
|
|
||||||
futures.add(_ref.read(backgroundSyncProvider).syncRemote());
|
|
||||||
|
|
||||||
await Future.wait(futures);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,9 +51,13 @@ class SyncLinkedAlbumService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
Future<void> manageLinkedAlbums(List<LocalAlbum> localAlbums, String ownerId) async {
|
||||||
|
try {
|
||||||
for (final album in localAlbums) {
|
for (final album in localAlbums) {
|
||||||
await _processLocalAlbum(album, ownerId);
|
await _processLocalAlbum(album, ownerId);
|
||||||
}
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_log.severe("Error managing linked albums", error, stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a single local album to ensure proper linking with remote albums
|
/// Processes a single local album to ensure proper linking with remote albums
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue