From a2a5b7e75865e5a0e660c4384f24d14e68a4edb3 Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Thu, 4 Dec 2025 02:09:52 +0530 Subject: [PATCH] more fixes --- .../domain/models/asset/base_asset.model.dart | 1 + .../models/asset/local_asset.model.dart | 9 ++- .../lib/domain/utils/migrate_cloud_ids.dart | 64 +++++++------------ mobile/lib/extensions/drift_extensions.dart | 17 +++++ mobile/lib/extensions/num_extensions.dart | 8 +++ .../entities/merged_asset.drift | 10 ++- .../entities/merged_asset.drift.dart | 11 +++- .../repositories/local_asset.repository.dart | 16 ++++- .../repositories/timeline.repository.dart | 3 + 9 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 mobile/lib/extensions/drift_extensions.dart create mode 100644 mobile/lib/extensions/num_extensions.dart diff --git a/mobile/lib/domain/models/asset/base_asset.model.dart b/mobile/lib/domain/models/asset/base_asset.model.dart index 3d62e85c5b..80f435e1db 100644 --- a/mobile/lib/domain/models/asset/base_asset.model.dart +++ b/mobile/lib/domain/models/asset/base_asset.model.dart @@ -1,4 +1,5 @@ import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/extensions/num_extensions.dart'; part 'local_asset.model.dart'; part 'remote_asset.model.dart'; diff --git a/mobile/lib/domain/models/asset/local_asset.model.dart b/mobile/lib/domain/models/asset/local_asset.model.dart index 4857e3adbd..6e59d483b4 100644 --- a/mobile/lib/domain/models/asset/local_asset.model.dart +++ b/mobile/lib/domain/models/asset/local_asset.model.dart @@ -42,8 +42,13 @@ class LocalAsset extends BaseAsset { @override String get heroTag => '${id}_${remoteId ?? checksum}'; - String get eTag => - "${createdAt.millisecondsSinceEpoch ~/ 1000}$kUploadETagDelimiter${(adjustmentTime?.millisecondsSinceEpoch ?? 0) ~/ 1000}$kUploadETagDelimiter${latitude ?? 0}$kUploadETagDelimiter${longitude ?? 0}"; + String get eTag { + final createdAt = this.createdAt.millisecondsSinceEpoch ~/ 1000; + final adjustmentTime = this.adjustmentTime?.millisecondsSinceEpoch ?? 0; + final latitude = this.latitude?.truncateTo(2).toStringAsFixed(2) ?? "0.00"; + final longitude = this.longitude?.truncateTo(2).toStringAsFixed(2) ?? "0.00"; + return "$createdAt$kUploadETagDelimiter$adjustmentTime$kUploadETagDelimiter$latitude$kUploadETagDelimiter$longitude"; + } bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0; diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index 0419b7a3a5..1e63bfb229 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -1,7 +1,8 @@ import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; @@ -33,12 +34,12 @@ Future syncCloudIds(ProviderContainer ref) async { for (final mapping in mappingsToUpdate) { final mobileMeta = AssetMetadataUpsertItemDto( key: AssetMetadataKey.mobileApp, - value: RemoteAssetMobileAppMetadata(cloudId: mapping.cloudId, eTag: mapping.eTag), + value: RemoteAssetMobileAppMetadata(cloudId: mapping.localAsset.cloudId, eTag: mapping.localAsset.eTag), ); try { - await assetApi.updateAssetMetadata(mapping.assetId, AssetMetadataUpsertDto(items: [mobileMeta])); + await assetApi.updateAssetMetadata(mapping.remoteAssetId, AssetMetadataUpsertDto(items: [mobileMeta])); } catch (error, stack) { - Logger('migrateCloudIds').warning('Failed to update metadata for asset ${mapping.assetId}', error, stack); + Logger('migrateCloudIds').warning('Failed to update metadata for asset ${mapping.remoteAssetId}', error, stack); } } } @@ -52,47 +53,30 @@ Future _populateCloudIds(Drift drift) async { await DriftLocalAlbumRepository(drift).updateCloudMapping(cloudMapping); } -typedef _CloudIdMapping = ({String assetId, String cloudId, String eTag}); +typedef _CloudIdMapping = ({String remoteAssetId, LocalAsset localAsset}); Future> _fetchCloudIdMappings(Drift drift, String userId) async { final query = - drift.remoteAssetEntity.selectOnly().join([ - leftOuterJoin( - drift.localAssetEntity, - drift.localAssetEntity.checksum.equalsExp(drift.remoteAssetEntity.checksum), - useColumns: false, - ), - leftOuterJoin( - drift.remoteAssetCloudIdEntity, - drift.localAssetEntity.iCloudId.equalsExp(drift.remoteAssetCloudIdEntity.cloudId), - useColumns: false, - ), - ]) - ..addColumns([ - drift.remoteAssetEntity.id, - drift.localAssetEntity.iCloudId, - drift.localAssetEntity.createdAt, - drift.localAssetEntity.adjustmentTime, - drift.localAssetEntity.latitude, - drift.localAssetEntity.longitude, - ]) - ..where( - drift.localAssetEntity.id.isNotNull() & - drift.localAssetEntity.iCloudId.isNotNull() & - drift.remoteAssetEntity.ownerId.equals(userId) & - drift.remoteAssetCloudIdEntity.cloudId.isNull(), - ); + drift.remoteAssetEntity.select().join([ + leftOuterJoin( + drift.localAssetEntity, + drift.localAssetEntity.checksum.equalsExp(drift.remoteAssetEntity.checksum), + ), + leftOuterJoin( + drift.remoteAssetCloudIdEntity, + drift.localAssetEntity.iCloudId.equalsExp(drift.remoteAssetCloudIdEntity.cloudId), + useColumns: false, + ), + ])..where( + drift.localAssetEntity.id.isNotNull() & + drift.localAssetEntity.iCloudId.isNotNull() & + drift.remoteAssetEntity.ownerId.equals(userId) & + drift.remoteAssetCloudIdEntity.cloudId.isNull(), + ); return query.map((row) { - final createdAt = row.read(drift.localAssetEntity.createdAt)!; - final adjustmentTime = row.read(drift.localAssetEntity.adjustmentTime); - final latitude = row.read(drift.localAssetEntity.latitude); - final longitude = row.read(drift.localAssetEntity.longitude); - final eTag = - "${createdAt.millisecondsSinceEpoch ~/ 1000}$kUploadETagDelimiter${(adjustmentTime?.millisecondsSinceEpoch ?? 0) ~/ 1000}$kUploadETagDelimiter${latitude ?? 0}$kUploadETagDelimiter${longitude ?? 0}"; return ( - assetId: row.read(drift.remoteAssetEntity.id)!, - cloudId: row.read(drift.localAssetEntity.iCloudId)!, - eTag: eTag, + remoteAssetId: row.read(drift.remoteAssetEntity.id)!, + localAsset: row.readTable(drift.localAssetEntity).toDto(), ); }).get(); } diff --git a/mobile/lib/extensions/drift_extensions.dart b/mobile/lib/extensions/drift_extensions.dart new file mode 100644 index 0000000000..edb819682c --- /dev/null +++ b/mobile/lib/extensions/drift_extensions.dart @@ -0,0 +1,17 @@ +import 'dart:math'; + +import 'package:drift/drift.dart'; +// ignore: invalid_use_of_internal_member, implementation_imports +import 'package:drift/src/runtime/query_builder/expressions/internal.dart'; + +extension DoubleTruncateExpression on Expression { + Expression truncateTo(int fractionDigits) { + final mod = Constant(pow(10, fractionDigits).toDouble()); + return BaseInfixOperator( + BaseInfixOperator(this, '*', mod, precedence: Precedence.mulDivide).cast(DriftSqlType.int), + '/', + mod, + precedence: Precedence.mulDivide, + ); + } +} diff --git a/mobile/lib/extensions/num_extensions.dart b/mobile/lib/extensions/num_extensions.dart new file mode 100644 index 0000000000..621e96d7dc --- /dev/null +++ b/mobile/lib/extensions/num_extensions.dart @@ -0,0 +1,8 @@ +import 'dart:math' as math; + +extension DoubleTruncate on double { + double truncateTo(int fractionDigits) { + final mod = math.pow(10.0, fractionDigits); + return ((this * mod).truncate() / mod); + } +} diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift b/mobile/lib/infrastructure/entities/merged_asset.drift index bdf34cfa83..93d7f0c90d 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift +++ b/mobile/lib/infrastructure/entities/merged_asset.drift @@ -22,7 +22,10 @@ SELECT rae.live_photo_video_id, 0 as orientation, rae.stack_id, - NULL as i_cloud_id + NULL as i_cloud_id, + NULL as latitude, + NULL as longitude, + NULL as adjustmentTime FROM remote_asset_entity rae LEFT JOIN @@ -55,7 +58,10 @@ SELECT NULL as live_photo_video_id, lae.orientation, NULL as stack_id, - lae.i_cloud_id + lae.i_cloud_id, + lae.latitude, + lae.longitude, + lae.adjustment_time FROM local_asset_entity lae WHERE NOT EXISTS ( diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift.dart b/mobile/lib/infrastructure/entities/merged_asset.drift.dart index 1d4eba406a..169004b45d 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift.dart +++ b/mobile/lib/infrastructure/entities/merged_asset.drift.dart @@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor { ); $arrayStartIndex += generatedlimit.amountOfVariables; return customSelect( - 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', + 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', variables: [ for (var $ in userIds) i0.Variable($), ...generatedlimit.introducedVariables, @@ -63,6 +63,9 @@ class MergedAssetDrift extends i1.ModularAccessor { orientation: row.read('orientation'), stackId: row.readNullable('stack_id'), iCloudId: row.readNullable('i_cloud_id'), + latitude: row.readNullable('latitude'), + longitude: row.readNullable('longitude'), + adjustmentTime: row.readNullable('adjustmentTime'), ), ); } @@ -131,6 +134,9 @@ class MergedAssetResult { final int orientation; final String? stackId; final String? iCloudId; + final double? latitude; + final double? longitude; + final DateTime? adjustmentTime; MergedAssetResult({ this.remoteId, this.localId, @@ -149,6 +155,9 @@ class MergedAssetResult { required this.orientation, this.stackId, this.iCloudId, + this.latitude, + this.longitude, + this.adjustmentTime, }); } diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index 7e21f3ba39..af7abe7a95 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -1,8 +1,11 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/drift_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; @@ -133,10 +136,17 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository { } Future> getHashMappingFromCloudId() async { - final createdAt = coalesce([_db.localAssetEntity.createdAt.strftime('%s'), const Constant('0')]); + final createdAt = _db.localAssetEntity.createdAt.strftime('%s'); final adjustmentTime = coalesce([_db.localAssetEntity.adjustmentTime.strftime('%s'), const Constant('0')]); - final latitude = coalesce([_db.localAssetEntity.latitude.cast(DriftSqlType.string), const Constant('0')]); - final longitude = coalesce([_db.localAssetEntity.longitude.cast(DriftSqlType.string), const Constant('0')]); + final latitude = coalesce([ + _db.localAssetEntity.latitude, + const Constant(0.0), + ]).truncateTo(2).cast(DriftSqlType.string); + final longitude = coalesce([ + _db.localAssetEntity.longitude, + const Constant(0.0), + ]).truncateTo(2).cast(DriftSqlType.string); + final delimiter = const Constant(kUploadETagDelimiter); final eTag = createdAt + delimiter + adjustmentTime + delimiter + latitude + delimiter + longitude; diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index c817ef3e34..d35363c6ba 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -85,6 +85,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository { durationInSeconds: row.durationInSeconds, orientation: row.orientation, cloudId: row.iCloudId, + latitude: row.latitude, + longitude: row.longitude, + adjustmentTime: row.adjustmentTime, ), ) .get();