From adb358ba5cb18806bd471d5272b0d7827984e5ad Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 10:11:18 +0530 Subject: [PATCH 01/15] [mob][photos] Write function in RemoteAssetService that returns the asset when given a remote path if it's different from the existing local copy of asset. Else return null --- .../lib/services/remote_assets_service.dart | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/mobile/lib/services/remote_assets_service.dart b/mobile/lib/services/remote_assets_service.dart index 487f2f8b11..7828a121bf 100644 --- a/mobile/lib/services/remote_assets_service.dart +++ b/mobile/lib/services/remote_assets_service.dart @@ -32,6 +32,35 @@ class RemoteAssetsService { } } + ///Returns asset if the remote asset is new compared to the local copy of it + Future getAssetIfUpdated(String remotePath) async { + try { + final path = await _getLocalPath(remotePath); + final file = File(path); + if (!file.existsSync()) { + final tempFile = File(path + ".temp"); + await _downloadFile(remotePath, tempFile.path); + tempFile.renameSync(path); + return File(path); + } else { + final existingFileSize = File(path).lengthSync(); + final tempFile = File(path + ".temp"); + await _downloadFile(remotePath, tempFile.path); + final newFileSize = tempFile.lengthSync(); + if (existingFileSize != newFileSize) { + tempFile.renameSync(path); + return File(path); + } else { + tempFile.deleteSync(); + return null; + } + } + } catch (e) { + _logger.warning("Error getting asset if updated", e); + return null; + } + } + Future hasAsset(String remotePath) async { final path = await _getLocalPath(remotePath); return File(path).exists(); @@ -60,8 +89,8 @@ class RemoteAssetsService { Future _downloadFile(String url, String savePath) async { _logger.info("Downloading " + url); final existingFile = File(savePath); - if (await existingFile.exists()) { - await existingFile.delete(); + if (existingFile.existsSync()) { + existingFile.deleteSync(); } await NetworkClient.instance.getDio().download( From f5873d2aded354a9e2c124fc08c0231456701b50 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 10:19:36 +0530 Subject: [PATCH 02/15] [mob][photos] MagicCache model and MagicCacheService --- mobile/lib/main.dart | 3 + .../semantic_search_service.dart | 43 ++++++ mobile/lib/services/magic_cache_service.dart | 142 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 mobile/lib/services/magic_cache_service.dart diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index f180f2bfff..cba068c51b 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -37,6 +37,7 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart'; import "package:photos/services/machine_learning/machine_learning_controller.dart"; import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; +import "package:photos/services/magic_cache_service.dart"; import 'package:photos/services/memories_service.dart'; import 'package:photos/services/push_service.dart'; import 'package:photos/services/remote_sync_service.dart'; @@ -303,6 +304,8 @@ Future _init(bool isBackground, {String via = ''}) async { preferences, ); + MagicCacheService.instance.init(preferences); + initComplete = true; _logger.info("Initialization done"); } catch (e, s) { diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index d65c67aba3..1de02434e3 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -277,6 +277,49 @@ class SemanticSearchService { return results; } + Future> getMatchingFileIDs(String query, double minScore) async { + final textEmbedding = await _getTextEmbedding(query); + + final queryResults = + await _getScores(textEmbedding, scoreThreshold: minScore); + + final queryResultIds = []; + for (QueryResult result in queryResults) { + queryResultIds.add(result.id); + } + + final filesMap = await FilesDB.instance.getFilesFromIDs( + queryResultIds, + ); + final results = []; + + final ignoredCollections = + CollectionsService.instance.getHiddenCollectionIds(); + final deletedEntries = []; + for (final result in queryResults) { + final file = filesMap[result.id]; + if (file != null && !ignoredCollections.contains(file.collectionID)) { + results.add(file); + } + if (file == null) { + deletedEntries.add(result.id); + } + } + + _logger.info(results.length.toString() + " results"); + + if (deletedEntries.isNotEmpty) { + unawaited(EmbeddingsDB.instance.deleteEmbeddings(deletedEntries)); + } + + final matchingFileIDs = []; + for (EnteFile file in results) { + matchingFileIDs.add(file.uploadedFileID!); + } + + return matchingFileIDs; + } + void _addToQueue(EnteFile file) { if (!LocalSettings.instance.hasEnabledMagicSearch()) { return; diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart new file mode 100644 index 0000000000..e02f07b77a --- /dev/null +++ b/mobile/lib/services/magic_cache_service.dart @@ -0,0 +1,142 @@ +import "dart:convert"; +import 'dart:math'; + +import "package:logging/logging.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/search/generic_search_result.dart"; +import "package:photos/models/search/search_types.dart"; +import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart"; +import "package:photos/services/remote_assets_service.dart"; +import "package:photos/services/search_service.dart"; +import "package:shared_preferences/shared_preferences.dart"; + +class MagicCache { + final String title; + final List fileUploadedIDs; + MagicCache(this.title, this.fileUploadedIDs); + + factory MagicCache.fromJson(Map json) { + return MagicCache( + json['title'], + List.from(json['fileUploadedIDs']), + ); + } + + Map toJson() { + return { + 'title': title, + 'fileUploadedIDs': fileUploadedIDs, + }; + } + + static String encodeListToJson(List magicCaches) { + final jsonList = magicCaches.map((cache) => cache.toJson()).toList(); + return jsonEncode(jsonList); + } + + static List decodeJsonToList(String jsonString) { + final jsonList = jsonDecode(jsonString) as List; + return jsonList.map((json) => MagicCache.fromJson(json)).toList(); + } +} + +extension MagicCacheServiceExtension on MagicCache { + Future toGenericSearchResult() async { + final allEnteFiles = await SearchService.instance.getAllFiles(); + final enteFilesInMagicCache = []; + for (EnteFile file in allEnteFiles) { + if (file.uploadedFileID != null && + fileUploadedIDs.contains(file.uploadedFileID as int)) { + enteFilesInMagicCache.add(file); + } + } + return GenericSearchResult( + ResultType.magic, + title, + enteFilesInMagicCache, + ); + } +} + +class MagicCacheService { + static const _key = "magic_cache"; + static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; + + late SharedPreferences prefs; + final Logger _logger = Logger((MagicCacheService).toString()); + MagicCacheService._privateConstructor(); + + static final MagicCacheService instance = + MagicCacheService._privateConstructor(); + + void init(SharedPreferences preferences) { + prefs = preferences; + _updateCacheIfNeeded(); + } + + Future _updateCacheIfNeeded() async { + final jsonFile = await RemoteAssetsService.instance + .getAssetIfUpdated(_kMagicPromptsDataUrl); + if (jsonFile == null) {} + } + + Future>> getMatchingFileIDsForPromptData( + Map promptData, + ) async { + final result = await SemanticSearchService.instance.getMatchingFileIDs( + promptData["prompt"] as String, + promptData["minimumScore"] as double, + ); + + return {promptData["title"] as String: result}; + } + + Future updateMagicCache() async { + final magicCaches = []; + await prefs.setString( + _key, + MagicCache.encodeListToJson(magicCaches), + ); + } + + Future?> getMagicCache() async { + final jsonString = prefs.getString(_key); + if (jsonString == null) { + _logger.info("No $_key in shared preferences"); + return null; + } + return MagicCache.decodeJsonToList(jsonString); + } + + Future clearMagicCache() async { + await prefs.remove(_key); + } + + Future> getMagicGenericSearchResult() async { + final magicCaches = await getMagicCache(); + if (magicCaches == null) { + _logger.info("No magic cache found"); + return []; + } + final List genericSearchResults = []; + for (MagicCache magicCache in magicCaches) { + final genericSearchResult = await magicCache.toGenericSearchResult(); + genericSearchResults.add(genericSearchResult); + } + return genericSearchResults; + } + + ///Generates from 0 to max non-repeating random numbers + List _generateUniqueRandomNumbers(int max, int count) { + final numbers = []; + for (int i = 1; i <= count;) { + final randomNumber = Random().nextInt(max + 1); + if (numbers.contains(randomNumber)) { + continue; + } + numbers.add(randomNumber); + i++; + } + return numbers; + } +} From 11666eeb3310afa165117739187d4fca52c11fda Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 15:28:31 +0530 Subject: [PATCH 03/15] [mob][photos] Write function that selected 4 prompts from list of prompts at random and returns them as MagicCache objects --- mobile/lib/services/magic_cache_service.dart | 58 ++++++++++++++----- .../lib/services/remote_assets_service.dart | 4 +- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index e02f07b77a..aba9d61533 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -1,5 +1,5 @@ +import "dart:async"; import "dart:convert"; -import 'dart:math'; import "package:logging/logging.dart"; import "package:photos/models/file/file.dart"; @@ -77,22 +77,23 @@ class MagicCacheService { Future _updateCacheIfNeeded() async { final jsonFile = await RemoteAssetsService.instance .getAssetIfUpdated(_kMagicPromptsDataUrl); - if (jsonFile == null) {} + if (jsonFile != null) {} } - Future>> getMatchingFileIDsForPromptData( - Map promptData, + Future> getMatchingFileIDsForPromptData( + Map promptData, ) async { final result = await SemanticSearchService.instance.getMatchingFileIDs( promptData["prompt"] as String, promptData["minimumScore"] as double, ); - return {promptData["title"] as String: result}; + return result; } Future updateMagicCache() async { - final magicCaches = []; + final magicPromptsData = await _loadMagicPrompts(); + final magicCaches = await nonEmptyMagicResults(magicPromptsData); await prefs.setString( _key, MagicCache.encodeListToJson(magicCaches), @@ -126,17 +127,42 @@ class MagicCacheService { return genericSearchResults; } - ///Generates from 0 to max non-repeating random numbers - List _generateUniqueRandomNumbers(int max, int count) { - final numbers = []; - for (int i = 1; i <= count;) { - final randomNumber = Random().nextInt(max + 1); - if (numbers.contains(randomNumber)) { - continue; + Future> _loadMagicPrompts() async { + final file = + await RemoteAssetsService.instance.getAsset(_kMagicPromptsDataUrl); + + final json = jsonDecode(await file.readAsString()); + return json["prompts"]; + } + + ///Returns random non-empty magic results from magicPromptsData + ///Length is capped at [limit], can be less than [limit] if there are not enough + ///non-empty results + Future> nonEmptyMagicResults( + List magicPromptsData, + ) async { + const limit = 4; + final results = []; + final randomIndexes = List.generate( + magicPromptsData.length, + (index) => index, + growable: false, + )..shuffle(); + for (final index in randomIndexes) { + final files = + await getMatchingFileIDsForPromptData(magicPromptsData[index]); + if (files.isNotEmpty) { + results.add( + MagicCache( + magicPromptsData[index]["title"] as String, + files, + ), + ); + } + if (results.length >= limit) { + break; } - numbers.add(randomNumber); - i++; } - return numbers; + return results; } } diff --git a/mobile/lib/services/remote_assets_service.dart b/mobile/lib/services/remote_assets_service.dart index 7828a121bf..b9bed09b50 100644 --- a/mobile/lib/services/remote_assets_service.dart +++ b/mobile/lib/services/remote_assets_service.dart @@ -21,13 +21,13 @@ class RemoteAssetsService { Future getAsset(String remotePath, {bool refetch = false}) async { final path = await _getLocalPath(remotePath); final file = File(path); - if (await file.exists() && !refetch) { + if (file.existsSync() && !refetch) { _logger.info("Returning cached file for $remotePath"); return file; } else { final tempFile = File(path + ".temp"); await _downloadFile(remotePath, tempFile.path); - await tempFile.rename(path); + tempFile.renameSync(path); return File(path); } } From 815a730d5940736c8cf0c345730112cac30d8668 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 15:29:56 +0530 Subject: [PATCH 04/15] [mob][photos] Schedule update magic cache in 10 seconds if remote asset has changed --- mobile/lib/services/magic_cache_service.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index aba9d61533..b1cdd6c8c9 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -77,7 +77,11 @@ class MagicCacheService { Future _updateCacheIfNeeded() async { final jsonFile = await RemoteAssetsService.instance .getAssetIfUpdated(_kMagicPromptsDataUrl); - if (jsonFile != null) {} + if (jsonFile != null) { + Future.delayed(const Duration(seconds: 10), () { + unawaited(updateMagicCache()); + }); + } } Future> getMatchingFileIDsForPromptData( From 98c444fab9f9ad4dfdba63a4e87cd9d8bc372c7d Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 15:43:16 +0530 Subject: [PATCH 05/15] [mob][photos] Make methods private --- mobile/lib/services/magic_cache_service.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index b1cdd6c8c9..59f3f69717 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -84,7 +84,7 @@ class MagicCacheService { } } - Future> getMatchingFileIDsForPromptData( + Future> _getMatchingFileIDsForPromptData( Map promptData, ) async { final result = await SemanticSearchService.instance.getMatchingFileIDs( @@ -104,7 +104,7 @@ class MagicCacheService { ); } - Future?> getMagicCache() async { + Future?> _getMagicCache() async { final jsonString = prefs.getString(_key); if (jsonString == null) { _logger.info("No $_key in shared preferences"); @@ -118,7 +118,7 @@ class MagicCacheService { } Future> getMagicGenericSearchResult() async { - final magicCaches = await getMagicCache(); + final magicCaches = await _getMagicCache(); if (magicCaches == null) { _logger.info("No magic cache found"); return []; @@ -154,7 +154,7 @@ class MagicCacheService { )..shuffle(); for (final index in randomIndexes) { final files = - await getMatchingFileIDsForPromptData(magicPromptsData[index]); + await _getMatchingFileIDsForPromptData(magicPromptsData[index]); if (files.isNotEmpty) { results.add( MagicCache( From 9cb4420bbfcf0ec68f0b0e2c9ff099aa40ce378e Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 15:43:56 +0530 Subject: [PATCH 06/15] [mob][photos] Show magic section in search tab from cached results --- mobile/lib/services/search_service.dart | 44 +++++++++---------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index e55684ed9b..50dcc1f0c7 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -1,4 +1,3 @@ -import "dart:convert"; import "dart:math"; import "package:flutter/cupertino.dart"; @@ -33,13 +32,14 @@ import "package:photos/services/location_service.dart"; import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; -import "package:photos/services/remote_assets_service.dart"; +import "package:photos/services/magic_cache_service.dart"; import "package:photos/states/location_screen_state.dart"; import "package:photos/ui/viewer/location/add_location_sheet.dart"; import "package:photos/ui/viewer/location/location_screen.dart"; import "package:photos/ui/viewer/people/cluster_page.dart"; import "package:photos/ui/viewer/people/people_page.dart"; import 'package:photos/utils/date_time_util.dart'; +import "package:photos/utils/local_settings.dart"; import "package:photos/utils/navigation_util.dart"; import 'package:tuple/tuple.dart'; @@ -49,9 +49,9 @@ class SearchService { final _logger = Logger((SearchService).toString()); final _collectionService = CollectionsService.instance; static const _maximumResultsLimit = 20; - static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; + // static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; - var magicPromptsData = []; + // var magicPromptsData = []; SearchService._privateConstructor(); @@ -64,17 +64,17 @@ class SearchService { _cachedHiddenFilesFuture = null; }); if (flagService.internalUser) { - _loadMagicPrompts(); + // _loadMagicPrompts(); } } - Future _loadMagicPrompts() async { - final file = await RemoteAssetsService.instance - .getAsset(_kMagicPromptsDataUrl, refetch: true); + // Future _loadMagicPrompts() async { + // final file = await RemoteAssetsService.instance + // .getAsset(_kMagicPromptsDataUrl, refetch: true); - final json = jsonDecode(await file.readAsString()); - magicPromptsData = json["prompts"]; - } + // final json = jsonDecode(await file.readAsString()); + // magicPromptsData = json["prompts"]; + // } Set ignoreCollections() { return CollectionsService.instance.getHiddenCollectionIds(); @@ -192,26 +192,12 @@ class SearchService { } Future> getMagicSectionResutls() async { - if (!SemanticSearchService.instance.isMagicSearchEnabledAndReady()) { + if (LocalSettings.instance.hasEnabledMagicSearch() && + flagService.internalUser) { + return MagicCacheService.instance.getMagicGenericSearchResult(); + } else { return []; } - final searchResuts = []; - for (Map magicPrompt in magicPromptsData) { - final files = await SemanticSearchService.instance.getMatchingFiles( - magicPrompt["prompt"], - scoreThreshold: magicPrompt["minimumScore"], - ); - if (files.isNotEmpty) { - searchResuts.add( - GenericSearchResult( - ResultType.magic, - magicPrompt["title"], - files, - ), - ); - } - } - return searchResuts; } Future> getRandomMomentsSearchResults( From 908e37f56fed47b2f3d8e4c6a8f4e33b70230480 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 16:48:25 +0530 Subject: [PATCH 07/15] [mob][photos] Update MagicCache every 3 days --- mobile/lib/services/magic_cache_service.dart | 64 +++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index 59f3f69717..40e7f9c1ae 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -5,6 +5,7 @@ import "package:logging/logging.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/search/generic_search_result.dart"; import "package:photos/models/search/search_types.dart"; +import "package:photos/service_locator.dart"; import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart"; import "package:photos/services/remote_assets_service.dart"; import "package:photos/services/search_service.dart"; @@ -60,9 +61,14 @@ extension MagicCacheServiceExtension on MagicCache { class MagicCacheService { static const _key = "magic_cache"; + static const _lastMagicCacheUpdateTime = "last_magic_cache_update_time"; static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; - late SharedPreferences prefs; + /// 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); + + late SharedPreferences _prefs; final Logger _logger = Logger((MagicCacheService).toString()); MagicCacheService._privateConstructor(); @@ -70,15 +76,35 @@ class MagicCacheService { MagicCacheService._privateConstructor(); void init(SharedPreferences preferences) { - prefs = preferences; - _updateCacheIfNeeded(); + _prefs = preferences; + _updateCacheIfTheTimeHasCome(); } - Future _updateCacheIfNeeded() async { + Future resetLastMagicCacheUpdateTime() async { + await _prefs.setInt( + _lastMagicCacheUpdateTime, + DateTime.now().millisecondsSinceEpoch, + ); + } + + int get lastMagicCacheUpdateTime { + return _prefs.getInt(_lastMagicCacheUpdateTime) ?? 0; + } + + Future _updateCacheIfTheTimeHasCome() async { final jsonFile = await RemoteAssetsService.instance .getAssetIfUpdated(_kMagicPromptsDataUrl); if (jsonFile != null) { - Future.delayed(const Duration(seconds: 10), () { + Future.delayed(_kCacheUpdateDelay, () { + unawaited(updateMagicCache()); + }); + return; + } + if (lastMagicCacheUpdateTime < + DateTime.now() + .subtract(const Duration(days: 3)) + .millisecondsSinceEpoch) { + Future.delayed(_kCacheUpdateDelay, () { unawaited(updateMagicCache()); }); } @@ -96,16 +122,25 @@ class MagicCacheService { } Future updateMagicCache() async { - final magicPromptsData = await _loadMagicPrompts(); - final magicCaches = await nonEmptyMagicResults(magicPromptsData); - await prefs.setString( - _key, - MagicCache.encodeListToJson(magicCaches), - ); + try { + _logger.info("updating magic cache"); + final magicPromptsData = await _loadMagicPrompts(); + final magicCaches = await nonEmptyMagicResults(magicPromptsData); + await _prefs + .setString( + _key, + MagicCache.encodeListToJson(magicCaches), + ) + .then((value) { + resetLastMagicCacheUpdateTime(); + }); + } catch (e) { + _logger.info("Error updating magic cache", e); + } } Future?> _getMagicCache() async { - final jsonString = prefs.getString(_key); + final jsonString = _prefs.getString(_key); if (jsonString == null) { _logger.info("No $_key in shared preferences"); return null; @@ -114,7 +149,7 @@ class MagicCacheService { } Future clearMagicCache() async { - await prefs.remove(_key); + await _prefs.remove(_key); } Future> getMagicGenericSearchResult() async { @@ -145,7 +180,8 @@ class MagicCacheService { Future> nonEmptyMagicResults( List magicPromptsData, ) async { - const limit = 4; + //Show all magic prompts to internal users for feedback on results + final limit = flagService.internalUser ? magicPromptsData.length : 6; final results = []; final randomIndexes = List.generate( magicPromptsData.length, From 84f2c6d102cd069ee3fbd583147d1efe9884c0e0 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 16:53:00 +0530 Subject: [PATCH 08/15] [mob][photos] Show 4 instead of 6 magic results in magic section for non-internal users --- mobile/lib/services/magic_cache_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index 40e7f9c1ae..bcfd1c43e6 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -102,7 +102,7 @@ class MagicCacheService { } if (lastMagicCacheUpdateTime < DateTime.now() - .subtract(const Duration(days: 3)) + .subtract(const Duration(seconds: 1)) .millisecondsSinceEpoch) { Future.delayed(_kCacheUpdateDelay, () { unawaited(updateMagicCache()); @@ -181,7 +181,7 @@ class MagicCacheService { List magicPromptsData, ) async { //Show all magic prompts to internal users for feedback on results - final limit = flagService.internalUser ? magicPromptsData.length : 6; + final limit = flagService.internalUser ? magicPromptsData.length : 4; final results = []; final randomIndexes = List.generate( magicPromptsData.length, From b99b2d1381f6283b234006b9c327b90e6c4971a6 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 16:55:36 +0530 Subject: [PATCH 09/15] [mob][photos] Remove commented out code that was used before using data from magic cache --- mobile/lib/services/search_service.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index 50dcc1f0c7..48abdacfcb 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -49,9 +49,6 @@ class SearchService { final _logger = Logger((SearchService).toString()); final _collectionService = CollectionsService.instance; static const _maximumResultsLimit = 20; - // static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; - - // var magicPromptsData = []; SearchService._privateConstructor(); @@ -68,14 +65,6 @@ class SearchService { } } - // Future _loadMagicPrompts() async { - // final file = await RemoteAssetsService.instance - // .getAsset(_kMagicPromptsDataUrl, refetch: true); - - // final json = jsonDecode(await file.readAsString()); - // magicPromptsData = json["prompts"]; - // } - Set ignoreCollections() { return CollectionsService.instance.getHiddenCollectionIds(); } From b21fe76b3f14f86b6798d2bd79a42c4b3b7187a9 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 24 Jun 2024 17:30:33 +0530 Subject: [PATCH 10/15] [mob][photos] Save magic cache to local path instead of saving it in shared preferences --- mobile/lib/services/magic_cache_service.dart | 43 ++++++++++++-------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index bcfd1c43e6..eccb6951da 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -1,7 +1,9 @@ import "dart:async"; import "dart:convert"; +import "dart:io"; import "package:logging/logging.dart"; +import "package:path_provider/path_provider.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/search/generic_search_result.dart"; import "package:photos/models/search/search_types.dart"; @@ -60,7 +62,6 @@ extension MagicCacheServiceExtension on MagicCache { } class MagicCacheService { - static const _key = "magic_cache"; static const _lastMagicCacheUpdateTime = "last_magic_cache_update_time"; static const _kMagicPromptsDataUrl = "https://discover.ente.io/v1.json"; @@ -96,7 +97,7 @@ class MagicCacheService { .getAssetIfUpdated(_kMagicPromptsDataUrl); if (jsonFile != null) { Future.delayed(_kCacheUpdateDelay, () { - unawaited(updateMagicCache()); + unawaited(_updateCache()); }); return; } @@ -105,11 +106,15 @@ class MagicCacheService { .subtract(const Duration(seconds: 1)) .millisecondsSinceEpoch) { Future.delayed(_kCacheUpdateDelay, () { - unawaited(updateMagicCache()); + unawaited(_updateCache()); }); } } + Future _getCachePath() async { + return (await getApplicationSupportDirectory()).path + "/cache/magic_cache"; + } + Future> _getMatchingFileIDsForPromptData( Map promptData, ) async { @@ -121,35 +126,41 @@ class MagicCacheService { return result; } - Future updateMagicCache() async { + Future _updateCache() async { try { _logger.info("updating magic cache"); final magicPromptsData = await _loadMagicPrompts(); final magicCaches = await nonEmptyMagicResults(magicPromptsData); - await _prefs - .setString( - _key, - MagicCache.encodeListToJson(magicCaches), - ) - .then((value) { - resetLastMagicCacheUpdateTime(); - }); + final file = File(await _getCachePath()); + if (!file.existsSync()) { + file.createSync(recursive: true); + } + file.writeAsBytesSync(MagicCache.encodeListToJson(magicCaches).codeUnits); + unawaited( + resetLastMagicCacheUpdateTime().onError((error, stackTrace) { + _logger.warning( + "Error resetting last magic cache update time", + error, + ); + }), + ); } catch (e) { _logger.info("Error updating magic cache", e); } } Future?> _getMagicCache() async { - final jsonString = _prefs.getString(_key); - if (jsonString == null) { - _logger.info("No $_key in shared preferences"); + final file = File(await _getCachePath()); + if (!file.existsSync()) { + _logger.info("No magic cache found"); return null; } + final jsonString = file.readAsStringSync(); return MagicCache.decodeJsonToList(jsonString); } Future clearMagicCache() async { - await _prefs.remove(_key); + File(await _getCachePath()).deleteSync(); } Future> getMagicGenericSearchResult() async { From 8fae7719b5a44239528ae9650e2c4586e8161b47 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 25 Jun 2024 09:31:24 +0530 Subject: [PATCH 11/15] [mob][photos] Update magic cache every 3 days --- mobile/lib/services/magic_cache_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index eccb6951da..b801846f0c 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -103,7 +103,7 @@ class MagicCacheService { } if (lastMagicCacheUpdateTime < DateTime.now() - .subtract(const Duration(seconds: 1)) + .subtract(const Duration(days: 3)) .millisecondsSinceEpoch) { Future.delayed(_kCacheUpdateDelay, () { unawaited(_updateCache()); From 35b42d474307a36ce7dbd5245c0d55cbca1db698 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 25 Jun 2024 09:43:50 +0530 Subject: [PATCH 12/15] [mob][photos] clean up --- mobile/lib/services/magic_cache_service.dart | 1 + mobile/lib/services/search_service.dart | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index b801846f0c..b6d630a20e 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -169,6 +169,7 @@ class MagicCacheService { _logger.info("No magic cache found"); return []; } + final List genericSearchResults = []; for (MagicCache magicCache in magicCaches) { final genericSearchResult = await magicCache.toGenericSearchResult(); diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index 48abdacfcb..2161e654b7 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -60,9 +60,6 @@ class SearchService { _cachedFilesFuture = null; _cachedHiddenFilesFuture = null; }); - if (flagService.internalUser) { - // _loadMagicPrompts(); - } } Set ignoreCollections() { From fcb79907cfe997488d55b98f0dbace6c60b176d2 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 25 Jun 2024 09:51:25 +0530 Subject: [PATCH 13/15] [mob][photos] Remove code that was for handling edge cases when magic section was depending on newly computed results every time instead of using cache --- mobile/lib/models/search/search_types.dart | 3 --- .../ui/viewer/search_tab/magic_section.dart | 23 ------------------- 2 files changed, 26 deletions(-) diff --git a/mobile/lib/models/search/search_types.dart b/mobile/lib/models/search/search_types.dart index e6dab467e1..1b5a186522 100644 --- a/mobile/lib/models/search/search_types.dart +++ b/mobile/lib/models/search/search_types.dart @@ -13,7 +13,6 @@ import "package:photos/models/collection/collection_items.dart"; import "package:photos/models/search/search_result.dart"; import "package:photos/models/typedefs.dart"; import "package:photos/services/collections_service.dart"; -import "package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart"; import "package:photos/services/search_service.dart"; import "package:photos/ui/viewer/gallery/collection_page.dart"; import "package:photos/ui/viewer/location/add_location_sheet.dart"; @@ -292,8 +291,6 @@ extension SectionTypeExtensions on SectionType { switch (this) { case SectionType.location: return [Bus.instance.on()]; - case SectionType.magic: - return [Bus.instance.on()]; default: return []; } diff --git a/mobile/lib/ui/viewer/search_tab/magic_section.dart b/mobile/lib/ui/viewer/search_tab/magic_section.dart index d088de92e5..c3a4a98712 100644 --- a/mobile/lib/ui/viewer/search_tab/magic_section.dart +++ b/mobile/lib/ui/viewer/search_tab/magic_section.dart @@ -33,28 +33,6 @@ class _MagicSectionState extends State { super.initState(); _magicSearchResults = widget.magicSearchResults; - //At times, ml framework is not initialized when the search results are - //requested (widget.momentsSearchResults is empty) and is initialized - //(which fires MLFrameworkInitializationUpdateEvent with - //InitializationState.initialized) before initState of this widget is - //called. We do listen to MLFrameworkInitializationUpdateEvent and reload - //this widget but the event with InitializationState.initialized would have - //already been fired in the above case. - if (_magicSearchResults.isEmpty) { - SectionType.magic - .getData( - context, - limit: kSearchSectionLimit, - ) - .then((value) { - if (mounted) { - setState(() { - _magicSearchResults = value as List; - }); - } - }); - } - final streamsToListenTo = SectionType.magic.sectionUpdateEvents(); for (Stream stream in streamsToListenTo) { streamSubscriptions.add( @@ -84,7 +62,6 @@ class _MagicSectionState extends State { @override void didUpdateWidget(covariant MagicSection oldWidget) { super.didUpdateWidget(oldWidget); - //widget.magicSearch is empty when doing a hot reload if (widget.magicSearchResults.isNotEmpty) { _magicSearchResults = widget.magicSearchResults; } From e722024f8ff8773a1344cc1caa00f8e10a5cc87e Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 25 Jun 2024 10:13:43 +0530 Subject: [PATCH 14/15] [mob][photos] Catch any exceptions or errors when getting results for magic section and return an empty list to avoid showing a blank search tab --- mobile/lib/services/magic_cache_service.dart | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index b6d630a20e..a379b0388a 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -164,18 +164,23 @@ class MagicCacheService { } Future> getMagicGenericSearchResult() async { - final magicCaches = await _getMagicCache(); - if (magicCaches == null) { - _logger.info("No magic cache found"); + try { + final magicCaches = await _getMagicCache(); + if (magicCaches == null) { + _logger.info("No magic cache found"); + return []; + } + + final List genericSearchResults = []; + for (MagicCache magicCache in magicCaches) { + final genericSearchResult = await magicCache.toGenericSearchResult(); + genericSearchResults.add(genericSearchResult); + } + return genericSearchResults; + } catch (e) { + _logger.info("Error getting magic generic search result", e); return []; } - - final List genericSearchResults = []; - for (MagicCache magicCache in magicCaches) { - final genericSearchResult = await magicCache.toGenericSearchResult(); - genericSearchResults.add(genericSearchResult); - } - return genericSearchResults; } Future> _loadMagicPrompts() async { From 75dcf18d75c3bbfbbd30082206a8a6491dfbb129 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 25 Jun 2024 10:15:16 +0530 Subject: [PATCH 15/15] [mob][photos] Minor UI fix --- mobile/lib/ui/viewer/search_tab/magic_section.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/viewer/search_tab/magic_section.dart b/mobile/lib/ui/viewer/search_tab/magic_section.dart index c3a4a98712..b5ec65b02a 100644 --- a/mobile/lib/ui/viewer/search_tab/magic_section.dart +++ b/mobile/lib/ui/viewer/search_tab/magic_section.dart @@ -239,7 +239,7 @@ class MagicRecommendation extends StatelessWidget { ), ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 76, + maxWidth: 88, ), child: Padding( padding: const EdgeInsets.only(