소스 검색

fix(mobile): asset deletion state management (#4568)

shenlong 1 년 전
부모
커밋
52e09b4857

+ 6 - 7
mobile/lib/modules/trash/providers/trashed_asset.provider.dart

@@ -2,9 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
 import 'package:immich_mobile/modules/trash/services/trash.service.dart';
 import 'package:immich_mobile/shared/models/asset.dart';
-import 'package:immich_mobile/shared/models/exif_info.dart';
 import 'package:immich_mobile/shared/providers/db.provider.dart';
 import 'package:immich_mobile/shared/providers/user.provider.dart';
+import 'package:immich_mobile/shared/services/sync.service.dart';
 import 'package:isar/isar.dart';
 import 'package:logging/logging.dart';
 
@@ -28,19 +28,18 @@ class TrashNotifier extends StateNotifier<bool> {
       }
       await _trashService.emptyTrash();
 
-      final dbIds = await _db.assets
+      final idsToRemove = await _db.assets
           .where()
           .remoteIdIsNotNull()
           .filter()
           .ownerIdEqualTo(user.isarId)
           .isTrashedEqualTo(true)
-          .idProperty()
+          .remoteIdProperty()
           .findAll();
 
-      await _db.writeTxn(() async {
-        await _db.exifInfos.deleteAll(dbIds);
-        await _db.assets.deleteAll(dbIds);
-      });
+      _ref
+          .read(syncServiceProvider)
+          .handleRemoteAssetRemoval(idsToRemove.cast<String>().toList());
     } catch (error, stack) {
       _log.severe("Cannot empty trash ${error.toString()}", error, stack);
     }

+ 62 - 18
mobile/lib/shared/providers/asset.provider.dart

@@ -94,7 +94,7 @@ class AssetNotifier extends StateNotifier<bool> {
 
   Future<bool> deleteAssets(
     Iterable<Asset> deleteAssets, {
-    bool? force = false,
+    bool force = false,
   }) async {
     _deleteInProgress = true;
     state = true;
@@ -102,25 +102,69 @@ class AssetNotifier extends StateNotifier<bool> {
       final localDeleted = await _deleteLocalAssets(deleteAssets);
       final remoteDeleted = await _deleteRemoteAssets(deleteAssets, force);
       if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
-        List<Asset>? assetsToUpdate;
-        // Local only assets are permanently deleted for now. So always remove them from db
-        final dbIds = deleteAssets
-            .where((a) => a.isLocal && !a.isRemote)
-            .map((e) => e.id)
-            .toList();
-        if (force == null || !force) {
-          assetsToUpdate = remoteDeleted.map((e) {
-            e.isTrashed = true;
-            return e;
-          }).toList();
-        } else {
-          // Add all remote assets to be deleted from isar as since they are permanently deleted
-          dbIds.addAll(remoteDeleted.map((e) => e.id));
+        final dbIds = <int>[];
+        final dbUpdates = <Asset>[];
+
+        // Local assets are removed
+        if (localDeleted.isNotEmpty) {
+          // Permanently remove local only assets from isar
+          dbIds.addAll(
+            deleteAssets
+                .where((a) => a.storage == AssetState.local)
+                .map((e) => e.id),
+          );
+
+          if (remoteDeleted.any((e) => e.isLocal)) {
+            // Force delete: Add all local assets including merged assets
+            if (force) {
+              dbIds.addAll(remoteDeleted.map((e) => e.id));
+              // Soft delete: Remove local Id from asset and trash it
+            } else {
+              dbUpdates.addAll(
+                remoteDeleted.map((e) {
+                  e.localId = null;
+                  e.isTrashed = true;
+                  return e;
+                }),
+              );
+            }
+          }
         }
-        await _db.writeTxn(() async {
-          if (assetsToUpdate != null) {
-            await _db.assets.putAll(assetsToUpdate);
+
+        // Handle remote deletion
+        if (remoteDeleted.isNotEmpty) {
+          if (force) {
+            // Remove remote only assets
+            dbIds.addAll(
+              deleteAssets
+                  .where((a) => a.storage == AssetState.remote)
+                  .map((e) => e.id),
+            );
+            // Local assets are not removed and there are merged assets
+            final hasLocal = remoteDeleted.any((e) => e.isLocal);
+            if (localDeleted.isEmpty && hasLocal) {
+              // Remove remote Id from local assets
+              dbUpdates.addAll(
+                remoteDeleted.map((e) {
+                  e.remoteId = null;
+                  // Remove from trashed if remote asset is removed
+                  e.isTrashed = false;
+                  return e;
+                }),
+              );
+            }
+          } else {
+            dbUpdates.addAll(
+              remoteDeleted.map((e) {
+                e.isTrashed = true;
+                return e;
+              }),
+            );
           }
+        }
+
+        await _db.writeTxn(() async {
+          await _db.assets.putAll(dbUpdates);
           await _db.exifInfos.deleteAll(dbIds);
           await _db.assets.deleteAll(dbIds);
         });

+ 8 - 1
mobile/lib/shared/services/sync.service.dart

@@ -173,7 +173,14 @@ class SyncService {
   /// Deletes remote-only assets, updates merged assets to be local-only
   Future<void> handleRemoteAssetRemoval(List<String> idsToDelete) {
     return _db.writeTxn(() async {
-      await _db.assets.remote(idsToDelete).filter().localIdIsNull().deleteAll();
+      final idsToRemove = await _db.assets
+          .remote(idsToDelete)
+          .filter()
+          .localIdIsNull()
+          .idProperty()
+          .findAll();
+      await _db.assets.deleteAll(idsToRemove);
+      await _db.exifInfos.deleteAll(idsToRemove);
       final onlyLocal = await _db.assets.remote(idsToDelete).findAll();
       if (onlyLocal.isNotEmpty) {
         for (final Asset a in onlyLocal) {