[mob][photos] More robust caching and models

This commit is contained in:
laurenspriem
2025-02-28 14:11:33 +05:30
parent 7cecf84841
commit 8e8610beda
9 changed files with 225 additions and 138 deletions

View File

@@ -47,27 +47,32 @@ class ToShowMemory {
final MemoryType type;
final int firstTimeToShow;
final int lastTimeToShow;
final int calculationTime;
final String? personID;
final PeopleMemoryType? peopleMemoryType;
final Location? location;
bool get shouldShowNow {
final now = DateTime.now().microsecondsSinceEpoch;
return now >= firstTimeToShow && now <= lastTimeToShow;
}
bool get isOld {
final now = DateTime.now().microsecondsSinceEpoch;
return now > lastTimeToShow;
}
bool shouldShowNow() {
final now = DateTime.now().microsecondsSinceEpoch;
final relevantForNow = now >= firstTimeToShow && now < lastTimeToShow;
final calculatedForNow = (now >= calculationTime) &&
(now < calculationTime + kMemoriesUpdateFrequency.inMicroseconds);
return relevantForNow && calculatedForNow;
}
ToShowMemory(
this.title,
this.fileUploadedIDs,
this.type,
this.firstTimeToShow,
this.lastTimeToShow, {
this.lastTimeToShow,
this.calculationTime, {
this.personID,
this.peopleMemoryType,
this.location,
@@ -80,8 +85,7 @@ class ToShowMemory {
"PersonID and peopleMemoryType must be provided for people memory type, and location must be provided for trips memory type",
);
factory ToShowMemory.fromSmartMemory(SmartMemory memory) {
assert(memory.firstDateToShow != null && memory.lastDateToShow != null);
factory ToShowMemory.fromSmartMemory(SmartMemory memory, DateTime calcTime) {
String? personID;
PeopleMemoryType? peopleMemoryType;
Location? location;
@@ -92,11 +96,12 @@ class ToShowMemory {
location = memory.location;
}
return ToShowMemory(
memory.name,
memory.title,
memory.memories.map((m) => m.file.uploadedFileID!).toList(),
memory.type,
memory.firstDateToShow!,
memory.lastDateToShow!,
memory.firstDateToShow,
memory.lastDateToShow,
calcTime.microsecondsSinceEpoch,
personID: personID,
peopleMemoryType: peopleMemoryType,
location: location,
@@ -110,6 +115,7 @@ class ToShowMemory {
memoryTypeFromString(json['type']),
json['firstTimeToShow'],
json['lastTimeToShow'],
json['calculationTime'],
personID: json['personID'],
peopleMemoryType: peopleMemoryTypeFromString(json['peopleMemoryType']),
location: json['location'] != null
@@ -128,6 +134,7 @@ class ToShowMemory {
'type': type.toString().split('.').last,
'firstTimeToShow': firstTimeToShow,
'lastTimeToShow': lastTimeToShow,
'calculationTime': calculationTime,
'personID': personID,
'peopleMemoryType': peopleMemoryType.toString().split('.').last,
'location': location != null

View File

@@ -61,35 +61,40 @@ class PeopleMemory extends SmartMemory {
PeopleMemory(
List<Memory> memories,
String title,
int firstDateToShow,
int lastDateToShow,
this.peopleMemoryType,
this.personID, {
super.name,
super.firstCreationTime,
super.lastCreationTime,
super.firstDateToShow,
super.lastDateToShow,
}) : super(memories, MemoryType.people);
}) : super(
memories,
MemoryType.people,
title,
firstDateToShow,
lastDateToShow,
);
@override
PeopleMemory copyWith({
List<Memory>? memories,
PeopleMemoryType? peopleMemoryType,
String? personID,
String? name,
int? firstCreationTime,
int? lastCreationTime,
String? title,
int? firstDateToShow,
int? lastDateToShow,
PeopleMemoryType? peopleMemoryType,
String? personID,
int? firstCreationTime,
int? lastCreationTime,
}) {
return PeopleMemory(
memories ?? super.memories,
memories ?? this.memories,
title ?? this.title,
firstDateToShow ?? this.firstDateToShow,
lastDateToShow ?? this.lastDateToShow,
peopleMemoryType ?? this.peopleMemoryType,
personID ?? this.personID,
name: name ?? super.name,
firstCreationTime: firstCreationTime ?? super.firstCreationTime,
lastCreationTime: lastCreationTime ?? super.lastCreationTime,
firstDateToShow: firstDateToShow ?? super.firstDateToShow,
lastDateToShow: lastDateToShow ?? super.lastDateToShow,
firstCreationTime: firstCreationTime ?? this.firstCreationTime,
lastCreationTime: lastCreationTime ?? this.lastCreationTime,
);
}
}

View File

@@ -1,5 +1,8 @@
import "package:photos/models/memory.dart";
const kMemoriesUpdateFrequency = Duration(days: 7);
const kMemoriesMargin = Duration(days: 2);
enum MemoryType {
people,
trips,
@@ -25,44 +28,34 @@ MemoryType memoryTypeFromString(String type) {
class SmartMemory {
final List<Memory> memories;
final MemoryType type;
String name;
String title;
int firstDateToShow;
int lastDateToShow;
int? firstCreationTime;
int? lastCreationTime;
int? firstDateToShow;
int? lastDateToShow;
// TODO: lau: make the above two non-nullable!!!
// TODO: lau: actually use this in calculated filters
SmartMemory(
this.memories,
this.type, {
name,
this.type,
title,
this.firstDateToShow,
this.lastDateToShow, {
this.firstCreationTime,
this.lastCreationTime,
this.firstDateToShow,
this.lastDateToShow,
}) : name = name != null ? name + "(I)" : null;
}) : title = title + "(I)";
// TODO: lau: remove (I) from name when opening up the feature flag
bool get notForShow => firstDateToShow == 0 && lastDateToShow == 0;
bool isOld() {
if (firstDateToShow == null || lastDateToShow == null) {
return false;
}
final now = DateTime.now().microsecondsSinceEpoch;
return lastDateToShow! < now;
}
bool hasShowTime() {
return firstDateToShow != null && lastDateToShow != null;
return lastDateToShow < DateTime.now().microsecondsSinceEpoch;
}
bool shouldShowNow() {
if (!hasShowTime()) {
return false;
}
final int now = DateTime.now().microsecondsSinceEpoch;
return now >= firstDateToShow! && now <= lastDateToShow!;
return now >= firstDateToShow && now <= lastDateToShow;
}
int averageCreationTime() {
@@ -74,6 +67,11 @@ class SmartMemory {
.map((memory) => memory.file.creationTime!)
.toList();
if (creationTimes.length < 2) {
if (creationTimes.isEmpty) {
firstCreationTime = 0;
lastCreationTime = 0;
return 0;
}
return creationTimes.isEmpty ? 0 : creationTimes.first;
}
creationTimes.sort();

View File

@@ -3,38 +3,17 @@ import "package:photos/models/smart_memory.dart";
class TimeMemory extends SmartMemory {
TimeMemory(
List<Memory> memories, {
String? name,
List<Memory> memories,
String title,
int firstDateToShow,
int lastDateToShow, {
int? firstCreationTime,
int? lastCreationTime,
int? firstDateToShow,
int? lastDateToShow,
}) : super(
memories,
MemoryType.time,
name: name,
firstCreationTime: firstCreationTime,
lastCreationTime: lastCreationTime,
firstDateToShow: firstDateToShow,
lastDateToShow: lastDateToShow,
title,
firstDateToShow,
lastDateToShow,
);
@override
TimeMemory copyWith({
List<Memory>? memories,
String? name,
int? firstCreationTime,
int? lastCreationTime,
int? firstDateToShow,
int? lastDateToShow,
}) {
return TimeMemory(
memories ?? this.memories,
name: name ?? this.name,
firstCreationTime: firstCreationTime ?? super.firstCreationTime,
lastCreationTime: lastCreationTime ?? super.lastCreationTime,
firstDateToShow: firstDateToShow ?? super.firstDateToShow,
lastDateToShow: lastDateToShow ?? super.lastDateToShow,
);
}
}

View File

@@ -7,32 +7,37 @@ class TripMemory extends SmartMemory {
TripMemory(
List<Memory> memories,
String title,
int firstDateToShow,
int lastDateToShow,
this.location, {
super.name,
super.firstCreationTime,
super.lastCreationTime,
super.firstDateToShow,
super.lastDateToShow,
}) : super(memories, MemoryType.trips);
}) : super(
memories,
MemoryType.trips,
title,
firstDateToShow,
lastDateToShow,
);
@override
TripMemory copyWith({
List<Memory>? memories,
Location? location,
String? name,
int? firstCreationTime,
int? lastCreationTime,
String? title,
int? firstDateToShow,
int? lastDateToShow,
Location? location,
int? firstCreationTime,
int? lastCreationTime,
}) {
return TripMemory(
memories ?? super.memories,
memories ?? this.memories,
title ?? this.title,
firstDateToShow ?? this.firstDateToShow,
lastDateToShow ?? this.lastDateToShow,
location ?? this.location,
name: name ?? super.name,
firstCreationTime: firstCreationTime ?? super.firstCreationTime,
lastCreationTime: lastCreationTime ?? super.lastCreationTime,
firstDateToShow: firstDateToShow ?? super.firstDateToShow,
lastDateToShow: lastDateToShow ?? super.lastDateToShow,
firstCreationTime: firstCreationTime ?? this.firstCreationTime,
lastCreationTime: lastCreationTime ?? this.lastCreationTime,
);
}
}

View File

@@ -25,7 +25,6 @@ class MemoriesCacheService {
/// 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");
@@ -48,7 +47,9 @@ class MemoriesCacheService {
unawaited(_memoriesDB.getSeenTimes().then((value) => _seenTimes = value));
unawaited(
_memoriesDB.clearMemoriesSeenBeforeTime(
DateTime.now().subtract(_kUpdateFrequency).microsecondsSinceEpoch,
DateTime.now()
.subtract(kMemoriesUpdateFrequency)
.microsecondsSinceEpoch,
),
);
@@ -86,14 +87,16 @@ class MemoriesCacheService {
Future<void> _checkIfTimeToUpdateCache() async {
if (lastMemoriesCacheUpdateTime <
DateTime.now().subtract(_kUpdateFrequency).microsecondsSinceEpoch) {
DateTime.now()
.subtract(kMemoriesUpdateFrequency)
.microsecondsSinceEpoch) {
_shouldUpdate = true;
}
}
Future<String> _getCachePath() async {
return (await getApplicationSupportDirectory()).path +
"/cache//test1/memories_cache";
"/cache//test2/memories_cache";
// TODO: lau: remove the test1 directory after testing
}
@@ -133,20 +136,21 @@ class MemoriesCacheService {
w?.start();
// calculate memories for this period and for the next period
final now = DateTime.now();
final next = now.add(_kUpdateFrequency);
final List<SmartMemory> memories = [];
memories.addAll(await SmartMemoriesService.instance.calcMemories(now));
memories.addAll(await SmartMemoriesService.instance.calcMemories(next));
final next = now.add(kMemoriesUpdateFrequency);
final nowMemories = await SmartMemoriesService.instance.calcMemories(now);
final nextMemories =
await SmartMemoriesService.instance.calcMemories(next);
w?.log("calculated new memories");
final oldCache = await _readCacheFromDisk();
w?.log("gotten old cache");
final MemoriesCache memoryCache = _toCache(memories, oldCache);
final MemoriesCache memoryCache =
_toCache(nowMemories, now, nextMemories, next, oldCache);
w?.log("gotten cache from memories");
final file = File(await _getCachePath());
if (!file.existsSync()) {
file.createSync(recursive: true);
}
_cachedMemories = memories;
_cachedMemories = nowMemories;
await file.writeAsBytes(
MemoriesCache.encodeToJsonString(memoryCache).codeUnits,
);
@@ -162,18 +166,20 @@ class MemoriesCacheService {
}
MemoriesCache _toCache(
List<SmartMemory> memories,
List<SmartMemory> nowMemories,
DateTime now,
List<SmartMemory> nextMemories,
DateTime next,
MemoriesCache? oldCache,
) {
final List<ToShowMemory> toShowMemories = [];
final List<PeopleShownLog> peopleShownLogs = [];
final List<TripsShownLog> tripsShownLogs = [];
for (final memory in memories) {
if (memory.hasShowTime()) {
toShowMemories.add(ToShowMemory.fromSmartMemory(memory));
} else {
_logger.severe('Memory has no first or last date to show');
}
for (final nowMemory in nowMemories) {
toShowMemories.add(ToShowMemory.fromSmartMemory(nowMemory, now));
}
for (final nextMemory in nextMemories) {
toShowMemories.add(ToShowMemory.fromSmartMemory(nextMemory, next));
}
if (oldCache != null) {
peopleShownLogs.addAll(oldCache.peopleShownLogs);
@@ -224,7 +230,7 @@ class MemoriesCacheService {
}
for (final ToShowMemory memory in cache.toShowMemories) {
if (memory.shouldShowNow) {
if (memory.shouldShowNow()) {
memories.add(
SmartMemory(
memory.fileUploadedIDs
@@ -234,9 +240,9 @@ class MemoriesCacheService {
)
.toList(),
memory.type,
name: memory.title,
firstDateToShow: memory.firstTimeToShow,
lastDateToShow: memory.lastTimeToShow,
memory.title,
memory.firstTimeToShow,
memory.lastTimeToShow,
),
);
}

View File

@@ -1196,15 +1196,14 @@ class SearchService {
final memories = await memoriesCacheService.getMemories(limit);
final searchResults = <GenericSearchResult>[];
for (final memory in memories) {
final name = memory.name ?? "memory";
final files = Memory.filesFromMemories(memory.memories);
searchResults.add(
GenericSearchResult(
ResultType.event,
name,
memory.title,
files,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
filterName: memory.title,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(files),

View File

@@ -208,9 +208,11 @@ class SmartMemoriesService {
);
final spotlightMemory = PeopleMemory(
selectSpotlightMemories,
title,
currentTime.microsecondsSinceEpoch,
currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch,
PeopleMemoryType.spotlight,
personID,
name: title,
);
personToMemories
.putIfAbsent(personID, () => {})
@@ -236,9 +238,11 @@ class SmartMemoriesService {
);
final youAndThemMemory = PeopleMemory(
selectYouAndThemMemories,
title,
currentTime.microsecondsSinceEpoch,
currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch,
PeopleMemoryType.youAndThem,
personID,
name: title,
);
personToMemories
.putIfAbsent(personID, () => {})
@@ -281,9 +285,11 @@ class SmartMemoriesService {
);
final activityMemory = PeopleMemory(
selectActivityMemories,
title,
currentTime.microsecondsSinceEpoch,
currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch,
PeopleMemoryType.doingSomethingTogether,
personID,
name: title,
);
personToMemories.putIfAbsent(personID, () => {}).putIfAbsent(
PeopleMemoryType.doingSomethingTogether,
@@ -326,9 +332,11 @@ class SmartMemoriesService {
lastTimeYouSawThemFiles
.map((f) => Memory.fromFile(f, _seenTimes))
.toList(),
title,
currentTime.microsecondsSinceEpoch,
currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch,
PeopleMemoryType.lastTimeYouSawThem,
personID,
name: title,
);
personToMemories.putIfAbsent(personID, () => {}).putIfAbsent(
PeopleMemoryType.lastTimeYouSawThem,
@@ -528,7 +536,10 @@ class SmartMemoriesService {
Memory.fromFiles(
currentBlockFiles,
_seenTimes,
), // TODO: lau: properly check last seen times
),
'Trip1',
0,
0,
location,
firstCreationTime: blockStart,
lastCreationTime: lastTime,
@@ -553,6 +564,9 @@ class SmartMemoriesService {
tripLocations.add(
TripMemory(
Memory.fromFiles(currentBlockFiles, _seenTimes),
'Trip2',
0,
0,
location,
firstCreationTime: blockStart,
lastCreationTime: lastTime,
@@ -584,6 +598,9 @@ class SmartMemoriesService {
)) {
mergedTrips[idx] = TripMemory(
otherTrip.memories + trip.memories,
'Trip3',
0,
0,
otherTrip.location,
firstCreationTime:
min(otherTrip.firstCreationTime!, trip.firstCreationTime!),
@@ -599,6 +616,9 @@ class SmartMemoriesService {
mergedTrips.add(
TripMemory(
trip.memories,
'Trip4',
0,
0,
trip.location,
firstCreationTime: trip.firstCreationTime,
lastCreationTime: trip.lastCreationTime,
@@ -629,8 +649,10 @@ class SmartMemoriesService {
memoryResults.add(
TripMemory(
Memory.fromFiles(baseLocation.files, _seenTimes),
name,
0,
0,
baseLocation.location,
name: name,
),
);
}
@@ -676,10 +698,28 @@ class SmartMemoriesService {
name = "Last year's trip";
}
final photoSelection = await _bestSelection(trip.memories);
final firstCreationDate = DateTime.fromMicrosecondsSinceEpoch(
trip.firstCreationTime!,
);
final firstDateToShow = DateTime(
currentTime.year,
firstCreationDate.month,
firstCreationDate.day,
).subtract(kMemoriesMargin).microsecondsSinceEpoch;
final lastCreationDate = DateTime.fromMicrosecondsSinceEpoch(
trip.lastCreationTime!,
);
final lastDateToShow = DateTime(
currentTime.year,
lastCreationDate.month,
lastCreationDate.day,
).add(kMemoriesMargin).microsecondsSinceEpoch;
memoryResults.add(
trip.copyWith(
memories: photoSelection,
name: name,
title: name,
firstDateToShow: firstDateToShow,
lastDateToShow: lastDateToShow,
),
);
}
@@ -717,10 +757,28 @@ class SmartMemoriesService {
name = "Last year's trip";
}
final photoSelection = await _bestSelection(trip.memories);
final firstCreationDate = DateTime.fromMicrosecondsSinceEpoch(
trip.firstCreationTime!,
);
final firstDateToShow = DateTime(
currentTime.year,
firstCreationDate.month,
firstCreationDate.day,
).subtract(kMemoriesMargin).microsecondsSinceEpoch;
final lastCreationDate = DateTime.fromMicrosecondsSinceEpoch(
trip.lastCreationTime!,
);
final lastDateToShow = DateTime(
currentTime.year,
lastCreationDate.month,
lastCreationDate.day,
).add(kMemoriesMargin).microsecondsSinceEpoch;
memoryResults.add(
trip.copyWith(
memories: photoSelection,
name: name,
title: name,
firstDateToShow: firstDateToShow,
lastDateToShow: lastDateToShow,
),
);
break checkUpcomingMonths;
@@ -741,6 +799,7 @@ class SmartMemoriesService {
final currentDayMonth = currentTime.month * 100 + currentTime.day;
final currentWeek = _getWeekNumber(currentTime);
final currentMonth = currentTime.month;
final currentYear = currentTime.year;
final cutOffTime = currentTime.subtract(const Duration(days: 365));
final averageDailyPhotos = allFiles.length / 365;
final significantDayThreshold = averageDailyPhotos * 0.25;
@@ -766,7 +825,7 @@ class SmartMemoriesService {
// Process each nearby day-month to find significant days
for (final dayMonth in dayMonthYearGroups.keys) {
final dayDiff = dayMonth - currentDayMonth;
if (dayDiff < 0 || dayDiff > 2) continue;
if (dayDiff < 0 || dayDiff > kMemoriesUpdateFrequency.inDays) continue;
// TODO: lau: this doesn't cover month changes properly
final yearGroups = dayMonthYearGroups[dayMonth]!;
@@ -776,7 +835,6 @@ class SmartMemoriesService {
.toList();
if (significantDays.length >= 3) {
// THE ISSUE IS HERE, MOST LIKELY IN THE SELECTION!
// Combine all years for this day-month
final date =
DateTime(currentTime.year, dayMonth ~/ 100, dayMonth % 100);
@@ -786,24 +844,35 @@ class SmartMemoriesService {
memoryResult.add(
TimeMemory(
photoSelection,
name: "${DateFormat('MMMM d').format(date)} through the years",
"${DateFormat('MMMM d').format(date)} through the years",
date.subtract(kMemoriesMargin).microsecondsSinceEpoch,
date.add(const Duration(days: 1)).microsecondsSinceEpoch,
),
);
} else {
// Individual entries for significant years
for (final year in significantDays) {
final date = DateTime(year, dayMonth ~/ 100, dayMonth % 100);
final showDate =
DateTime(currentYear, dayMonth ~/ 100, dayMonth % 100);
final files = yearGroups[year]!;
final photoSelection = await _bestSelection(files);
String name = DateFormat.yMMMd(_locale?.languageCode).format(date);
if (date.day == currentTime.day && date.month == currentTime.month) {
name = "This day, ${currentTime.year - date.year} years back";
}
memoryResult.add(
TimeMemory(
photoSelection,
name: name,
name,
showDate.subtract(kMemoriesMargin).microsecondsSinceEpoch,
showDate.microsecondsSinceEpoch,
),
);
name = "This day, ${currentTime.year - date.year} years back";
memoryResult.add(
TimeMemory(
photoSelection,
name,
showDate.microsecondsSinceEpoch,
showDate.add(const Duration(days: 1)).microsecondsSinceEpoch,
),
);
}
@@ -843,7 +912,9 @@ class SmartMemoriesService {
memoryResult.add(
TimeMemory(
photoSelection,
name: name,
name,
currentTime.subtract(kMemoriesMargin).microsecondsSinceEpoch,
currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch,
),
);
} else {
@@ -860,7 +931,11 @@ class SmartMemoriesService {
memoryResult.add(
TimeMemory(
photoSelection,
name: name,
name,
currentTime.subtract(kMemoriesMargin).microsecondsSinceEpoch,
currentTime
.add(kMemoriesUpdateFrequency)
.microsecondsSinceEpoch,
),
);
}
@@ -908,11 +983,18 @@ class SmartMemoriesService {
);
final monthName = DateFormat.MMMM(_locale?.languageCode)
.format(DateTime(year, currentMonth));
final daysLeftInMonth = DateTime(currentYear, currentMonth + 1, 0).day -
currentTime.day +
1;
final name = monthName + ", ${currentTime.year - year} years back";
memoryResult.add(
TimeMemory(
photoSelection,
name: name,
name,
currentTime.microsecondsSinceEpoch,
currentTime
.add(Duration(days: daysLeftInMonth))
.microsecondsSinceEpoch,
),
);
}
@@ -926,17 +1008,22 @@ class SmartMemoriesService {
await _bestSelection(allPhotos, prefferedSize: monthSelectionSize);
final monthName = DateFormat.MMMM(_locale?.languageCode)
.format(DateTime(currentTime.year, currentMonth));
final daysLeftInMonth =
DateTime(currentYear, currentMonth + 1, 0).day - currentTime.day + 1;
final name = monthName + " through the years";
memoryResult.add(
TimeMemory(
photoSelection,
name: name,
name,
currentTime.microsecondsSinceEpoch,
currentTime.add(Duration(days: daysLeftInMonth)).microsecondsSinceEpoch,
),
);
return memoryResult;
}
/// TODO: lau: replace this by just taking next 7 days
int _getWeekNumber(DateTime date) {
// Get day of year (1-366)
final int dayOfYear = int.parse(DateFormat('D').format(date));

View File

@@ -128,7 +128,8 @@ class _MemoriesWidgetState extends State<MemoriesWidget> {
}
Widget _buildSmartMemories(List<SmartMemory> memories) {
final collatedMemories = memories.map((e) => (e.memories, e.name)).toList();
final collatedMemories =
memories.map((e) => (e.memories, e.title)).toList();
return SizedBox(
height: _maxHeight + MemoryCoverWidget.outerStrokeWidth * 2,