feat(mobile): remote album sync (#18876)

* feat(mobile): remote album sync

* fix: lint

* missing createdAt field

* lint
This commit is contained in:
Daimolean
2025-06-09 23:09:14 +08:00
committed by GitHub
parent 74f79cae69
commit 242817c49a
17 changed files with 2525 additions and 6 deletions
@@ -3,12 +3,15 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
import 'package:immich_mobile/infrastructure/entities/album_user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.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';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:isar/isar.dart';
@@ -38,8 +41,11 @@ class IsarDatabaseRepository implements IDatabaseRepository {
LocalAlbumEntity,
LocalAssetEntity,
LocalAlbumAssetEntity,
RemoteAssetEntity,
RemoteExifEntity,
RemoteAssetEntity,
RemoteAlbumEntity,
RemoteAlbumAssetEntity,
AlbumUserEntity,
],
)
class Drift extends $Drift implements IDatabaseRepository {
@@ -17,6 +17,12 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.
as i7;
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
as i8;
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'
as i9;
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
as i10;
import 'package:immich_mobile/infrastructure/entities/album_user.entity.drift.dart'
as i11;
abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e);
@@ -36,6 +42,12 @@ abstract class $Drift extends i0.GeneratedDatabase {
i7.$RemoteAssetEntityTable(this);
late final i8.$RemoteExifEntityTable remoteExifEntity =
i8.$RemoteExifEntityTable(this);
late final i9.$RemoteAlbumEntityTable remoteAlbumEntity =
i9.$RemoteAlbumEntityTable(this);
late final i10.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity =
i10.$RemoteAlbumAssetEntityTable(this);
late final i11.$AlbumUserEntityTable albumUserEntity =
i11.$AlbumUserEntityTable(this);
@override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@@ -49,6 +61,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
localAlbumAssetEntity,
remoteAssetEntity,
remoteExifEntity,
remoteAlbumEntity,
remoteAlbumAssetEntity,
albumUserEntity,
i5.idxLocalAssetChecksum,
i7.uQRemoteAssetOwnerChecksum
];
@@ -108,6 +123,50 @@ abstract class $Drift extends i0.GeneratedDatabase {
i0.TableUpdate('remote_exif_entity', kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('user_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.update),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_asset_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('remote_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_album_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('remote_album_asset_entity',
kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('remote_album_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('album_user_entity', kind: i0.UpdateKind.delete),
],
),
i0.WritePropagation(
on: i0.TableUpdateQuery.onTableName('user_entity',
limitUpdateKind: i0.UpdateKind.delete),
result: [
i0.TableUpdate('album_user_entity', kind: i0.UpdateKind.delete),
],
),
],
);
@override
@@ -134,4 +193,11 @@ class $DriftManager {
i7.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity);
i8.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
i8.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
i9.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity =>
i9.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity);
i10.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
i10.$$RemoteAlbumAssetEntityTableTableManager(
_db, _db.remoteAlbumAssetEntity);
i11.$$AlbumUserEntityTableTableManager get albumUserEntity =>
i11.$$AlbumUserEntityTableTableManager(_db, _db.albumUserEntity);
}
@@ -50,6 +50,9 @@ class SyncApiRepository implements ISyncApiRepository {
SyncRequestType.partnerAssetsV1,
SyncRequestType.assetExifsV1,
SyncRequestType.partnerAssetExifsV1,
SyncRequestType.albumsV1,
// SyncRequestType.albumAssetsV1,
SyncRequestType.albumUsersV1,
],
).toJson(),
);
@@ -140,4 +143,10 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
// SyncEntityType.albumAssetV1: SyncAlbumAssetV1.fromJson,
// SyncEntityType.albumAssetDeleteV1: SyncAlbumAssetDeleteV1.fromJson,
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
};
@@ -3,12 +3,19 @@ import 'package:immich_mobile/domain/interfaces/sync_stream.interface.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/album_user.model.dart';
import 'package:immich_mobile/infrastructure/entities/album_user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
// import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart' as api show AssetVisibility;
import 'package:openapi/api.dart' hide AssetVisibility;
import 'package:openapi/api.dart' as api
show AssetVisibility, AssetOrder, AlbumUserRole;
import 'package:openapi/api.dart'
hide AssetVisibility, AssetOrder, AlbumUserRole;
class DriftSyncStreamRepository extends DriftDatabaseRepository
implements ISyncStreamRepository {
@@ -161,6 +168,135 @@ class DriftSyncStreamRepository extends DriftDatabaseRepository
}
}
@override
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
try {
await _db.batch((batch) {
for (final album in data) {
final companion = RemoteAlbumEntityCompanion(
name: Value(album.name),
description: Value(album.description),
ownerId: Value(album.ownerId),
thumbnailAssetId: Value(album.thumbnailAssetId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order.toAssetOrder()),
);
batch.insert(
_db.remoteAlbumEntity,
companion.copyWith(id: Value(album.id)),
onConflict: DoUpdate((_) => companion),
);
}
});
} catch (e, s) {
_logger.severe('Error while processing updateAlbumsV1', e, s);
rethrow;
}
}
@override
Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async {
try {
_db.batch((batch) {
for (final album in data) {
batch.delete(
_db.remoteAlbumEntity,
RemoteAlbumEntityCompanion(id: Value(album.albumId)),
);
}
});
} catch (e, s) {
_logger.severe('Error while processing deleteAlbumsV1', e, s);
rethrow;
}
}
// @override
// Future<void> updateAlbumAssetsV1(Iterable<SyncAlbumAssetV1> data) async {
// try {
// await _db.remoteAlbumAssetEntity.insertAll(
// data.map(
// (albumAsset) => RemoteAlbumAssetEntityCompanion.insert(
// albumId: albumAsset.albumId,
// assetId: albumAsset.assetId,
// ),
// ),
// mode: InsertMode.insertOrIgnore,
// );
// } catch (e, s) {
// _logger.severe('Error while processing updateAlbumAssetsV1', e, s);
// rethrow;
// }
// }
// @override
// Future<void> deleteAlbumAssetsV1(Iterable<SyncAlbumAssetDeleteV1> data) async {
// try {
// await _db.batch((batch) {
// for (final albumAsset in data) {
// batch.delete(
// _db.remoteAlbumAssetEntity,
// RemoteAlbumAssetEntityCompanion(
// albumId: Value(albumAsset.albumId),
// assetId: Value(albumAsset.assetId),
// ),
// );
// }
// });
// } catch (e, s) {
// _logger.severe('Error while processing deleteAlbumAssetsV1', e, s);
// rethrow;
// }
// }
@override
Future<void> updateAlbumUsersV1(Iterable<SyncAlbumUserV1> data) async {
try {
await _db.batch((batch) {
for (final albumUser in data) {
final companion = AlbumUserEntityCompanion(
role: Value(albumUser.role.toAlbumUserRole()),
);
batch.insert(
_db.albumUserEntity,
companion.copyWith(
albumId: Value(albumUser.albumId),
userId: Value(albumUser.userId),
),
onConflict: DoUpdate((_) => companion),
);
}
});
} catch (e, s) {
_logger.severe('Error while processing updateAlbumUsersV1', e, s);
rethrow;
}
}
@override
Future<void> deleteAlbumUsersV1(Iterable<SyncAlbumUserDeleteV1> data) async {
try {
await _db.batch((batch) {
for (final albumUser in data) {
batch.delete(
_db.albumUserEntity,
AlbumUserEntityCompanion(
albumId: Value(albumUser.albumId),
userId: Value(albumUser.userId),
),
);
}
});
} catch (e, s) {
_logger.severe('Error while processing deleteAlbumUsersV1', e, s);
rethrow;
}
}
Future<void> _updateAssetsV1(Iterable<SyncAssetV1> data) =>
_db.batch((batch) {
for (final asset in data) {
@@ -251,3 +387,19 @@ extension on api.AssetVisibility {
_ => throw Exception('Unknown AssetVisibility value: $this'),
};
}
extension on api.AssetOrder {
AssetOrder toAssetOrder() => switch (this) {
api.AssetOrder.asc => AssetOrder.asc,
api.AssetOrder.desc => AssetOrder.desc,
_ => throw Exception('Unknown AssetOrder value: $this'),
};
}
extension on api.AlbumUserRole {
AlbumUserRole toAlbumUserRole() => switch (this) {
api.AlbumUserRole.editor => AlbumUserRole.editor,
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
_ => throw Exception('Unknown AlbumUserRole value: $this'),
};
}