diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index 0ed184a02d..6ff78823c2 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -21,6 +21,7 @@ Future syncCloudIds(ProviderContainer ref) async { if (!CurrentPlatform.isIOS) { return; } + final logger = Logger('migrateCloudIds'); final db = ref.read(driftProvider); // Populate cloud IDs for local assets that don't have one yet @@ -29,9 +30,7 @@ Future syncCloudIds(ProviderContainer ref) async { final serverInfo = await ref.read(serverInfoProvider.notifier).getServerInfo(); final canUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 4); if (!canUpdateMetadata) { - Logger( - 'migrateCloudIds', - ).fine('Server version does not support asset metadata updates. Skipping cloudId migration.'); + logger.fine('Server version does not support asset metadata updates. Skipping cloudId migration.'); return; } final canBulkUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 5); @@ -40,25 +39,35 @@ Future syncCloudIds(ProviderContainer ref) async { try { await ref.read(syncStreamServiceProvider).sync(); } catch (e, s) { - Logger('migrateCloudIds').fine('Failed to complete remote sync before cloudId migration.', e, s); + logger.fine('Failed to complete remote sync before cloudId migration.', e, s); return; } // Fetch the mapping for backed up assets that have a cloud ID locally but do not have a cloud ID on the server final currentUser = ref.read(currentUserProvider); if (currentUser == null) { - Logger('migrateCloudIds').warning('Current user is null. Aborting cloudId migration.'); + logger.warning('Current user is null. Aborting cloudId migration.'); return; } final mappingsToUpdate = await _fetchCloudIdMappings(db, currentUser.id); + // Deduplicate mappings as a single remote asset ID can match multiple local assets + final seenRemoteAssetIds = {}; + final uniqueMapping = mappingsToUpdate.where((mapping) { + if (!seenRemoteAssetIds.add(mapping.remoteAssetId)) { + logger.fine('Duplicate remote asset ID found: ${mapping.remoteAssetId}. Skipping duplicate entry.'); + return false; + } + return true; + }).toList(); + final assetApi = ref.read(apiServiceProvider).assetsApi; if (canBulkUpdateMetadata) { - await _bulkUpdateCloudIds(assetApi, mappingsToUpdate); + await _bulkUpdateCloudIds(assetApi, uniqueMapping); return; } - await _sequentialUpdateCloudIds(assetApi, mappingsToUpdate); + await _sequentialUpdateCloudIds(assetApi, uniqueMapping); } Future _sequentialUpdateCloudIds(AssetsApi assetsApi, List<_CloudIdMapping> mappings) async { @@ -152,10 +161,10 @@ Future> _fetchCloudIdMappings(Drift drift, String userId) // Skip locked assets as we cannot update them without unlocking first drift.remoteAssetEntity.visibility.isNotValue(AssetVisibility.locked.index) & (drift.remoteAssetCloudIdEntity.cloudId.isNull() | - ((drift.remoteAssetCloudIdEntity.adjustmentTime.isNotExp(drift.localAssetEntity.adjustmentTime)) & - (drift.remoteAssetCloudIdEntity.latitude.isNotExp(drift.localAssetEntity.latitude)) & - (drift.remoteAssetCloudIdEntity.longitude.isNotExp(drift.localAssetEntity.longitude)) & - (drift.remoteAssetCloudIdEntity.createdAt.isNotExp(drift.localAssetEntity.createdAt)))), + drift.remoteAssetCloudIdEntity.adjustmentTime.isNotExp(drift.localAssetEntity.adjustmentTime) | + drift.remoteAssetCloudIdEntity.latitude.isNotExp(drift.localAssetEntity.latitude) | + drift.remoteAssetCloudIdEntity.longitude.isNotExp(drift.localAssetEntity.longitude) | + drift.remoteAssetCloudIdEntity.createdAt.isNotExp(drift.localAssetEntity.createdAt)), ); return query.map((row) { return (