mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor to per row store
This commit is contained in:
@@ -1,55 +0,0 @@
|
||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_kind.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_value.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
|
||||
class CachedMetadataRepository {
|
||||
final MetadataRepository _repository;
|
||||
final Map<MetadataKind, MetadataValue> _cache = {};
|
||||
|
||||
CachedMetadataRepository._(this._repository);
|
||||
|
||||
static CachedMetadataRepository? _instance;
|
||||
static CachedMetadataRepository get instance {
|
||||
if (_instance == null) {
|
||||
throw UnsupportedError('CachedMetadataRepository not initialized. Call ensureInitialized() first');
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
static Future<CachedMetadataRepository> ensureInitialized({required MetadataRepository repository}) async {
|
||||
if (_instance == null) {
|
||||
final instance = CachedMetadataRepository._(repository);
|
||||
await instance._hydrate();
|
||||
_instance = instance;
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<void> _hydrate() async {
|
||||
for (final kind in MetadataKind.values) {
|
||||
_cache[kind] = await _repository.get(kind);
|
||||
}
|
||||
}
|
||||
|
||||
T read<T extends MetadataValue>(MetadataKind<T> kind) => (_cache[kind] as T?) ?? kind.defaultValue;
|
||||
|
||||
Future<void> update<T extends MetadataValue>(MetadataKind<T> kind, T Function(T current) mutator) async {
|
||||
final current = read(kind);
|
||||
final updated = mutator(current);
|
||||
if (_cache[kind] == updated) return;
|
||||
await _repository.set(kind, updated);
|
||||
_cache[kind] = updated;
|
||||
}
|
||||
|
||||
Future<void> setAppConfig(AppConfig Function(AppConfig current) mutator) {
|
||||
return update(MetadataKind.appConfig, (c) => mutator.call(c));
|
||||
}
|
||||
|
||||
Future<void> clear<T extends MetadataValue>(MetadataKind<T> kind) async {
|
||||
await _repository.delete(kind);
|
||||
_cache[kind] = kind.defaultValue;
|
||||
}
|
||||
|
||||
Stream<T> watch<T extends MetadataValue>(MetadataKind<T> kind) => _repository.watch(kind);
|
||||
}
|
||||
@@ -1,43 +1,110 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_kind.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_value.dart';
|
||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/log_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
|
||||
class MetadataRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
final Map<MetadataKey, Object> _cache = {};
|
||||
|
||||
const MetadataRepository(this._db) : super(_db);
|
||||
MetadataRepository._(this._db) : super(_db);
|
||||
|
||||
Future<T> get<T extends MetadataValue>(MetadataKind<T> kind) async {
|
||||
final row = await (_db.select(_db.metadataEntity)..where((t) => t.key.equals(kind.key))).getSingleOrNull();
|
||||
return _toValue(kind, row) as T;
|
||||
static MetadataRepository? _instance;
|
||||
|
||||
static MetadataRepository get instance {
|
||||
final instance = _instance;
|
||||
if (instance == null) {
|
||||
throw StateError('MetadataRepository not initialized. Call ensureInitialized() first');
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
Future<void> set<T extends MetadataValue>(MetadataKind<T> kind, T value) async {
|
||||
static Future<MetadataRepository> ensureInitialized(Drift db) async {
|
||||
if (_instance == null) {
|
||||
final instance = MetadataRepository._(db);
|
||||
await instance._hydrate();
|
||||
_instance = instance;
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
static Future<void> refresh() async {
|
||||
instance._cache.clear();
|
||||
await instance._hydrate();
|
||||
}
|
||||
|
||||
Future<void> _hydrate() async {
|
||||
final rows = await _db.select(_db.metadataEntity).get();
|
||||
for (final row in rows) {
|
||||
final key = MetadataKey.fromKey(row.key);
|
||||
if (key != null) _cache[key] = _decode(key, row.value);
|
||||
}
|
||||
}
|
||||
|
||||
T _read<T extends Object>(MetadataKey<T> key) => (_cache[key] as T?) ?? key.defaultValue;
|
||||
|
||||
Future<void> write<T extends Object>(MetadataKey<T> key, T value) async {
|
||||
if (_read(key) == value) return;
|
||||
|
||||
await _db
|
||||
.into(_db.metadataEntity)
|
||||
.insertOnConflictUpdate(
|
||||
MetadataEntityCompanion.insert(
|
||||
key: kind.key,
|
||||
value: jsonEncode(value.toJson()),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
MetadataEntityCompanion.insert(key: key.key, value: _encode(value), updatedAt: Value(DateTime.now())),
|
||||
);
|
||||
_cache[key] = value;
|
||||
}
|
||||
|
||||
Future<void> delete<T extends MetadataValue>(MetadataKind<T> kind) async {
|
||||
await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(kind.key))).go();
|
||||
String _encode<T extends Object>(T value) => switch (value) {
|
||||
Enum() => value.name,
|
||||
DateTime() => value.toIso8601String(),
|
||||
_ => throw ArgumentError('Unsupported metadata value type: ${value.runtimeType}'),
|
||||
};
|
||||
|
||||
T _decode<T extends Object>(MetadataKey<T> key, String raw) {
|
||||
final enumValues = key.enumValues;
|
||||
if (enumValues != null) {
|
||||
return enumValues.where((v) => (v as Enum).name == raw).firstOrNull ?? key.defaultValue;
|
||||
}
|
||||
return switch (key.defaultValue) {
|
||||
DateTime() => (DateTime.tryParse(raw) ?? key.defaultValue) as T,
|
||||
_ => throw ArgumentError('Unsupported metadata value type: ${key.defaultValue.runtimeType}'),
|
||||
};
|
||||
}
|
||||
|
||||
Stream<T> watch<T extends MetadataValue>(MetadataKind<T> kind) {
|
||||
return (_db.select(
|
||||
_db.metadataEntity,
|
||||
)..where((t) => t.key.equals(kind.key))).watchSingleOrNull().map((row) => _toValue(kind, row) as T);
|
||||
Future<void> delete<T extends Object>(MetadataKey<T> key) async {
|
||||
_cache[key] = key.defaultValue;
|
||||
await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(key.key))).go();
|
||||
}
|
||||
|
||||
MetadataValue _toValue(MetadataKind kind, MetadataEntityData? row) =>
|
||||
row == null ? kind.defaultValue : kind.fromJson(jsonDecode(row.value) as Map<String, Object?>);
|
||||
Future<void> clearDomain(MetadataDomain domain) async {
|
||||
for (final k in MetadataKey.values.where((k) => k.domain == domain)) {
|
||||
_cache[k] = k.defaultValue;
|
||||
}
|
||||
|
||||
await (_db.delete(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'))).go();
|
||||
}
|
||||
|
||||
AppConfig get appConfig => AppConfig(theme: ThemeConfig(mode: _read(MetadataKey.themeMode)));
|
||||
|
||||
SystemConfig get systemConfig => SystemConfig(log: LogConfig(level: _read(MetadataKey.logLevel)));
|
||||
|
||||
Stream<AppConfig> watchAppConfig() => _watchDomain(MetadataDomain.appConfig).map((_) => appConfig).distinct();
|
||||
|
||||
Stream<SystemConfig> watchSystemConfig() =>
|
||||
_watchDomain(MetadataDomain.systemConfig).map((_) => systemConfig).distinct();
|
||||
|
||||
Stream<void> _watchDomain(MetadataDomain domain) {
|
||||
final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'));
|
||||
return query.watch().map((rows) => rows.forEach(_updateCacheForRow));
|
||||
}
|
||||
|
||||
void _updateCacheForRow(MetadataEntityData row) {
|
||||
final key = MetadataKey.fromKey(row.key);
|
||||
if (key == null) return;
|
||||
_cache[key] = _decode(key, row.value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user