[mob][photos] Improve caching for memories and magic (#6252)

## Description

- Fixes issues with chinese characters
- Runs decoding in computer

## Tests

Tested in debug mode on my pixel phone.
This commit is contained in:
Laurens Priem
2025-06-14 10:55:43 +05:30
committed by GitHub
4 changed files with 156 additions and 47 deletions

View File

@@ -24,6 +24,7 @@ import "package:photos/services/machine_learning/semantic_search/semantic_search
import "package:photos/services/remote_assets_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/ui/viewer/search/result/magic_result_screen.dart";
import "package:photos/utils/cache_util.dart";
import "package:photos/utils/file_util.dart";
import "package:photos/utils/navigation_util.dart";
import "package:shared_preferences/shared_preferences.dart";
@@ -251,13 +252,12 @@ class MagicCacheService {
final List<MagicCache> magicCaches =
await _nonEmptyMagicResults(magicPromptsData);
w?.log("resultComputed");
final file = File(await _getCachePath());
if (!file.existsSync()) {
file.createSync(recursive: true);
}
_magicCacheFuture = Future.value(magicCaches);
await file
.writeAsBytes(MagicCache.encodeListToJson(magicCaches).codeUnits);
await writeToJsonFile<List<MagicCache>>(
await _getCachePath(),
magicCaches,
MagicCache.encodeListToJson,
);
w?.log("cacheWritten");
await _resetLastMagicCacheUpdateTime();
w?.logAndReset('done');
@@ -300,20 +300,11 @@ class MagicCacheService {
Future<List<MagicCache>> _readResultFromDisk() async {
_logger.info("Reading magic cache result from disk");
final file = File(await _getCachePath());
if (!file.existsSync()) {
_logger.info("No magic cache found");
return [];
}
try {
final bytes = await file.readAsBytes();
final jsonString = String.fromCharCodes(bytes);
return MagicCache.decodeJsonToList(jsonString);
} catch (e, s) {
_logger.severe("Error reading or decoding cache file", e, s);
await file.delete();
rethrow;
}
final cache = await decodeJsonFile<List<MagicCache>>(
await _getCachePath(),
MagicCache.decodeJsonToList,
);
return cache ?? [];
}
Future<void> clearMagicCache() async {

View File

@@ -26,6 +26,7 @@ import "package:photos/services/notification_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/ui/home/memories/full_screen_memory.dart";
import "package:photos/ui/viewer/people/people_page.dart";
import "package:photos/utils/cache_util.dart";
import "package:photos/utils/navigation_util.dart";
import "package:shared_preferences/shared_preferences.dart";
import "package:synchronized/synchronized.dart";
@@ -40,7 +41,7 @@ class MemoriesCacheService {
static const _kCacheUpdateDelay = Duration(seconds: 5);
final SharedPreferences _prefs;
late final Logger _logger = Logger("MemoriesCacheService");
static final Logger _logger = Logger("MemoriesCacheService");
final _memoriesDB = MemoriesDB.instance;
@@ -195,35 +196,26 @@ class MemoriesCacheService {
if (cache == null) {
return null;
}
final result = await _fromCacheToMemories(cache);
final result = await fromCacheToMemories(cache);
return result;
}
Future<MemoriesCache?> _readCacheFromDisk() async {
_logger.info("Reading memories cache result from disk");
final file = File(await _getCachePath());
if (!file.existsSync()) {
_logger.info("No memories cache found");
return null;
}
try {
final bytes = await file.readAsBytes();
final jsonString = String.fromCharCodes(bytes);
final cache = MemoriesCache.decodeFromJsonString(jsonString);
_logger.info("Reading memories cache result from disk done");
return cache;
} catch (e, s) {
_logger.severe("Error reading or decoding cache file", e, s);
await file.delete();
return null;
}
final cache = decodeJsonFile<MemoriesCache>(
await _getCachePath(),
MemoriesCache.decodeFromJsonString,
);
return cache;
}
Future<List<SmartMemory>> _fromCacheToMemories(MemoriesCache cache) async {
static Future<List<SmartMemory>> fromCacheToMemories(
MemoriesCache cache,
) async {
try {
_logger.info('Processing disk cache memories to smart memories');
final List<SmartMemory> memories = [];
final seenTimes = await _memoriesDB.getSeenTimes();
final seenTimes = await MemoriesDB.instance.getSeenTimes();
final minimalFileIDs = <int>{};
for (final ToShowMemory memory in cache.toShowMemories) {
if (memory.shouldShowNow()) {
@@ -325,10 +317,6 @@ class MemoriesCacheService {
}
newCache.baseLocations.addAll(nowResult.baseLocations);
w?.log("added memories to cache");
final file = File(await _getCachePath());
if (!file.existsSync()) {
file.createSync(recursive: true);
}
_cachedMemories = nowResult.memories
.where((memory) => memory.shouldShowNow())
.toList();
@@ -336,8 +324,10 @@ class MemoriesCacheService {
[...nowResult.memories, ...nextResult.memories],
);
locationService.baseLocations = nowResult.baseLocations;
await file.writeAsBytes(
MemoriesCache.encodeToJsonString(newCache).codeUnits,
await writeToJsonFile<MemoriesCache>(
await _getCachePath(),
newCache,
MemoriesCache.encodeToJsonString,
);
w?.log("cacheWritten");
await _cacheUpdated();

View File

@@ -5,6 +5,7 @@ import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:intl/intl.dart";
import 'package:logging/logging.dart';
import "package:path_provider/path_provider.dart";
import "package:photos/core/configuration.dart";
import "package:photos/core/constants.dart";
import 'package:photos/core/event_bus.dart';
@@ -24,6 +25,7 @@ import 'package:photos/models/file/file_type.dart';
import "package:photos/models/local_entity_data.dart";
import "package:photos/models/location/location.dart";
import "package:photos/models/location_tag/location_tag.dart";
import "package:photos/models/memories/memories_cache.dart";
import "package:photos/models/memories/memory.dart";
import "package:photos/models/memories/smart_memory.dart";
import "package:photos/models/ml/face/person.dart";
@@ -47,12 +49,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/memories_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/ui/viewer/search/result/magic_result_screen.dart";
import "package:photos/utils/cache_util.dart";
import "package:photos/utils/file_util.dart";
import "package:photos/utils/navigation_util.dart";
import 'package:photos/utils/standalone/date_time.dart';
@@ -1275,7 +1279,33 @@ class SearchService {
final memoriesResult = await smartMemoriesService
.calcSmartMemories(calcTime, cache, debugSurfaceAll: true);
locationService.baseLocations = memoriesResult.baseLocations;
memories = memoriesResult.memories;
for (final nowMemory in memoriesResult.memories) {
cache.toShowMemories
.add(ToShowMemory.fromSmartMemory(nowMemory, calcTime));
}
cache.baseLocations.addAll(memoriesResult.baseLocations);
// memories = memoriesResult.memories;
final tempCachePath = (await getTemporaryDirectory()).path +
"/cache/test/memories_cache_test";
await writeToJsonFile(
tempCachePath,
cache,
MemoriesCache.encodeToJsonString,
);
_logger.info(
"Smart memories cache written to $tempCachePath",
);
final decodedCache = await decodeJsonFile(
tempCachePath,
MemoriesCache.decodeFromJsonString,
);
_logger.info(
"Smart memories cache decoded from $tempCachePath",
);
memories = await MemoriesCacheService.fromCacheToMemories(decodedCache!);
_logger.info(
"Smart memories cache converted to memories",
);
}
final searchResults = <GenericSearchResult>[];
for (final memory in memories) {

View File

@@ -0,0 +1,98 @@
import "dart:convert" show utf8;
import "dart:developer" show log;
import "dart:io";
import "package:computer/computer.dart";
import "package:logging/logging.dart";
final _computer = Computer.shared();
final _logger = Logger("CacheUtil");
/// Writes data to a JSON file at the specified path using the provided method, inside computer.
/// The method should convert the data to a JSON string.
/// The JSON string is then UTF-8 encoded and written to the file.
Future<void> writeToJsonFile<P>(
String filePath,
P data,
String Function(P) toJsonString,
) async {
final args = {
"filePath": filePath,
"data": data,
"toJsonString": toJsonString,
};
await _computer.compute<Map<String, dynamic>, void>(
_writeToJsonFile<P>,
param: args,
taskName: "writeToJsonFile",
);
}
Future<void> _writeToJsonFile<P>(Map<String, dynamic> args) async {
try {
final file = File(args["filePath"] as String);
if (!file.existsSync()) {
file.createSync(recursive: true);
}
final toJsonStringMethod = args["toJsonString"] as String Function(P);
final jsonString = toJsonStringMethod(args["data"] as P);
final encodedData = utf8.encode(jsonString);
await file.writeAsBytes(encodedData);
} catch (e, s) {
log("Error writing to JSON file with UTF-8 encoding, $e, \n $s");
}
}
/// Reads a JSON file from the specified path using the provided method, inside computer.
/// The method should decode the JSON string into an object.
/// The JSON string is expected to be UTF-8 encoded.
Future<P?> decodeJsonFile<P>(
String filePath,
P Function(String) jsonDecodeMethod,
) async {
final args = {
"filePath": filePath,
"jsonDecode": jsonDecodeMethod,
};
final cache = await _computer.compute<Map<String, dynamic>, P?>(
_decodeJsonFile<P>,
param: args,
taskName: "decodeJsonFile",
);
if (cache == null) {
_logger.warning("Failed to decode JSON file at $filePath");
} else {
_logger.info("Successfully decoded JSON file at $filePath");
}
return cache;
}
Future<P?> _decodeJsonFile<P>(Map<String, dynamic> args) async {
final file = File(args["filePath"] as String);
if (!file.existsSync()) {
log("File does not exist: ${args["filePath"]}");
return null;
}
try {
final bytes = await file.readAsBytes();
final jsonDecodeMethod = args["jsonDecode"] as P Function(String);
P decodedData;
try {
final jsonString = utf8.decode(bytes);
decodedData = jsonDecodeMethod(jsonString);
log("Successfully decoded JSON file as UTF-8");
} catch (e, s) {
log("Failed to decode bytes as UTF-8, trying UTF-16 $e \n $s");
final jsonString =
String.fromCharCodes(bytes); // Fallback to UTF-16 decoding
decodedData = jsonDecodeMethod(jsonString);
log("Successfully decoded JSON file as UTF-16");
}
return decodedData;
} catch (e, s) {
log("Error decoding JSON file, deleting this cache $e, \n $s");
await file.delete();
return null;
}
}