feat(mobile): asset face v3 (#31615)

* feat(mobile): asset face v3

* fix: manually (cascade) delete asset faces
This commit is contained in:
Jason Rasmussen
2026-09-16 16:27:02 -04:00
committed by GitHub
parent 54e7ffa4e6
commit efbbd32e55
7 changed files with 3620 additions and 10 deletions
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -159,7 +159,7 @@ class Drift extends $Drift {
}
@override
int get schemaVersion => 32;
int get schemaVersion => 33;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -360,6 +360,10 @@ class Drift extends $Drift {
from31To32: (m, v32) async {
await m.addColumn(v32.localAssetEntity, v32.localAssetEntity.previousChecksum);
},
from32To33: (m, v33) async {
// Removed foreign key constraints on asset_face.assetId and asset_face.personId
await m.alterTable(TableMigration(v33.assetFaceEntity));
},
),
),
);
@@ -1,6 +1,4 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/data/db/main/table/people/person.dart';
import 'package:immich_mobile/data/db/main/table/remote/asset.dart';
import 'package:immich_mobile/data/db/util/defaults_mixin.dart';
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)')
@@ -15,9 +13,9 @@ class AssetFaceEntity extends Table with DriftDefaultsMixin {
TextColumn get id => text()();
TextColumn get assetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
TextColumn get assetId => text()();
TextColumn get personId => text().nullable().references(PersonEntity, #id, onDelete: KeyAction.setNull)();
TextColumn get personId => text().nullable()();
IntColumn get imageWidth => integer()();
@@ -9,6 +9,7 @@ enum ServerCapability {
syncV2(SemVer(major: 3, minor: 0, patch: 0)),
syncAssetEditsV1(SemVer(major: 2, minor: 6, patch: 0)),
syncAssetFacesV2(SemVer(major: 2, minor: 6, patch: 0)),
syncAssetFacesV3(SemVer(major: 3, minor: 3, patch: 0)),
syncAssetOcrV1(SemVer(major: 3, minor: 0, patch: 0)),
syncAuthUsersV2(SemVer(major: 3, minor: 3, patch: 0)),
@@ -324,9 +324,8 @@ class SyncStreamService {
case SyncEntityType.assetFaceV1:
return _syncStreamRepository.updateAssetFacesV1(data.cast());
case SyncEntityType.assetFaceV2:
return _syncStreamRepository.updateAssetFacesV2(data.cast());
case SyncEntityType.assetFaceV3:
throw UnimplementedError('SyncEntityType.assetFaceV3 is not implemented yet');
return _syncStreamRepository.updateAssetFacesV3(data.cast());
case SyncEntityType.assetFaceDeleteV1:
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
case SyncEntityType.assetOcrV1:
@@ -63,7 +63,11 @@ class SyncApiRepository {
SyncRequestType.partnerStacksV1,
SyncRequestType.userMetadataV1,
SyncRequestType.peopleV1,
serverVersion.supports(.syncAssetFacesV2) ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1,
serverVersion.supports(.syncAssetFacesV3)
? SyncRequestType.assetFacesV3
: serverVersion.supports(.syncAssetFacesV2)
? SyncRequestType.assetFacesV2
: SyncRequestType.assetFacesV1,
if (serverVersion.supports(.syncAssetOcrV1)) SyncRequestType.assetOcrV1,
],
).toJson(),
@@ -195,6 +199,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson,
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
SyncEntityType.assetFaceV2: SyncAssetFaceV3.fromJson,
SyncEntityType.assetFaceV3: SyncAssetFaceV3.fromJson,
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
SyncEntityType.assetOcrV1: SyncAssetOcrV1.fromJson,
SyncEntityType.assetOcrDeleteV1: SyncAssetOcrDeleteV1.fromJson,
@@ -218,6 +218,8 @@ class SyncStreamRepository extends DatabaseAccessor<Drift> with $SyncStreamRepos
try {
await _db.batch((batch) {
for (final asset in data) {
// cannot use FK here, so manually cascade
batch.deleteWhere(_db.assetFaceEntity, (row) => row.assetId.equals(asset.assetId));
batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(asset.assetId));
}
});
@@ -840,7 +842,7 @@ class SyncStreamRepository extends DatabaseAccessor<Drift> with $SyncStreamRepos
}
}
Future<void> updateAssetFacesV2(Iterable<SyncAssetFaceV3> data) async {
Future<void> updateAssetFacesV3(Iterable<SyncAssetFaceV3> data) async {
try {
await _db.batch((batch) {
for (final assetFace in data) {
@@ -866,7 +868,7 @@ class SyncStreamRepository extends DatabaseAccessor<Drift> with $SyncStreamRepos
}
});
} catch (error, stack) {
_logger.severe('Error: updateAssetFacesV2', error, stack);
_logger.severe('Error: updateAssetFacesV3', error, stack);
rethrow;
}
}