From 11562903775971d4bd735e740548d7491c793fb3 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Fri, 14 Oct 2022 23:57:55 +0200 Subject: [PATCH 1/8] Add asset response cache --- mobile/lib/constants/hive_box.dart | 4 + mobile/lib/main.dart | 1 + .../home/services/asset_cache.service.dart | 84 +++++++++++++++++++ .../lib/shared/providers/asset.provider.dart | 13 ++- 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 mobile/lib/modules/home/services/asset_cache.service.dart diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index 7faf6555f69d0..4b69f6d7539c8 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -25,3 +25,7 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box const String backupFailedSince = "immichBackupFailedSince"; // Key 1 const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2 const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3 + +// Asset cache +const String assetListCacheBox = "assetListCacheBox"; +const String assetListCachedAssets = "assetListCachedAssets"; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index ee5209b5c2371..22231e5eedaff 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -37,6 +37,7 @@ void main() async { await Hive.openBox(hiveBackupInfoBox); await Hive.openBox(hiveGithubReleaseInfoBox); await Hive.openBox(userSettingInfoBox); + await Hive.openBox(assetListCacheBox); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart new file mode 100644 index 0000000000000..694bb50c9b4b2 --- /dev/null +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -0,0 +1,84 @@ +import 'package:collection/collection.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:openapi/api.dart'; + +final assetCacheServiceProvider = Provider( + (ref) => AssetCacheService(), +); + +typedef CacheEntry = Map; +typedef CacheList = List; + +class AssetCacheService { + final _cacheBox = Hive.box(assetListCacheBox); + + bool isValid() { + return _cacheBox.containsKey(assetListCachedAssets) && _rawGet().isNotEmpty; + } + + void putAssets(List assets) { + _rawPut(assets.map((e) => _serialize(e)).toList()); + } + + List getAssets() { + return _rawGet().map((e) => _deserialize(e)).whereNotNull().toList(); + } + + Future> getAssetsAsync() async { + return Future.microtask(() => getAssets()); + } + + List _rawGet() { + return _cacheBox.get(assetListCachedAssets) as List; + } + + void _rawPut(CacheList data) { + _cacheBox.put(assetListCachedAssets, data); + } + + CacheEntry _serialize(AssetResponseDto a) { + return { + "id": a.id, + "cat": a.createdAt, + "did": a.deviceAssetId, + "oid": a.ownerId, + "dev": a.deviceId, + "dur": a.duration, + "mat": a.modifiedAt, + "opa": a.originalPath, + "typ": a.type.value, + "exif": a.exifInfo?.toJson(), + "fav": a.isFavorite, + "evp": a.encodedVideoPath, + "mim": a.mimeType, + "rsp": a.resizePath, + "wbp": a.webpPath, + }; + } + + AssetResponseDto? _deserialize(CacheEntry map) { + try { + return AssetResponseDto( + type: AssetTypeEnum.values + .firstWhere((element) => element.value == map["typ"]), + id: map["id"], + deviceAssetId: map["did"], + ownerId: map["oid"], + deviceId: map["dev"], + originalPath: map["opa"], + resizePath: map["rsp"], + createdAt: map["cat"], + modifiedAt: map["mat"], + isFavorite: map["fav"], + mimeType: map["mim"], + duration: map["dur"], + webpPath: map["wbp"], + encodedVideoPath: map["evp"], + ); + } catch (e) { + return null; + } + } +} diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 6329995ade07d..5b09935df6bb7 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/services/asset.service.dart'; +import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; import 'package:immich_mobile/shared/services/device_info.service.dart'; import 'package:collection/collection.dart'; import 'package:intl/intl.dart'; @@ -9,15 +10,22 @@ import 'package:photo_manager/photo_manager.dart'; class AssetNotifier extends StateNotifier> { final AssetService _assetService; + final AssetCacheService _assetCacheService; + final DeviceInfoService _deviceInfoService = DeviceInfoService(); - AssetNotifier(this._assetService) : super([]); + AssetNotifier(this._assetService, this._assetCacheService) : super([]); getAllAsset() async { + if (_assetCacheService.isValid() && state.isEmpty) { + state = await _assetCacheService.getAssetsAsync(); + } + var allAssets = await _assetService.getAllAsset(); if (allAssets != null) { state = allAssets; + _assetCacheService.putAssets(allAssets); } } @@ -70,7 +78,8 @@ class AssetNotifier extends StateNotifier> { final assetProvider = StateNotifierProvider>((ref) { - return AssetNotifier(ref.watch(assetServiceProvider)); + return AssetNotifier( + ref.watch(assetServiceProvider), ref.watch(assetCacheServiceProvider)); }); final assetGroupByDateTimeProvider = StateProvider((ref) { From 894eea739e96c4b5023678b3f635f3b7bb17f659 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Sat, 15 Oct 2022 23:20:15 +0200 Subject: [PATCH 2/8] JSON based caching --- .../home/services/asset_cache.service.dart | 80 ++++++------------- .../lib/shared/providers/asset.provider.dart | 10 ++- 2 files changed, 32 insertions(+), 58 deletions(-) diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index 694bb50c9b4b2..068acbafb92e6 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -1,4 +1,7 @@ +import 'dart:convert'; + import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/hive_box.dart'; @@ -8,77 +11,40 @@ final assetCacheServiceProvider = Provider( (ref) => AssetCacheService(), ); -typedef CacheEntry = Map; -typedef CacheList = List; - class AssetCacheService { final _cacheBox = Hive.box(assetListCacheBox); bool isValid() { - return _cacheBox.containsKey(assetListCachedAssets) && _rawGet().isNotEmpty; + return _cacheBox.containsKey(assetListCachedAssets) && + _cacheBox.get(assetListCachedAssets) is String; } void putAssets(List assets) { - _rawPut(assets.map((e) => _serialize(e)).toList()); - } + final mapList = assets.map((e) => e.toJson()).toList(); + final jsonString = json.encode(mapList); - List getAssets() { - return _rawGet().map((e) => _deserialize(e)).whereNotNull().toList(); + _cacheBox.put(assetListCachedAssets, jsonString); } - Future> getAssetsAsync() async { - return Future.microtask(() => getAssets()); - } + List getAssets() { + try { + final jsonString = _cacheBox.get(assetListCachedAssets); + final mapList = json.decode(jsonString) as List; - List _rawGet() { - return _cacheBox.get(assetListCachedAssets) as List; - } + final responseData = mapList + .map((e) => AssetResponseDto.fromJson(e)) + .whereNotNull() + .toList(); - void _rawPut(CacheList data) { - _cacheBox.put(assetListCachedAssets, data); - } + return responseData; + } catch (e) { + debugPrint(e.toString()); - CacheEntry _serialize(AssetResponseDto a) { - return { - "id": a.id, - "cat": a.createdAt, - "did": a.deviceAssetId, - "oid": a.ownerId, - "dev": a.deviceId, - "dur": a.duration, - "mat": a.modifiedAt, - "opa": a.originalPath, - "typ": a.type.value, - "exif": a.exifInfo?.toJson(), - "fav": a.isFavorite, - "evp": a.encodedVideoPath, - "mim": a.mimeType, - "rsp": a.resizePath, - "wbp": a.webpPath, - }; + return []; + } } - AssetResponseDto? _deserialize(CacheEntry map) { - try { - return AssetResponseDto( - type: AssetTypeEnum.values - .firstWhere((element) => element.value == map["typ"]), - id: map["id"], - deviceAssetId: map["did"], - ownerId: map["oid"], - deviceId: map["dev"], - originalPath: map["opa"], - resizePath: map["rsp"], - createdAt: map["cat"], - modifiedAt: map["mat"], - isFavorite: map["fav"], - mimeType: map["mim"], - duration: map["dur"], - webpPath: map["wbp"], - encodedVideoPath: map["evp"], - ); - } catch (e) { - return null; - } + Future> getAssetsAsync() async { + return Future.microtask(() => getAssets()); } } diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 5b09935df6bb7..4b354d4c24b6b 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -16,6 +16,10 @@ class AssetNotifier extends StateNotifier> { AssetNotifier(this._assetService, this._assetCacheService) : super([]); + _cacheState() { + _assetCacheService.putAssets(state); + } + getAllAsset() async { if (_assetCacheService.isValid() && state.isEmpty) { state = await _assetCacheService.getAssetsAsync(); @@ -25,16 +29,18 @@ class AssetNotifier extends StateNotifier> { if (allAssets != null) { state = allAssets; - _assetCacheService.putAssets(allAssets); + _cacheState(); } } clearAllAsset() { state = []; + _cacheState(); } onNewAssetUploaded(AssetResponseDto newAsset) { state = [...state, newAsset]; + _cacheState(); } deleteAssets(Set deleteAssets) async { @@ -73,6 +79,8 @@ class AssetNotifier extends StateNotifier> { state.where((immichAsset) => immichAsset.id != asset.id).toList(); } } + + _cacheState(); } } From 75d8ca13063946bed4fbb6823ae96cd2d1b9162f Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Sun, 16 Oct 2022 09:50:31 +0200 Subject: [PATCH 3/8] Invalidation on logout and timing measurements --- .../modules/home/services/asset_cache.service.dart | 4 ++++ .../login/providers/authentication.provider.dart | 6 +++++- mobile/lib/shared/providers/asset.provider.dart | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index 068acbafb92e6..f5250f38e7e3c 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -19,6 +19,10 @@ class AssetCacheService { _cacheBox.get(assetListCachedAssets) is String; } + void invalidate() { + _cacheBox.clear(); + } + void putAssets(List assets) { final mapList = assets.map((e) => e.toJson()).toList(); final jsonString = json.encode(mapList); diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 2ccf616a7b0b4..f86a9735fc8f2 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; import 'package:immich_mobile/modules/backup/services/backup.service.dart'; @@ -15,6 +16,7 @@ class AuthenticationNotifier extends StateNotifier { this._deviceInfoService, this._backupService, this._apiService, + this._assetCacheService, ) : super( AuthenticationState( deviceId: "", @@ -41,6 +43,7 @@ class AuthenticationNotifier extends StateNotifier { final DeviceInfoService _deviceInfoService; final BackupService _backupService; final ApiService _apiService; + final AssetCacheService _assetCacheService; Future login( String email, @@ -151,7 +154,7 @@ class AuthenticationNotifier extends StateNotifier { Future logout() async { Hive.box(userInfoBox).delete(accessTokenKey); state = state.copyWith(isAuthenticated: false); - + _assetCacheService.invalidate(); return true; } @@ -197,5 +200,6 @@ final authenticationProvider = ref.watch(deviceInfoServiceProvider), ref.watch(backupServiceProvider), ref.watch(apiServiceProvider), + ref.watch(assetCacheServiceProvider), ); }); diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 4b354d4c24b6b..f0c90c74f1d60 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -21,15 +21,28 @@ class AssetNotifier extends StateNotifier> { } getAllAsset() async { + final stopwatch = Stopwatch(); + + if (_assetCacheService.isValid() && state.isEmpty) { + stopwatch.start(); state = await _assetCacheService.getAssetsAsync(); + debugPrint("Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms"); + stopwatch.reset(); } + stopwatch.start(); var allAssets = await _assetService.getAllAsset(); + debugPrint("Query assets from API: ${stopwatch.elapsedMilliseconds}ms"); + stopwatch.reset(); if (allAssets != null) { state = allAssets; + + stopwatch.start(); _cacheState(); + debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms"); + stopwatch.reset(); } } From d310c77fc8772cbd10f5775ebbbe1c653b1a72b9 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Mon, 17 Oct 2022 14:53:27 +0200 Subject: [PATCH 4/8] Add album list response caching --- mobile/lib/constants/hive_box.dart | 4 ++ mobile/lib/main.dart | 1 + .../album/providers/album.provider.dart | 22 +++++++- .../album/services/album_cache.service.dart | 38 +++++++++++++ .../home/services/asset_cache.service.dart | 54 +++++++++++++------ .../providers/authentication.provider.dart | 5 ++ .../lib/shared/providers/asset.provider.dart | 4 +- 7 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 mobile/lib/modules/album/services/album_cache.service.dart diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index 4b69f6d7539c8..e58ca2caf6f3c 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -29,3 +29,7 @@ const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3 // Asset cache const String assetListCacheBox = "assetListCacheBox"; const String assetListCachedAssets = "assetListCachedAssets"; + +// Album cache +const String albumListCacheBox = "albumListCacheBox"; +const String albumListCachedAssets = "albumListCachedAssets"; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 22231e5eedaff..2b79e4fbaee8f 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -38,6 +38,7 @@ void main() async { await Hive.openBox(hiveGithubReleaseInfoBox); await Hive.openBox(userSettingInfoBox); await Hive.openBox(assetListCacheBox); + await Hive.openBox(albumListCacheBox); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( diff --git a/mobile/lib/modules/album/providers/album.provider.dart b/mobile/lib/modules/album/providers/album.provider.dart index f86ffa3ee544f..85e3b8cbcd98d 100644 --- a/mobile/lib/modules/album/providers/album.provider.dart +++ b/mobile/lib/modules/album/providers/album.provider.dart @@ -1,22 +1,35 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; +import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; import 'package:openapi/api.dart'; class AlbumNotifier extends StateNotifier> { - AlbumNotifier(this._albumService) : super([]); + AlbumNotifier(this._albumService, this._albumCacheService) : super([]); final AlbumService _albumService; + final AlbumCacheService _albumCacheService; + + _cacheState() { + _albumCacheService.put(state); + } getAllAlbums() async { + + if (_albumCacheService.isValid() && state.isEmpty) { + state = await _albumCacheService.getAsync(); + } + List? albums = await _albumService.getAlbums(isShared: false); if (albums != null) { state = albums; + _cacheState(); } } deleteAlbum(String albumId) { state = state.where((album) => album.id != albumId).toList(); + _cacheState(); } Future createAlbum( @@ -28,6 +41,8 @@ class AlbumNotifier extends StateNotifier> { if (album != null) { state = [...state, album]; + _cacheState(); + return album; } return null; @@ -36,5 +51,8 @@ class AlbumNotifier extends StateNotifier> { final albumProvider = StateNotifierProvider>((ref) { - return AlbumNotifier(ref.watch(albumServiceProvider)); + return AlbumNotifier( + ref.watch(albumServiceProvider), + ref.watch(albumCacheServiceProvider), + ); }); diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart new file mode 100644 index 0000000000000..f4e5974afd557 --- /dev/null +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -0,0 +1,38 @@ + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; +import 'package:openapi/api.dart'; + +class AlbumCacheService extends JsonCache> { + AlbumCacheService() : super(albumListCacheBox, albumListCachedAssets); + + @override + void put(List data) { + putRawData(data.map((e) => e.toJson()).toList()); + } + + @override + List get() { + try { + final mapList = readRawData() as List; + + final responseData = mapList + .map((e) => AlbumResponseDto.fromJson(e)) + .whereNotNull() + .toList(); + + return responseData; + } catch (e) { + debugPrint(e.toString()); + return []; + } + } + +} + +final albumCacheServiceProvider = Provider( + (ref) => AlbumCacheService(), +); diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index f5250f38e7e3c..232ae68bfbb30 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -4,36 +4,55 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:http/http.dart'; import 'package:immich_mobile/constants/hive_box.dart'; import 'package:openapi/api.dart'; -final assetCacheServiceProvider = Provider( - (ref) => AssetCacheService(), -); +abstract class JsonCache { + final String boxName; + final String valueKey; + final Box _cacheBox; -class AssetCacheService { - final _cacheBox = Hive.box(assetListCacheBox); + JsonCache(this.boxName, this.valueKey) : _cacheBox = Hive.box(boxName); bool isValid() { - return _cacheBox.containsKey(assetListCachedAssets) && - _cacheBox.get(assetListCachedAssets) is String; + return _cacheBox.containsKey(valueKey) && _cacheBox.get(valueKey) is String; } void invalidate() { _cacheBox.clear(); } - void putAssets(List assets) { - final mapList = assets.map((e) => e.toJson()).toList(); - final jsonString = json.encode(mapList); + void putRawData(dynamic data) { + final jsonString = json.encode(data); + _cacheBox.put(valueKey, jsonString); + } + + dynamic readRawData() { + return json.decode(_cacheBox.get(valueKey)); + } + + void put(T data); - _cacheBox.put(assetListCachedAssets, jsonString); + T get(); + + Future getAsync() async { + return Future.microtask(() => get()); } +} - List getAssets() { +class AssetCacheService extends JsonCache> { + AssetCacheService() : super(assetListCacheBox, assetListCachedAssets); + + @override + void put(List data) { + putRawData(data.map((e) => e.toJson()).toList()); + } + + @override + List get() { try { - final jsonString = _cacheBox.get(assetListCachedAssets); - final mapList = json.decode(jsonString) as List; + final mapList = readRawData() as List; final responseData = mapList .map((e) => AssetResponseDto.fromJson(e)) @@ -48,7 +67,8 @@ class AssetCacheService { } } - Future> getAssetsAsync() async { - return Future.microtask(() => getAssets()); - } } + +final assetCacheServiceProvider = Provider( + (ref) => AssetCacheService(), +); diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index f86a9735fc8f2..8b8f039853244 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/hive_box.dart'; +import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; @@ -17,6 +18,7 @@ class AuthenticationNotifier extends StateNotifier { this._backupService, this._apiService, this._assetCacheService, + this._albumCacheService, ) : super( AuthenticationState( deviceId: "", @@ -44,6 +46,7 @@ class AuthenticationNotifier extends StateNotifier { final BackupService _backupService; final ApiService _apiService; final AssetCacheService _assetCacheService; + final AlbumCacheService _albumCacheService; Future login( String email, @@ -155,6 +158,7 @@ class AuthenticationNotifier extends StateNotifier { Hive.box(userInfoBox).delete(accessTokenKey); state = state.copyWith(isAuthenticated: false); _assetCacheService.invalidate(); + _albumCacheService.invalidate(); return true; } @@ -201,5 +205,6 @@ final authenticationProvider = ref.watch(backupServiceProvider), ref.watch(apiServiceProvider), ref.watch(assetCacheServiceProvider), + ref.watch(albumCacheServiceProvider), ); }); diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index f0c90c74f1d60..572f72c463e93 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -17,7 +17,7 @@ class AssetNotifier extends StateNotifier> { AssetNotifier(this._assetService, this._assetCacheService) : super([]); _cacheState() { - _assetCacheService.putAssets(state); + _assetCacheService.put(state); } getAllAsset() async { @@ -26,7 +26,7 @@ class AssetNotifier extends StateNotifier> { if (_assetCacheService.isValid() && state.isEmpty) { stopwatch.start(); - state = await _assetCacheService.getAssetsAsync(); + state = await _assetCacheService.getAsync(); debugPrint("Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms"); stopwatch.reset(); } From d08475d5af3ac334fb52d776ce824d5c720a3ccf Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Mon, 17 Oct 2022 16:40:51 +0200 Subject: [PATCH 5/8] Switch to lazyBox --- mobile/lib/constants/hive_box.dart | 4 ++-- mobile/lib/main.dart | 10 ++++++-- .../album/providers/album.provider.dart | 2 +- .../album/services/album_cache.service.dart | 4 ++-- .../home/services/asset_cache.service.dart | 23 ++++++++----------- .../lib/shared/providers/asset.provider.dart | 2 +- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index e58ca2caf6f3c..d36cfba48b747 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -27,9 +27,9 @@ const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2 const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3 // Asset cache -const String assetListCacheBox = "assetListCacheBox"; +const String assetListCacheBox = "assetListCacheBoxl"; const String assetListCachedAssets = "assetListCachedAssets"; // Album cache -const String albumListCacheBox = "albumListCacheBox"; +const String albumListCacheBox = "albumListCacheBoxl"; const String albumListCachedAssets = "albumListCachedAssets"; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 2b79e4fbaee8f..24b3cd2a79127 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -37,8 +37,14 @@ void main() async { await Hive.openBox(hiveBackupInfoBox); await Hive.openBox(hiveGithubReleaseInfoBox); await Hive.openBox(userSettingInfoBox); - await Hive.openBox(assetListCacheBox); - await Hive.openBox(albumListCacheBox); + + final sw = Stopwatch(); + sw.start(); + + await Hive.openLazyBox(assetListCacheBox); + await Hive.openLazyBox(albumListCacheBox); + + debugPrint("Hive box open took ${sw.elapsedMilliseconds} ms"); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( diff --git a/mobile/lib/modules/album/providers/album.provider.dart b/mobile/lib/modules/album/providers/album.provider.dart index 85e3b8cbcd98d..497377928640d 100644 --- a/mobile/lib/modules/album/providers/album.provider.dart +++ b/mobile/lib/modules/album/providers/album.provider.dart @@ -15,7 +15,7 @@ class AlbumNotifier extends StateNotifier> { getAllAlbums() async { if (_albumCacheService.isValid() && state.isEmpty) { - state = await _albumCacheService.getAsync(); + state = await _albumCacheService.get(); } List? albums = diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart index f4e5974afd557..856423bd0d136 100644 --- a/mobile/lib/modules/album/services/album_cache.service.dart +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -15,9 +15,9 @@ class AlbumCacheService extends JsonCache> { } @override - List get() { + Future> get() async { try { - final mapList = readRawData() as List; + final mapList = await readRawData() as List; final responseData = mapList .map((e) => AlbumResponseDto.fromJson(e)) diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index 232ae68bfbb30..f3f0845c21d2f 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -11,12 +11,12 @@ import 'package:openapi/api.dart'; abstract class JsonCache { final String boxName; final String valueKey; - final Box _cacheBox; + final LazyBox _cacheBox; - JsonCache(this.boxName, this.valueKey) : _cacheBox = Hive.box(boxName); + JsonCache(this.boxName, this.valueKey) : _cacheBox = Hive.lazyBox(boxName); bool isValid() { - return _cacheBox.containsKey(valueKey) && _cacheBox.get(valueKey) is String; + return _cacheBox.containsKey(valueKey) && _cacheBox.containsKey(valueKey); } void invalidate() { @@ -28,17 +28,13 @@ abstract class JsonCache { _cacheBox.put(valueKey, jsonString); } - dynamic readRawData() { - return json.decode(_cacheBox.get(valueKey)); + dynamic readRawData() async { + final data = await _cacheBox.get(valueKey); + return json.decode(data); } void put(T data); - - T get(); - - Future getAsync() async { - return Future.microtask(() => get()); - } + Future get(); } class AssetCacheService extends JsonCache> { @@ -50,9 +46,9 @@ class AssetCacheService extends JsonCache> { } @override - List get() { + Future> get() async { try { - final mapList = readRawData() as List; + final mapList = await readRawData() as List; final responseData = mapList .map((e) => AssetResponseDto.fromJson(e)) @@ -66,7 +62,6 @@ class AssetCacheService extends JsonCache> { return []; } } - } final assetCacheServiceProvider = Provider( diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 572f72c463e93..589a94a84de47 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -26,7 +26,7 @@ class AssetNotifier extends StateNotifier> { if (_assetCacheService.isValid() && state.isEmpty) { stopwatch.start(); - state = await _assetCacheService.getAsync(); + state = await _assetCacheService.get(); debugPrint("Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms"); stopwatch.reset(); } From 6796462b13f3c97d2c98d635677909fe3f3c2485 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Mon, 17 Oct 2022 18:02:43 +0200 Subject: [PATCH 6/8] Switch to plain fs based caching mechanism --- mobile/lib/constants/hive_box.dart | 8 ---- mobile/lib/main.dart | 8 ---- .../album/providers/album.provider.dart | 2 +- .../album/services/album_cache.service.dart | 3 +- .../home/services/asset_cache.service.dart | 44 +++++++++++++------ .../lib/shared/providers/asset.provider.dart | 2 +- 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart index d36cfba48b747..7faf6555f69d0 100644 --- a/mobile/lib/constants/hive_box.dart +++ b/mobile/lib/constants/hive_box.dart @@ -25,11 +25,3 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box const String backupFailedSince = "immichBackupFailedSince"; // Key 1 const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2 const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3 - -// Asset cache -const String assetListCacheBox = "assetListCacheBoxl"; -const String assetListCachedAssets = "assetListCachedAssets"; - -// Album cache -const String albumListCacheBox = "albumListCacheBoxl"; -const String albumListCachedAssets = "albumListCachedAssets"; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 24b3cd2a79127..ee5209b5c2371 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -38,14 +38,6 @@ void main() async { await Hive.openBox(hiveGithubReleaseInfoBox); await Hive.openBox(userSettingInfoBox); - final sw = Stopwatch(); - sw.start(); - - await Hive.openLazyBox(assetListCacheBox); - await Hive.openLazyBox(albumListCacheBox); - - debugPrint("Hive box open took ${sw.elapsedMilliseconds} ms"); - SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, diff --git a/mobile/lib/modules/album/providers/album.provider.dart b/mobile/lib/modules/album/providers/album.provider.dart index 497377928640d..9a098aa49b208 100644 --- a/mobile/lib/modules/album/providers/album.provider.dart +++ b/mobile/lib/modules/album/providers/album.provider.dart @@ -14,7 +14,7 @@ class AlbumNotifier extends StateNotifier> { getAllAlbums() async { - if (_albumCacheService.isValid() && state.isEmpty) { + if (await _albumCacheService.isValid() && state.isEmpty) { state = await _albumCacheService.get(); } diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart index 856423bd0d136..d7ed5b7334e57 100644 --- a/mobile/lib/modules/album/services/album_cache.service.dart +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -2,12 +2,11 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; import 'package:openapi/api.dart'; class AlbumCacheService extends JsonCache> { - AlbumCacheService() : super(albumListCacheBox, albumListCachedAssets); + AlbumCacheService() : super("album_cache"); @override void put(List data) { diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index f3f0845c21d2f..0ba669d4c1ff9 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -1,35 +1,51 @@ import 'dart:convert'; +import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'package:hive_flutter/hive_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; -import 'package:immich_mobile/constants/hive_box.dart'; import 'package:openapi/api.dart'; +import 'package:path_provider/path_provider.dart'; abstract class JsonCache { - final String boxName; - final String valueKey; - final LazyBox _cacheBox; + final String cacheFileName; - JsonCache(this.boxName, this.valueKey) : _cacheBox = Hive.lazyBox(boxName); + JsonCache(this.cacheFileName); - bool isValid() { - return _cacheBox.containsKey(valueKey) && _cacheBox.containsKey(valueKey); + Future _getCacheFile() async { + final basePath = await getTemporaryDirectory(); + final basePathName = basePath.path; + + final file = File("$basePathName/$cacheFileName.bin"); + + return file; + } + + Future isValid() async { + final file = await _getCacheFile(); + return await file.exists(); } - void invalidate() { - _cacheBox.clear(); + Future invalidate() async { + final file = await _getCacheFile(); + await file.delete(); } - void putRawData(dynamic data) { + Future putRawData(dynamic data) async { final jsonString = json.encode(data); - _cacheBox.put(valueKey, jsonString); + final file = await _getCacheFile(); + + if (!await file.exists()) { + await file.create(); + } + + await file.writeAsString(jsonString); } dynamic readRawData() async { - final data = await _cacheBox.get(valueKey); + final file = await _getCacheFile(); + final data = await file.readAsString(); return json.decode(data); } @@ -38,7 +54,7 @@ abstract class JsonCache { } class AssetCacheService extends JsonCache> { - AssetCacheService() : super(assetListCacheBox, assetListCachedAssets); + AssetCacheService() : super("asset_cache"); @override void put(List data) { diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 589a94a84de47..386d71cae0fcd 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -24,7 +24,7 @@ class AssetNotifier extends StateNotifier> { final stopwatch = Stopwatch(); - if (_assetCacheService.isValid() && state.isEmpty) { + if (await _assetCacheService.isValid() && state.isEmpty) { stopwatch.start(); state = await _assetCacheService.get(); debugPrint("Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms"); From d77e25425e57fc6921f393f6ffeadb9c03e7a397 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Tue, 18 Oct 2022 14:06:35 +0200 Subject: [PATCH 7/8] Add cache for shared albums --- .../providers/shared_album.provider.dart | 21 +++++++++++++++++-- .../album/services/album_cache.service.dart | 16 ++++++++++++-- .../providers/authentication.provider.dart | 4 ++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/mobile/lib/modules/album/providers/shared_album.provider.dart b/mobile/lib/modules/album/providers/shared_album.provider.dart index 202e8241f022a..c759f3263b7cd 100644 --- a/mobile/lib/modules/album/providers/shared_album.provider.dart +++ b/mobile/lib/modules/album/providers/shared_album.provider.dart @@ -1,12 +1,18 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; +import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; import 'package:openapi/api.dart'; class SharedAlbumNotifier extends StateNotifier> { - SharedAlbumNotifier(this._sharedAlbumService) : super([]); + SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService) : super([]); final AlbumService _sharedAlbumService; + final SharedAlbumCacheService _sharedAlbumCacheService; + + _cacheState() { + _sharedAlbumCacheService.put(state); + } Future createSharedAlbum( String albumName, @@ -22,6 +28,7 @@ class SharedAlbumNotifier extends StateNotifier> { if (newAlbum != null) { state = [...state, newAlbum]; + _cacheState(); } return newAlbum; @@ -33,16 +40,22 @@ class SharedAlbumNotifier extends StateNotifier> { } getAllSharedAlbums() async { + if (await _sharedAlbumCacheService.isValid() && state.isEmpty) { + state = await _sharedAlbumCacheService.get(); + } + List? sharedAlbums = await _sharedAlbumService.getAlbums(isShared: true); if (sharedAlbums != null) { state = sharedAlbums; + _cacheState(); } } deleteAlbum(String albumId) async { state = state.where((album) => album.id != albumId).toList(); + _cacheState(); } Future leaveAlbum(String albumId) async { @@ -50,6 +63,7 @@ class SharedAlbumNotifier extends StateNotifier> { if (res) { state = state.where((album) => album.id != albumId).toList(); + _cacheState(); return true; } else { return false; @@ -72,7 +86,10 @@ class SharedAlbumNotifier extends StateNotifier> { final sharedAlbumProvider = StateNotifierProvider>((ref) { - return SharedAlbumNotifier(ref.watch(albumServiceProvider)); + return SharedAlbumNotifier( + ref.watch(albumServiceProvider), + ref.watch(sharedAlbumCacheServiceProvider), + ); }); final sharedAlbumDetailProvider = FutureProvider.autoDispose diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart index d7ed5b7334e57..4db41a37755c3 100644 --- a/mobile/lib/modules/album/services/album_cache.service.dart +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -5,8 +5,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; import 'package:openapi/api.dart'; -class AlbumCacheService extends JsonCache> { - AlbumCacheService() : super("album_cache"); +class BaseAlbumCacheService extends JsonCache> { + BaseAlbumCacheService(super.cacheFileName); @override void put(List data) { @@ -29,9 +29,21 @@ class AlbumCacheService extends JsonCache> { return []; } } +} + +class AlbumCacheService extends BaseAlbumCacheService { + AlbumCacheService() : super("album_cache"); +} +class SharedAlbumCacheService extends BaseAlbumCacheService { + SharedAlbumCacheService() : super("shared_album_cache"); } final albumCacheServiceProvider = Provider( (ref) => AlbumCacheService(), ); + +final sharedAlbumCacheServiceProvider = Provider( + (ref) => SharedAlbumCacheService(), +); + diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 8b8f039853244..4778ad3fcc2f8 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -19,6 +19,7 @@ class AuthenticationNotifier extends StateNotifier { this._apiService, this._assetCacheService, this._albumCacheService, + this._sharedAlbumCacheService, ) : super( AuthenticationState( deviceId: "", @@ -47,6 +48,7 @@ class AuthenticationNotifier extends StateNotifier { final ApiService _apiService; final AssetCacheService _assetCacheService; final AlbumCacheService _albumCacheService; + final SharedAlbumCacheService _sharedAlbumCacheService; Future login( String email, @@ -159,6 +161,7 @@ class AuthenticationNotifier extends StateNotifier { state = state.copyWith(isAuthenticated: false); _assetCacheService.invalidate(); _albumCacheService.invalidate(); + _sharedAlbumCacheService.invalidate(); return true; } @@ -206,5 +209,6 @@ final authenticationProvider = ref.watch(apiServiceProvider), ref.watch(assetCacheServiceProvider), ref.watch(albumCacheServiceProvider), + ref.watch(sharedAlbumCacheServiceProvider), ); }); From 36174338589b405e0d4fb917f34a208f08461535 Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Wed, 19 Oct 2022 22:03:54 +0200 Subject: [PATCH 8/8] Refactor abstract class to separate file --- .../album/services/album_cache.service.dart | 2 +- .../home/services/asset_cache.service.dart | 50 +------------------ mobile/lib/shared/services/json_cache.dart | 49 ++++++++++++++++++ 3 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 mobile/lib/shared/services/json_cache.dart diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart index 4db41a37755c3..0e16056585ba3 100644 --- a/mobile/lib/modules/album/services/album_cache.service.dart +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/modules/home/services/asset_cache.service.dart'; +import 'package:immich_mobile/shared/services/json_cache.dart'; import 'package:openapi/api.dart'; class BaseAlbumCacheService extends JsonCache> { diff --git a/mobile/lib/modules/home/services/asset_cache.service.dart b/mobile/lib/modules/home/services/asset_cache.service.dart index 0ba669d4c1ff9..9675938b3b2f6 100644 --- a/mobile/lib/modules/home/services/asset_cache.service.dart +++ b/mobile/lib/modules/home/services/asset_cache.service.dart @@ -1,57 +1,9 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:http/http.dart'; +import 'package:immich_mobile/shared/services/json_cache.dart'; import 'package:openapi/api.dart'; -import 'package:path_provider/path_provider.dart'; - -abstract class JsonCache { - final String cacheFileName; - - JsonCache(this.cacheFileName); - - Future _getCacheFile() async { - final basePath = await getTemporaryDirectory(); - final basePathName = basePath.path; - - final file = File("$basePathName/$cacheFileName.bin"); - return file; - } - - Future isValid() async { - final file = await _getCacheFile(); - return await file.exists(); - } - - Future invalidate() async { - final file = await _getCacheFile(); - await file.delete(); - } - - Future putRawData(dynamic data) async { - final jsonString = json.encode(data); - final file = await _getCacheFile(); - - if (!await file.exists()) { - await file.create(); - } - - await file.writeAsString(jsonString); - } - - dynamic readRawData() async { - final file = await _getCacheFile(); - final data = await file.readAsString(); - return json.decode(data); - } - - void put(T data); - Future get(); -} class AssetCacheService extends JsonCache> { AssetCacheService() : super("asset_cache"); diff --git a/mobile/lib/shared/services/json_cache.dart b/mobile/lib/shared/services/json_cache.dart new file mode 100644 index 0000000000000..b8a403abbaf2f --- /dev/null +++ b/mobile/lib/shared/services/json_cache.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:path_provider/path_provider.dart'; + +abstract class JsonCache { + final String cacheFileName; + + JsonCache(this.cacheFileName); + + Future _getCacheFile() async { + final basePath = await getTemporaryDirectory(); + final basePathName = basePath.path; + + final file = File("$basePathName/$cacheFileName.bin"); + + return file; + } + + Future isValid() async { + final file = await _getCacheFile(); + return await file.exists(); + } + + Future invalidate() async { + final file = await _getCacheFile(); + await file.delete(); + } + + Future putRawData(dynamic data) async { + final jsonString = json.encode(data); + final file = await _getCacheFile(); + + if (!await file.exists()) { + await file.create(); + } + + await file.writeAsString(jsonString); + } + + dynamic readRawData() async { + final file = await _getCacheFile(); + final data = await file.readAsString(); + return json.decode(data); + } + + void put(T data); + Future get(); +} \ No newline at end of file