mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix(mobile): self-heal stale linked album cache on 400
if the server forgets an album that mobile still has cached, every upload hits 400 on addAssets and spams severe forever. catch that 400, drop the cache row, fk cascade nulls the link. next manage pass recreates or re-links by name.
This commit is contained in:
@@ -39,24 +39,35 @@ class SyncLinkedAlbumService {
|
||||
|
||||
await Future.wait(
|
||||
selectedAlbums.map((localAlbum) async {
|
||||
final linkedRemoteAlbumId = localAlbum.linkedRemoteAlbumId;
|
||||
if (linkedRemoteAlbumId == null) {
|
||||
_log.warning("No linked remote album ID found for local album: ${localAlbum.name}");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final linkedRemoteAlbumId = localAlbum.linkedRemoteAlbumId;
|
||||
if (linkedRemoteAlbumId == null) {
|
||||
_log.warning("No linked remote album ID found for local album: ${localAlbum.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
final remoteAlbum = await _remoteAlbumRepository.get(linkedRemoteAlbumId);
|
||||
if (remoteAlbum == null) {
|
||||
_log.warning("Linked remote album not found for ID: $linkedRemoteAlbumId");
|
||||
return;
|
||||
}
|
||||
final remoteAlbum = await _remoteAlbumRepository.get(linkedRemoteAlbumId);
|
||||
if (remoteAlbum == null) {
|
||||
_log.warning("Linked remote album not found for ID: $linkedRemoteAlbumId");
|
||||
return;
|
||||
}
|
||||
|
||||
// get assets that are uploaded but not in the remote album
|
||||
final assetIds = await _remoteAlbumRepository.getLinkedAssetIds(userId, localAlbum.id, linkedRemoteAlbumId);
|
||||
_log.fine("Syncing ${assetIds.length} assets to remote album: ${remoteAlbum.name}");
|
||||
if (assetIds.isNotEmpty) {
|
||||
final album = await _albumApiRepository.addAssets(remoteAlbum.id, assetIds);
|
||||
await _remoteAlbumRepository.addAssets(remoteAlbum.id, album.added);
|
||||
// get assets that are uploaded but not in the remote album
|
||||
final assetIds = await _remoteAlbumRepository.getLinkedAssetIds(userId, localAlbum.id, linkedRemoteAlbumId);
|
||||
_log.fine("Syncing ${assetIds.length} assets to remote album: ${remoteAlbum.name}");
|
||||
if (assetIds.isNotEmpty) {
|
||||
final album = await _albumApiRepository.addAssets(remoteAlbum.id, assetIds);
|
||||
await _remoteAlbumRepository.addAssets(remoteAlbum.id, album.added);
|
||||
}
|
||||
} on RemoteAlbumNotFoundException catch (e) {
|
||||
// server doesn't have the linked album anymore. drop the cached row;
|
||||
// KeyAction.setNull on LocalAlbumEntity.linkedRemoteAlbumId nulls
|
||||
// the link via FK cascade, and the next manageLinkedAlbums run
|
||||
// will recreate or re-link by name.
|
||||
_log.warning("Pruning stale linked album for ${localAlbum.name} (server returned 'Album not found' for ${e.albumId})");
|
||||
await _remoteAlbumRepository.deleteAlbum(e.albumId);
|
||||
} catch (error, stack) {
|
||||
_log.severe("Linked album sync failed for ${localAlbum.name}", error, stack);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -42,17 +42,24 @@ class DriftAlbumApiRepository extends ApiRepository {
|
||||
}
|
||||
|
||||
Future<({List<String> added, List<String> failed})> addAssets(String albumId, Iterable<String> assetIds) async {
|
||||
final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList())));
|
||||
final List<String> added = [], failed = [];
|
||||
for (final dto in response) {
|
||||
if (dto.success) {
|
||||
added.add(dto.id);
|
||||
} else {
|
||||
failed.add(dto.id);
|
||||
try {
|
||||
final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList())));
|
||||
final List<String> added = [], failed = [];
|
||||
for (final dto in response) {
|
||||
if (dto.success) {
|
||||
added.add(dto.id);
|
||||
} else {
|
||||
failed.add(dto.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (added: added, failed: failed);
|
||||
return (added: added, failed: failed);
|
||||
} on ApiException catch (e) {
|
||||
if (e.code == 400 && (e.message?.contains('"message":"Album not found"') ?? false)) {
|
||||
throw RemoteAlbumNotFoundException(albumId);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<RemoteAlbum> updateAlbum(
|
||||
@@ -104,6 +111,14 @@ class DriftAlbumApiRepository extends ApiRepository {
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteAlbumNotFoundException implements Exception {
|
||||
final String albumId;
|
||||
const RemoteAlbumNotFoundException(this.albumId);
|
||||
|
||||
@override
|
||||
String toString() => 'RemoteAlbumNotFoundException: $albumId';
|
||||
}
|
||||
|
||||
extension on AlbumResponseDto {
|
||||
RemoteAlbum toRemoteAlbum(final UserDto user) {
|
||||
return RemoteAlbum(
|
||||
|
||||
Reference in New Issue
Block a user