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:
Santo Shakil
2026-05-09 00:33:04 +06:00
parent 832ed4d015
commit 1120caca10
2 changed files with 51 additions and 25 deletions
@@ -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(