feat: drift store migration
This commit is contained in:
@@ -12,6 +12,7 @@ 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_album_user.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/store.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';
|
||||
@@ -46,6 +47,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
||||
RemoteAlbumEntity,
|
||||
RemoteAlbumAssetEntity,
|
||||
RemoteAlbumUserEntity,
|
||||
StoreEntity,
|
||||
],
|
||||
include: {
|
||||
'package:immich_mobile/infrastructure/entities/merged_asset.drift',
|
||||
|
||||
+11
-5
@@ -23,9 +23,11 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
|
||||
as i10;
|
||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||
as i11;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||
as i12;
|
||||
import 'package:drift/internal/modular.dart' as i13;
|
||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||
as i13;
|
||||
import 'package:drift/internal/modular.dart' as i14;
|
||||
|
||||
abstract class $Drift extends i0.GeneratedDatabase {
|
||||
$Drift(i0.QueryExecutor e) : super(e);
|
||||
@@ -51,8 +53,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
i10.$RemoteAlbumAssetEntityTable(this);
|
||||
late final i11.$RemoteAlbumUserEntityTable remoteAlbumUserEntity =
|
||||
i11.$RemoteAlbumUserEntityTable(this);
|
||||
i12.MergedAssetDrift get mergedAssetDrift => i13.ReadDatabaseContainer(this)
|
||||
.accessor<i12.MergedAssetDrift>(i12.MergedAssetDrift.new);
|
||||
late final i12.$StoreEntityTable storeEntity = i12.$StoreEntityTable(this);
|
||||
i13.MergedAssetDrift get mergedAssetDrift => i14.ReadDatabaseContainer(this)
|
||||
.accessor<i13.MergedAssetDrift>(i13.MergedAssetDrift.new);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@@ -71,7 +74,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
||||
remoteExifEntity,
|
||||
remoteAlbumEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity
|
||||
remoteAlbumUserEntity,
|
||||
storeEntity
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
@@ -208,4 +212,6 @@ class $DriftManager {
|
||||
_db, _db.remoteAlbumAssetEntity);
|
||||
i11.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i11
|
||||
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
||||
i12.$$StoreEntityTableTableManager get storeEntity =>
|
||||
i12.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/drift_user.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
|
||||
final driftStoreRepositoryProvider = Provider<DriftStoreRepository>(
|
||||
(ref) => DriftStoreRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
|
||||
class DriftStoreRepository implements IStoreRepository {
|
||||
final Drift _db;
|
||||
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
||||
|
||||
DriftStoreRepository(this._db);
|
||||
|
||||
@override
|
||||
Future<bool> deleteAll() async {
|
||||
return await _db.transaction(() async {
|
||||
await _db.delete(_db.storeEntity).go();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<StoreDto<Object>> watchAll() {
|
||||
return (_db.select(_db.storeEntity)
|
||||
..where((tbl) => tbl.id.isIn(validStoreKeys)))
|
||||
.watch()
|
||||
.asyncExpand(
|
||||
(entities) => Stream.fromFutures(
|
||||
entities.map((e) async => _toUpdateEvent(e)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete<T>(StoreKey<T> key) async {
|
||||
return await _db.transaction(() async {
|
||||
await (_db.delete(_db.storeEntity)..where((tbl) => tbl.id.equals(key.id)))
|
||||
.go();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insert<T>(StoreKey<T> key, T value) async {
|
||||
return await _db.transaction(() async {
|
||||
await _db
|
||||
.into(_db.storeEntity)
|
||||
.insertOnConflictUpdate(await _fromValue(key, value));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
||||
final entity = await (_db.select(_db.storeEntity)
|
||||
..where((tbl) => tbl.id.equals(key.id)))
|
||||
.getSingleOrNull();
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return await _toValue(key, entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> update<T>(StoreKey<T> key, T value) async {
|
||||
return await _db.transaction(() async {
|
||||
await _db
|
||||
.into(_db.storeEntity)
|
||||
.insertOnConflictUpdate(await _fromValue(key, value));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
||||
yield* (_db.select(_db.storeEntity)..where((tbl) => tbl.id.equals(key.id)))
|
||||
.watchSingleOrNull()
|
||||
.asyncMap((e) async => e == null ? null : await _toValue(key, e));
|
||||
}
|
||||
|
||||
Future<StoreDto<Object>> _toUpdateEvent(StoreEntityData entity) async {
|
||||
final key = StoreKey.values.firstWhere((e) => e.id == entity.id)
|
||||
as StoreKey<Object>;
|
||||
final value = await _toValue(key, entity);
|
||||
return StoreDto(key, value);
|
||||
}
|
||||
|
||||
Future<T?> _toValue<T>(StoreKey<T> key, StoreEntityData entity) async =>
|
||||
switch (key.type) {
|
||||
const (int) => entity.intValue,
|
||||
const (String) => entity.strValue,
|
||||
const (bool) => entity.intValue == 1,
|
||||
const (DateTime) => entity.intValue == null
|
||||
? null
|
||||
: DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||
const (UserDto) => entity.strValue == null
|
||||
? null
|
||||
: await DriftUserRepository(_db).getByUserId(entity.strValue!),
|
||||
_ => null,
|
||||
} as T?;
|
||||
|
||||
Future<StoreEntityData> _fromValue<T>(StoreKey<T> key, T value) async {
|
||||
final (int? intValue, String? strValue) = switch (key.type) {
|
||||
const (int) => (value as int, null),
|
||||
const (String) => (null, value as String),
|
||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||
const (UserDto) => (
|
||||
null,
|
||||
(await DriftUserRepository(_db).update(value as UserDto)).id,
|
||||
),
|
||||
_ => throw UnsupportedError(
|
||||
"Unsupported primitive type: ${key.type} for key: ${key.name}",
|
||||
),
|
||||
};
|
||||
return StoreEntityData(
|
||||
id: key.id,
|
||||
intValue: intValue,
|
||||
strValue: strValue,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StoreDto<Object>>> getAll() async {
|
||||
final entities = await (_db.select(_db.storeEntity)
|
||||
..where((tbl) => tbl.id.isIn(validStoreKeys)))
|
||||
.get();
|
||||
return Future.wait(entities.map((e) => _toUpdateEvent(e)).toList());
|
||||
}
|
||||
}
|
||||
|
||||
abstract class IStoreRepository {
|
||||
Future<bool> deleteAll();
|
||||
Stream<StoreDto<Object>> watchAll();
|
||||
Future<void> delete<T>(StoreKey<T> key);
|
||||
Future<bool> insert<T>(StoreKey<T> key, T value);
|
||||
Future<T?> tryGet<T>(StoreKey<T> key);
|
||||
Future<bool> update<T>(StoreKey<T> key, T value);
|
||||
Stream<T?> watch<T>(StoreKey<T> key);
|
||||
Future<List<StoreDto<Object>>> getAll();
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
||||
|
||||
class DriftUserRepository {
|
||||
final Drift _db;
|
||||
const DriftUserRepository(this._db);
|
||||
|
||||
Future<void> delete(List<String> ids) async {
|
||||
await _db.transaction(() async {
|
||||
await (_db.delete(_db.userEntity)..where((tbl) => tbl.id.isIn(ids))).go();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteAll() async {
|
||||
await _db.transaction(() async {
|
||||
await _db.delete(_db.userEntity).go();
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
|
||||
var query = _db.select(_db.userEntity);
|
||||
|
||||
if (sortBy != null) {
|
||||
switch (sortBy) {
|
||||
case SortUserBy.id:
|
||||
query = query..orderBy([(u) => OrderingTerm.asc(u.id)]);
|
||||
}
|
||||
}
|
||||
|
||||
final users = await query.get();
|
||||
return users.map((u) => _toDto(u)).toList();
|
||||
}
|
||||
|
||||
Future<UserDto?> getByUserId(String id) async {
|
||||
final user = await (_db.select(_db.userEntity)
|
||||
..where((tbl) => tbl.id.equals(id)))
|
||||
.getSingleOrNull();
|
||||
return user != null ? _toDto(user) : null;
|
||||
}
|
||||
|
||||
Future<List<UserDto?>> getByUserIds(List<String> ids) async {
|
||||
final users = await (_db.select(_db.userEntity)
|
||||
..where((tbl) => tbl.id.isIn(ids)))
|
||||
.get();
|
||||
|
||||
// Create a map for quick lookup
|
||||
final userMap = {for (var user in users) user.id: _toDto(user)};
|
||||
|
||||
// Return results in the same order as input ids
|
||||
return ids.map((id) => userMap[id]).toList();
|
||||
}
|
||||
|
||||
Future<bool> insert(UserDto user) async {
|
||||
await _db.transaction(() async {
|
||||
await _db.into(_db.userEntity).insertOnConflictUpdate(_fromDto(user));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<UserDto> update(UserDto user) async {
|
||||
await _db.transaction(() async {
|
||||
await _db.into(_db.userEntity).insertOnConflictUpdate(_fromDto(user));
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
||||
Future<bool> updateAll(List<UserDto> users) async {
|
||||
await _db.transaction(() async {
|
||||
await _db.batch((batch) {
|
||||
for (final user in users) {
|
||||
batch.insert(_db.userEntity, _fromDto(user),
|
||||
mode: InsertMode.insertOrReplace);
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
UserDto _toDto(UserEntityData entity) {
|
||||
return UserDto(
|
||||
id: entity.id,
|
||||
updatedAt: entity.updatedAt,
|
||||
email: entity.email,
|
||||
name: entity.name,
|
||||
isAdmin: entity.isAdmin,
|
||||
profileImagePath: entity.profileImagePath ?? '',
|
||||
// Note: These fields are not in the current UserEntity table but are in UserDto
|
||||
// You may need to add them to the table or provide defaults
|
||||
isPartnerSharedBy: false,
|
||||
isPartnerSharedWith: false,
|
||||
avatarColor: AvatarColor.primary,
|
||||
memoryEnabled: true,
|
||||
inTimeline: false,
|
||||
quotaUsageInBytes: entity.quotaUsageInBytes,
|
||||
quotaSizeInBytes: entity.quotaSizeInBytes ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
UserEntityCompanion _fromDto(UserDto dto) {
|
||||
return UserEntityCompanion(
|
||||
id: Value(dto.id),
|
||||
name: Value(dto.name),
|
||||
isAdmin: Value(dto.isAdmin),
|
||||
email: Value(dto.email),
|
||||
profileImagePath: Value.absentIfNull(
|
||||
dto.profileImagePath?.isEmpty == true ? null : dto.profileImagePath),
|
||||
updatedAt: Value(dto.updatedAt),
|
||||
quotaSizeInBytes: Value.absentIfNull(
|
||||
dto.quotaSizeInBytes == 0 ? null : dto.quotaSizeInBytes),
|
||||
quotaUsageInBytes: Value(dto.quotaUsageInBytes),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/isar_store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
class IsarStoreRepository extends IsarDatabaseRepository
|
||||
implements IStoreRepository {
|
||||
final Isar _db;
|
||||
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
||||
|
||||
IsarStoreRepository(super.db) : _db = db;
|
||||
|
||||
@override
|
||||
Future<bool> deleteAll() async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.clear();
|
||||
@@ -18,6 +21,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<StoreDto<Object>> watchAll() {
|
||||
return _db.storeValues
|
||||
.filter()
|
||||
@@ -30,10 +34,12 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete<T>(StoreKey<T> key) async {
|
||||
return await transaction(() async => await _db.storeValues.delete(key.id));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> insert<T>(StoreKey<T> key, T value) async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.put(await _fromValue(key, value));
|
||||
@@ -41,6 +47,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
||||
final entity = (await _db.storeValues.get(key.id));
|
||||
if (entity == null) {
|
||||
@@ -49,6 +56,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
return await _toValue(key, entity);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> update<T>(StoreKey<T> key, T value) async {
|
||||
return await transaction(() async {
|
||||
await _db.storeValues.put(await _fromValue(key, value));
|
||||
@@ -56,6 +64,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
||||
yield* _db.storeValues
|
||||
.watchObject(key.id, fireImmediately: true)
|
||||
@@ -100,6 +109,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
||||
return StoreValue(key.id, intValue: intValue, strValue: strValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StoreDto<Object>>> getAll() async {
|
||||
final entities = await _db.storeValues
|
||||
.filter()
|
||||
|
||||
Reference in New Issue
Block a user