feat(mobile): asset face sync (#20022)

* feat(mobile): asset face sync

* fix: lint

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Daimolean 2025-07-23 01:17:52 +08:00 committed by GitHub
parent ab61bcfcc8
commit ac44f6d1e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 7270 additions and 81 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,98 @@
// Model for an asset face stored in the server
class AssetFace {
final String id;
final String assetId;
final String? personId;
final int imageWidth;
final int imageHeight;
final int boundingBoxX1;
final int boundingBoxY1;
final int boundingBoxX2;
final int boundingBoxY2;
final String sourceType;
const AssetFace({
required this.id,
required this.assetId,
this.personId,
required this.imageWidth,
required this.imageHeight,
required this.boundingBoxX1,
required this.boundingBoxY1,
required this.boundingBoxX2,
required this.boundingBoxY2,
required this.sourceType,
});
AssetFace copyWith({
String? id,
String? assetId,
String? personId,
int? imageWidth,
int? imageHeight,
int? boundingBoxX1,
int? boundingBoxY1,
int? boundingBoxX2,
int? boundingBoxY2,
String? sourceType,
}) {
return AssetFace(
id: id ?? this.id,
assetId: assetId ?? this.assetId,
personId: personId ?? this.personId,
imageWidth: imageWidth ?? this.imageWidth,
imageHeight: imageHeight ?? this.imageHeight,
boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1,
boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1,
boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2,
boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2,
sourceType: sourceType ?? this.sourceType,
);
}
@override
String toString() {
return '''AssetFace {
id: $id,
assetId: $assetId,
personId: ${personId ?? "<NA>"},
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;
}
}

View File

@ -103,7 +103,6 @@ class Person {
final String ownerId; final String ownerId;
final String name; final String name;
final String? faceAssetId; final String? faceAssetId;
final String thumbnailPath;
final bool isFavorite; final bool isFavorite;
final bool isHidden; final bool isHidden;
final String? color; final String? color;
@ -116,7 +115,6 @@ class Person {
required this.ownerId, required this.ownerId,
required this.name, required this.name,
this.faceAssetId, this.faceAssetId,
required this.thumbnailPath,
required this.isFavorite, required this.isFavorite,
required this.isHidden, required this.isHidden,
required this.color, required this.color,
@ -130,7 +128,6 @@ class Person {
String? ownerId, String? ownerId,
String? name, String? name,
String? faceAssetId, String? faceAssetId,
String? thumbnailPath,
bool? isFavorite, bool? isFavorite,
bool? isHidden, bool? isHidden,
String? color, String? color,
@ -143,7 +140,6 @@ class Person {
ownerId: ownerId ?? this.ownerId, ownerId: ownerId ?? this.ownerId,
name: name ?? this.name, name: name ?? this.name,
faceAssetId: faceAssetId ?? this.faceAssetId, faceAssetId: faceAssetId ?? this.faceAssetId,
thumbnailPath: thumbnailPath ?? this.thumbnailPath,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
isHidden: isHidden ?? this.isHidden, isHidden: isHidden ?? this.isHidden,
color: color ?? this.color, color: color ?? this.color,
@ -160,7 +156,6 @@ class Person {
ownerId: $ownerId, ownerId: $ownerId,
name: $name, name: $name,
faceAssetId: ${faceAssetId ?? "<NA>"}, faceAssetId: ${faceAssetId ?? "<NA>"},
thumbnailPath: $thumbnailPath,
isFavorite: $isFavorite, isFavorite: $isFavorite,
isHidden: $isHidden, isHidden: $isHidden,
color: ${color ?? "<NA>"}, color: ${color ?? "<NA>"},
@ -178,7 +173,6 @@ class Person {
other.ownerId == ownerId && other.ownerId == ownerId &&
other.name == name && other.name == name &&
other.faceAssetId == faceAssetId && other.faceAssetId == faceAssetId &&
other.thumbnailPath == thumbnailPath &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isHidden == isHidden && other.isHidden == isHidden &&
other.color == color && other.color == color &&
@ -193,7 +187,6 @@ class Person {
ownerId.hashCode ^ ownerId.hashCode ^
name.hashCode ^ name.hashCode ^
faceAssetId.hashCode ^ faceAssetId.hashCode ^
thumbnailPath.hashCode ^
isFavorite.hashCode ^ isFavorite.hashCode ^
isHidden.hashCode ^ isHidden.hashCode ^
color.hashCode ^ color.hashCode ^

View File

@ -244,6 +244,10 @@ class SyncStreamService {
return _syncStreamRepository.updatePeopleV1(data.cast()); return _syncStreamRepository.updatePeopleV1(data.cast());
case SyncEntityType.personDeleteV1: case SyncEntityType.personDeleteV1:
return _syncStreamRepository.deletePeopleV1(data.cast()); return _syncStreamRepository.deletePeopleV1(data.cast());
case SyncEntityType.assetFaceV1:
return _syncStreamRepository.updateAssetFacesV1(data.cast());
case SyncEntityType.assetFaceDeleteV1:
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
default: default:
_logger.warning("Unknown sync data type: $type"); _logger.warning("Unknown sync data type: $type");
} }

View File

@ -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<Column> get primaryKey => {id};
}

File diff suppressed because it is too large Load Diff

View File

@ -16,11 +16,8 @@ class PersonEntity extends Table with DriftDefaultsMixin {
TextColumn get name => text()(); TextColumn get name => text()();
// TODO: foreign key refering to asset faces
TextColumn get faceAssetId => text().nullable()(); TextColumn get faceAssetId => text().nullable()();
TextColumn get thumbnailPath => text()();
BoolColumn get isFavorite => boolean()(); BoolColumn get isFavorite => boolean()();
BoolColumn get isHidden => boolean()(); BoolColumn get isHidden => boolean()();

View File

@ -17,7 +17,6 @@ typedef $$PersonEntityTableCreateCompanionBuilder = i1.PersonEntityCompanion
required String ownerId, required String ownerId,
required String name, required String name,
i0.Value<String?> faceAssetId, i0.Value<String?> faceAssetId,
required String thumbnailPath,
required bool isFavorite, required bool isFavorite,
required bool isHidden, required bool isHidden,
i0.Value<String?> color, i0.Value<String?> color,
@ -31,7 +30,6 @@ typedef $$PersonEntityTableUpdateCompanionBuilder = i1.PersonEntityCompanion
i0.Value<String> ownerId, i0.Value<String> ownerId,
i0.Value<String> name, i0.Value<String> name,
i0.Value<String?> faceAssetId, i0.Value<String?> faceAssetId,
i0.Value<String> thumbnailPath,
i0.Value<bool> isFavorite, i0.Value<bool> isFavorite,
i0.Value<bool> isHidden, i0.Value<bool> isHidden,
i0.Value<String?> color, i0.Value<String?> color,
@ -94,10 +92,6 @@ class $$PersonEntityTableFilterComposer
column: $table.faceAssetId, column: $table.faceAssetId,
builder: (column) => i0.ColumnFilters(column)); builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get thumbnailPath => $composableBuilder(
column: $table.thumbnailPath,
builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<bool> get isFavorite => $composableBuilder( i0.ColumnFilters<bool> get isFavorite => $composableBuilder(
column: $table.isFavorite, builder: (column) => i0.ColumnFilters(column)); column: $table.isFavorite, builder: (column) => i0.ColumnFilters(column));
@ -160,10 +154,6 @@ class $$PersonEntityTableOrderingComposer
column: $table.faceAssetId, column: $table.faceAssetId,
builder: (column) => i0.ColumnOrderings(column)); builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get thumbnailPath => $composableBuilder(
column: $table.thumbnailPath,
builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<bool> get isFavorite => $composableBuilder( i0.ColumnOrderings<bool> get isFavorite => $composableBuilder(
column: $table.isFavorite, column: $table.isFavorite,
builder: (column) => i0.ColumnOrderings(column)); builder: (column) => i0.ColumnOrderings(column));
@ -225,9 +215,6 @@ class $$PersonEntityTableAnnotationComposer
i0.GeneratedColumn<String> get faceAssetId => $composableBuilder( i0.GeneratedColumn<String> get faceAssetId => $composableBuilder(
column: $table.faceAssetId, builder: (column) => column); column: $table.faceAssetId, builder: (column) => column);
i0.GeneratedColumn<String> get thumbnailPath => $composableBuilder(
column: $table.thumbnailPath, builder: (column) => column);
i0.GeneratedColumn<bool> get isFavorite => $composableBuilder( i0.GeneratedColumn<bool> get isFavorite => $composableBuilder(
column: $table.isFavorite, builder: (column) => column); column: $table.isFavorite, builder: (column) => column);
@ -293,7 +280,6 @@ class $$PersonEntityTableTableManager extends i0.RootTableManager<
i0.Value<String> ownerId = const i0.Value.absent(), i0.Value<String> ownerId = const i0.Value.absent(),
i0.Value<String> name = const i0.Value.absent(), i0.Value<String> name = const i0.Value.absent(),
i0.Value<String?> faceAssetId = const i0.Value.absent(), i0.Value<String?> faceAssetId = const i0.Value.absent(),
i0.Value<String> thumbnailPath = const i0.Value.absent(),
i0.Value<bool> isFavorite = const i0.Value.absent(), i0.Value<bool> isFavorite = const i0.Value.absent(),
i0.Value<bool> isHidden = const i0.Value.absent(), i0.Value<bool> isHidden = const i0.Value.absent(),
i0.Value<String?> color = const i0.Value.absent(), i0.Value<String?> color = const i0.Value.absent(),
@ -306,7 +292,6 @@ class $$PersonEntityTableTableManager extends i0.RootTableManager<
ownerId: ownerId, ownerId: ownerId,
name: name, name: name,
faceAssetId: faceAssetId, faceAssetId: faceAssetId,
thumbnailPath: thumbnailPath,
isFavorite: isFavorite, isFavorite: isFavorite,
isHidden: isHidden, isHidden: isHidden,
color: color, color: color,
@ -319,7 +304,6 @@ class $$PersonEntityTableTableManager extends i0.RootTableManager<
required String ownerId, required String ownerId,
required String name, required String name,
i0.Value<String?> faceAssetId = const i0.Value.absent(), i0.Value<String?> faceAssetId = const i0.Value.absent(),
required String thumbnailPath,
required bool isFavorite, required bool isFavorite,
required bool isHidden, required bool isHidden,
i0.Value<String?> color = const i0.Value.absent(), i0.Value<String?> color = const i0.Value.absent(),
@ -332,7 +316,6 @@ class $$PersonEntityTableTableManager extends i0.RootTableManager<
ownerId: ownerId, ownerId: ownerId,
name: name, name: name,
faceAssetId: faceAssetId, faceAssetId: faceAssetId,
thumbnailPath: thumbnailPath,
isFavorite: isFavorite, isFavorite: isFavorite,
isHidden: isHidden, isHidden: isHidden,
color: color, color: color,
@ -443,12 +426,6 @@ class $PersonEntityTable extends i2.PersonEntity
late final i0.GeneratedColumn<String> faceAssetId = late final i0.GeneratedColumn<String> faceAssetId =
i0.GeneratedColumn<String>('face_asset_id', aliasedName, true, i0.GeneratedColumn<String>('face_asset_id', aliasedName, true,
type: i0.DriftSqlType.string, requiredDuringInsert: false); type: i0.DriftSqlType.string, requiredDuringInsert: false);
static const i0.VerificationMeta _thumbnailPathMeta =
const i0.VerificationMeta('thumbnailPath');
@override
late final i0.GeneratedColumn<String> thumbnailPath =
i0.GeneratedColumn<String>('thumbnail_path', aliasedName, false,
type: i0.DriftSqlType.string, requiredDuringInsert: true);
static const i0.VerificationMeta _isFavoriteMeta = static const i0.VerificationMeta _isFavoriteMeta =
const i0.VerificationMeta('isFavorite'); const i0.VerificationMeta('isFavorite');
@override @override
@ -487,7 +464,6 @@ class $PersonEntityTable extends i2.PersonEntity
ownerId, ownerId,
name, name,
faceAssetId, faceAssetId,
thumbnailPath,
isFavorite, isFavorite,
isHidden, isHidden,
color, color,
@ -535,14 +511,6 @@ class $PersonEntityTable extends i2.PersonEntity
faceAssetId.isAcceptableOrUnknown( faceAssetId.isAcceptableOrUnknown(
data['face_asset_id']!, _faceAssetIdMeta)); data['face_asset_id']!, _faceAssetIdMeta));
} }
if (data.containsKey('thumbnail_path')) {
context.handle(
_thumbnailPathMeta,
thumbnailPath.isAcceptableOrUnknown(
data['thumbnail_path']!, _thumbnailPathMeta));
} else if (isInserting) {
context.missing(_thumbnailPathMeta);
}
if (data.containsKey('is_favorite')) { if (data.containsKey('is_favorite')) {
context.handle( context.handle(
_isFavoriteMeta, _isFavoriteMeta,
@ -586,8 +554,6 @@ class $PersonEntityTable extends i2.PersonEntity
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!, .read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
faceAssetId: attachedDatabase.typeMapping.read( faceAssetId: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, data['${effectivePrefix}face_asset_id']), i0.DriftSqlType.string, data['${effectivePrefix}face_asset_id']),
thumbnailPath: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string, data['${effectivePrefix}thumbnail_path'])!,
isFavorite: attachedDatabase.typeMapping isFavorite: attachedDatabase.typeMapping
.read(i0.DriftSqlType.bool, data['${effectivePrefix}is_favorite'])!, .read(i0.DriftSqlType.bool, data['${effectivePrefix}is_favorite'])!,
isHidden: attachedDatabase.typeMapping isHidden: attachedDatabase.typeMapping
@ -618,7 +584,6 @@ class PersonEntityData extends i0.DataClass
final String ownerId; final String ownerId;
final String name; final String name;
final String? faceAssetId; final String? faceAssetId;
final String thumbnailPath;
final bool isFavorite; final bool isFavorite;
final bool isHidden; final bool isHidden;
final String? color; final String? color;
@ -630,7 +595,6 @@ class PersonEntityData extends i0.DataClass
required this.ownerId, required this.ownerId,
required this.name, required this.name,
this.faceAssetId, this.faceAssetId,
required this.thumbnailPath,
required this.isFavorite, required this.isFavorite,
required this.isHidden, required this.isHidden,
this.color, this.color,
@ -646,7 +610,6 @@ class PersonEntityData extends i0.DataClass
if (!nullToAbsent || faceAssetId != null) { if (!nullToAbsent || faceAssetId != null) {
map['face_asset_id'] = i0.Variable<String>(faceAssetId); map['face_asset_id'] = i0.Variable<String>(faceAssetId);
} }
map['thumbnail_path'] = i0.Variable<String>(thumbnailPath);
map['is_favorite'] = i0.Variable<bool>(isFavorite); map['is_favorite'] = i0.Variable<bool>(isFavorite);
map['is_hidden'] = i0.Variable<bool>(isHidden); map['is_hidden'] = i0.Variable<bool>(isHidden);
if (!nullToAbsent || color != null) { if (!nullToAbsent || color != null) {
@ -668,7 +631,6 @@ class PersonEntityData extends i0.DataClass
ownerId: serializer.fromJson<String>(json['ownerId']), ownerId: serializer.fromJson<String>(json['ownerId']),
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
faceAssetId: serializer.fromJson<String?>(json['faceAssetId']), faceAssetId: serializer.fromJson<String?>(json['faceAssetId']),
thumbnailPath: serializer.fromJson<String>(json['thumbnailPath']),
isFavorite: serializer.fromJson<bool>(json['isFavorite']), isFavorite: serializer.fromJson<bool>(json['isFavorite']),
isHidden: serializer.fromJson<bool>(json['isHidden']), isHidden: serializer.fromJson<bool>(json['isHidden']),
color: serializer.fromJson<String?>(json['color']), color: serializer.fromJson<String?>(json['color']),
@ -685,7 +647,6 @@ class PersonEntityData extends i0.DataClass
'ownerId': serializer.toJson<String>(ownerId), 'ownerId': serializer.toJson<String>(ownerId),
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'faceAssetId': serializer.toJson<String?>(faceAssetId), 'faceAssetId': serializer.toJson<String?>(faceAssetId),
'thumbnailPath': serializer.toJson<String>(thumbnailPath),
'isFavorite': serializer.toJson<bool>(isFavorite), 'isFavorite': serializer.toJson<bool>(isFavorite),
'isHidden': serializer.toJson<bool>(isHidden), 'isHidden': serializer.toJson<bool>(isHidden),
'color': serializer.toJson<String?>(color), 'color': serializer.toJson<String?>(color),
@ -700,7 +661,6 @@ class PersonEntityData extends i0.DataClass
String? ownerId, String? ownerId,
String? name, String? name,
i0.Value<String?> faceAssetId = const i0.Value.absent(), i0.Value<String?> faceAssetId = const i0.Value.absent(),
String? thumbnailPath,
bool? isFavorite, bool? isFavorite,
bool? isHidden, bool? isHidden,
i0.Value<String?> color = const i0.Value.absent(), i0.Value<String?> color = const i0.Value.absent(),
@ -712,7 +672,6 @@ class PersonEntityData extends i0.DataClass
ownerId: ownerId ?? this.ownerId, ownerId: ownerId ?? this.ownerId,
name: name ?? this.name, name: name ?? this.name,
faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId,
thumbnailPath: thumbnailPath ?? this.thumbnailPath,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
isHidden: isHidden ?? this.isHidden, isHidden: isHidden ?? this.isHidden,
color: color.present ? color.value : this.color, color: color.present ? color.value : this.color,
@ -727,9 +686,6 @@ class PersonEntityData extends i0.DataClass
name: data.name.present ? data.name.value : this.name, name: data.name.present ? data.name.value : this.name,
faceAssetId: faceAssetId:
data.faceAssetId.present ? data.faceAssetId.value : this.faceAssetId, data.faceAssetId.present ? data.faceAssetId.value : this.faceAssetId,
thumbnailPath: data.thumbnailPath.present
? data.thumbnailPath.value
: this.thumbnailPath,
isFavorite: isFavorite:
data.isFavorite.present ? data.isFavorite.value : this.isFavorite, data.isFavorite.present ? data.isFavorite.value : this.isFavorite,
isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden,
@ -747,7 +703,6 @@ class PersonEntityData extends i0.DataClass
..write('ownerId: $ownerId, ') ..write('ownerId: $ownerId, ')
..write('name: $name, ') ..write('name: $name, ')
..write('faceAssetId: $faceAssetId, ') ..write('faceAssetId: $faceAssetId, ')
..write('thumbnailPath: $thumbnailPath, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
..write('isHidden: $isHidden, ') ..write('isHidden: $isHidden, ')
..write('color: $color, ') ..write('color: $color, ')
@ -758,7 +713,7 @@ class PersonEntityData extends i0.DataClass
@override @override
int get hashCode => Object.hash(id, createdAt, updatedAt, ownerId, name, int get hashCode => Object.hash(id, createdAt, updatedAt, ownerId, name,
faceAssetId, thumbnailPath, isFavorite, isHidden, color, birthDate); faceAssetId, isFavorite, isHidden, color, birthDate);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@ -769,7 +724,6 @@ class PersonEntityData extends i0.DataClass
other.ownerId == this.ownerId && other.ownerId == this.ownerId &&
other.name == this.name && other.name == this.name &&
other.faceAssetId == this.faceAssetId && other.faceAssetId == this.faceAssetId &&
other.thumbnailPath == this.thumbnailPath &&
other.isFavorite == this.isFavorite && other.isFavorite == this.isFavorite &&
other.isHidden == this.isHidden && other.isHidden == this.isHidden &&
other.color == this.color && other.color == this.color &&
@ -783,7 +737,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
final i0.Value<String> ownerId; final i0.Value<String> ownerId;
final i0.Value<String> name; final i0.Value<String> name;
final i0.Value<String?> faceAssetId; final i0.Value<String?> faceAssetId;
final i0.Value<String> thumbnailPath;
final i0.Value<bool> isFavorite; final i0.Value<bool> isFavorite;
final i0.Value<bool> isHidden; final i0.Value<bool> isHidden;
final i0.Value<String?> color; final i0.Value<String?> color;
@ -795,7 +748,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
this.ownerId = const i0.Value.absent(), this.ownerId = const i0.Value.absent(),
this.name = const i0.Value.absent(), this.name = const i0.Value.absent(),
this.faceAssetId = const i0.Value.absent(), this.faceAssetId = const i0.Value.absent(),
this.thumbnailPath = const i0.Value.absent(),
this.isFavorite = const i0.Value.absent(), this.isFavorite = const i0.Value.absent(),
this.isHidden = const i0.Value.absent(), this.isHidden = const i0.Value.absent(),
this.color = const i0.Value.absent(), this.color = const i0.Value.absent(),
@ -808,7 +760,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
required String ownerId, required String ownerId,
required String name, required String name,
this.faceAssetId = const i0.Value.absent(), this.faceAssetId = const i0.Value.absent(),
required String thumbnailPath,
required bool isFavorite, required bool isFavorite,
required bool isHidden, required bool isHidden,
this.color = const i0.Value.absent(), this.color = const i0.Value.absent(),
@ -816,7 +767,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
}) : id = i0.Value(id), }) : id = i0.Value(id),
ownerId = i0.Value(ownerId), ownerId = i0.Value(ownerId),
name = i0.Value(name), name = i0.Value(name),
thumbnailPath = i0.Value(thumbnailPath),
isFavorite = i0.Value(isFavorite), isFavorite = i0.Value(isFavorite),
isHidden = i0.Value(isHidden); isHidden = i0.Value(isHidden);
static i0.Insertable<i1.PersonEntityData> custom({ static i0.Insertable<i1.PersonEntityData> custom({
@ -826,7 +776,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
i0.Expression<String>? ownerId, i0.Expression<String>? ownerId,
i0.Expression<String>? name, i0.Expression<String>? name,
i0.Expression<String>? faceAssetId, i0.Expression<String>? faceAssetId,
i0.Expression<String>? thumbnailPath,
i0.Expression<bool>? isFavorite, i0.Expression<bool>? isFavorite,
i0.Expression<bool>? isHidden, i0.Expression<bool>? isHidden,
i0.Expression<String>? color, i0.Expression<String>? color,
@ -839,7 +788,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
if (ownerId != null) 'owner_id': ownerId, if (ownerId != null) 'owner_id': ownerId,
if (name != null) 'name': name, if (name != null) 'name': name,
if (faceAssetId != null) 'face_asset_id': faceAssetId, if (faceAssetId != null) 'face_asset_id': faceAssetId,
if (thumbnailPath != null) 'thumbnail_path': thumbnailPath,
if (isFavorite != null) 'is_favorite': isFavorite, if (isFavorite != null) 'is_favorite': isFavorite,
if (isHidden != null) 'is_hidden': isHidden, if (isHidden != null) 'is_hidden': isHidden,
if (color != null) 'color': color, if (color != null) 'color': color,
@ -854,7 +802,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
i0.Value<String>? ownerId, i0.Value<String>? ownerId,
i0.Value<String>? name, i0.Value<String>? name,
i0.Value<String?>? faceAssetId, i0.Value<String?>? faceAssetId,
i0.Value<String>? thumbnailPath,
i0.Value<bool>? isFavorite, i0.Value<bool>? isFavorite,
i0.Value<bool>? isHidden, i0.Value<bool>? isHidden,
i0.Value<String?>? color, i0.Value<String?>? color,
@ -866,7 +813,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
ownerId: ownerId ?? this.ownerId, ownerId: ownerId ?? this.ownerId,
name: name ?? this.name, name: name ?? this.name,
faceAssetId: faceAssetId ?? this.faceAssetId, faceAssetId: faceAssetId ?? this.faceAssetId,
thumbnailPath: thumbnailPath ?? this.thumbnailPath,
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
isHidden: isHidden ?? this.isHidden, isHidden: isHidden ?? this.isHidden,
color: color ?? this.color, color: color ?? this.color,
@ -895,9 +841,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
if (faceAssetId.present) { if (faceAssetId.present) {
map['face_asset_id'] = i0.Variable<String>(faceAssetId.value); map['face_asset_id'] = i0.Variable<String>(faceAssetId.value);
} }
if (thumbnailPath.present) {
map['thumbnail_path'] = i0.Variable<String>(thumbnailPath.value);
}
if (isFavorite.present) { if (isFavorite.present) {
map['is_favorite'] = i0.Variable<bool>(isFavorite.value); map['is_favorite'] = i0.Variable<bool>(isFavorite.value);
} }
@ -922,7 +865,6 @@ class PersonEntityCompanion extends i0.UpdateCompanion<i1.PersonEntityData> {
..write('ownerId: $ownerId, ') ..write('ownerId: $ownerId, ')
..write('name: $name, ') ..write('name: $name, ')
..write('faceAssetId: $faceAssetId, ') ..write('faceAssetId: $faceAssetId, ')
..write('thumbnailPath: $thumbnailPath, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
..write('isHidden: $isHidden, ') ..write('isHidden: $isHidden, ')
..write('color: $color, ') ..write('color: $color, ')

View File

@ -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<List<AssetFace>> 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,
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/interfaces/db.interface.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/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.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
@ -56,6 +57,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
MemoryAssetEntity, MemoryAssetEntity,
StackEntity, StackEntity,
PersonEntity, PersonEntity,
AssetFaceEntity,
], ],
include: { include: {
'package:immich_mobile/infrastructure/entities/merged_asset.drift', 'package:immich_mobile/infrastructure/entities/merged_asset.drift',
@ -72,7 +74,7 @@ class Drift extends $Drift implements IDatabaseRepository {
); );
@override @override
int get schemaVersion => 3; int get schemaVersion => 4;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
@ -94,6 +96,10 @@ class Drift extends $Drift implements IDatabaseRepository {
// Removed foreign key constraint on stack.primaryAssetId // Removed foreign key constraint on stack.primaryAssetId
await m.alterTable(TableMigration(v3.stackEntity)); await m.alterTable(TableMigration(v3.stackEntity));
}, },
from3To4: (m, v4) async {
await m.alterTable(TableMigration(v4.personEntity));
await m.create(v4.assetFaceEntity);
},
), ),
); );

View File

@ -31,9 +31,11 @@ import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.
as i14; as i14;
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
as i15; as i15;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
as i16; as i16;
import 'package:drift/internal/modular.dart' as i17; import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i17;
import 'package:drift/internal/modular.dart' as i18;
abstract class $Drift extends i0.GeneratedDatabase { abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e); $Drift(i0.QueryExecutor e) : super(e);
@ -64,8 +66,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
late final i14.$MemoryAssetEntityTable memoryAssetEntity = late final i14.$MemoryAssetEntityTable memoryAssetEntity =
i14.$MemoryAssetEntityTable(this); i14.$MemoryAssetEntityTable(this);
late final i15.$PersonEntityTable personEntity = i15.$PersonEntityTable(this); late final i15.$PersonEntityTable personEntity = i15.$PersonEntityTable(this);
i16.MergedAssetDrift get mergedAssetDrift => i17.ReadDatabaseContainer(this) late final i16.$AssetFaceEntityTable assetFaceEntity =
.accessor<i16.MergedAssetDrift>(i16.MergedAssetDrift.new); i16.$AssetFaceEntityTable(this);
i17.MergedAssetDrift get mergedAssetDrift => i18.ReadDatabaseContainer(this)
.accessor<i17.MergedAssetDrift>(i17.MergedAssetDrift.new);
@override @override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables => Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>(); allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@ -88,7 +92,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
remoteAlbumUserEntity, remoteAlbumUserEntity,
memoryEntity, memoryEntity,
memoryAssetEntity, memoryAssetEntity,
personEntity personEntity,
assetFaceEntity
]; ];
@override @override
i0.StreamQueryUpdateRules get streamUpdateRules => i0.StreamQueryUpdateRules get streamUpdateRules =>
@ -227,6 +232,20 @@ abstract class $Drift extends i0.GeneratedDatabase {
i0.TableUpdate('person_entity', kind: i0.UpdateKind.delete), i0.TableUpdate('person_entity', kind: i0.UpdateKind.delete),
], ],
), ),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('person_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.update),
],
),
], ],
); );
@override @override
@ -268,4 +287,6 @@ class $DriftManager {
i14.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity); i14.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
i15.$$PersonEntityTableTableManager get personEntity => i15.$$PersonEntityTableTableManager get personEntity =>
i15.$$PersonEntityTableTableManager(_db, _db.personEntity); i15.$$PersonEntityTableTableManager(_db, _db.personEntity);
i16.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
i16.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
} }

View File

@ -1266,9 +1266,451 @@ final class Schema3 extends i0.VersionedSchema {
i1.GeneratedColumn<String> _column_75(String aliasedName) => i1.GeneratedColumn<String> _column_75(String aliasedName) =>
i1.GeneratedColumn<String>('primary_asset_id', aliasedName, false, i1.GeneratedColumn<String>('primary_asset_id', aliasedName, false,
type: i1.DriftSqlType.string); type: i1.DriftSqlType.string);
final class Schema4 extends i0.VersionedSchema {
Schema4({required super.database}) : super(version: 4);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
localAssetEntity,
stackEntity,
idxLocalAssetChecksum,
uQRemoteAssetOwnerChecksum,
idxRemoteAssetChecksum,
userMetadataEntity,
partnerEntity,
localAlbumEntity,
localAlbumAssetEntity,
remoteExifEntity,
remoteAlbumEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
];
late final Shape0 userEntity = Shape0(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 remoteAssetEntity = Shape1(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_13,
_column_14,
_column_15,
_column_16,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 localAssetEntity = Shape2(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_22,
_column_14,
_column_23,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 stackEntity = Shape3(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_75,
],
attachedDatabase: database,
),
alias: null);
final i1.Index idxLocalAssetChecksum = i1.Index('idx_local_asset_checksum',
'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)');
final i1.Index uQRemoteAssetOwnerChecksum = i1.Index(
'UQ_remote_asset_owner_checksum',
'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)');
final i1.Index idxRemoteAssetChecksum = i1.Index('idx_remote_asset_checksum',
'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)');
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(user_id, "key")',
],
columns: [
_column_25,
_column_26,
_column_27,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 partnerEntity = Shape5(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(shared_by_id, shared_with_id)',
],
columns: [
_column_28,
_column_29,
_column_30,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 localAlbumEntity = Shape6(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_1,
_column_5,
_column_31,
_column_32,
_column_33,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 localAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id, album_id)',
],
columns: [
_column_34,
_column_35,
],
attachedDatabase: database,
),
alias: null);
late final Shape8 remoteExifEntity = Shape8(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id)',
],
columns: [
_column_36,
_column_37,
_column_38,
_column_39,
_column_40,
_column_41,
_column_11,
_column_10,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
_column_48,
_column_49,
_column_50,
_column_51,
_column_52,
_column_53,
_column_54,
_column_55,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 remoteAlbumEntity = Shape9(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_1,
_column_56,
_column_9,
_column_5,
_column_15,
_column_57,
_column_58,
_column_59,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id, album_id)',
],
columns: [
_column_36,
_column_60,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(album_id, user_id)',
],
columns: [
_column_60,
_column_25,
_column_61,
],
attachedDatabase: database,
),
alias: null);
late final Shape11 memoryEntity = Shape11(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_9,
_column_5,
_column_18,
_column_15,
_column_8,
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_67,
],
attachedDatabase: database,
),
alias: null);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(asset_id, memory_id)',
],
columns: [
_column_36,
_column_68,
],
attachedDatabase: database,
),
alias: null);
late final Shape14 personEntity = Shape14(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_1,
_column_69,
_column_71,
_column_72,
_column_73,
_column_74,
],
attachedDatabase: database,
),
alias: null);
late final Shape15 assetFaceEntity = Shape15(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_0,
_column_36,
_column_76,
_column_77,
_column_78,
_column_79,
_column_80,
_column_81,
_column_82,
_column_83,
],
attachedDatabase: database,
),
alias: null);
}
class Shape14 extends i0.VersionedTable {
Shape14({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<String> get ownerId =>
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get faceAssetId =>
columnsByName['face_asset_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isFavorite =>
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get isHidden =>
columnsByName['is_hidden']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<String> get color =>
columnsByName['color']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get birthDate =>
columnsByName['birth_date']! as i1.GeneratedColumn<DateTime>;
}
class Shape15 extends i0.VersionedTable {
Shape15({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get assetId =>
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get personId =>
columnsByName['person_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get imageWidth =>
columnsByName['image_width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get imageHeight =>
columnsByName['image_height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxX1 =>
columnsByName['bounding_box_x1']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxY1 =>
columnsByName['bounding_box_y1']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxX2 =>
columnsByName['bounding_box_x2']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get boundingBoxY2 =>
columnsByName['bounding_box_y2']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get sourceType =>
columnsByName['source_type']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_76(String aliasedName) =>
i1.GeneratedColumn<String>('person_id', aliasedName, true,
type: i1.DriftSqlType.string,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'REFERENCES person_entity (id) ON DELETE SET NULL'));
i1.GeneratedColumn<int> _column_77(String aliasedName) =>
i1.GeneratedColumn<int>('image_width', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_78(String aliasedName) =>
i1.GeneratedColumn<int>('image_height', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_79(String aliasedName) =>
i1.GeneratedColumn<int>('bounding_box_x1', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_80(String aliasedName) =>
i1.GeneratedColumn<int>('bounding_box_y1', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_81(String aliasedName) =>
i1.GeneratedColumn<int>('bounding_box_x2', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_82(String aliasedName) =>
i1.GeneratedColumn<int>('bounding_box_y2', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<String> _column_83(String aliasedName) =>
i1.GeneratedColumn<String>('source_type', aliasedName, false,
type: i1.DriftSqlType.string);
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -1282,6 +1724,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema); await from2To3(migrator, schema);
return 3; return 3;
case 3:
final schema = Schema4(database: database);
final migrator = i1.Migrator(database, schema);
await from3To4(migrator, schema);
return 4;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -1291,9 +1738,11 @@ i0.MigrationStepWithVersion migrationSteps({
i1.OnUpgrade stepByStep({ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
}) => }) =>
i0.VersionedSchema.stepByStepHelper( i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
from1To2: from1To2, from1To2: from1To2,
from2To3: from2To3, from2To3: from2To3,
from3To4: from3To4,
)); ));

View File

@ -26,7 +26,6 @@ extension on PersonEntityData {
ownerId: ownerId, ownerId: ownerId,
name: name, name: name,
faceAssetId: faceAssetId, faceAssetId: faceAssetId,
thumbnailPath: thumbnailPath,
isFavorite: isFavorite, isFavorite: isFavorite,
isHidden: isHidden, isHidden: isHidden,
color: color, color: color,

View File

@ -58,6 +58,7 @@ class SyncApiRepository {
SyncRequestType.partnerStacksV1, SyncRequestType.partnerStacksV1,
SyncRequestType.userMetadataV1, SyncRequestType.userMetadataV1,
SyncRequestType.peopleV1, SyncRequestType.peopleV1,
SyncRequestType.assetFacesV1,
], ],
).toJson(), ).toJson(),
); );
@ -176,6 +177,8 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.userMetadataDeleteV1: SyncUserMetadataDeleteV1.fromJson, SyncEntityType.userMetadataDeleteV1: SyncUserMetadataDeleteV1.fromJson,
SyncEntityType.personV1: SyncPersonV1.fromJson, SyncEntityType.personV1: SyncPersonV1.fromJson,
SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson, SyncEntityType.personDeleteV1: SyncPersonDeleteV1.fromJson,
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
}; };
class _SyncAckV1 { class _SyncAckV1 {

View File

@ -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/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/memory.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/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/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart';
@ -546,11 +547,62 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Iterable<SyncPersonDeleteV1> data, Iterable<SyncPersonDeleteV1> data,
) async { ) async {
try { try {
await _db.personEntity.deleteWhere( await _db.batch((batch) {
(row) => row.id.isIn(data.map((e) => e.personId)), for (final person in data) {
); batch.deleteWhere(
_db.personEntity,
(row) => row.id.equals(person.personId),
);
}
});
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: deletePeopleV1', error, stack); _logger.severe('Error: deletePeopleV1', error, stack);
rethrow;
}
}
Future<void> updateAssetFacesV1(Iterable<SyncAssetFaceV1> 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<void> deleteAssetFacesV1(Iterable<SyncAssetFaceDeleteV1> 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;
} }
} }
} }

View File

@ -121,6 +121,7 @@ final _features = [
await db.memoryAssetEntity.deleteAll(); await db.memoryAssetEntity.deleteAll();
await db.stackEntity.deleteAll(); await db.stackEntity.deleteAll();
await db.personEntity.deleteAll(); await db.personEntity.deleteAll();
await db.assetFaceEntity.deleteAll();
}, },
), ),
_Feature( _Feature(

View File

@ -170,6 +170,10 @@ final _remoteStats = [
name: 'People', name: 'People',
load: (db) => db.managers.personEntity.count(), load: (db) => db.managers.personEntity.count(),
), ),
_Stat(
name: 'AssetFaces',
load: (db) => db.managers.assetFaceEntity.count(),
),
]; ];
@RoutePage() @RoutePage()

View File

@ -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<DriftAssetFaceRepository>(
(ref) => DriftAssetFaceRepository(ref.watch(driftProvider)),
);

View File

@ -34,11 +34,12 @@ class AuthRepository extends DatabaseRepository {
_drift.userMetadataEntity.deleteAll(), _drift.userMetadataEntity.deleteAll(),
_drift.partnerEntity.deleteAll(), _drift.partnerEntity.deleteAll(),
_drift.stackEntity.deleteAll(), _drift.stackEntity.deleteAll(),
_drift.personEntity.deleteAll(), _drift.assetFaceEntity.deleteAll(),
]); ]);
// Drift deletions - parent entities // Drift deletions - parent entities
await Future.wait([ await Future.wait([
_drift.memoryEntity.deleteAll(), _drift.memoryEntity.deleteAll(),
_drift.personEntity.deleteAll(),
_drift.remoteAlbumEntity.deleteAll(), _drift.remoteAlbumEntity.deleteAll(),
_drift.remoteAssetEntity.deleteAll(), _drift.remoteAssetEntity.deleteAll(),
_drift.userEntity.deleteAll(), _drift.userEntity.deleteAll(),

View File

@ -109,6 +109,10 @@ void main() {
.thenAnswer(successHandler); .thenAnswer(successHandler);
when(() => mockSyncStreamRepo.deletePeopleV1(any())) when(() => mockSyncStreamRepo.deletePeopleV1(any()))
.thenAnswer(successHandler); .thenAnswer(successHandler);
when(() => mockSyncStreamRepo.updateAssetFacesV1(any()))
.thenAnswer(successHandler);
when(() => mockSyncStreamRepo.deleteAssetFacesV1(any()))
.thenAnswer(successHandler);
sut = SyncStreamService( sut = SyncStreamService(
syncApiRepository: mockSyncApiRepo, syncApiRepository: mockSyncApiRepo,

View File

@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart';
import 'schema_v1.dart' as v1; import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2; import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3; import 'schema_v3.dart' as v3;
import 'schema_v4.dart' as v4;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v2.DatabaseAtV2(db); return v2.DatabaseAtV2(db);
case 3: case 3:
return v3.DatabaseAtV3(db); return v3.DatabaseAtV3(db);
case 4:
return v4.DatabaseAtV4(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
} }
static const versions = const [1, 2, 3]; static const versions = const [1, 2, 3, 4];
} }

File diff suppressed because it is too large Load Diff