immich/mobile/lib/domain/services/store.service.dart
luzpaz 46869f664d
fix: various typos (#22867)
Found via `codespell -q 3 -S "*.svg,./i18n,./docs/package-lock.json,./readme_i18n,./mobile/assets/i18n" -L afterall,devlop,finaly,inout,nd,optin,renderd,sade`
2025-10-13 15:25:23 +05:30

104 lines
3.6 KiB
Dart

import 'dart:async';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
/// Provides access to a persistent key-value store with an in-memory cache.
/// Listens for repository changes to keep the cache updated.
class StoreService {
final IStoreRepository _storeRepository;
/// In-memory cache. Keys are [StoreKey.id]
final Map<int, Object?> _cache = {};
StreamSubscription<List<StoreDto>>? _storeUpdateSubscription;
StoreService._({required IStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository;
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
static StoreService? _instance;
static StoreService get I {
if (_instance == null) {
throw UnsupportedError("StoreService not initialized. Call init() first");
}
return _instance!;
}
// TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({required IStoreRepository storeRepository, bool listenUpdates = true}) async {
_instance ??= await create(storeRepository: storeRepository, listenUpdates: listenUpdates);
return _instance!;
}
static Future<StoreService> create({required IStoreRepository storeRepository, bool listenUpdates = true}) async {
final instance = StoreService._(isarStoreRepository: storeRepository);
await instance.populateCache();
if (listenUpdates) {
instance._storeUpdateSubscription = instance._listenForChange();
}
return instance;
}
Future<void> populateCache() async {
final storeValues = await _storeRepository.getAll();
for (StoreDto storeValue in storeValues) {
_cache[storeValue.key.id] = storeValue.value;
}
}
StreamSubscription<List<StoreDto>> _listenForChange() => _storeRepository.watchAll().listen((events) {
for (final event in events) {
_cache[event.key.id] = event.value;
}
});
/// Disposes the store and cancels the subscription. To reuse the store call init() again
Future<void> dispose() async {
await _storeUpdateSubscription?.cancel();
_cache.clear();
}
/// Returns the cached value for [key], or `null`
T? tryGet<T>(StoreKey<T> key) => _cache[key.id] as T?;
/// Returns the stored value for [key] or [defaultValue].
/// Throws [StoreKeyNotFoundException] if value and [defaultValue] are null.
T get<T>(StoreKey<T> key, [T? defaultValue]) {
final value = tryGet(key) ?? defaultValue;
if (value == null) {
throw StoreKeyNotFoundException(key);
}
return value;
}
/// Stores the [value] for the [key]. Skips write if value hasn't changed.
Future<void> put<U extends StoreKey<T>, T>(U key, T value) async {
if (_cache[key.id] == value) return;
await _storeRepository.upsert(key, value);
_cache[key.id] = value;
}
/// Returns a stream that emits the value for [key] on change.
Stream<T?> watch<T>(StoreKey<T> key) => _storeRepository.watch(key);
/// Removes the value for [key]
Future<void> delete<T>(StoreKey<T> key) async {
await _storeRepository.delete(key);
_cache.remove(key.id);
}
/// Clears all values from the store (cache and DB)
Future<void> clear() async {
await _storeRepository.deleteAll();
_cache.clear();
}
bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? true;
}
class StoreKeyNotFoundException implements Exception {
final StoreKey key;
const StoreKeyNotFoundException(this.key);
@override
String toString() => "Key - <${key.name}> not available in Store";
}