merge main

This commit is contained in:
shenlong-tanwen
2025-09-17 16:08:34 +05:30
528 changed files with 17365 additions and 6390 deletions
@@ -1,8 +1,8 @@
import 'dart:async';
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/utils/isolate_lock_manager.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
@@ -19,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/server_info.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/services/app_settings.service.dart';
import 'package:immich_mobile/services/background.service.dart';
@@ -125,93 +124,58 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
}
}
Future<void> _safeRun(Future<void> action, String debugName) async {
if (!_shouldContinueOperation()) {
return;
}
try {
await action;
} catch (e, stackTrace) {
_log.warning("Error during $debugName operation", e, stackTrace);
}
}
Future<void> _handleBetaTimelineResume() async {
_ref.read(backupProvider.notifier).cancelBackup();
final lockManager = _ref.read(isolateLockManagerProvider(kIsolateLockManagerPort));
// Give isolates time to complete any ongoing database transactions
await Future.delayed(const Duration(milliseconds: 500));
lockManager.requestHolderToClose();
// Add timeout to prevent deadlock on lock acquisition
try {
await lockManager.acquireLock().timeout(
const Duration(seconds: 10),
onTimeout: () {
_log.warning("Lock acquisition timed out, proceeding without lock");
throw TimeoutException("Lock acquisition timed out", const Duration(seconds: 10));
},
);
} catch (e) {
_log.warning("Failed to acquire lock: $e");
return;
}
final backgroundManager = _ref.read(backgroundSyncProvider);
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
try {
// Run operations sequentially with state checks and error handling for each
if (_shouldContinueOperation()) {
try {
await backgroundManager.syncLocal();
} catch (e, stackTrace) {
_log.warning("Failed syncLocal: $e", e, stackTrace);
}
}
await Future.wait([
_safeRun(backgroundManager.syncLocal(), "syncLocal"),
_safeRun(backgroundManager.syncRemote(), "syncRemote"),
]);
// Check if app is still active before hashing
if (_shouldContinueOperation()) {
try {
await backgroundManager.hashAssets();
} catch (e, stackTrace) {
_log.warning("Failed hashAssets: $e", e, stackTrace);
}
}
await Future.wait([
_safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) {
_resumeBackup();
}),
_resumeBackup(),
]);
// Check if app is still active before remote sync
if (_shouldContinueOperation()) {
try {
await backgroundManager.syncRemote();
} catch (e, stackTrace) {
_log.warning("Failed syncRemote: $e", e, stackTrace);
}
if (isAlbumLinkedSyncEnable && _shouldContinueOperation()) {
try {
await backgroundManager.syncLinkedAlbum();
} catch (e, stackTrace) {
_log.warning("Failed syncLinkedAlbum: $e", e, stackTrace);
}
}
}
// Handle backup resume only if still active
if (_shouldContinueOperation()) {
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
if (isEnableBackup) {
final currentUser = _ref.read(currentUserProvider);
if (currentUser != null) {
try {
await _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
_log.fine("Completed backup resume");
} catch (e, stackTrace) {
_log.warning("Failed backup resume: $e", e, stackTrace);
}
}
}
if (isAlbumLinkedSyncEnable) {
await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
}
} catch (e, stackTrace) {
_log.severe("Error during background sync", e, stackTrace);
} finally {
// Ensure lock is released even if operations fail
try {
lockManager.releaseLock();
_log.fine("Lock released after background sync operations");
} catch (lockError) {
_log.warning("Failed to release lock after error: $lockError");
}
}
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",
);
}
}
}
@@ -263,28 +227,6 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
if (_ref.read(backupProvider.notifier).backupProgress != BackUpProgressEnum.manualInProgress) {
_ref.read(backupProvider.notifier).cancelBackup();
}
} else {
final backgroundManager = _ref.read(backgroundSyncProvider);
// Cancel operations with extended timeout to allow database transactions to complete
try {
await Future.wait([
backgroundManager.cancel().timeout(const Duration(seconds: 10)),
backgroundManager.cancelLocal().timeout(const Duration(seconds: 10)),
]).timeout(const Duration(seconds: 15));
// Give additional time for isolates to clean up database connections
await Future.delayed(const Duration(milliseconds: 1000));
} catch (e) {
_log.warning("Timeout during background cancellation: $e");
}
// Always release the lock, even if cancellation failed
try {
_ref.read(isolateLockManagerProvider(kIsolateLockManagerPort)).releaseLock();
} catch (e) {
_log.warning("Failed to release lock on pause: $e");
}
}
_ref.read(websocketProvider.notifier).disconnect();
@@ -312,7 +254,6 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
} catch (_) {}
if (Store.isBetaTimelineEnabled) {
_ref.read(isolateLockManagerProvider(kIsolateLockManagerPort)).releaseLock();
return;
}