[mob][photos] Unify all caching in cache service only

This commit is contained in:
laurenspriem
2025-02-25 17:15:45 +05:30
parent a5cb78bf95
commit fdf4a3d336
6 changed files with 132 additions and 101 deletions

View File

@@ -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());

View File

@@ -22,7 +22,7 @@ MemoryType memoryTypeFromString(String type) {
}
}
abstract class SmartMemory {
class SmartMemory {
final List<Memory> 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<Memory>? memories,
String? name,
int? firstCreationTime,
int? lastCreationTime,
});
}

View File

@@ -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<MemoriesCache?>? _memoriesCacheFuture;
final _memoriesDB = MemoriesDB.instance;
List<SmartMemory>? _cachedMemories;
bool _shouldUpdate = false;
bool _isUpdateInProgress = false;
late Map<int, int> _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<FilesUpdatedEvent>().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<void> _updateCacheIfTheTimeHasCome() async {
bool get showAnyMemories {
return _prefs.getBool(_showAnyMemoryKey) ?? true;
}
bool get enableSmartMemories => flagService.showSmartMemories;
Future<void> _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<void> 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<SmartMemory> memories,
MemoriesCache? oldCache,
) {
@@ -340,15 +398,61 @@ class MemoriesCacheService {
);
}
Future<MemoriesCache?> getMemoriesCache() async {
if (_memoriesCacheFuture != null) {
return _memoriesCacheFuture!;
Future<List<SmartMemory>> _fromCacheToMemories(MemoriesCache cache) async {
final List<SmartMemory> memories = [];
final allFiles = Set<EnteFile>.from(
await SearchService.instance.getAllFilesForSearch(),
);
final allFileIdsToFile = <int, EnteFile>{};
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<MemoriesCache?> _readResultFromDisk() async {
Future<List<SmartMemory>> _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<List<SmartMemory>> getMemories(int? limit) async {
if (!showAnyMemories) {
return [];
}
if (_cachedMemories != null) {
return _cachedMemories!;
}
_cachedMemories = await _getMemoriesFromCache();
return _cachedMemories!;
}
Future<MemoriesCache?> _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<void> clearMemoriesCache() async {
await File(await _getCachePath()).delete();
_cachedMemories = null;
}
}

View File

@@ -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 = <GenericSearchResult>[];
for (final memory in memories) {
final name = memory.name ?? "memory";

View File

@@ -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<int, int> _seenTimes;
List<SmartMemory>? _cachedMemories;
Future<List<SmartMemory>>? _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<PeopleActivity, Vector> _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<FilesUpdatedEvent>().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<List<SmartMemory>> 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<List<SmartMemory>> _calcMemories() async {
Future<List<SmartMemory>> calcMemories() async {
try {
await init();
final List<SmartMemory> 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);

View File

@@ -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<MemoriesWidget> {
if (!MemoriesService.instance.showMemories) {
return const SizedBox.shrink();
}
if (SmartMemoriesService.instance.enableSmartMemories) {
if (memoriesCacheService.enableSmartMemories) {
return FutureBuilder<List<SmartMemory>>(
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();