0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-11 01:18:24 -05:00

refactor repositories

This commit is contained in:
shenlong-tanwen 2024-09-21 20:04:05 +05:30
parent 947a70d48c
commit 775213aeb6
23 changed files with 142 additions and 127 deletions

View file

@ -9,7 +9,7 @@ class Asset extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get checksum => text().unique()();
TextColumn get hash => text().unique()();
IntColumn get height => integer().nullable()();
IntColumn get width => integer().nullable()();
IntColumn get type => intEnum<AssetType>()();

View file

@ -1,27 +1,23 @@
import 'dart:async';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
abstract class IAssetRepository {
/// Batch insert asset
FutureOr<bool> addAll(Iterable<Asset> assets);
abstract interface class IAssetRepository {
/// Batch upsert asset
FutureOr<bool> upsertAll(Iterable<Asset> assets);
/// Removes assets with the [localIds]
FutureOr<List<Asset>> fetchLocalAssetsForIds(List<String> localIds);
FutureOr<List<Asset>> getForLocalIds(List<String> localIds);
/// Removes assets with the [remoteIds]
FutureOr<List<Asset>> fetchRemoteAssetsForIds(List<String> remoteIds);
/// Removes assets with the given [ids]
FutureOr<void> deleteAssetsForIds(List<int> ids);
/// Removes all assets
FutureOr<bool> clearAll();
FutureOr<List<Asset>> getForRemoteIds(List<String> remoteIds);
/// Fetch assets from the [offset] with the [limit]
FutureOr<List<Asset>> fetchAssets({int? offset, int? limit});
FutureOr<List<Asset>> getAll({int? offset, int? limit});
/// Streams assets as groups grouped by the group type passed
Stream<RenderList> watchRenderList();
/// Removes assets with the given [ids]
FutureOr<void> deleteIds(List<int> ids);
/// Removes all assets
FutureOr<bool> deleteAll();
}

View file

@ -2,19 +2,19 @@ import 'dart:async';
import 'package:immich_mobile/domain/models/log.model.dart';
abstract class ILogRepository {
/// Fetches all logs
FutureOr<List<LogMessage>> fetchAll();
abstract interface class ILogRepository {
/// Inserts a new log into the DB
FutureOr<bool> add(LogMessage log);
FutureOr<bool> create(LogMessage log);
/// Bulk insert logs into DB
FutureOr<bool> addAll(List<LogMessage> log);
FutureOr<bool> createAll(List<LogMessage> log);
/// Fetches all logs
FutureOr<List<LogMessage>> getAll();
/// Clears all logs
FutureOr<bool> clear();
FutureOr<bool> deleteAll();
/// Truncates the logs to the most recent [limit]. Defaults to recent 250 logs
FutureOr<void> truncateLogs({int limit = 250});
FutureOr<void> truncate({int limit = 250});
}

View file

@ -0,0 +1,6 @@
import 'package:immich_mobile/domain/models/render_list.model.dart';
abstract interface class IRenderListRepository {
/// Streams the [RenderList] for the main timeline
Stream<RenderList> watchAll();
}

View file

@ -12,16 +12,16 @@ abstract class IStoreConverter<T, U> {
FutureOr<T?> fromPrimitive(U value);
}
abstract class IStoreRepository {
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key);
abstract interface class IStoreRepository {
FutureOr<bool> upsert<T, U>(StoreKey<T, U> key, T value);
FutureOr<T> get<T, U>(StoreKey<T, U> key);
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value);
FutureOr<void> delete(StoreKey key);
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key);
Stream<T?> watch<T, U>(StoreKey<T, U> key);
FutureOr<void> clearStore();
FutureOr<void> delete(StoreKey key);
FutureOr<void> deleteAll();
}

View file

@ -2,10 +2,10 @@ import 'dart:async';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract class IUserRepository {
/// Fetches user
FutureOr<User?> fetch(String userId);
abstract interface class IUserRepository {
/// Insert user
FutureOr<bool> add(User user);
FutureOr<bool> upsert(User user);
/// Fetches user
FutureOr<User?> getForId(String userId);
}

View file

@ -14,7 +14,7 @@ enum AssetType {
class Asset {
final int id;
final String name;
final String checksum;
final String hash;
final int? height;
final int? width;
final AssetType type;
@ -36,7 +36,7 @@ class Asset {
const Asset({
required this.id,
required this.name,
required this.checksum,
required this.hash,
this.height,
this.width,
required this.type,
@ -55,7 +55,7 @@ class Asset {
duration: dto.duration.tryParseInt() ?? 0,
height: dto.exifInfo?.exifImageHeight?.toInt(),
width: dto.exifInfo?.exifImageWidth?.toInt(),
checksum: dto.checksum,
hash: dto.checksum,
name: dto.originalFileName,
livePhotoVideoId: dto.livePhotoVideoId,
modifiedTime: dto.fileModifiedAt,
@ -65,7 +65,7 @@ class Asset {
Asset copyWith({
int? id,
String? name,
String? checksum,
String? hash,
int? height,
int? width,
AssetType? type,
@ -79,7 +79,7 @@ class Asset {
return Asset(
id: id ?? this.id,
name: name ?? this.name,
checksum: checksum ?? this.checksum,
hash: hash ?? this.hash,
height: height ?? this.height,
width: width ?? this.width,
type: type ?? this.type,
@ -119,7 +119,7 @@ class Asset {
"remoteId": "${remoteId ?? "-"}",
"localId": "${localId ?? "-"}",
"name": "$name",
"checksum": "$checksum",
"hash": "$hash",
"height": ${height ?? "-"},
"width": ${width ?? "-"},
"type": "$type",
@ -135,7 +135,7 @@ class Asset {
return other.id == id &&
other.name == name &&
other.checksum == checksum &&
other.hash == hash &&
other.height == height &&
other.width == width &&
other.type == type &&
@ -151,7 +151,7 @@ class Asset {
int get hashCode {
return id.hashCode ^
name.hashCode ^
checksum.hashCode ^
hash.hashCode ^
height.hashCode ^
width.hashCode ^
type.hashCode ^

View file

@ -4,19 +4,16 @@ import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/entities/asset.entity.drift.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
class AssetDriftRepository with LogMixin implements IAssetRepository {
final DriftDatabaseRepository _db;
const RemoteAssetDriftRepository(this._db);
const AssetDriftRepository(this._db);
@override
Future<bool> addAll(Iterable<Asset> assets) async {
Future<bool> upsertAll(Iterable<Asset> assets) async {
try {
await _db.batch((batch) => batch.insertAllOnConflictUpdate(
_db.asset,
@ -31,7 +28,7 @@ class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
}
@override
Future<bool> clearAll() async {
Future<bool> deleteAll() async {
try {
await _db.asset.deleteAll();
return true;
@ -42,7 +39,7 @@ class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
}
@override
Future<List<Asset>> fetchAssets({int? offset, int? limit}) async {
Future<List<Asset>> getAll({int? offset, int? limit}) async {
final query = _db.asset.select()
..orderBy([(asset) => OrderingTerm.desc(asset.createdTime)]);
@ -54,40 +51,7 @@ class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
}
@override
Stream<RenderList> watchRenderList() {
final assetCountExp = _db.asset.id.count();
final createdTimeExp = _db.asset.createdTime;
final monthYearExp = _db.asset.createdTime.strftime('%m-%Y');
final query = _db.asset.selectOnly()
..addColumns([assetCountExp, createdTimeExp])
..groupBy([monthYearExp])
..orderBy([OrderingTerm.desc(createdTimeExp)]);
int lastAssetOffset = 0;
return query
.expand((row) {
final createdTime = row.read<DateTime>(createdTimeExp)!;
final assetCount = row.read(assetCountExp)!;
final assetOffset = lastAssetOffset;
lastAssetOffset += assetCount;
return [
RenderListMonthHeaderElement(date: createdTime),
RenderListAssetElement(
date: createdTime,
assetCount: assetCount,
assetOffset: assetOffset,
),
];
})
.watch()
.map((elements) => RenderList(elements: elements));
}
@override
Future<List<Asset>> fetchLocalAssetsForIds(List<String> localIds) async {
Future<List<Asset>> getForLocalIds(List<String> localIds) async {
final query = _db.asset.select()
..where((row) => row.localId.isIn(localIds))
..orderBy([(asset) => OrderingTerm.asc(asset.localId)]);
@ -96,7 +60,7 @@ class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
}
@override
Future<List<Asset>> fetchRemoteAssetsForIds(List<String> remoteIds) async {
Future<List<Asset>> getForRemoteIds(List<String> remoteIds) async {
final query = _db.asset.select()
..where((row) => row.remoteId.isIn(remoteIds))
..orderBy([(asset) => OrderingTerm.asc(asset.remoteId)]);
@ -105,7 +69,7 @@ class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
}
@override
FutureOr<void> deleteAssetsForIds(List<int> ids) async {
FutureOr<void> deleteIds(List<int> ids) async {
await _db.asset.deleteWhere((row) => row.id.isIn(ids));
}
}
@ -115,7 +79,7 @@ AssetCompanion _toEntity(Asset asset) {
localId: Value(asset.localId),
remoteId: Value(asset.remoteId),
name: asset.name,
checksum: asset.checksum,
hash: asset.hash,
height: Value(asset.height),
width: Value(asset.width),
type: asset.type,
@ -133,7 +97,7 @@ Asset _toModel(AssetData asset) {
remoteId: asset.remoteId,
name: asset.name,
type: asset.type,
checksum: asset.checksum,
hash: asset.hash,
createdTime: asset.createdTime,
modifiedTime: asset.modifiedTime,
height: asset.height,

View file

@ -13,12 +13,12 @@ class LogDriftRepository implements ILogRepository {
const LogDriftRepository(this._db);
@override
Future<List<LogMessage>> fetchAll() async {
Future<List<LogMessage>> getAll() async {
return await _db.managers.logs.map(_toModel).get();
}
@override
Future<void> truncateLogs({int limit = 250}) async {
Future<void> truncate({int limit = 250}) async {
final totalCount = await _db.managers.logs.count();
if (totalCount > limit) {
final rowsToDelete = totalCount - limit;
@ -30,7 +30,7 @@ class LogDriftRepository implements ILogRepository {
}
@override
FutureOr<bool> add(LogMessage log) async {
FutureOr<bool> create(LogMessage log) async {
try {
await _db.into(_db.logs).insert(LogsCompanion.insert(
content: log.content,
@ -48,7 +48,7 @@ class LogDriftRepository implements ILogRepository {
}
@override
FutureOr<bool> addAll(List<LogMessage> logs) async {
FutureOr<bool> createAll(List<LogMessage> logs) async {
try {
await _db.batch((b) {
b.insertAll(
@ -71,7 +71,7 @@ class LogDriftRepository implements ILogRepository {
}
@override
FutureOr<bool> clear() async {
FutureOr<bool> deleteAll() async {
try {
await _db.managers.logs.delete();
return true;

View file

@ -0,0 +1,46 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class RenderListDriftRepository with LogMixin implements IRenderListRepository {
final DriftDatabaseRepository _db;
const RenderListDriftRepository(this._db);
@override
Stream<RenderList> watchAll() {
final assetCountExp = _db.asset.id.count();
final createdTimeExp = _db.asset.createdTime;
final monthYearExp = _db.asset.createdTime.strftime('%m-%Y');
final query = _db.asset.selectOnly()
..addColumns([assetCountExp, createdTimeExp])
..groupBy([monthYearExp])
..orderBy([OrderingTerm.desc(createdTimeExp)]);
int lastAssetOffset = 0;
return query
.expand((row) {
final createdTime = row.read<DateTime>(createdTimeExp)!;
final assetCount = row.read(assetCountExp)!;
final assetOffset = lastAssetOffset;
lastAssetOffset += assetCount;
return [
RenderListMonthHeaderElement(date: createdTime),
RenderListAssetElement(
date: createdTime,
assetCount: assetCount,
assetOffset: assetOffset,
),
];
})
.watch()
.map((elements) => RenderList(elements: elements));
}
}

View file

@ -30,7 +30,7 @@ class StoreDriftRepository with LogMixin implements IStoreRepository {
}
@override
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value) async {
FutureOr<bool> upsert<T, U>(StoreKey<T, U> key, T value) async {
try {
final storeValue = key.converter.toPrimitive(value);
final intValue = (key.type == int) ? storeValue as int : null;
@ -61,7 +61,7 @@ class StoreDriftRepository with LogMixin implements IStoreRepository {
}
@override
FutureOr<void> clearStore() async {
FutureOr<void> deleteAll() async {
await _db.managers.store.delete();
}

View file

@ -13,7 +13,7 @@ class UserDriftRepository with LogMixin implements IUserRepository {
const UserDriftRepository(this._db);
@override
FutureOr<User?> fetch(String userId) async {
FutureOr<User?> getForId(String userId) async {
return await _db.managers.user
.filter((f) => f.id.equals(userId))
.map(_toModel)
@ -21,7 +21,7 @@ class UserDriftRepository with LogMixin implements IUserRepository {
}
@override
FutureOr<bool> add(User user) async {
FutureOr<bool> upsert(User user) async {
try {
await _db.into(_db.user).insertOnConflictUpdate(
UserCompanion.insert(

View file

@ -6,16 +6,16 @@ class AppSettingService {
const AppSettingService(this._store);
Future<T> getSetting<T>(AppSetting<T> setting) async {
Future<T> get<T>(AppSetting<T> setting) async {
final value = await _store.tryGet(setting.storeKey);
return value ?? setting.defaultValue;
}
Future<bool> setSetting<T>(AppSetting<T> setting, T value) async {
return await _store.set(setting.storeKey, value);
Future<bool> upsert<T>(AppSetting<T> setting, T value) async {
return await _store.upsert(setting.storeKey, value);
}
Stream<T> watchSetting<T>(AppSetting<T> setting) {
Stream<T> watch<T>(AppSetting<T> setting) {
return _store
.watch(setting.storeKey)
.map((value) => value ?? setting.defaultValue);

View file

@ -16,7 +16,7 @@ import 'package:openapi/api.dart';
class AssetSyncService with LogMixin {
const AssetSyncService();
Future<bool> doFullRemoteSyncForUserDrift(
Future<bool> performFullRemoteSyncForUser(
User user, {
DateTime? updatedUtil,
int? limit,
@ -49,12 +49,11 @@ class AssetSyncService with LogMixin {
final assetsFromServer =
assets.map(Asset.remote).sorted(Asset.compareByRemoteId);
final assetsInDb =
await di<IAssetRepository>().fetchRemoteAssetsForIds(
final assetsInDb = await di<IAssetRepository>().getForRemoteIds(
assetsFromServer.map((a) => a.remoteId!).toList(),
);
await _syncAssetsToDbDrift(
await _syncAssetsToDb(
assetsFromServer,
assetsInDb,
Asset.compareByRemoteId,
@ -73,7 +72,7 @@ class AssetSyncService with LogMixin {
});
}
Future<void> _syncAssetsToDbDrift(
Future<void> _syncAssetsToDb(
List<Asset> newAssets,
List<Asset> existingAssets,
Comparator<Asset> compare, {
@ -88,9 +87,9 @@ class AssetSyncService with LogMixin {
final assetsToAdd = toAdd.followedBy(toUpdate);
await di<IAssetRepository>().addAll(assetsToAdd);
await di<IAssetRepository>().upsertAll(assetsToAdd);
await di<IAssetRepository>()
.deleteAssetsForIds(assetsToRemove.map((a) => a.id).toList());
.deleteIds(assetsToRemove.map((a) => a.id).toList());
}
/// Returns a triple (toAdd, toUpdate, toRemove)

View file

@ -107,7 +107,7 @@ class LoginService with LogMixin {
return null;
}
Future<bool> tryLoginFromSplash() async {
Future<bool> tryAutoLogin() async {
final serverEndpoint =
await di<IStoreRepository>().tryGet(StoreKey.serverEndpoint);
if (serverEndpoint == null) {

View file

@ -50,7 +50,7 @@ class StoreUserConverter extends IStoreConverter<User, String> {
@override
Future<User?> fromPrimitive(String value) async {
return await di<IUserRepository>().fetch(value);
return await di<IUserRepository>().getForId(value);
}
@override

View file

@ -37,7 +37,7 @@ class _ImSwitchListTileState<T> extends State<ImSwitchListTile<T>> {
final value = T != bool ? widget.toAppSetting!(enabled) : enabled as T;
if (value != null &&
await _appSettingService.setSetting(widget.setting, value) &&
await _appSettingService.upsert(widget.setting, value) &&
context.mounted) {
setState(() {
isEnabled = enabled;
@ -48,7 +48,7 @@ class _ImSwitchListTileState<T> extends State<ImSwitchListTile<T>> {
@override
void initState() {
super.initState();
_appSettingService.getSetting(widget.setting).then((value) {
_appSettingService.get(widget.setting).then((value) {
if (context.mounted) {
setState(() {
isEnabled = T != bool ? widget.fromAppSetting!(value) : value as bool;

View file

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
import 'package:immich_mobile/presentation/components/grid/immich_asset_grid.state.dart';
import 'package:immich_mobile/presentation/components/grid/immich_asset_grid.widget.dart';
import 'package:immich_mobile/service_locator.dart';
@ -15,8 +16,8 @@ class HomePage extends StatelessWidget {
return Scaffold(
body: BlocProvider(
create: (_) => ImmichAssetGridCubit(
renderStream: di<IAssetRepository>().watchRenderList(),
assetProvider: di<IAssetRepository>().fetchAssets,
renderStream: di<IRenderListRepository>().watchAll(),
assetProvider: di<IAssetRepository>().getAll,
),
child: const ImAssetGrid(),
),

View file

@ -65,7 +65,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
// Check for /.well-known/immich
url = await loginService.resolveEndpoint(uri);
di<IStoreRepository>().set(StoreKey.serverEndpoint, url);
di<IStoreRepository>().upsert(StoreKey.serverEndpoint, url);
ServiceLocator.registerApiClient(url);
ServiceLocator.registerPostValidationServices();
ServiceLocator.registerPostGlobalStates();
@ -123,7 +123,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
}
Future<void> _postLogin(String accessToken) async {
await di<IStoreRepository>().set(StoreKey.accessToken, accessToken);
await di<IStoreRepository>().upsert(StoreKey.accessToken, accessToken);
/// Set token to interceptor
await di<ImmichApiClient>().init(accessToken: accessToken);
@ -136,10 +136,10 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
// Register user
ServiceLocator.registerCurrentUser(user);
await di<IUserRepository>().add(user);
await di<IUserRepository>().upsert(user);
// Remove and Sync assets in background
await di<IAssetRepository>().clearAll();
unawaited(di<AssetSyncService>().doFullRemoteSyncForUserDrift(user));
await di<IAssetRepository>().deleteAll();
unawaited(di<AssetSyncService>().performFullRemoteSyncForUser(user));
emit(state.copyWith(
isValidationInProgress: false,

View file

@ -10,9 +10,8 @@ class AppThemeCubit extends Cubit<AppTheme> {
late final StreamSubscription _appSettingSubscription;
AppThemeCubit(this._appSettings) : super(AppTheme.blue) {
_appSettingSubscription = _appSettings
.watchSetting(AppSetting.appTheme)
.listen((theme) => emit(theme));
_appSettingSubscription =
_appSettings.watch(AppSetting.appTheme).listen((theme) => emit(theme));
}
@override

View file

@ -3,8 +3,10 @@ import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/services/asset_sync.service.dart';
import 'package:immich_mobile/domain/services/login.service.dart';
import 'package:immich_mobile/presentation/components/image/immich_logo.widget.dart';
import 'package:immich_mobile/presentation/modules/common/states/current_user.state.dart';
import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart';
import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/service_locator.dart';
@ -49,7 +51,9 @@ class _SplashScreenState extends State<SplashScreenPage>
}
Future<void> _tryLogin() async {
if (await di<LoginService>().tryLoginFromSplash() && mounted) {
if (await di<LoginService>().tryAutoLogin() && mounted) {
unawaited(di<AssetSyncService>()
.performFullRemoteSyncForUser(di<CurrentUserCubit>().state));
unawaited(context.replaceRoute(const TabControllerRoute()));
} else {
unawaited(context.replaceRoute(const LoginRoute()));

View file

@ -69,7 +69,7 @@ class ServiceLocator {
_registerFactory<AppSettingService>(() => AppSettingService(di()));
_registerFactory<IUserRepository>(() => UserDriftRepository(di()));
_registerFactory<IAssetRepository>(
() => RemoteAssetDriftRepository(di()),
() => AssetDriftRepository(di()),
);
/// Services

View file

@ -53,7 +53,7 @@ class LogManager {
_timer = null;
final buffer = _msgBuffer;
_msgBuffer = [];
di<ILogRepository>().addAll(buffer);
di<ILogRepository>().createAll(buffer);
}
void init() {
@ -78,7 +78,7 @@ class LogManager {
_timer?.cancel();
_timer = null;
_msgBuffer.clear();
di<ILogRepository>().clear();
di<ILogRepository>().deleteAll();
}
static void setGlobalErrorCallbacks() {