Fix loading notes when approaching newest

This commit is contained in:
2025-06-09 09:39:12 +02:00
parent 559e0fea36
commit 73e27c5f5b

View File

@@ -331,6 +331,12 @@ class MainPageState extends State<MainPage> with WindowListener {
bool _isSearching = false; bool _isSearching = false;
List<Note> _searchResults = []; List<Note> _searchResults = [];
// Add flag to track if we've reached the newest note
bool _reachedNewestNote = false;
// Track the newest note timestamp we know about
int? _newestNoteTimestamp;
// Add network activity tracker // Add network activity tracker
bool _isNetworkActive = false; bool _isNetworkActive = false;
int _pendingNetworkRequests = 0; int _pendingNetworkRequests = 0;
@@ -606,7 +612,20 @@ class MainPageState extends State<MainPage> with WindowListener {
// Special case: we're at the latest note (previousNote is same as current) // Special case: we're at the latest note (previousNote is same as current)
final isLatestNote = _currentlyDisplayedNote!.epochTime == previousNote?.epochTime; final isLatestNote = _currentlyDisplayedNote!.epochTime == previousNote?.epochTime;
if (!isLatestNote) { if (isLatestNote) {
// If we're at the latest note, there's no next note by definition
canGoNext = false;
_reachedNewestNote = true;
// Make sure the newest timestamp is set
if (_newestNoteTimestamp == null || _currentlyDisplayedNote!.epochTime > _newestNoteTimestamp!) {
_newestNoteTimestamp = _currentlyDisplayedNote!.epochTime;
}
} else if (_newestNoteTimestamp != null && _currentlyDisplayedNote!.epochTime >= _newestNoteTimestamp!) {
// If we're at or beyond the newest timestamp we know about, there's no next
canGoNext = false;
_reachedNewestNote = true;
} else if (!_reachedNewestNote) {
final next = await _trackNetworkActivity( final next = await _trackNetworkActivity(
() => meili.getNextTo(_currentlyDisplayedNote!.epochTime), () => meili.getNextTo(_currentlyDisplayedNote!.epochTime),
operation: "Checking for next notes", operation: "Checking for next notes",
@@ -615,10 +634,20 @@ class MainPageState extends State<MainPage> with WindowListener {
if (next != null) { if (next != null) {
// Add to cache // Add to cache
_noteCache[next.epochTime] = next; _noteCache[next.epochTime] = next;
// Update newest note timestamp if applicable
if (_newestNoteTimestamp == null || next.epochTime > _newestNoteTimestamp!) {
_newestNoteTimestamp = next.epochTime;
}
} else {
// If there's no next note, we've reached the newest
_reachedNewestNote = true;
// Update newest note timestamp
if (_newestNoteTimestamp == null || _currentlyDisplayedNote!.epochTime > _newestNoteTimestamp!) {
_newestNoteTimestamp = _currentlyDisplayedNote!.epochTime;
}
} }
} else {
// If we're at the latest note, there's no next note by definition
canGoNext = false;
} }
} }
@@ -682,7 +711,7 @@ class MainPageState extends State<MainPage> with WindowListener {
); );
// Update cache with the modified note // Update cache with the modified note
_noteCache[_currentlyDisplayedNote!.epochTime] = _currentlyDisplayedNote!; _noteCache[_currentlyDisplayedNote!.epochTime] = _noteCache[_currentlyDisplayedNote!.epochTime] ?? _currentlyDisplayedNote!;
} }
} }
@@ -695,15 +724,53 @@ class MainPageState extends State<MainPage> with WindowListener {
_previousEntryController.text = nextNote.content; _previousEntryController.text = nextNote.content;
}); });
// Prefetch more notes in the background if we're getting close to the edge of our cache // Check if we've reached the newest note (latest note we initially loaded)
final timestamp = nextNote.epochTime; if (previousNote != null && nextNote.epochTime == previousNote!.epochTime) {
final countAfter = _noteCache.keys.where((t) => t > timestamp).length; setState(() {
_reachedNewestNote = true;
});
debugPrint("Reached newest note, won't try to load more ahead");
} else {
// Reset the flag if we're not at the newest note
setState(() {
_reachedNewestNote = false;
});
// Only prefetch if we have less than 25% of our desired cache size ahead // Prefetch more notes in the background if we're getting close to the edge of our cache
// This prevents excessive prefetching when we already have plenty of notes final timestamp = nextNote.epochTime;
if (countAfter < _cacheSizeAfter / 4) {
debugPrint("Only $countAfter notes ahead in cache, prefetching more"); // Check if we already have all notes to the newest timestamp
_loadNotesAfter(timestamp); bool haveAllNotesToNewest = false;
if (_newestNoteTimestamp != null) {
// Get all timestamps in our cache between current and newest
final cachedTimestamps = _noteCache.keys
.where((t) => t > timestamp && t <= _newestNoteTimestamp!)
.toList();
// Count notes ahead in cache
final countAfter = cachedTimestamps.length;
// Check if we received fewer notes than the cache size in our last prefetch
// This indicates we already have all notes up to the newest
final haveFetchedAll = _noteCache.containsKey(_newestNoteTimestamp);
debugPrint("Have $countAfter notes cached between current and newest timestamp");
debugPrint("Newest timestamp is cached: $haveFetchedAll");
// We have all notes if we've fetched the newest timestamp
haveAllNotesToNewest = haveFetchedAll;
}
// Only prefetch if:
// 1. The current timestamp is below the newest we know about AND
// 2. We don't already have all notes between current and newest
if (!haveAllNotesToNewest &&
(_newestNoteTimestamp == null || timestamp < _newestNoteTimestamp!)) {
debugPrint("Don't have all notes to newest timestamp, prefetching more");
_loadNotesAfter(timestamp);
} else {
debugPrint("Already have all notes to newest timestamp, skipping prefetch");
}
} }
await _checkNavigation(); await _checkNavigation();
@@ -751,6 +818,12 @@ class MainPageState extends State<MainPage> with WindowListener {
_currentlyDisplayedNote = note; _currentlyDisplayedNote = note;
_previousEntryController.text = _currentlyDisplayedNote?.content ?? ""; _previousEntryController.text = _currentlyDisplayedNote?.content ?? "";
// Initialize the newest note timestamp
if (note != null) {
_newestNoteTimestamp = note.epochTime;
_reachedNewestNote = true; // We're starting at the newest note
}
final scratch = await _trackNetworkActivity( final scratch = await _trackNetworkActivity(
() => meili.getLatestScratch(), () => meili.getLatestScratch(),
operation: "Loading latest scratch", operation: "Loading latest scratch",
@@ -1738,9 +1811,28 @@ class MainPageState extends State<MainPage> with WindowListener {
// Load a batch of notes after the given timestamp // Load a batch of notes after the given timestamp
Future<void> _loadNotesAfter(int timestamp) async { Future<void> _loadNotesAfter(int timestamp) async {
if (_isCacheLoading) return; if (_isCacheLoading) return;
// Don't even attempt to load if we know this timestamp is at or beyond the newest note
if (_newestNoteTimestamp != null && timestamp >= _newestNoteTimestamp!) {
debugPrint("Skipping forward load - timestamp $timestamp is at or beyond newest note timestamp ${_newestNoteTimestamp}");
return;
}
// Check if we already have all notes to the newest timestamp
if (_newestNoteTimestamp != null && _noteCache.containsKey(_newestNoteTimestamp)) {
debugPrint("Skipping forward load - already have newest note in cache");
return;
}
_isCacheLoading = true; _isCacheLoading = true;
try { try {
// Skip loading if we've reached the newest note
if (_reachedNewestNote) {
debugPrint("Skipping forward load - already at newest note");
return;
}
// Use the proper cache size as configured // Use the proper cache size as configured
_totalPrefetchOperations++; _totalPrefetchOperations++;
_forwardPrefetchOperations++; _forwardPrefetchOperations++;
@@ -1753,10 +1845,33 @@ class MainPageState extends State<MainPage> with WindowListener {
setState(() { setState(() {
for (var note in notes) { for (var note in notes) {
_noteCache[note.epochTime] = note; _noteCache[note.epochTime] = note;
// Track the newest note we've seen
if (_newestNoteTimestamp == null || note.epochTime > _newestNoteTimestamp!) {
_newestNoteTimestamp = note.epochTime;
}
}
// If we got fewer notes than requested, we've likely reached the end
if (notes.length < _cacheSizeAfter) {
_reachedNewestNote = true;
debugPrint("Received fewer notes than requested (${notes.length} < $_cacheSizeAfter), marking as reached newest");
// If we got any notes, the newest one is the newest timestamp
if (notes.isNotEmpty) {
final newestInBatch = notes.map((n) => n.epochTime).reduce((a, b) => a > b ? a : b);
if (_newestNoteTimestamp == null || newestInBatch > _newestNoteTimestamp!) {
_newestNoteTimestamp = newestInBatch;
}
} else if (_newestNoteTimestamp == null || timestamp > _newestNoteTimestamp!) {
// If we got no notes, then the timestamp we queried is the newest
_newestNoteTimestamp = timestamp;
}
} }
}); });
debugPrint("Cached ${notes.length} notes after $timestamp (total prefetch ops: $_totalPrefetchOperations, forward: $_forwardPrefetchOperations)"); debugPrint("Cached ${notes.length} notes after $timestamp (total prefetch ops: $_totalPrefetchOperations, forward: $_forwardPrefetchOperations)");
debugPrint("Current newest note timestamp: $_newestNoteTimestamp");
} catch (e) { } catch (e) {
debugPrint("Error loading notes batch after $timestamp: $e"); debugPrint("Error loading notes batch after $timestamp: $e");
} finally { } finally {
@@ -1882,6 +1997,13 @@ class MainPageState extends State<MainPage> with WindowListener {
return note; return note;
} }
// If we know we've reached the newest note and the timestamp is already
// at or beyond the newest timestamp, don't even try to fetch
if (_newestNoteTimestamp != null && timestamp >= _newestNoteTimestamp!) {
debugPrint("Not fetching next note - already at or beyond newest note timestamp");
return null;
}
// Not in cache, fetch from server // Not in cache, fetch from server
_cacheMisses++; _cacheMisses++;
debugPrint("Next note cache miss (Hits: $_cacheHits, Misses: $_cacheMisses)"); debugPrint("Next note cache miss (Hits: $_cacheHits, Misses: $_cacheMisses)");
@@ -1897,10 +2019,20 @@ class MainPageState extends State<MainPage> with WindowListener {
debugPrint("Adding note from server to cache: ${note.epochTime}"); debugPrint("Adding note from server to cache: ${note.epochTime}");
_noteCache[note.epochTime] = note; _noteCache[note.epochTime] = note;
// Update newest note timestamp if this is newer
if (_newestNoteTimestamp == null || note.epochTime > _newestNoteTimestamp!) {
_newestNoteTimestamp = note.epochTime;
}
// Don't automatically prefetch here - let the _goToNextNote method decide // Don't automatically prefetch here - let the _goToNextNote method decide
// This prevents redundant prefetching when we just need one note // This prevents redundant prefetching when we just need one note
} else { } else {
debugPrint("No next note found on server"); debugPrint("No next note found on server");
// If we didn't find a next note, we've reached the newest
if (_newestNoteTimestamp == null || timestamp > _newestNoteTimestamp!) {
_newestNoteTimestamp = timestamp;
}
} }
return note; return note;