mirror of
https://github.com/immich-app/immich.git
synced 2025-06-09 08:34:32 -04:00
move empty checks inside repo
This commit is contained in:
parent
a7032bb3d9
commit
65b55e64f6
@ -2,24 +2,17 @@ import 'package:immich_mobile/domain/models/asset/asset.model.dart';
|
|||||||
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
|
||||||
abstract interface class IAlbumMediaRepository {
|
abstract interface class IAlbumMediaRepository {
|
||||||
Future<List<LocalAlbum>> getAll({
|
Future<List<LocalAlbum>> getAll();
|
||||||
bool withModifiedTime = false,
|
|
||||||
bool withAssetCount = false,
|
|
||||||
bool withAssetTitle = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<LocalAsset>> getAssetsForAlbum(
|
Future<List<LocalAsset>> getAssetsForAlbum(
|
||||||
String albumId, {
|
String albumId, {
|
||||||
bool withModifiedTime = false,
|
|
||||||
bool withAssetTitle = true,
|
|
||||||
DateTimeFilter? updateTimeCond,
|
DateTimeFilter? updateTimeCond,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<LocalAlbum> refresh(
|
Future<LocalAlbum> refresh(
|
||||||
String albumId, {
|
String albumId, {
|
||||||
bool withModifiedTime = false,
|
bool withModifiedTime = true,
|
||||||
bool withAssetCount = false,
|
bool withAssetCount = true,
|
||||||
bool withAssetTitle = false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,13 @@ import 'package:immich_mobile/utils/diff.dart';
|
|||||||
import 'package:immich_mobile/utils/nullable_value.dart';
|
import 'package:immich_mobile/utils/nullable_value.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class SyncService {
|
class DeviceSyncService {
|
||||||
final IAlbumMediaRepository _albumMediaRepository;
|
final IAlbumMediaRepository _albumMediaRepository;
|
||||||
final ILocalAlbumRepository _localAlbumRepository;
|
final ILocalAlbumRepository _localAlbumRepository;
|
||||||
final ILocalAssetRepository _localAssetRepository;
|
final ILocalAssetRepository _localAssetRepository;
|
||||||
final Logger _log = Logger("SyncService");
|
final Logger _log = Logger("SyncService");
|
||||||
|
|
||||||
SyncService({
|
DeviceSyncService({
|
||||||
required IAlbumMediaRepository albumMediaRepository,
|
required IAlbumMediaRepository albumMediaRepository,
|
||||||
required ILocalAlbumRepository localAlbumRepository,
|
required ILocalAlbumRepository localAlbumRepository,
|
||||||
required ILocalAssetRepository localAssetRepository,
|
required ILocalAssetRepository localAssetRepository,
|
||||||
@ -25,46 +25,41 @@ class SyncService {
|
|||||||
_localAlbumRepository = localAlbumRepository,
|
_localAlbumRepository = localAlbumRepository,
|
||||||
_localAssetRepository = localAssetRepository;
|
_localAssetRepository = localAssetRepository;
|
||||||
|
|
||||||
Future<bool> syncLocalAlbums() async {
|
Future<void> syncAlbums() async {
|
||||||
try {
|
try {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
// The deviceAlbums will not have the updatedAt field
|
// The deviceAlbums will not have the updatedAt field
|
||||||
// and the assetCount will be 0. They are refreshed later
|
// and the assetCount will be 0. They are refreshed later
|
||||||
// after the comparison
|
// after the comparison. The orderby in the filter sorts the assets
|
||||||
|
// and not the albums.
|
||||||
final deviceAlbums =
|
final deviceAlbums =
|
||||||
(await _albumMediaRepository.getAll()).sortedBy((a) => a.id);
|
(await _albumMediaRepository.getAll()).sortedBy((a) => a.id);
|
||||||
final dbAlbums =
|
final dbAlbums =
|
||||||
await _localAlbumRepository.getAll(sortBy: SortLocalAlbumsBy.id);
|
await _localAlbumRepository.getAll(sortBy: SortLocalAlbumsBy.id);
|
||||||
|
|
||||||
final hasChange = await diffSortedLists(
|
await diffSortedLists(
|
||||||
dbAlbums,
|
dbAlbums,
|
||||||
deviceAlbums,
|
deviceAlbums,
|
||||||
compare: (a, b) => a.id.compareTo(b.id),
|
compare: (a, b) => a.id.compareTo(b.id),
|
||||||
both: syncLocalAlbum,
|
both: updateAlbum,
|
||||||
onlyFirst: removeLocalAlbum,
|
onlyFirst: removeAlbum,
|
||||||
onlySecond: addLocalAlbum,
|
onlySecond: addAlbum,
|
||||||
);
|
);
|
||||||
|
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
_log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
_log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms");
|
||||||
return hasChange;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.severe("Error performing full device sync", e, s);
|
_log.severe("Error performing full device sync", e, s);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addLocalAlbum(LocalAlbum newAlbum) async {
|
Future<void> addAlbum(LocalAlbum newAlbum) async {
|
||||||
try {
|
try {
|
||||||
_log.info("Adding device album ${newAlbum.name}");
|
_log.info("Adding device album ${newAlbum.name}");
|
||||||
final deviceAlbum = await _albumMediaRepository.refresh(
|
final deviceAlbum = await _albumMediaRepository.refresh(newAlbum.id);
|
||||||
newAlbum.id,
|
|
||||||
withModifiedTime: true,
|
|
||||||
withAssetCount: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
final assets = deviceAlbum.assetCount > 0
|
final assets = deviceAlbum.assetCount > 0
|
||||||
? (await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id))
|
? await _albumMediaRepository.getAssetsForAlbum(deviceAlbum.id)
|
||||||
: <LocalAsset>[];
|
: <LocalAsset>[];
|
||||||
|
|
||||||
final album = deviceAlbum.copyWith(
|
final album = deviceAlbum.copyWith(
|
||||||
@ -73,13 +68,13 @@ class SyncService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _localAlbumRepository.insert(album, assets);
|
await _localAlbumRepository.insert(album, assets);
|
||||||
_log.info("Successfully added device album ${album.name}");
|
_log.fine("Successfully added device album ${album.name}");
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.warning("Error while adding device album", e, s);
|
_log.warning("Error while adding device album", e, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeLocalAlbum(LocalAlbum a) async {
|
Future<void> removeAlbum(LocalAlbum a) async {
|
||||||
_log.info("Removing device album ${a.name}");
|
_log.info("Removing device album ${a.name}");
|
||||||
try {
|
try {
|
||||||
// Asset deletion is handled in the repository
|
// Asset deletion is handled in the repository
|
||||||
@ -90,39 +85,26 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The deviceAlbum is ignored since we are going to refresh it anyways
|
// The deviceAlbum is ignored since we are going to refresh it anyways
|
||||||
FutureOr<bool> syncLocalAlbum(LocalAlbum dbAlbum, LocalAlbum _) async {
|
FutureOr<bool> updateAlbum(LocalAlbum dbAlbum, LocalAlbum _) async {
|
||||||
try {
|
try {
|
||||||
_log.info("Syncing device album ${dbAlbum.name}");
|
_log.info("Syncing device album ${dbAlbum.name}");
|
||||||
|
|
||||||
final deviceAlbum = await _albumMediaRepository.refresh(
|
final deviceAlbum = await _albumMediaRepository.refresh(dbAlbum.id);
|
||||||
dbAlbum.id,
|
|
||||||
withModifiedTime: true,
|
|
||||||
withAssetCount: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Early return if album hasn't changed
|
// Early return if album hasn't changed
|
||||||
if (deviceAlbum.updatedAt.isAtSameMomentAs(dbAlbum.updatedAt) &&
|
if (deviceAlbum.updatedAt.isAtSameMomentAs(dbAlbum.updatedAt) &&
|
||||||
deviceAlbum.assetCount == dbAlbum.assetCount) {
|
deviceAlbum.assetCount == dbAlbum.assetCount) {
|
||||||
_log.info(
|
_log.fine(
|
||||||
"Device album ${dbAlbum.name} has not changed. Skipping sync.",
|
"Device album ${dbAlbum.name} has not changed. Skipping sync.",
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip empty albums that don't need syncing
|
_log.fine("Device album ${dbAlbum.name} has changed. Syncing...");
|
||||||
if (deviceAlbum.assetCount == 0 && dbAlbum.assetCount == 0) {
|
|
||||||
await _localAlbumRepository.update(
|
|
||||||
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
|
||||||
);
|
|
||||||
_log.info("Album ${dbAlbum.name} is empty. Only metadata updated.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.info("Device album ${dbAlbum.name} has changed. Syncing...");
|
// Faster path - only new assets added
|
||||||
|
if (await checkAddition(dbAlbum, deviceAlbum)) {
|
||||||
// Faster path - only assets added
|
_log.fine("Fast synced device album ${dbAlbum.name}");
|
||||||
if (await tryFastSync(dbAlbum, deviceAlbum)) {
|
|
||||||
_log.info("Fast synced device album ${dbAlbum.name}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,19 +119,15 @@ class SyncService {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
// The [deviceAlbum] is expected to be refreshed before calling this method
|
// The [deviceAlbum] is expected to be refreshed before calling this method
|
||||||
// with modified time and asset count
|
// with modified time and asset count
|
||||||
Future<bool> tryFastSync(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
|
Future<bool> checkAddition(
|
||||||
|
LocalAlbum dbAlbum,
|
||||||
|
LocalAlbum deviceAlbum,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
_log.info("Fast syncing device album ${dbAlbum.name}");
|
_log.fine("Fast syncing device album ${dbAlbum.name}");
|
||||||
if (!deviceAlbum.updatedAt.isAfter(dbAlbum.updatedAt)) {
|
|
||||||
_log.info(
|
|
||||||
"Local album ${deviceAlbum.name} has modifications. Proceeding to full sync",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assets has been modified
|
// Assets has been modified
|
||||||
if (deviceAlbum.assetCount <= dbAlbum.assetCount) {
|
if (deviceAlbum.assetCount <= dbAlbum.assetCount) {
|
||||||
_log.info("Local album has modifications. Proceeding to full sync");
|
_log.fine("Local album has modifications. Proceeding to full sync");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,15 +142,15 @@ class SyncService {
|
|||||||
|
|
||||||
// Early return if no new assets were found
|
// Early return if no new assets were found
|
||||||
if (newAssets.isEmpty) {
|
if (newAssets.isEmpty) {
|
||||||
_log.info(
|
_log.fine(
|
||||||
"No new assets found despite album changes. Proceeding to full sync for ${dbAlbum.name}",
|
"No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}",
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether there is only addition or if there has been deletions
|
// Check whether there is only addition or if there has been deletions
|
||||||
if (deviceAlbum.assetCount != dbAlbum.assetCount + newAssets.length) {
|
if (deviceAlbum.assetCount != dbAlbum.assetCount + newAssets.length) {
|
||||||
_log.info("Local album has modifications. Proceeding to full sync");
|
_log.fine("Local album has modifications. Proceeding to full sync");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +168,7 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _handleUpdate(
|
await _updateAlbum(
|
||||||
deviceAlbum.copyWith(
|
deviceAlbum.copyWith(
|
||||||
thumbnailId: NullableValue.valueOrEmpty(thumbnailId),
|
thumbnailId: NullableValue.valueOrEmpty(thumbnailId),
|
||||||
backupSelection: dbAlbum.backupSelection,
|
backupSelection: dbAlbum.backupSelection,
|
||||||
@ -221,7 +199,7 @@ class SyncService {
|
|||||||
_log.fine(
|
_log.fine(
|
||||||
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
|
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
|
||||||
);
|
);
|
||||||
await _handleUpdate(
|
await _updateAlbum(
|
||||||
deviceAlbum.copyWith(
|
deviceAlbum.copyWith(
|
||||||
// Clear thumbnail for empty album
|
// Clear thumbnail for empty album
|
||||||
thumbnailId: const NullableValue.empty(),
|
thumbnailId: const NullableValue.empty(),
|
||||||
@ -246,37 +224,38 @@ class SyncService {
|
|||||||
_log.fine(
|
_log.fine(
|
||||||
"Device album ${deviceAlbum.name} is empty. Adding assets to DB.",
|
"Device album ${deviceAlbum.name} is empty. Adding assets to DB.",
|
||||||
);
|
);
|
||||||
await _handleUpdate(updatedDeviceAlbum, assetsToUpsert: assetsInDevice);
|
await _updateAlbum(updatedDeviceAlbum, assetsToUpsert: assetsInDevice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort assets by localId for the diffSortedLists function
|
assert(assetsInDb.isSortedBy((a) => a.localId));
|
||||||
assetsInDb.sort((a, b) => a.localId.compareTo(b.localId));
|
|
||||||
assetsInDevice.sort((a, b) => a.localId.compareTo(b.localId));
|
assetsInDevice.sort((a, b) => a.localId.compareTo(b.localId));
|
||||||
|
|
||||||
final assetsToAddOrUpdate = <LocalAsset>[];
|
final assetsToUpsert = <LocalAsset>[];
|
||||||
final assetIdsToDelete = <String>[];
|
final assetsToDelete = <String>[];
|
||||||
|
|
||||||
diffSortedListsSync(
|
diffSortedListsSync(
|
||||||
assetsInDb,
|
assetsInDb,
|
||||||
assetsInDevice,
|
assetsInDevice,
|
||||||
compare: (a, b) => a.localId.compareTo(b.localId),
|
compare: (a, b) => a.localId.compareTo(b.localId),
|
||||||
both: (dbAsset, deviceAsset) {
|
both: (dbAsset, deviceAsset) {
|
||||||
|
// Custom comparison to check if the asset has been modified without
|
||||||
|
// comparing the checksum
|
||||||
if (!_assetsEqual(dbAsset, deviceAsset)) {
|
if (!_assetsEqual(dbAsset, deviceAsset)) {
|
||||||
assetsToAddOrUpdate.add(deviceAsset);
|
assetsToUpsert.add(deviceAsset);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onlyFirst: (dbAsset) => assetIdsToDelete.add(dbAsset.localId),
|
onlyFirst: (dbAsset) => assetsToDelete.add(dbAsset.localId),
|
||||||
onlySecond: (deviceAsset) => assetsToAddOrUpdate.add(deviceAsset),
|
onlySecond: (deviceAsset) => assetsToUpsert.add(deviceAsset),
|
||||||
);
|
);
|
||||||
|
|
||||||
_log.info(
|
_log.fine(
|
||||||
"Syncing ${deviceAlbum.name}. ${assetsToAddOrUpdate.length} assets to add/update and ${assetIdsToDelete.length} assets to delete",
|
"Syncing ${deviceAlbum.name}. ${assetsToUpsert.length} assets to add/update and ${assetsToDelete.length} assets to delete",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (assetsToAddOrUpdate.isEmpty && assetIdsToDelete.isEmpty) {
|
if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) {
|
||||||
_log.fine(
|
_log.fine(
|
||||||
"No asset changes detected in album ${deviceAlbum.name}. Updating metadata.",
|
"No asset changes detected in album ${deviceAlbum.name}. Updating metadata.",
|
||||||
);
|
);
|
||||||
@ -284,40 +263,30 @@ class SyncService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _handleUpdate(
|
await _updateAlbum(
|
||||||
updatedDeviceAlbum,
|
updatedDeviceAlbum,
|
||||||
assetsToUpsert: assetsToAddOrUpdate,
|
assetsToUpsert: assetsToUpsert,
|
||||||
assetIdsToDelete: assetIdsToDelete,
|
assetIdsToDelete: assetsToDelete,
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.warning("Error on full syncing local album: ${dbAlbum.name}", e, s);
|
_log.warning("Error on full syncing local album: ${dbAlbum.name}", e, s);
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleUpdate(
|
Future<void> _updateAlbum(
|
||||||
LocalAlbum album, {
|
LocalAlbum album, {
|
||||||
Iterable<LocalAsset>? assetsToUpsert,
|
Iterable<LocalAsset> assetsToUpsert = const [],
|
||||||
Iterable<String>? assetIdsToDelete,
|
Iterable<String> assetIdsToDelete = const [],
|
||||||
}) =>
|
}) =>
|
||||||
_localAlbumRepository.transaction(() async {
|
_localAlbumRepository.transaction(() async {
|
||||||
if (assetsToUpsert != null && assetsToUpsert.isNotEmpty) {
|
|
||||||
await _localAlbumRepository.addAssets(album.id, assetsToUpsert);
|
await _localAlbumRepository.addAssets(album.id, assetsToUpsert);
|
||||||
}
|
|
||||||
|
|
||||||
await _localAlbumRepository.update(album);
|
await _localAlbumRepository.update(album);
|
||||||
|
await _localAlbumRepository.removeAssets(album.id, assetIdsToDelete);
|
||||||
if (assetIdsToDelete != null && assetIdsToDelete.isNotEmpty) {
|
|
||||||
await _localAlbumRepository.removeAssets(
|
|
||||||
album.id,
|
|
||||||
assetIdsToDelete,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper method to check if assets are equal without relying on the checksum
|
|
||||||
bool _assetsEqual(LocalAsset a, LocalAsset b) {
|
bool _assetsEqual(LocalAsset a, LocalAsset b) {
|
||||||
return a.updatedAt.isAtSameMomentAs(b.updatedAt) &&
|
return a.updatedAt.isAtSameMomentAs(b.updatedAt) &&
|
||||||
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
a.createdAt.isAtSameMomentAs(b.createdAt) &&
|
@ -29,7 +29,7 @@ class BackgroundSyncManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_deviceAlbumSyncTask = runInIsolateGentle(
|
_deviceAlbumSyncTask = runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncServiceProvider).syncLocalAlbums(),
|
computation: (ref) => ref.read(deviceSyncServiceProvider).syncAlbums(),
|
||||||
);
|
);
|
||||||
return _deviceAlbumSyncTask!.whenComplete(() {
|
return _deviceAlbumSyncTask!.whenComplete(() {
|
||||||
_deviceAlbumSyncTask = null;
|
_deviceAlbumSyncTask = null;
|
||||||
|
@ -36,41 +36,24 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LocalAlbum>> getAll({
|
Future<List<LocalAlbum>> getAll() {
|
||||||
withModifiedTime = false,
|
final filter = AdvancedCustomFilter(
|
||||||
withAssetCount = false,
|
|
||||||
withAssetTitle = false,
|
|
||||||
}) async {
|
|
||||||
final filter = withModifiedTime || withAssetTitle
|
|
||||||
? _getAlbumFilter(
|
|
||||||
withAssetTitle: withAssetTitle,
|
|
||||||
withModifiedTime: withModifiedTime,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use an AdvancedCustomFilter to get all albums faster
|
|
||||||
: AdvancedCustomFilter(
|
|
||||||
orderBy: [OrderByItem.asc(CustomColumns.base.id)],
|
orderBy: [OrderByItem.asc(CustomColumns.base.id)],
|
||||||
);
|
);
|
||||||
|
|
||||||
final entities = await PhotoManager.getAssetPathList(
|
return PhotoManager.getAssetPathList(hasAll: true, filterOption: filter)
|
||||||
hasAll: true,
|
.then((e) => e.toDtoList());
|
||||||
filterOption: filter,
|
|
||||||
);
|
|
||||||
return entities.toDtoList(withAssetCount: withAssetCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<asset.LocalAsset>> getAssetsForAlbum(
|
Future<List<asset.LocalAsset>> getAssetsForAlbum(
|
||||||
String albumId, {
|
String albumId, {
|
||||||
withModifiedTime = false,
|
|
||||||
withAssetTitle = true,
|
|
||||||
DateTimeFilter? updateTimeCond,
|
DateTimeFilter? updateTimeCond,
|
||||||
}) async {
|
}) async {
|
||||||
final assetPathEntity = await AssetPathEntity.obtainPathFromProperties(
|
final assetPathEntity = await AssetPathEntity.obtainPathFromProperties(
|
||||||
id: albumId,
|
id: albumId,
|
||||||
optionGroup: _getAlbumFilter(
|
optionGroup: _getAlbumFilter(
|
||||||
withAssetTitle: withAssetTitle,
|
withAssetTitle: true,
|
||||||
withModifiedTime: withModifiedTime,
|
|
||||||
updateTimeCond: updateTimeCond,
|
updateTimeCond: updateTimeCond,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -91,23 +74,17 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
|||||||
@override
|
@override
|
||||||
Future<LocalAlbum> refresh(
|
Future<LocalAlbum> refresh(
|
||||||
String albumId, {
|
String albumId, {
|
||||||
withModifiedTime = false,
|
bool withModifiedTime = true,
|
||||||
withAssetCount = false,
|
bool withAssetCount = true,
|
||||||
withAssetTitle = false,
|
}) =>
|
||||||
}) async =>
|
AssetPathEntity.obtainPathFromProperties(
|
||||||
(await AssetPathEntity.obtainPathFromProperties(
|
|
||||||
id: albumId,
|
id: albumId,
|
||||||
optionGroup: _getAlbumFilter(
|
optionGroup: _getAlbumFilter(withModifiedTime: withModifiedTime),
|
||||||
withAssetTitle: withAssetTitle,
|
).then((a) => a.toDto(withAssetCount: withAssetCount));
|
||||||
withModifiedTime: withModifiedTime,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toDto(withAssetCount: withAssetCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on AssetEntity {
|
extension on AssetEntity {
|
||||||
Future<asset.LocalAsset> toDto() async {
|
Future<asset.LocalAsset> toDto() async => asset.LocalAsset(
|
||||||
return asset.LocalAsset(
|
|
||||||
localId: id,
|
localId: id,
|
||||||
name: title ?? await titleAsync,
|
name: title ?? await titleAsync,
|
||||||
type: switch (type) {
|
type: switch (type) {
|
||||||
@ -123,7 +100,6 @@ extension on AssetEntity {
|
|||||||
durationInSeconds: duration,
|
durationInSeconds: duration,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension on List<AssetEntity> {
|
extension on List<AssetEntity> {
|
||||||
Future<List<asset.LocalAsset>> toDtoList() =>
|
Future<List<asset.LocalAsset>> toDtoList() =>
|
||||||
|
@ -52,9 +52,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
final assetsToDelete = _platform.isIOS
|
final assetsToDelete = _platform.isIOS
|
||||||
? await _getUniqueAssetsInAlbum(albumId)
|
? await _getUniqueAssetsInAlbum(albumId)
|
||||||
: await _getAssetsIdsInAlbum(albumId);
|
: await _getAssetsIdsInAlbum(albumId);
|
||||||
if (assetsToDelete.isNotEmpty) {
|
|
||||||
await _deleteAssets(assetsToDelete);
|
await _deleteAssets(assetsToDelete);
|
||||||
}
|
|
||||||
|
|
||||||
// All the other assets that are still associated will be unlinked automatically on-cascade
|
// All the other assets that are still associated will be unlinked automatically on-cascade
|
||||||
await _db.managers.localAlbumEntity
|
await _db.managers.localAlbumEntity
|
||||||
@ -65,35 +63,36 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
@override
|
@override
|
||||||
Future<void> insert(LocalAlbum localAlbum, Iterable<LocalAsset> assets) =>
|
Future<void> insert(LocalAlbum localAlbum, Iterable<LocalAsset> assets) =>
|
||||||
transaction(() async {
|
transaction(() async {
|
||||||
if (localAlbum.assetCount > 0) {
|
|
||||||
await _upsertAssets(assets);
|
await _upsertAssets(assets);
|
||||||
}
|
|
||||||
// Needs to be after asset upsert to link the thumbnail
|
// Needs to be after asset upsert to link the thumbnail
|
||||||
await _upsertAlbum(localAlbum);
|
await _upsertAlbum(localAlbum);
|
||||||
|
|
||||||
if (localAlbum.assetCount > 0) {
|
|
||||||
await _linkAssetsToAlbum(localAlbum.id, assets);
|
await _linkAssetsToAlbum(localAlbum.id, assets);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAssets(String albumId, Iterable<LocalAsset> assets) =>
|
Future<void> addAssets(String albumId, Iterable<LocalAsset> assets) {
|
||||||
transaction(() async {
|
if (assets.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
return transaction(() async {
|
||||||
await _upsertAssets(assets);
|
await _upsertAssets(assets);
|
||||||
await _linkAssetsToAlbum(albumId, assets);
|
await _linkAssetsToAlbum(albumId, assets);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeAssets(String albumId, Iterable<String> assetIds) async {
|
Future<void> removeAssets(String albumId, Iterable<String> assetIds) async {
|
||||||
|
if (assetIds.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
if (_platform.isAndroid) {
|
if (_platform.isAndroid) {
|
||||||
await _deleteAssets(assetIds);
|
return _deleteAssets(assetIds);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);
|
final uniqueAssets = await _getUniqueAssetsInAlbum(albumId);
|
||||||
if (uniqueAssets.isEmpty) {
|
if (uniqueAssets.isEmpty) {
|
||||||
await _unlinkAssetsFromAlbum(albumId, assetIds);
|
return _unlinkAssetsFromAlbum(albumId, assetIds);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Delete unique assets and unlink others
|
// Delete unique assets and unlink others
|
||||||
final uniqueSet = uniqueAssets.toSet();
|
final uniqueSet = uniqueAssets.toSet();
|
||||||
@ -106,8 +105,10 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
assetsToUnLink.add(assetId);
|
assetsToUnLink.add(assetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return transaction(() async {
|
||||||
await _unlinkAssetsFromAlbum(albumId, assetsToUnLink);
|
await _unlinkAssetsFromAlbum(albumId, assetsToUnLink);
|
||||||
await _deleteAssets(assetsToDelete);
|
await _deleteAssets(assetsToDelete);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -123,7 +124,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
.equalsExp(_db.localAssetEntity.localId),
|
.equalsExp(_db.localAssetEntity.localId),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)..where(_db.localAlbumAssetEntity.albumId.equals(albumId));
|
)
|
||||||
|
..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
|
||||||
|
..orderBy([OrderingTerm.desc(_db.localAssetEntity.localId)]);
|
||||||
return query
|
return query
|
||||||
.map((row) => row.readTable(_db.localAssetEntity).toDto())
|
.map((row) => row.readTable(_db.localAssetEntity).toDto())
|
||||||
.get();
|
.get();
|
||||||
@ -147,8 +150,12 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
Future<void> _linkAssetsToAlbum(
|
Future<void> _linkAssetsToAlbum(
|
||||||
String albumId,
|
String albumId,
|
||||||
Iterable<LocalAsset> assets,
|
Iterable<LocalAsset> assets,
|
||||||
) =>
|
) {
|
||||||
_db.batch(
|
if (assets.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch(
|
||||||
(batch) => batch.insertAll(
|
(batch) => batch.insertAll(
|
||||||
_db.localAlbumAssetEntity,
|
_db.localAlbumAssetEntity,
|
||||||
assets.map(
|
assets.map(
|
||||||
@ -160,17 +167,23 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrIgnore,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _unlinkAssetsFromAlbum(
|
Future<void> _unlinkAssetsFromAlbum(
|
||||||
String albumId,
|
String albumId,
|
||||||
Iterable<String> assetIds,
|
Iterable<String> assetIds,
|
||||||
) =>
|
) {
|
||||||
_db.batch(
|
if (assetIds.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch(
|
||||||
(batch) => batch.deleteWhere(
|
(batch) => batch.deleteWhere(
|
||||||
_db.localAlbumAssetEntity,
|
_db.localAlbumAssetEntity,
|
||||||
(f) => f.assetId.isIn(assetIds) & f.albumId.equals(albumId),
|
(f) => f.assetId.isIn(assetIds) & f.albumId.equals(albumId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<String>> _getAssetsIdsInAlbum(String albumId) {
|
Future<List<String>> _getAssetsIdsInAlbum(String albumId) {
|
||||||
final query = _db.localAlbumAssetEntity.select()
|
final query = _db.localAlbumAssetEntity.select()
|
||||||
@ -193,8 +206,12 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
return query.map((row) => row.read(assetId)!).get();
|
return query.map((row) => row.read(assetId)!).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _upsertAssets(Iterable<LocalAsset> localAssets) =>
|
Future<void> _upsertAssets(Iterable<LocalAsset> localAssets) {
|
||||||
_db.batch((batch) async {
|
if (localAssets.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch((batch) async {
|
||||||
batch.insertAllOnConflictUpdate(
|
batch.insertAllOnConflictUpdate(
|
||||||
_db.localAssetEntity,
|
_db.localAssetEntity,
|
||||||
localAssets.map(
|
localAssets.map(
|
||||||
@ -212,11 +229,18 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _deleteAssets(Iterable<String> ids) => _db.batch(
|
Future<void> _deleteAssets(Iterable<String> ids) {
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch(
|
||||||
(batch) => batch.deleteWhere(
|
(batch) => batch.deleteWhere(
|
||||||
_db.localAssetEntity,
|
_db.localAssetEntity,
|
||||||
(f) => f.localId.isIn(ids),
|
(f) => f.localId.isIn(ids),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/sync.service.dart';
|
import 'package:immich_mobile/domain/services/device_sync.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
@ -9,8 +9,8 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
final syncServiceProvider = Provider(
|
final deviceSyncServiceProvider = Provider(
|
||||||
(ref) => SyncService(
|
(ref) => DeviceSyncService(
|
||||||
albumMediaRepository: ref.watch(albumMediaRepositoryProvider),
|
albumMediaRepository: ref.watch(albumMediaRepositoryProvider),
|
||||||
localAlbumRepository: ref.watch(localAlbumRepository),
|
localAlbumRepository: ref.watch(localAlbumRepository),
|
||||||
localAssetRepository: ref.watch(localAssetProvider),
|
localAssetRepository: ref.watch(localAssetProvider),
|
||||||
|
File diff suppressed because it is too large
Load Diff
10
mobile/test/fixtures/local_album.stub.dart
vendored
10
mobile/test/fixtures/local_album.stub.dart
vendored
@ -22,4 +22,14 @@ abstract final class LocalAlbumStub {
|
|||||||
backupSelection: BackupSelection.selected,
|
backupSelection: BackupSelection.selected,
|
||||||
isAll: true,
|
isAll: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static LocalAlbum get album3 => LocalAlbum(
|
||||||
|
id: "album3",
|
||||||
|
name: "Album 3",
|
||||||
|
updatedAt: DateTime(2020),
|
||||||
|
assetCount: 20,
|
||||||
|
thumbnailId: "123",
|
||||||
|
backupSelection: BackupSelection.excluded,
|
||||||
|
isAll: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user