From ac44f6d1e0ad026c509e8bb71fddf4457defb6fd Mon Sep 17 00:00:00 2001 From: Daimolean <92239625+wuzihao051119@users.noreply.github.com> Date: Wed, 23 Jul 2025 01:17:52 +0800 Subject: [PATCH] feat(mobile): asset face sync (#20022) * feat(mobile): asset face sync * fix: lint --------- Co-authored-by: Alex --- .../drift_schemas/main/drift_schema_v4.json | Bin 0 -> 32634 bytes .../lib/domain/models/asset_face.model.dart | 98 ++++++++++++++++++ mobile/lib/domain/models/person.model.dart | 7 -- .../domain/services/sync_stream.service.dart | 4 + .../entities/asset_face.entity.dart | 34 ++++++ .../entities/asset_face.entity.drift.dart | Bin 0 -> 40358 bytes .../entities/person.entity.dart | 3 - .../entities/person.entity.drift.dart | Bin 36665 -> 34043 bytes .../repositories/asset_face.repository.dart | 33 ++++++ .../repositories/db.repository.dart | 8 +- .../repositories/db.repository.drift.dart | Bin 11793 -> 12714 bytes .../repositories/db.repository.steps.dart | Bin 48480 -> 62056 bytes .../repositories/person.repository.dart | 1 - .../repositories/sync_api.repository.dart | 3 + .../repositories/sync_stream.repository.dart | 58 ++++++++++- .../pages/dev/feat_in_development.page.dart | 1 + .../pages/dev/media_stat.page.dart | 4 + .../infrastructure/asset_face.provider.dart | 7 ++ mobile/lib/repositories/auth.repository.dart | 3 +- .../services/sync_stream_service_test.dart | 4 + mobile/test/drift/main/generated/schema.dart | Bin 718 -> 802 bytes .../test/drift/main/generated/schema_v4.dart | Bin 0 -> 206970 bytes 22 files changed, 252 insertions(+), 16 deletions(-) create mode 100644 mobile/drift_schemas/main/drift_schema_v4.json create mode 100644 mobile/lib/domain/models/asset_face.model.dart create mode 100644 mobile/lib/infrastructure/entities/asset_face.entity.dart create mode 100644 mobile/lib/infrastructure/entities/asset_face.entity.drift.dart create mode 100644 mobile/lib/infrastructure/repositories/asset_face.repository.dart create mode 100644 mobile/lib/providers/infrastructure/asset_face.provider.dart create mode 100644 mobile/test/drift/main/generated/schema_v4.dart diff --git a/mobile/drift_schemas/main/drift_schema_v4.json b/mobile/drift_schemas/main/drift_schema_v4.json new file mode 100644 index 0000000000000000000000000000000000000000..82ef30adae0890ca19151b41a2dc3b982d7b2984 GIT binary patch literal 32634 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"}, + imageWidth: $imageWidth, + imageHeight: $imageHeight, + boundingBoxX1: $boundingBoxX1, + boundingBoxY1: $boundingBoxY1, + boundingBoxX2: $boundingBoxX2, + boundingBoxY2: $boundingBoxY2, + sourceType: $sourceType, +}'''; + } + + @override + bool operator ==(covariant AssetFace other) { + if (identical(this, other)) return true; + + return other.id == id && + other.assetId == assetId && + other.personId == personId && + other.imageWidth == imageWidth && + other.imageHeight == imageHeight && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY2 == boundingBoxY2 && + other.sourceType == sourceType; + } + + @override + int get hashCode { + return id.hashCode ^ + assetId.hashCode ^ + personId.hashCode ^ + imageWidth.hashCode ^ + imageHeight.hashCode ^ + boundingBoxX1.hashCode ^ + boundingBoxY1.hashCode ^ + boundingBoxX2.hashCode ^ + boundingBoxY2.hashCode ^ + sourceType.hashCode; + } +} diff --git a/mobile/lib/domain/models/person.model.dart b/mobile/lib/domain/models/person.model.dart index d9eee9ae0..2a6b31cb1 100644 --- a/mobile/lib/domain/models/person.model.dart +++ b/mobile/lib/domain/models/person.model.dart @@ -103,7 +103,6 @@ class Person { final String ownerId; final String name; final String? faceAssetId; - final String thumbnailPath; final bool isFavorite; final bool isHidden; final String? color; @@ -116,7 +115,6 @@ class Person { required this.ownerId, required this.name, this.faceAssetId, - required this.thumbnailPath, required this.isFavorite, required this.isHidden, required this.color, @@ -130,7 +128,6 @@ class Person { String? ownerId, String? name, String? faceAssetId, - String? thumbnailPath, bool? isFavorite, bool? isHidden, String? color, @@ -143,7 +140,6 @@ class Person { ownerId: ownerId ?? this.ownerId, name: name ?? this.name, faceAssetId: faceAssetId ?? this.faceAssetId, - thumbnailPath: thumbnailPath ?? this.thumbnailPath, isFavorite: isFavorite ?? this.isFavorite, isHidden: isHidden ?? this.isHidden, color: color ?? this.color, @@ -160,7 +156,6 @@ class Person { ownerId: $ownerId, name: $name, faceAssetId: ${faceAssetId ?? ""}, - thumbnailPath: $thumbnailPath, isFavorite: $isFavorite, isHidden: $isHidden, color: ${color ?? ""}, @@ -178,7 +173,6 @@ class Person { other.ownerId == ownerId && other.name == name && other.faceAssetId == faceAssetId && - other.thumbnailPath == thumbnailPath && other.isFavorite == isFavorite && other.isHidden == isHidden && other.color == color && @@ -193,7 +187,6 @@ class Person { ownerId.hashCode ^ name.hashCode ^ faceAssetId.hashCode ^ - thumbnailPath.hashCode ^ isFavorite.hashCode ^ isHidden.hashCode ^ color.hashCode ^ diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 9a7d91ced..ca8295fc8 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -244,6 +244,10 @@ class SyncStreamService { return _syncStreamRepository.updatePeopleV1(data.cast()); case SyncEntityType.personDeleteV1: return _syncStreamRepository.deletePeopleV1(data.cast()); + case SyncEntityType.assetFaceV1: + return _syncStreamRepository.updateAssetFacesV1(data.cast()); + case SyncEntityType.assetFaceDeleteV1: + return _syncStreamRepository.deleteAssetFacesV1(data.cast()); default: _logger.warning("Unknown sync data type: $type"); } diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.dart b/mobile/lib/infrastructure/entities/asset_face.entity.dart new file mode 100644 index 000000000..c54e4e184 --- /dev/null +++ b/mobile/lib/infrastructure/entities/asset_face.entity.dart @@ -0,0 +1,34 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/infrastructure/entities/person.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; + +class AssetFaceEntity extends Table with DriftDefaultsMixin { + const AssetFaceEntity(); + + TextColumn get id => text()(); + + TextColumn get assetId => + text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)(); + + TextColumn get personId => text() + .nullable() + .references(PersonEntity, #id, onDelete: KeyAction.setNull)(); + + IntColumn get imageWidth => integer()(); + + IntColumn get imageHeight => integer()(); + + IntColumn get boundingBoxX1 => integer()(); + + IntColumn get boundingBoxY1 => integer()(); + + IntColumn get boundingBoxX2 => integer()(); + + IntColumn get boundingBoxY2 => integer()(); + + TextColumn get sourceType => text()(); + + @override + Set get primaryKey => {id}; +} diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart b/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart new file mode 100644 index 0000000000000000000000000000000000000000..140af60de10be1ee66b00ea42ad250c7cbd813d2 GIT binary patch literal 40358 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 text()(); - // TODO: foreign key refering to asset faces TextColumn get faceAssetId => text().nullable()(); - TextColumn get thumbnailPath => text()(); - BoolColumn get isFavorite => boolean()(); BoolColumn get isHidden => boolean()(); diff --git a/mobile/lib/infrastructure/entities/person.entity.drift.dart b/mobile/lib/infrastructure/entities/person.entity.drift.dart index f0ced63f0e9871b10d8a2d41f127d64cd0ffea3e..70639adc2f035f7f79608d3eb972f9adb72f833d 100644 GIT binary patch delta 256 zcmV+b0ssEFo&x)c0@n~0clYR1^<$2!PsDegdcxWqwK~Vc#Ayq>>MD}-=x-ACUa3y(*JG(R@U7j$Q-H)NJ6gJP>G3uC zY_N1zN-iPp)LU$}t(9>1x%U}`>8yyKF1Q&P5xQ86yL!9=-Bj!=HS4`o^lFb7L5>~7 zTYcvwwY=n9-6+g!VYj~;xe%%RiX^n zbD-qCd(vdX*}#f0#UQBG^h+dV^thV^mx(?goRKcmJfYo@Hk9GNQB>zwjgPH@V@qv zK|z!n>tuAbh71%HSG_N_H4)4`GRijJyJn&od>$#pr=uU(A-cm!}tqA CzC(8a diff --git a/mobile/lib/infrastructure/repositories/asset_face.repository.dart b/mobile/lib/infrastructure/repositories/asset_face.repository.dart new file mode 100644 index 000000000..a9ad753d8 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/asset_face.repository.dart @@ -0,0 +1,33 @@ +import 'package:drift/drift.dart'; +import 'package:immich_mobile/domain/models/asset_face.model.dart'; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; + +class DriftAssetFaceRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftAssetFaceRepository(this._db) : super(_db); + + Future> getAll() { + return _db.assetFaceEntity + .select() + .map((assetFace) => assetFace.toDto()) + .get(); + } +} + +extension on AssetFaceEntityData { + AssetFace toDto() { + return AssetFace( + id: id, + assetId: assetId, + personId: personId, + imageWidth: imageWidth, + imageHeight: imageHeight, + boundingBoxX1: boundingBoxX1, + boundingBoxY1: boundingBoxY1, + boundingBoxX2: boundingBoxX2, + boundingBoxY2: boundingBoxY2, + sourceType: sourceType, + ); + } +} diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 7562cf6ff..ced148f85 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -5,6 +5,7 @@ import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter/foundation.dart'; import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; @@ -56,6 +57,7 @@ class IsarDatabaseRepository implements IDatabaseRepository { MemoryAssetEntity, StackEntity, PersonEntity, + AssetFaceEntity, ], include: { 'package:immich_mobile/infrastructure/entities/merged_asset.drift', @@ -72,7 +74,7 @@ class Drift extends $Drift implements IDatabaseRepository { ); @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration => MigrationStrategy( @@ -94,6 +96,10 @@ class Drift extends $Drift implements IDatabaseRepository { // Removed foreign key constraint on stack.primaryAssetId await m.alterTable(TableMigration(v3.stackEntity)); }, + from3To4: (m, v4) async { + await m.alterTable(TableMigration(v4.personEntity)); + await m.create(v4.assetFaceEntity); + }, ), ); diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index 0f822e57ebd3b542446cb3fe689d455cf03f873d..7b722dfff60a4fb37195a794f190d15f8b9e8805 100644 GIT binary patch delta 332 zcmbOjvnqLmAsbg>adB!%d|G01>g0ngvXg_^geM%OtD=HAMko{$@S_UeU?-rB5L{yHRm6TS013aem(9 zh2mwXGIEnQN$YNwR{g>u2(k<49E1-hFVvOMz;L=9vfGgrYfDUaP~#Lq(*X2DEf)Z7 CCTgbu delta 66 zcmV-I0KNaJW0732F$R;71|pMK1{0IU1|k7Bv*`vn1_Nm^Hj{)6ECDx@u?;T)Hk08E Y6#+K0_YK_wv+WQE6|*cT^auid3g5XEm;e9( diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index a0703c3714129ab682cdedada113f870901b633a..d8c35707ed0edce515340059806f01c3a18863e9 100644 GIT binary patch delta 909 zcmZ{j-D}fO7{)nS&1zjIb)@2oYp`fr@MGy$Ql)6SwuoSH*x?i<&1(7qYtuFzv_t&3 z5J5y%UKlvNXzx`D!C|`z1@Ttqh29Ch@WS2r2MB^E?b5+$b8+&V^Zw3rI8WYm{}R7{ zjWy?5;98$T9zZb)t{@Xux%G3%qdZ2t=m<>5o9T@#hJ(GLjLUVeqT{}B9SvU~4w z<;I#F8I#NSyutZ+5AuSw`~?!vm&t-ylBGPLI~Gkg9)`(mPDqQlpc_2dmpuFn&h_P0 z2!>{~RX!9pE=eo-``LUtBvlp}V9uO4s;%%#QiZQDAS`%+UNB7TgSUr()nVYGYlx$3 znBx$z+6U{=iNmtAQc8;PY5^MJG})iQn^a7V{acvR?w+_DfIkoJY15tbafq+d&?y-S zmW87K0=|b%@38ms;elZ(=tLZlV{Ph(3(TtJNv)g_v*}D3nh{O~2-dF|m>l+l5or)N zI|{<_>_NC2YOL=-$}X)+aITRS8iZRZs)wC`zSmCkezKPi5AVHx=sq<-xYdC|%XiE} z%gKzG6P$FaB<0+3$)gbM&xlM4vPipuZ(DKmKWZm*Y6)I#+3@f3X=@sd*bLvdpUuz% QuZsh9)AYn459fkHy#N3J delta 32 qcmV+*0N?-UBXc1cg diff --git a/mobile/lib/infrastructure/repositories/person.repository.dart b/mobile/lib/infrastructure/repositories/person.repository.dart index 859765d63..fa336c548 100644 --- a/mobile/lib/infrastructure/repositories/person.repository.dart +++ b/mobile/lib/infrastructure/repositories/person.repository.dart @@ -26,7 +26,6 @@ extension on PersonEntityData { ownerId: ownerId, name: name, faceAssetId: faceAssetId, - thumbnailPath: thumbnailPath, isFavorite: isFavorite, isHidden: isHidden, color: color, diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 11d58663e..e8be84eff 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -58,6 +58,7 @@ class SyncApiRepository { SyncRequestType.partnerStacksV1, SyncRequestType.userMetadataV1, SyncRequestType.peopleV1, + SyncRequestType.assetFacesV1, ], ).toJson(), ); @@ -176,6 +177,8 @@ const _kResponseMap = { SyncEntityType.userMetadataDeleteV1: SyncUserMetadataDeleteV1.fromJson, SyncEntityType.personV1: SyncPersonV1.fromJson, SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson, + SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson, + SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson, }; class _SyncAckV1 { diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index e141c387b..1cca90356 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/user_metadata.model.dart'; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'; @@ -546,11 +547,62 @@ class SyncStreamRepository extends DriftDatabaseRepository { Iterable data, ) async { try { - await _db.personEntity.deleteWhere( - (row) => row.id.isIn(data.map((e) => e.personId)), - ); + await _db.batch((batch) { + for (final person in data) { + batch.deleteWhere( + _db.personEntity, + (row) => row.id.equals(person.personId), + ); + } + }); } catch (error, stack) { _logger.severe('Error: deletePeopleV1', error, stack); + rethrow; + } + } + + Future updateAssetFacesV1(Iterable data) async { + try { + await _db.batch((batch) { + for (final assetFace in data) { + final companion = AssetFaceEntityCompanion( + assetId: Value(assetFace.assetId), + personId: Value(assetFace.personId), + imageWidth: Value(assetFace.imageWidth), + imageHeight: Value(assetFace.imageHeight), + boundingBoxX1: Value(assetFace.boundingBoxX1), + boundingBoxY1: Value(assetFace.boundingBoxY1), + boundingBoxX2: Value(assetFace.boundingBoxX2), + boundingBoxY2: Value(assetFace.boundingBoxY2), + sourceType: Value(assetFace.sourceType), + ); + + batch.insert( + _db.assetFaceEntity, + companion.copyWith(id: Value(assetFace.id)), + onConflict: DoUpdate((_) => companion), + ); + } + }); + } catch (error, stack) { + _logger.severe('Error: updateAssetFacesV1', error, stack); + rethrow; + } + } + + Future deleteAssetFacesV1(Iterable data) async { + try { + await _db.batch((batch) { + for (final assetFace in data) { + batch.deleteWhere( + _db.assetFaceEntity, + (row) => row.id.equals(assetFace.assetFaceId), + ); + } + }); + } catch (error, stack) { + _logger.severe('Error: deleteAssetFacesV1', error, stack); + rethrow; } } } diff --git a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart index 7ee151f94..2334fc522 100644 --- a/mobile/lib/presentation/pages/dev/feat_in_development.page.dart +++ b/mobile/lib/presentation/pages/dev/feat_in_development.page.dart @@ -121,6 +121,7 @@ final _features = [ await db.memoryAssetEntity.deleteAll(); await db.stackEntity.deleteAll(); await db.personEntity.deleteAll(); + await db.assetFaceEntity.deleteAll(); }, ), _Feature( diff --git a/mobile/lib/presentation/pages/dev/media_stat.page.dart b/mobile/lib/presentation/pages/dev/media_stat.page.dart index acd7b219b..d1803498b 100644 --- a/mobile/lib/presentation/pages/dev/media_stat.page.dart +++ b/mobile/lib/presentation/pages/dev/media_stat.page.dart @@ -170,6 +170,10 @@ final _remoteStats = [ name: 'People', load: (db) => db.managers.personEntity.count(), ), + _Stat( + name: 'AssetFaces', + load: (db) => db.managers.assetFaceEntity.count(), + ), ]; @RoutePage() diff --git a/mobile/lib/providers/infrastructure/asset_face.provider.dart b/mobile/lib/providers/infrastructure/asset_face.provider.dart new file mode 100644 index 000000000..386609ba9 --- /dev/null +++ b/mobile/lib/providers/infrastructure/asset_face.provider.dart @@ -0,0 +1,7 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/infrastructure/repositories/asset_face.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; + +final driftAssetFaceProvider = Provider( + (ref) => DriftAssetFaceRepository(ref.watch(driftProvider)), +); diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index 5cf357d5a..1c51cedf9 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -34,11 +34,12 @@ class AuthRepository extends DatabaseRepository { _drift.userMetadataEntity.deleteAll(), _drift.partnerEntity.deleteAll(), _drift.stackEntity.deleteAll(), - _drift.personEntity.deleteAll(), + _drift.assetFaceEntity.deleteAll(), ]); // Drift deletions - parent entities await Future.wait([ _drift.memoryEntity.deleteAll(), + _drift.personEntity.deleteAll(), _drift.remoteAlbumEntity.deleteAll(), _drift.remoteAssetEntity.deleteAll(), _drift.userEntity.deleteAll(), diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index deb19dfcf..f9d9c4fbe 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -109,6 +109,10 @@ void main() { .thenAnswer(successHandler); when(() => mockSyncStreamRepo.deletePeopleV1(any())) .thenAnswer(successHandler); + when(() => mockSyncStreamRepo.updateAssetFacesV1(any())) + .thenAnswer(successHandler); + when(() => mockSyncStreamRepo.deleteAssetFacesV1(any())) + .thenAnswer(successHandler); sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index 209e70d78870329052c366f705c4d02422e59e5a..22131b11bbfe0d3540b8a71275c7bd95a2f3257e 100644 GIT binary patch delta 94 zcmX@dx`=H9Kcj4BZb5!giGq4@az<)yVtkp2UP@w7iMm2!u|k>2W?n{jMz!R`;#36_ qD=q~nC`v6UEy@E*>$xPBBqjl69ZSMYCVMb7a_A_S#9DLJasdEnQyb9$ delta 22 dcmZ3)c8+xeKjUUSMpwqkOPNYpW39Prxd2aw20;J- diff --git a/mobile/test/drift/main/generated/schema_v4.dart b/mobile/test/drift/main/generated/schema_v4.dart new file mode 100644 index 0000000000000000000000000000000000000000..d02e2ff9c49e711967d38cc501323341faf2f879 GIT binary patch literal 206970 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 literal 0 HcmV?d00001