Files
immich/mobile-v2/lib/domain/services/album_sync.service.dart
T
shenlong-tanwen 0e8b19e269 use asynccache
2025-02-26 08:58:19 +05:30

140 lines
5.1 KiB
Dart

import 'package:async/async.dart';
import 'package:immich_mobile/domain/interfaces/album.interface.dart';
import 'package:immich_mobile/domain/interfaces/album_asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/album_etag.interface.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
import 'package:immich_mobile/domain/interfaces/device_album.interface.dart';
import 'package:immich_mobile/domain/models/album.model.dart';
import 'package:immich_mobile/domain/models/album_etag.model.dart';
import 'package:immich_mobile/domain/services/asset_sync.service.dart';
import 'package:immich_mobile/domain/services/hash.service.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/collection_util.dart';
import 'package:immich_mobile/utils/isolate_helper.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class AlbumSyncService with LogMixin {
AlbumSyncService();
final _fullDeviceSyncCache = AsyncCache<bool>.ephemeral();
Future<bool> performFullDeviceSyncIsolate() async {
return await _fullDeviceSyncCache
.fetch(() async => await IsolateHelper.run(performFullDeviceSync));
}
Future<bool> performFullDeviceSync() async {
try {
final Stopwatch stopwatch = Stopwatch()..start();
final deviceAlbums = await di<IDeviceAlbumRepository>().getAll();
final dbAlbums = await di<IAlbumRepository>().getAll(localOnly: true);
final hasChange = await CollectionUtil.diffLists(
dbAlbums,
deviceAlbums,
compare: Album.compareByLocalId,
both: _syncDeviceAlbum,
// Album is in DB but not anymore in device. Remove album and album specific assets
onlyFirst: _removeDeviceAlbum,
onlySecond: _addDeviceAlbum,
);
log.i("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
return hasChange;
} catch (e, s) {
log.e("Error performing full device sync", e, s);
}
return false;
}
Future<bool> _syncDeviceAlbum(
Album dbAlbum,
Album deviceAlbum, {
DateTime? modifiedUntil,
}) async {
assert(dbAlbum.id != null, "Album ID from DB is null");
final albumEtag = await di<IAlbumETagRepository>().get(dbAlbum.id!) ??
AlbumETag.initial();
final assetCountInDevice =
await di<IDeviceAlbumRepository>().getAssetCount(deviceAlbum.localId!);
final albumNotUpdated = deviceAlbum.name == dbAlbum.name &&
dbAlbum.modifiedTime.isAtSameMomentAs(deviceAlbum.modifiedTime) &&
assetCountInDevice == albumEtag.assetCount;
if (albumNotUpdated) {
log.i("Device Album ${deviceAlbum.name} not updated. Skipping sync.");
return false;
}
await _addDeviceAlbum(dbAlbum, modifiedUntil: modifiedUntil);
return true;
}
Future<void> _addDeviceAlbum(Album album, {DateTime? modifiedUntil}) async {
try {
log.i("Syncing device album ${album.name}");
final albumId = (await di<IAlbumRepository>().upsert(album))?.id;
// break fast if we cannot add an album
if (albumId == null) {
log.d("Failed creating device album. Skipped assets from album");
return;
}
final assets = await di<HashService>().getHashedAssetsForAlbum(
album.localId!,
modifiedUntil: modifiedUntil,
);
await di<IDatabaseRepository>().txn(() async {
final albumAssetsInDB =
await di<IAlbumToAssetRepository>().getAssetsForAlbum(albumId);
await di<AssetSyncService>().upsertAssetsToDb(
assets,
albumAssetsInDB,
isRemoteSync: false,
);
// This is needed to get the updated assets for device album with valid db id field
final albumAssets = await di<IAssetRepository>()
.getForLocalIds(assets.map((a) => a.localId!));
await di<IAlbumToAssetRepository>().addAssetIds(
albumId,
albumAssets.map((a) => a.id!),
);
await di<IAlbumRepository>().upsert(
album.copyWith(thumbnailAssetId: albumAssets.firstOrNull?.id),
);
// Update ETag
final albumETag = AlbumETag(
albumId: albumId,
assetCount: assets.length,
modifiedTime: album.modifiedTime,
);
await di<IAlbumETagRepository>().upsert(albumETag);
});
} catch (e, s) {
log.w("Error while adding device album", e, s);
}
}
Future<void> _removeDeviceAlbum(Album album) async {
assert(album.id != null, "Album ID from DB is null");
log.i("Removing device album ${album.name}");
final albumId = album.id!;
try {
await di<IDatabaseRepository>().txn(() async {
final toRemove =
await di<IAlbumToAssetRepository>().getAssetIdsOnlyInAlbum(albumId);
await di<IAlbumRepository>().deleteId(albumId);
await di<IAlbumToAssetRepository>().deleteAlbumId(albumId);
await di<IAssetRepository>().deleteIds(toRemove);
});
} catch (e, s) {
log.w("Error while removing device album", e, s);
}
}
}