diff --git a/mobile/drift_schemas/main/drift_schema_v11.json b/mobile/drift_schemas/main/drift_schema_v11.json new file mode 100644 index 000000000..1c100ab37 Binary files /dev/null and b/mobile/drift_schemas/main/drift_schema_v11.json differ diff --git a/mobile/lib/infrastructure/entities/local_album_asset.entity.dart b/mobile/lib/infrastructure/entities/local_album_asset.entity.dart index 8de879a09..53f1a1066 100644 --- a/mobile/lib/infrastructure/entities/local_album_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_album_asset.entity.dart @@ -10,6 +10,9 @@ class LocalAlbumAssetEntity extends Table with DriftDefaultsMixin { TextColumn get albumId => text().references(LocalAlbumEntity, #id, onDelete: KeyAction.cascade)(); + // Used for mark & sweep + BoolColumn get marker_ => boolean().nullable()(); + @override Set get primaryKey => {assetId, albumId}; } diff --git a/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart index 78da361f6..70c298332 100644 Binary files a/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart and b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart differ diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index f04ed2777..65d26d974 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -93,7 +93,7 @@ class Drift extends $Drift implements IDatabaseRepository { } @override - int get schemaVersion => 10; + int get schemaVersion => 11; @override MigrationStrategy get migration => MigrationStrategy( @@ -156,6 +156,9 @@ class Drift extends $Drift implements IDatabaseRepository { await m.addColumn(v10.userEntity, v10.userEntity.avatarColor); await m.alterTable(TableMigration(v10.userEntity)); }, + from10To11: (m, v11) async { + await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_); + }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index be6d53d5a..7910d9fce 100644 Binary files a/mobile/lib/infrastructure/repositories/db.repository.steps.dart and b/mobile/lib/infrastructure/repositories/db.repository.steps.dart differ diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index b97884c27..e4bff2487 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -72,17 +72,33 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { return Future.value(); } - final deleteSmt = _db.localAssetEntity.delete(); - deleteSmt.where((localAsset) { - final subQuery = _db.localAlbumAssetEntity.selectOnly() - ..addColumns([_db.localAlbumAssetEntity.assetId]) - ..join([innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id))]); - subQuery.where( - _db.localAlbumEntity.id.equals(albumId) & _db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep), - ); - return localAsset.id.isInQuery(subQuery); + return _db.transaction(() async { + await _db.managers.localAlbumAssetEntity + .filter((row) => row.albumId.id.equals(albumId)) + .update((album) => album(marker_: const Value(true))); + + await _db.batch((batch) { + for (final assetId in assetIdsToKeep) { + batch.update( + _db.localAlbumAssetEntity, + const LocalAlbumAssetEntityCompanion(marker_: Value(null)), + where: (row) => row.assetId.equals(assetId) & row.albumId.equals(albumId), + ); + } + }); + + final query = _db.localAssetEntity.delete() + ..where( + (row) => row.id.isInQuery( + _db.localAlbumAssetEntity.selectOnly() + ..addColumns([_db.localAlbumAssetEntity.assetId]) + ..where( + _db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAlbumAssetEntity.marker_.isNotNull(), + ), + ), + ); + await query.go(); }); - await deleteSmt.go(); } Future upsert( @@ -198,10 +214,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { // List await _db.batch((batch) async { assetAlbums.cast>().forEach((assetId, albumIds) { - batch.deleteWhere( - _db.localAlbumAssetEntity, - (f) => f.albumId.isNotIn(albumIds.cast().nonNulls) & f.assetId.equals(assetId), - ); + for (final albumId in albumIds.cast().nonNulls) { + batch.deleteWhere(_db.localAlbumAssetEntity, (f) => f.albumId.equals(albumId) & f.assetId.equals(assetId)); + } }); }); await _db.batch((batch) async { @@ -288,12 +303,14 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { return transaction(() async { if (assetsToUnLink.isNotEmpty) { - await _db.batch( - (batch) => batch.deleteWhere( - _db.localAlbumAssetEntity, - (f) => f.assetId.isIn(assetsToUnLink) & f.albumId.equals(albumId), - ), - ); + await _db.batch((batch) { + for (final assetId in assetsToUnLink) { + batch.deleteWhere( + _db.localAlbumAssetEntity, + (row) => row.assetId.equals(assetId) & row.albumId.equals(albumId), + ); + } + }); } await _deleteAssets(assetsToDelete); @@ -320,7 +337,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { } return _db.batch((batch) { - batch.deleteWhere(_db.localAssetEntity, (f) => f.id.isIn(ids)); + for (final id in ids) { + batch.deleteWhere(_db.localAssetEntity, (row) => row.id.equals(id)); + } }); } diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index c4f522196..2b76472c9 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -1,4 +1,3 @@ -import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -58,8 +57,8 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository { } return _db.batch((batch) { - for (final slice in ids.slices(32000)) { - batch.deleteWhere(_db.localAssetEntity, (e) => e.id.isIn(slice)); + for (final id in ids) { + batch.deleteWhere(_db.localAssetEntity, (e) => e.id.equals(id)); } }); } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 78b56e743..5dfe4ac9b 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -166,8 +166,15 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository { ); } - Future removeAssets(String albumId, List assetIds) { - return _db.remoteAlbumAssetEntity.deleteWhere((tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds)); + Future removeAssets(String albumId, List assetIds) { + return _db.batch((batch) { + for (final assetId in assetIds) { + batch.deleteWhere( + _db.remoteAlbumAssetEntity, + (row) => row.albumId.equals(albumId) & row.assetId.equals(assetId), + ); + } + }); } FutureOr<(DateTime, DateTime)> getDateRange(String albumId) { diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index 40f397f0a..092bb728d 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -160,7 +160,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository { } Future delete(List ids) { - return _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(ids)); + return _db.batch((batch) { + for (final id in ids) { + batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(id)); + } + }); } Future updateLocation(List ids, LatLng location) { @@ -199,7 +203,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository { .map((row) => row.id) .get(); - await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds)); + await _db.batch((batch) { + for (final stackId in stackIds) { + batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId)); + } + }); await _db.batch((batch) { final companion = StackEntityCompanion(ownerId: Value(userId), primaryAssetId: Value(stack.primaryAssetId)); @@ -219,15 +227,21 @@ class RemoteAssetRepository extends DriftDatabaseRepository { Future unStack(List stackIds) { return _db.transaction(() async { - await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds)); + await _db.batch((batch) { + for (final stackId in stackIds) { + batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId)); + } + }); // TODO: delete this after adding foreign key on stackId await _db.batch((batch) { - batch.update( - _db.remoteAssetEntity, - const RemoteAssetEntityCompanion(stackId: Value(null)), - where: (e) => e.stackId.isIn(stackIds), - ); + for (final stackId in stackIds) { + batch.update( + _db.remoteAssetEntity, + const RemoteAssetEntityCompanion(stackId: Value(null)), + where: (e) => e.stackId.equals(stackId), + ); + } }); }); } diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index 960a84435..f4720fb11 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -93,7 +93,11 @@ class SyncStreamRepository extends DriftDatabaseRepository { Future deleteUsersV1(Iterable data) async { try { - await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId))); + await _db.batch((batch) { + for (final user in data) { + batch.deleteWhere(_db.userEntity, (row) => row.id.equals(user.userId)); + } + }); } catch (error, stack) { _logger.severe('Error: SyncUserDeleteV1', error, stack); rethrow; @@ -158,7 +162,11 @@ class SyncStreamRepository extends DriftDatabaseRepository { Future deleteAssetsV1(Iterable data, {String debugLabel = 'user'}) async { try { - await _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.assetId))); + await _db.batch((batch) { + for (final asset in data) { + batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(asset.assetId)); + } + }); } catch (error, stack) { _logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack); rethrow; @@ -243,7 +251,11 @@ class SyncStreamRepository extends DriftDatabaseRepository { Future deleteAlbumsV1(Iterable data) async { try { - await _db.remoteAlbumEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.albumId))); + await _db.batch((batch) { + for (final album in data) { + batch.deleteWhere(_db.remoteAlbumEntity, (row) => row.id.equals(album.albumId)); + } + }); } catch (error, stack) { _logger.severe('Error: deleteAlbumsV1', error, stack); rethrow; @@ -379,7 +391,11 @@ class SyncStreamRepository extends DriftDatabaseRepository { Future deleteMemoriesV1(Iterable data) async { try { - await _db.memoryEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.memoryId))); + await _db.batch((batch) { + for (final memory in data) { + batch.deleteWhere(_db.memoryEntity, (row) => row.id.equals(memory.memoryId)); + } + }); } catch (error, stack) { _logger.severe('Error: deleteMemoriesV1', error, stack); rethrow; @@ -443,7 +459,11 @@ class SyncStreamRepository extends DriftDatabaseRepository { Future deleteStacksV1(Iterable data, {String debugLabel = 'user'}) async { try { - await _db.stackEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.stackId))); + await _db.batch((batch) { + for (final stack in data) { + batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stack.stackId)); + } + }); } catch (error, stack) { _logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack); rethrow; diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 9a12745ac..4edd49fec 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -1,7 +1,6 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; @@ -11,6 +10,7 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/date_time_picker.dart'; @@ -199,14 +199,11 @@ class ActionService { } Future removeFromAlbum(List remoteIds, String albumId) async { - int removedCount = 0; final result = await _albumApiRepository.removeAssets(albumId, remoteIds); - if (result.removed.isNotEmpty) { - removedCount = await _remoteAlbumRepository.removeAssets(albumId, result.removed); + await _remoteAlbumRepository.removeAssets(albumId, result.removed); } - - return removedCount; + return result.removed.length; } Future updateDescription(String assetId, String description) async { diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 76573e499..1d78a4431 100644 Binary files a/mobile/test/drift/main/generated/schema.dart and b/mobile/test/drift/main/generated/schema.dart differ diff --git a/mobile/test/drift/main/generated/schema_v11.dart b/mobile/test/drift/main/generated/schema_v11.dart new file mode 100644 index 000000000..fe27f1bb3 Binary files /dev/null and b/mobile/test/drift/main/generated/schema_v11.dart differ