|
@@ -1,210 +0,0 @@
|
|
|
-// ignore_for_file: depend_on_referenced_packages, implementation_imports
|
|
|
-
|
|
|
-import 'dart:io';
|
|
|
-import 'dart:math';
|
|
|
-
|
|
|
-import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
|
-import 'package:flutter_cache_manager/src/storage/cache_object.dart';
|
|
|
-import 'package:hive/hive.dart';
|
|
|
-import 'package:path/path.dart' as p;
|
|
|
-import 'package:path_provider/path_provider.dart';
|
|
|
-
|
|
|
-// Implementation of a CacheInfoRepository based on Hive
|
|
|
-abstract class ImmichCacheRepository extends CacheInfoRepository {
|
|
|
- int getNumberOfCachedObjects();
|
|
|
- int getCacheSize();
|
|
|
-}
|
|
|
-
|
|
|
-class ImmichCacheInfoRepository extends ImmichCacheRepository {
|
|
|
- final String hiveBoxName;
|
|
|
- final String keyLookupHiveBoxName;
|
|
|
-
|
|
|
- // To circumvent some of the limitations of a non-relational key-value database,
|
|
|
- // we use two hive boxes per cache.
|
|
|
- // [cacheObjectLookupBox] maps ids to cache objects.
|
|
|
- // [keyLookupHiveBox] maps keys to ids.
|
|
|
- // The lookup of a cache object by key therefore involves two steps:
|
|
|
- // id = keyLookupHiveBox[key]
|
|
|
- // object = cacheObjectLookupBox[id]
|
|
|
- late Box<Map<dynamic, dynamic>> cacheObjectLookupBox;
|
|
|
- late Box<int> keyLookupHiveBox;
|
|
|
-
|
|
|
- ImmichCacheInfoRepository(this.hiveBoxName, this.keyLookupHiveBoxName);
|
|
|
-
|
|
|
- @override
|
|
|
- Future<bool> close() async {
|
|
|
- await cacheObjectLookupBox.close();
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<int> delete(int id) async {
|
|
|
- if (cacheObjectLookupBox.containsKey(id)) {
|
|
|
- await cacheObjectLookupBox.delete(id);
|
|
|
- return 1;
|
|
|
- }
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<int> deleteAll(Iterable<int> ids) async {
|
|
|
- int deleted = 0;
|
|
|
- for (var id in ids) {
|
|
|
- if (cacheObjectLookupBox.containsKey(id)) {
|
|
|
- deleted++;
|
|
|
- await cacheObjectLookupBox.delete(id);
|
|
|
- }
|
|
|
- }
|
|
|
- return deleted;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<void> deleteDataFile() async {
|
|
|
- await cacheObjectLookupBox.clear();
|
|
|
- await keyLookupHiveBox.clear();
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<bool> exists() async {
|
|
|
- return cacheObjectLookupBox.isNotEmpty && keyLookupHiveBox.isNotEmpty;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<CacheObject?> get(String key) async {
|
|
|
- if (!keyLookupHiveBox.containsKey(key)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- int id = keyLookupHiveBox.get(key)!;
|
|
|
- if (!cacheObjectLookupBox.containsKey(id)) {
|
|
|
- keyLookupHiveBox.delete(key);
|
|
|
- return null;
|
|
|
- }
|
|
|
- return _deserialize(cacheObjectLookupBox.get(id)!);
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<List<CacheObject>> getAllObjects() async {
|
|
|
- return cacheObjectLookupBox.values.map(_deserialize).toList();
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<List<CacheObject>> getObjectsOverCapacity(int capacity) async {
|
|
|
- if (cacheObjectLookupBox.length <= capacity) {
|
|
|
- return List.empty();
|
|
|
- }
|
|
|
- var values = cacheObjectLookupBox.values.map(_deserialize).toList();
|
|
|
- values.sort((CacheObject a, CacheObject b) {
|
|
|
- final aTouched = a.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
|
|
- final bTouched = b.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
|
|
-
|
|
|
- return aTouched.compareTo(bTouched);
|
|
|
- });
|
|
|
- return values.skip(capacity).take(10).toList();
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<List<CacheObject>> getOldObjects(Duration maxAge) async {
|
|
|
- return cacheObjectLookupBox.values
|
|
|
- .map(_deserialize)
|
|
|
- .where((CacheObject element) {
|
|
|
- DateTime touched =
|
|
|
- element.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
|
|
- return touched.isBefore(DateTime.now().subtract(maxAge));
|
|
|
- }).toList();
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<CacheObject> insert(
|
|
|
- CacheObject cacheObject, {
|
|
|
- bool setTouchedToNow = true,
|
|
|
- }) async {
|
|
|
- int newId = keyLookupHiveBox.length == 0
|
|
|
- ? 0
|
|
|
- : keyLookupHiveBox.values.reduce(max) + 1;
|
|
|
- cacheObject = cacheObject.copyWith(id: newId);
|
|
|
-
|
|
|
- keyLookupHiveBox.put(cacheObject.key, newId);
|
|
|
- cacheObjectLookupBox.put(newId, cacheObject.toMap());
|
|
|
-
|
|
|
- return cacheObject;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<bool> open() async {
|
|
|
- cacheObjectLookupBox = await Hive.openBox(hiveBoxName);
|
|
|
- keyLookupHiveBox = await Hive.openBox(keyLookupHiveBoxName);
|
|
|
-
|
|
|
- // The cache might have cleared by the operating system.
|
|
|
- // This could create inconsistencies between the file system cache and database.
|
|
|
- // To check whether the cache was cleared, a file within the cache directory
|
|
|
- // is created for each database. If the file is absent, the cache was cleared and therefore
|
|
|
- // the database has to be cleared as well.
|
|
|
- if (!await _checkAndCreateAnchorFile()) {
|
|
|
- await cacheObjectLookupBox.clear();
|
|
|
- await keyLookupHiveBox.clear();
|
|
|
- }
|
|
|
-
|
|
|
- return cacheObjectLookupBox.isOpen;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future<int> update(
|
|
|
- CacheObject cacheObject, {
|
|
|
- bool setTouchedToNow = true,
|
|
|
- }) async {
|
|
|
- if (cacheObject.id != null) {
|
|
|
- cacheObjectLookupBox.put(cacheObject.id, cacheObject.toMap());
|
|
|
- return 1;
|
|
|
- }
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Future updateOrInsert(CacheObject cacheObject) {
|
|
|
- if (cacheObject.id == null) {
|
|
|
- return insert(cacheObject);
|
|
|
- } else {
|
|
|
- return update(cacheObject);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- int getNumberOfCachedObjects() {
|
|
|
- return cacheObjectLookupBox.length;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- int getCacheSize() {
|
|
|
- final cacheElementsWithSize =
|
|
|
- cacheObjectLookupBox.values.map(_deserialize).map((e) => e.length ?? 0);
|
|
|
-
|
|
|
- if (cacheElementsWithSize.isEmpty) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- return cacheElementsWithSize.reduce((value, element) => value + element);
|
|
|
- }
|
|
|
-
|
|
|
- CacheObject _deserialize(Map serData) {
|
|
|
- Map<String, dynamic> converted = {};
|
|
|
-
|
|
|
- serData.forEach((key, value) {
|
|
|
- converted[key.toString()] = value;
|
|
|
- });
|
|
|
-
|
|
|
- return CacheObject.fromMap(converted);
|
|
|
- }
|
|
|
-
|
|
|
- Future<bool> _checkAndCreateAnchorFile() async {
|
|
|
- final tmpDir = await getTemporaryDirectory();
|
|
|
- final cacheFile = File(p.join(tmpDir.path, "$hiveBoxName.tmp"));
|
|
|
-
|
|
|
- if (await cacheFile.exists()) {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- await cacheFile.create();
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-}
|