From 52e09b48577be3a2f9653f790eb8c9552a9d5619 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:02:59 +0000 Subject: [PATCH] fix(mobile): asset deletion state management (#4568) --- .../providers/trashed_asset.provider.dart | 13 ++- .../lib/shared/providers/asset.provider.dart | 80 ++++++++++++++----- mobile/lib/shared/services/sync.service.dart | 9 ++- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart b/mobile/lib/modules/trash/providers/trashed_asset.provider.dart index 1f34741df19e1..2d85625a400fe 100644 --- a/mobile/lib/modules/trash/providers/trashed_asset.provider.dart +++ b/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 { } 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().toList()); } catch (error, stack) { _log.severe("Cannot empty trash ${error.toString()}", error, stack); } diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 8e6f8c12eb755..1694e1f26144f 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -94,7 +94,7 @@ class AssetNotifier extends StateNotifier { Future deleteAssets( Iterable deleteAssets, { - bool? force = false, + bool force = false, }) async { _deleteInProgress = true; state = true; @@ -102,25 +102,69 @@ class AssetNotifier extends StateNotifier { final localDeleted = await _deleteLocalAssets(deleteAssets); final remoteDeleted = await _deleteRemoteAssets(deleteAssets, force); if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) { - List? 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 = []; + final dbUpdates = []; + + // 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); }); diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index b38b66633183b..34d84401aad21 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/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 handleRemoteAssetRemoval(List 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) {