diff --git a/mobile/lib/domain/models/asset/asset_metadata.model.dart b/mobile/lib/domain/models/asset/asset_metadata.model.dart index ecdd3c3024..65fa24364d 100644 --- a/mobile/lib/domain/models/asset/asset_metadata.model.dart +++ b/mobile/lib/domain/models/asset/asset_metadata.model.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - enum RemoteAssetMetadataKey { mobileApp("mobile-app"); @@ -8,25 +6,30 @@ enum RemoteAssetMetadataKey { const RemoteAssetMetadataKey(this.key); } +abstract class RemoteAssetMetadataValue { + const RemoteAssetMetadataValue(); + + Map toJson(); +} + class RemoteAssetMetadataItem { final RemoteAssetMetadataKey key; - final Object value; + final RemoteAssetMetadataValue value; const RemoteAssetMetadataItem({required this.key, required this.value}); - Map toMap() { + Map toJson() { return {'key': key.key, 'value': value}; } - - String toJson() => json.encode(toMap()); } -class RemoteAssetMobileAppMetadata { +class RemoteAssetMobileAppMetadata extends RemoteAssetMetadataValue { final String? cloudId; const RemoteAssetMobileAppMetadata({this.cloudId}); - Map toMap() { + @override + Map toJson() { final map = {}; if (cloudId != null) { map["iCloudId"] = cloudId; @@ -34,6 +37,4 @@ class RemoteAssetMobileAppMetadata { return map; } - - String toJson() => json.encode(toMap()); } diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index a75f47b220..188ee9afbd 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -7,6 +7,8 @@ import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:logging/logging.dart'; // ignore: import_rule_openapi import 'package:openapi/api.dart'; @@ -19,14 +21,24 @@ Future migrateCloudIds(ProviderContainer ref) async { await ref.read(syncStreamServiceProvider).sync(); // Fetch the mapping for backed up assets that have a cloud ID locally but do not have a cloud ID on the server - final mappingsToUpdate = await _fetchCloudIdMappings(db); + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + Logger('migrateCloudIds').warning('Current user is null. Aborting cloudId migration.'); + return; + } + + final mappingsToUpdate = await _fetchCloudIdMappings(db, currentUser.id); final assetApi = ref.read(apiServiceProvider).assetsApi; for (final mapping in mappingsToUpdate) { final mobileMeta = AssetMetadataUpsertItemDto( key: AssetMetadataKey.mobileApp, - value: RemoteAssetMobileAppMetadata(cloudId: mapping.cloudId).toMap(), + value: RemoteAssetMobileAppMetadata(cloudId: mapping.cloudId), ); - await assetApi.updateAssetMetadata(mapping.assetId, AssetMetadataUpsertDto(items: [mobileMeta])); + try { + await assetApi.updateAssetMetadata(mapping.assetId, AssetMetadataUpsertDto(items: [mobileMeta])); + } catch (error, stack) { + Logger('migrateCloudIds').warning('Failed to update metadata for asset ${mapping.assetId}', error, stack); + } } } @@ -41,7 +53,7 @@ Future _populateCloudIds(Drift drift) async { typedef _CloudIdMapping = ({String assetId, String cloudId}); -Future> _fetchCloudIdMappings(Drift drift) async { +Future> _fetchCloudIdMappings(Drift drift, String userId) async { final query = drift.remoteAssetEntity.selectOnly().join([ leftOuterJoin( @@ -58,7 +70,8 @@ Future> _fetchCloudIdMappings(Drift drift) async { ]) ..addColumns([drift.remoteAssetEntity.id, drift.localAssetEntity.cloudId]) ..where( - drift.localAssetEntity.id.isNotNull() & + drift.remoteAssetEntity.ownerId.equals(userId) & + drift.localAssetEntity.id.isNotNull() & drift.localAssetEntity.cloudId.isNotNull() & drift.remoteAssetMetadataEntity.cloudId.isNull(), ); diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.dart b/mobile/lib/infrastructure/entities/local_asset.entity.dart index 54ccce8277..9c9986e2f5 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.dart @@ -37,5 +37,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData { width: width, remoteId: null, orientation: orientation, + cloudId: cloudId, ); } diff --git a/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.dart b/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.dart index 5783de7668..a50ff94a1a 100644 --- a/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:drift/drift.dart'; import 'package:drift/extensions/json1.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; @@ -17,7 +15,7 @@ class RemoteAssetMetadataEntity extends Table with DriftDefaultsMixin { BlobColumn get value => blob().map(assetMetadataConverter)(); - TextColumn get cloudId => text().generatedAs(key.jsonExtract(r'$.iCloudId'), stored: true)(); + TextColumn get cloudId => text().generatedAs(value.jsonExtract(r'$.iCloudId'), stored: true).nullable()(); @override Set get primaryKey => {assetId, key}; @@ -25,5 +23,4 @@ class RemoteAssetMetadataEntity extends Table with DriftDefaultsMixin { final JsonTypeConverter2, Uint8List, Object?> assetMetadataConverter = TypeConverter.jsonb( fromJson: (json) => json as Map, - toJson: (value) => jsonEncode(value), ); diff --git a/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.drift.dart index 13c153b039..b605239b2d 100644 --- a/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/remote_asset_metadata.entity.drift.dart @@ -425,9 +425,9 @@ class $RemoteAssetMetadataEntityTable extends i3.RemoteAssetMetadataEntity late final i0.GeneratedColumn cloudId = i0.GeneratedColumn( 'cloud_id', aliasedName, - false, + true, generatedAs: i0.GeneratedAs( - i4.JsonExtensions(key).jsonExtract(r'$.iCloudId'), + i4.JsonbExtensions(value).jsonExtract(r'$.iCloudId'), true, ), type: i0.DriftSqlType.string, @@ -498,7 +498,7 @@ class $RemoteAssetMetadataEntityTable extends i3.RemoteAssetMetadataEntity cloudId: attachedDatabase.typeMapping.read( i0.DriftSqlType.string, data['${effectivePrefix}cloud_id'], - )!, + ), ); } @@ -520,12 +520,12 @@ class RemoteAssetMetadataEntityData extends i0.DataClass final String assetId; final String key; final Map value; - final String cloudId; + final String? cloudId; const RemoteAssetMetadataEntityData({ required this.assetId, required this.key, required this.value, - required this.cloudId, + this.cloudId, }); @override Map toColumns(bool nullToAbsent) { @@ -551,7 +551,7 @@ class RemoteAssetMetadataEntityData extends i0.DataClass value: i1.$RemoteAssetMetadataEntityTable.$convertervalue.fromJson( serializer.fromJson(json['value']), ), - cloudId: serializer.fromJson(json['cloudId']), + cloudId: serializer.fromJson(json['cloudId']), ); } @override @@ -563,7 +563,7 @@ class RemoteAssetMetadataEntityData extends i0.DataClass 'value': serializer.toJson( i1.$RemoteAssetMetadataEntityTable.$convertervalue.toJson(value), ), - 'cloudId': serializer.toJson(cloudId), + 'cloudId': serializer.toJson(cloudId), }; } @@ -571,12 +571,12 @@ class RemoteAssetMetadataEntityData extends i0.DataClass String? assetId, String? key, Map? value, - String? cloudId, + i0.Value cloudId = const i0.Value.absent(), }) => i1.RemoteAssetMetadataEntityData( assetId: assetId ?? this.assetId, key: key ?? this.key, value: value ?? this.value, - cloudId: cloudId ?? this.cloudId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, ); @override String toString() { diff --git a/mobile/lib/services/upload.service.dart b/mobile/lib/services/upload.service.dart index 6c3dd05269..0d39d1e604 100644 --- a/mobile/lib/services/upload.service.dart +++ b/mobile/lib/services/upload.service.dart @@ -368,7 +368,7 @@ class UploadService { 'metadata': jsonEncode([ RemoteAssetMetadataItem( key: RemoteAssetMetadataKey.mobileApp, - value: RemoteAssetMobileAppMetadata(cloudId: cloudId).toMap(), + value: RemoteAssetMobileAppMetadata(cloudId: cloudId), ), ]), if (fields != null) ...fields,