refactor(mobile): asset provider (#16159)
* refactor(mobile): asset provider * wip * wip: delete local assets * wip: delete remote assets * wip: deletion logic * refactor * pr feedback
This commit is contained in:
@@ -310,7 +310,7 @@ class AlbumService {
|
||||
final List<int> idsToRemove =
|
||||
_syncService.sharedAssetsToRemove(foreignAssets, existing);
|
||||
if (idsToRemove.isNotEmpty) {
|
||||
await _assetRepository.deleteById(idsToRemove);
|
||||
await _assetRepository.deleteByIds(idsToRemove);
|
||||
}
|
||||
} else {
|
||||
await _albumRepository.delete(album.id);
|
||||
@@ -491,4 +491,8 @@ class AlbumService {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> clearTable() async {
|
||||
await _albumRepository.clearTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||
import 'package:immich_mobile/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/asset_media.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/backup.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
||||
import 'package:immich_mobile/repositories/exif_info.repository.dart';
|
||||
@@ -43,6 +45,7 @@ final assetServiceProvider = Provider(
|
||||
ref.watch(userServiceProvider),
|
||||
ref.watch(backupServiceProvider),
|
||||
ref.watch(albumServiceProvider),
|
||||
ref.watch(assetMediaRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -58,6 +61,7 @@ class AssetService {
|
||||
final UserService _userService;
|
||||
final BackupService _backupService;
|
||||
final AlbumService _albumService;
|
||||
final IAssetMediaRepository _assetMediaRepository;
|
||||
final log = Logger('AssetService');
|
||||
|
||||
AssetService(
|
||||
@@ -72,6 +76,7 @@ class AssetService {
|
||||
this._userService,
|
||||
this._backupService,
|
||||
this._albumService,
|
||||
this._assetMediaRepository,
|
||||
);
|
||||
|
||||
/// Checks the server for updated assets and updates the local database if
|
||||
@@ -158,30 +163,6 @@ class AssetService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteAssets(
|
||||
Iterable<Asset> deleteAssets, {
|
||||
bool? force = false,
|
||||
}) async {
|
||||
try {
|
||||
final List<String> payload = [];
|
||||
|
||||
for (final asset in deleteAssets) {
|
||||
payload.add(asset.remoteId!);
|
||||
}
|
||||
|
||||
await _apiService.assetsApi.deleteAssets(
|
||||
AssetBulkDeleteDto(
|
||||
ids: payload,
|
||||
force: force,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (error, stack) {
|
||||
log.severe("Error while deleting assets", error, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Loads the exif information from the database. If there is none, loads
|
||||
/// the exif info from the server (remote assets only)
|
||||
Future<Asset> loadExif(Asset a) async {
|
||||
@@ -432,4 +413,105 @@ class AssetService {
|
||||
Future<List<Asset>> getStackAssets(String stackId) {
|
||||
return _assetRepository.getStackAssets(stackId);
|
||||
}
|
||||
|
||||
Future<void> clearTable() {
|
||||
return _assetRepository.clearTable();
|
||||
}
|
||||
|
||||
/// Delete assets from local file system and unreference from the database
|
||||
Future<void> deleteLocalAssets(Iterable<Asset> assets) async {
|
||||
// Delete files from local gallery
|
||||
final candidates = assets.where((asset) => asset.isLocal);
|
||||
|
||||
final deletedIds = await _assetMediaRepository
|
||||
.deleteAll(candidates.map((asset) => asset.localId!).toList());
|
||||
|
||||
// Modify local database by removing the reference to the local assets
|
||||
if (deletedIds.isNotEmpty) {
|
||||
// Delete records from local database
|
||||
final isarIds = assets
|
||||
.where((asset) => asset.storage == AssetState.local)
|
||||
.map((asset) => asset.id)
|
||||
.toList();
|
||||
await _assetRepository.deleteByIds(isarIds);
|
||||
|
||||
// Modify Merged asset to be remote only
|
||||
final updatedAssets = assets
|
||||
.where((asset) => asset.storage == AssetState.merged)
|
||||
.map((asset) {
|
||||
asset.localId = null;
|
||||
return asset;
|
||||
}).toList();
|
||||
|
||||
await _assetRepository.updateAll(updatedAssets);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete assets from the server and unreference from the database
|
||||
Future<void> deleteRemoteAssets(
|
||||
Iterable<Asset> assets, {
|
||||
bool shouldDeletePermanently = false,
|
||||
}) async {
|
||||
final candidates = assets.where((a) => a.isRemote);
|
||||
if (candidates.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiService.assetsApi.deleteAssets(
|
||||
AssetBulkDeleteDto(
|
||||
ids: candidates.map((a) => a.remoteId!).toList(),
|
||||
force: shouldDeletePermanently,
|
||||
),
|
||||
);
|
||||
|
||||
/// Update asset info bassed on the deletion type.
|
||||
final payload = shouldDeletePermanently
|
||||
? assets
|
||||
.where((asset) => asset.storage == AssetState.merged)
|
||||
.map((asset) {
|
||||
asset.remoteId = null;
|
||||
return asset;
|
||||
})
|
||||
: assets.where((asset) => asset.isRemote).map((asset) {
|
||||
asset.isTrashed = true;
|
||||
return asset;
|
||||
});
|
||||
|
||||
await _assetRepository.transaction(() async {
|
||||
await _assetRepository.updateAll(payload.toList());
|
||||
|
||||
if (shouldDeletePermanently) {
|
||||
final remoteAssetIds = assets
|
||||
.where((asset) => asset.storage == AssetState.remote)
|
||||
.map((asset) => asset.id)
|
||||
.toList();
|
||||
await _assetRepository.deleteByIds(remoteAssetIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete assets on both local file system and the server.
|
||||
/// Unreference from the database.
|
||||
Future<void> deleteAssets(
|
||||
Iterable<Asset> assets, {
|
||||
bool shouldDeletePermanently = false,
|
||||
}) async {
|
||||
final hasLocal = assets.any((asset) => asset.isLocal);
|
||||
final hasRemote = assets.any((asset) => asset.isRemote);
|
||||
|
||||
if (hasLocal) {
|
||||
await deleteLocalAssets(assets);
|
||||
}
|
||||
|
||||
if (hasRemote) {
|
||||
await deleteRemoteAssets(
|
||||
assets,
|
||||
shouldDeletePermanently: shouldDeletePermanently,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Asset?> watchAsset(int id, {bool fireImmediately = false}) {
|
||||
return _assetRepository.watchAsset(id, fireImmediately: fireImmediately);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
||||
|
||||
final etagServiceProvider =
|
||||
Provider((ref) => ETagService(ref.watch(etagRepositoryProvider)));
|
||||
|
||||
class ETagService {
|
||||
final IETagRepository _eTagRepository;
|
||||
|
||||
ETagService(this._eTagRepository);
|
||||
|
||||
Future<void> clearTable() {
|
||||
return _eTagRepository.clearTable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
|
||||
import 'package:immich_mobile/repositories/exif_info.repository.dart';
|
||||
|
||||
final exifServiceProvider =
|
||||
Provider((ref) => ExifService(ref.watch(exifInfoRepositoryProvider)));
|
||||
|
||||
class ExifService {
|
||||
final IExifInfoRepository _exifInfoRepository;
|
||||
|
||||
ExifService(this._exifInfoRepository);
|
||||
|
||||
Future<void> clearTable() {
|
||||
return _exifInfoRepository.clearTable();
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ class SyncService {
|
||||
}
|
||||
final idsToDelete = toRemove.map((e) => e.id).toList();
|
||||
try {
|
||||
await _assetRepository.deleteById(idsToDelete);
|
||||
await _assetRepository.deleteByIds(idsToDelete);
|
||||
await upsertAssetsWithExif(toAdd + toUpdate);
|
||||
} catch (e) {
|
||||
_log.severe("Failed to sync remote assets to db", e);
|
||||
@@ -334,7 +334,7 @@ class SyncService {
|
||||
if (toDelete.isNotEmpty) {
|
||||
final List<int> idsToRemove = sharedAssetsToRemove(toDelete, existing);
|
||||
if (idsToRemove.isNotEmpty) {
|
||||
await _assetRepository.deleteById(idsToRemove);
|
||||
await _assetRepository.deleteByIds(idsToRemove);
|
||||
}
|
||||
} else {
|
||||
assert(toDelete.isEmpty);
|
||||
@@ -531,7 +531,7 @@ class SyncService {
|
||||
);
|
||||
if (toDelete.isNotEmpty || toUpdate.isNotEmpty) {
|
||||
await _assetRepository.transaction(() async {
|
||||
await _assetRepository.deleteById(toDelete);
|
||||
await _assetRepository.deleteByIds(toDelete);
|
||||
await _assetRepository.updateAll(toUpdate);
|
||||
});
|
||||
_log.info(
|
||||
@@ -826,7 +826,7 @@ class SyncService {
|
||||
final (toDelete, toUpdate) =
|
||||
_handleAssetRemoval(assets, [], remote: false);
|
||||
await _assetRepository.transaction(() async {
|
||||
await _assetRepository.deleteById(toDelete);
|
||||
await _assetRepository.deleteByIds(toDelete);
|
||||
await _assetRepository.updateAll(toUpdate);
|
||||
await _albumRepository.deleteAllLocal();
|
||||
});
|
||||
|
||||
@@ -103,4 +103,8 @@ class UserService {
|
||||
if (users == null) return false;
|
||||
return _syncService.syncUsersFromServer(users);
|
||||
}
|
||||
|
||||
Future<void> clearTable() {
|
||||
return _userRepository.clearTable();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user