From fdf4a3d336711bc7f201a4a3d788c1449e4a38ce Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Tue, 25 Feb 2025 17:15:45 +0530 Subject: [PATCH] [mob][photos] Unify all caching in cache service only --- mobile/lib/core/configuration.dart | 4 +- mobile/lib/models/smart_memory.dart | 12 +- .../lib/services/memories_cache_service.dart | 137 ++++++++++++++++-- mobile/lib/services/search_service.dart | 2 +- .../lib/services/smart_memories_service.dart | 70 +-------- .../lib/ui/home/memories/memories_widget.dart | 8 +- 6 files changed, 132 insertions(+), 101 deletions(-) diff --git a/mobile/lib/core/configuration.dart b/mobile/lib/core/configuration.dart index b27e153e4b..bd34f00b37 100644 --- a/mobile/lib/core/configuration.dart +++ b/mobile/lib/core/configuration.dart @@ -22,6 +22,7 @@ import 'package:photos/events/user_logged_out_event.dart'; import 'package:photos/models/key_attributes.dart'; import 'package:photos/models/key_gen_result.dart'; import 'package:photos/models/private_key_attributes.dart'; +import "package:photos/service_locator.dart"; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/favorites_service.dart'; import "package:photos/services/home_widget_service.dart"; @@ -29,7 +30,6 @@ import 'package:photos/services/ignored_files_service.dart'; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import 'package:photos/services/memories_service.dart'; import 'package:photos/services/search_service.dart'; -import "package:photos/services/smart_memories_service.dart"; import 'package:photos/services/sync_service.dart'; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/file_uploader.dart'; @@ -217,7 +217,7 @@ class Configuration { CollectionsService.instance.clearCache(); FavoritesService.instance.clearCache(); MemoriesService.instance.clearCache(); - SmartMemoriesService.instance.clearCache(); + unawaited(memoriesCacheService.clearMemoriesCache()); SearchService.instance.clearCache(); PersonService.instance.clearCache(); unawaited(HomeWidgetService.instance.clearHomeWidget()); diff --git a/mobile/lib/models/smart_memory.dart b/mobile/lib/models/smart_memory.dart index aa42e111f8..f85ea9be22 100644 --- a/mobile/lib/models/smart_memory.dart +++ b/mobile/lib/models/smart_memory.dart @@ -22,7 +22,7 @@ MemoryType memoryTypeFromString(String type) { } } -abstract class SmartMemory { +class SmartMemory { final List memories; final MemoryType type; String? name; @@ -33,13 +33,14 @@ abstract class SmartMemory { int? lastDateToShow; // TODO: lau: actually use this in calculated filters - SmartMemory( this.memories, this.type, { name, this.firstCreationTime, this.lastCreationTime, + this.firstDateToShow, + this.lastDateToShow, }) : name = name != null ? name + "(I)" : null; // TODO: lau: remove (I) from name when opening up the feature flag @@ -59,11 +60,4 @@ abstract class SmartMemory { lastCreationTime ??= creationTimes.last; return (firstCreationTime! + lastCreationTime!) ~/ 2; } - - SmartMemory copyWith({ - List? memories, - String? name, - int? firstCreationTime, - int? lastCreationTime, - }); } diff --git a/mobile/lib/services/memories_cache_service.dart b/mobile/lib/services/memories_cache_service.dart index 9517fcb4b7..9a78725489 100644 --- a/mobile/lib/services/memories_cache_service.dart +++ b/mobile/lib/services/memories_cache_service.dart @@ -1,15 +1,23 @@ +import "dart:async"; import "dart:convert"; import "dart:io" show File; import "package:flutter/foundation.dart" show kDebugMode; import "package:logging/logging.dart"; import "package:path_provider/path_provider.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/db/memories_db.dart"; +import "package:photos/events/files_updated_event.dart"; import "package:photos/extensions/stop_watch.dart"; +import "package:photos/models/file/file.dart"; import "package:photos/models/location/location.dart"; +import "package:photos/models/memory.dart"; import "package:photos/models/people_memory.dart"; import "package:photos/models/smart_memory.dart"; import "package:photos/models/trip_memory.dart"; +import "package:photos/service_locator.dart"; import "package:photos/services/location_service.dart"; +import "package:photos/services/search_service.dart"; import "package:photos/services/smart_memories_service.dart"; import "package:shared_preferences/shared_preferences.dart"; @@ -181,24 +189,50 @@ class TripsShownLogs { class MemoriesCacheService { static const _lastMemoriesCacheUpdateTimeKey = "lastMemoriesCacheUpdateTime"; + static const _showAnyMemoryKey = "memories.enabled"; /// Delay is for cache update to be done not during app init, during which a /// lot of other things are happening. static const _kCacheUpdateDelay = Duration(seconds: 10); - static const _kUpdateFrequency = Duration(days: 7); final SharedPreferences _prefs; late final Logger _logger = Logger("MemoriesCacheService"); - Future? _memoriesCacheFuture; + final _memoriesDB = MemoriesDB.instance; + + List? _cachedMemories; bool _shouldUpdate = false; bool _isUpdateInProgress = false; + late Map _seenTimes; + MemoriesCacheService(this._prefs) { _logger.fine("MemoriesCacheService constructor"); + Future.delayed(_kCacheUpdateDelay, () { - _updateCacheIfTheTimeHasCome(); + _checkIfTimeToUpdateCache(); + }); + + unawaited(_memoriesDB.getSeenTimes().then((value) => _seenTimes = value)); + unawaited( + _memoriesDB.clearMemoriesSeenBeforeTime( + DateTime.now().subtract(_kUpdateFrequency).microsecondsSinceEpoch, + ), + ); + + Bus.instance.on().where((event) { + return event.type == EventType.deletedFromEverywhere; + }).listen((event) { + if (_cachedMemories == null) return; + final generatedIDs = event.updatedFiles + .where((element) => element.generatedID != null) + .map((e) => e.generatedID!) + .toSet(); + for (final memory in _cachedMemories!) { + memory.memories + .removeWhere((m) => generatedIDs.contains(m.file.generatedID)); + } }); } @@ -213,7 +247,13 @@ class MemoriesCacheService { return _prefs.getInt(_lastMemoriesCacheUpdateTimeKey) ?? 0; } - Future _updateCacheIfTheTimeHasCome() async { + bool get showAnyMemories { + return _prefs.getBool(_showAnyMemoryKey) ?? true; + } + + bool get enableSmartMemories => flagService.showSmartMemories; + + Future _checkIfTimeToUpdateCache() async { if (lastMemoriesCacheUpdateTime < DateTime.now().subtract(_kUpdateFrequency).microsecondsSinceEpoch) { _shouldUpdate = true; @@ -226,8 +266,26 @@ class MemoriesCacheService { // TODO: lau: remove the test1 directory after testing } + Future markMemoryAsSeen(Memory memory) async { + memory.markSeen(); + await _memoriesDB.markMemoryAsSeen( + memory, + DateTime.now().microsecondsSinceEpoch, + ); + if (_cachedMemories != null && memory.file.generatedID != null) { + final generatedID = memory.file.generatedID!; + for (final smartMemory in _cachedMemories!) { + for (final mem in smartMemory.memories) { + if (mem.file.generatedID == generatedID) { + mem.markSeen(); + } + } + } + } + } + Future updateCache({bool forced = false}) async { - if (!SmartMemoriesService.instance.enableSmartMemories) { + if (!showAnyMemories || !enableSmartMemories) { return; } try { @@ -241,18 +299,18 @@ class MemoriesCacheService { _isUpdateInProgress = true; final EnteWatch? w = kDebugMode ? EnteWatch("memoriesCacheWatch") : null; w?.start(); - final memories = await SmartMemoriesService.instance.getMemories(null); + final memories = await SmartMemoriesService.instance.calcMemories(); w?.log("calculated new memories"); - final oldCache = await getMemoriesCache(); + final oldCache = await _readCacheFromDisk(); w?.log("gotten old cache"); final MemoriesCache memoryCache = - getCacheFromMemories(memories, oldCache); + _fromMemoriesToCache(memories, oldCache); w?.log("gotten cache from memories"); final file = File(await _getCachePath()); if (!file.existsSync()) { file.createSync(recursive: true); } - _memoriesCacheFuture = Future.value(memoryCache); + _cachedMemories = memories; await file.writeAsBytes( MemoriesCache.encodeToJsonString(memoryCache).codeUnits, ); @@ -267,7 +325,7 @@ class MemoriesCacheService { } } - MemoriesCache getCacheFromMemories( + MemoriesCache _fromMemoriesToCache( List memories, MemoriesCache? oldCache, ) { @@ -340,15 +398,61 @@ class MemoriesCacheService { ); } - Future getMemoriesCache() async { - if (_memoriesCacheFuture != null) { - return _memoriesCacheFuture!; + Future> _fromCacheToMemories(MemoriesCache cache) async { + final List memories = []; + final allFiles = Set.from( + await SearchService.instance.getAllFilesForSearch(), + ); + final allFileIdsToFile = {}; + for (final file in allFiles) { + if (file.uploadedFileID != null) { + allFileIdsToFile[file.uploadedFileID!] = file; + } } - _memoriesCacheFuture = _readResultFromDisk(); - return _memoriesCacheFuture!; + + for (final ToShowMemory memory in cache.toShowMemories) { + if (memory.shouldShowNow) { + memories.add( + SmartMemory( + memory.fileUploadedIDs + .map( + (fileID) => + Memory.fromFile(allFileIdsToFile[fileID]!, _seenTimes), + ) + .toList(), + memory.type, + name: memory.title, + firstDateToShow: memory.firstTimeToShow, + lastDateToShow: memory.lastTimeToShow, + ), + ); + } + } + return memories; } - Future _readResultFromDisk() async { + Future> _getMemoriesFromCache() async { + final cache = await _readCacheFromDisk(); + if (cache == null) { + // TODO: lau: if there's no cache, maybe we fall back to old memories? + return []; + } + final result = await _fromCacheToMemories(cache); + return result; + } + + Future> getMemories(int? limit) async { + if (!showAnyMemories) { + return []; + } + if (_cachedMemories != null) { + return _cachedMemories!; + } + _cachedMemories = await _getMemoriesFromCache(); + return _cachedMemories!; + } + + Future _readCacheFromDisk() async { _logger.info("Reading memories cache result from disk"); final file = File(await _getCachePath()); if (!file.existsSync()) { @@ -361,5 +465,6 @@ class MemoriesCacheService { Future clearMemoriesCache() async { await File(await _getCachePath()).delete(); + _cachedMemories = null; } } diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index 222246f815..312cba45be 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -1194,7 +1194,7 @@ class SearchService { BuildContext context, int? limit, ) async { - final memories = await SmartMemoriesService.instance.getMemories(limit); + final memories = await memoriesCacheService.getMemories(limit); final searchResults = []; for (final memory in memories) { final name = memory.name ?? "memory"; diff --git a/mobile/lib/services/smart_memories_service.dart b/mobile/lib/services/smart_memories_service.dart index 9f5ad6558f..11e3c52160 100644 --- a/mobile/lib/services/smart_memories_service.dart +++ b/mobile/lib/services/smart_memories_service.dart @@ -7,10 +7,8 @@ import "package:logging/logging.dart"; import "package:ml_linalg/vector.dart"; import "package:photos/core/configuration.dart"; import "package:photos/core/constants.dart"; -import "package:photos/core/event_bus.dart"; import "package:photos/db/memories_db.dart"; import "package:photos/db/ml/db.dart"; -import "package:photos/events/files_updated_event.dart"; import "package:photos/l10n/l10n.dart"; import "package:photos/models/base_location.dart"; import "package:photos/models/file/extensions/file_props.dart"; @@ -30,7 +28,6 @@ import "package:photos/services/location_service.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import "package:photos/services/machine_learning/ml_computer.dart"; import "package:photos/services/machine_learning/ml_result.dart"; -import "package:photos/services/memories_service.dart"; import "package:photos/services/search_service.dart"; class SmartMemoriesService { @@ -42,17 +39,12 @@ class SmartMemoriesService { Locale? _locale; late Map _seenTimes; - List? _cachedMemories; - Future>? _future; - Vector? _clipPositiveTextVector; static const String clipPositiveQuery = 'Photo of a precious and nostalgic memory radiating warmth, vibrant energy, or quiet beauty — alive with color, light, or emotion'; final Map _clipPeopleActivityVectors = {}; - static const int _calculationWindowDays = 14; - static const _clipSimilarImageThreshold = 0.75; static const _clipActivityQueryThreshold = 0.25; @@ -65,25 +57,6 @@ class SmartMemoriesService { if (_isInit) return; _locale = await getLocale(); - unawaited( - _memoriesDB.clearMemoriesSeenBeforeTime( - DateTime.now().microsecondsSinceEpoch - - (_calculationWindowDays * microSecondsInDay), - ), - ); - Bus.instance.on().where((event) { - return event.type == EventType.deletedFromEverywhere; - }).listen((event) { - if (_cachedMemories == null) return; - final generatedIDs = event.updatedFiles - .where((element) => element.generatedID != null) - .map((e) => e.generatedID!) - .toSet(); - for (final memory in _cachedMemories!) { - memory.memories - .removeWhere((m) => generatedIDs.contains(m.file.generatedID)); - } - }); unawaited( MLComputer.instance.runClipText(clipPositiveQuery).then((embedding) { _clipPositiveTextVector ??= Vector.fromList(embedding); @@ -103,47 +76,8 @@ class SmartMemoriesService { _logger.info("Smart memories service initialized"); } - bool get enableSmartMemories => flagService.showSmartMemories; - - void clearCache() { - _cachedMemories = null; - _future = null; - } - - Future markMemoryAsSeen(Memory memory) async { - memory.markSeen(); - await _memoriesDB.markMemoryAsSeen( - memory, - DateTime.now().microsecondsSinceEpoch, - ); - if (_cachedMemories != null && memory.file.generatedID != null) { - final generatedID = memory.file.generatedID!; - for (final smartMemory in _cachedMemories!) { - for (final mem in smartMemory.memories) { - if (mem.file.generatedID == generatedID) { - mem.markSeen(); - } - } - } - } - } - - Future> getMemories(int? limit) async { - if (!MemoriesService.instance.showMemories) { - return []; - } - if (_cachedMemories != null) { - return _cachedMemories!; - } - if (_future != null) { - return _future!; - } - _future = _calcMemories(); - return _future!; - } - // One general method to get all memories, which calls on internal methods for each separate memory type - Future> _calcMemories() async { + Future> calcMemories() async { try { await init(); final List memories = []; @@ -178,8 +112,6 @@ class SmartMemoriesService { // _deductUsedMemories(allFiles, fillerMemories); // memories.addAll(fillerMemories); // _logger.finest("All files length: ${allFiles.length}"); - - _cachedMemories = memories; return memories; } catch (e, s) { _logger.severe("Error calculating smart memories", e, s); diff --git a/mobile/lib/ui/home/memories/memories_widget.dart b/mobile/lib/ui/home/memories/memories_widget.dart index 76a74a0e72..e15a0c88b2 100644 --- a/mobile/lib/ui/home/memories/memories_widget.dart +++ b/mobile/lib/ui/home/memories/memories_widget.dart @@ -6,8 +6,8 @@ import "package:photos/core/event_bus.dart"; import "package:photos/events/memories_setting_changed.dart"; import 'package:photos/models/memory.dart'; import "package:photos/models/smart_memory.dart"; +import "package:photos/service_locator.dart"; import 'package:photos/services/memories_service.dart'; -import "package:photos/services/smart_memories_service.dart"; import "package:photos/ui/common/loading_widget.dart"; import 'package:photos/ui/home/memories/memory_cover_widget.dart'; @@ -57,11 +57,11 @@ class _MemoriesWidgetState extends State { if (!MemoriesService.instance.showMemories) { return const SizedBox.shrink(); } - if (SmartMemoriesService.instance.enableSmartMemories) { + if (memoriesCacheService.enableSmartMemories) { return FutureBuilder>( - future: SmartMemoriesService.instance.getMemories( + future: memoriesCacheService.getMemories( null, - ), // This is where I should hook in my feature flag [showSmartMemories] + ), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.isEmpty) { return const SizedBox.shrink();