From 59751349b751db05ac89b94d0f8098fb63714958 Mon Sep 17 00:00:00 2001 From: Guido Schmit Date: Tue, 7 Apr 2026 22:09:11 +0200 Subject: [PATCH] =?UTF-8?q?perf:=20Sliding-window=20Cache=20=E2=80=94=20Hi?= =?UTF-8?q?ntergrund-Prefetch=20bei=20Cache-Randn=C3=A4he=20Wenn=20die=20a?= =?UTF-8?q?ktuelle=20Ansicht=20weniger=20als=204=20Wochen=20vom=20Cache-Ra?= =?UTF-8?q?nd=20entfernt=20ist,=20werden=20im=20Hintergrund=208=20weitere?= =?UTF-8?q?=20Wochen=20in=20diese=20Richtung=20geladen=20und=20in=20den=20?= =?UTF-8?q?Cache=20gemergt.=20Der=20Cache=20w=C3=A4chst=20damit=20automati?= =?UTF-8?q?sch=20mit=20der=20Navigation=20mit=20=E2=80=94=20kein=20sichtba?= =?UTF-8?q?rer=20Ladevorgang=20auch=20bei=20langen=20Spr=C3=BCngen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/js/calendar.js | 56 +++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index af88aaa..3dca0ac 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -73,12 +73,54 @@ export async function initCalendar() { } // ── Event cache ─────────────────────────────────────────── -const eventCache = { start: null, end: null, events: [] }; +const CACHE_BUF = 56 * 86400000; // initial ±8 weeks +const PREFETCH_EXT = 56 * 86400000; // extend by 8 weeks when triggered +const PREFETCH_EDGE = 28 * 86400000; // trigger when within 4 weeks of cache edge + +const eventCache = { + start: null, end: null, events: [], + _fwdPending: false, _bwdPending: false, +}; function invalidateCache() { - eventCache.start = null; - eventCache.end = null; - eventCache.events = []; + eventCache.start = null; + eventCache.end = null; + eventCache.events = []; + eventCache._fwdPending = false; + eventCache._bwdPending = false; +} + +function _mergeEvents(newEvents) { + const seen = new Set(eventCache.events.map(e => e.id + '@@' + (e.url || ''))); + for (const e of newEvents) { + const k = e.id + '@@' + (e.url || ''); + if (!seen.has(k)) { seen.add(k); eventCache.events.push(e); } + } +} + +// Fire-and-forget: extend the cache toward whichever edge the view is approaching +function prefetchIfNeeded(viewStart, viewEnd) { + if (!eventCache.start || !eventCache.end) return; + + if (!eventCache._fwdPending && (eventCache.end - viewEnd) < PREFETCH_EDGE) { + eventCache._fwdPending = true; + const from = new Date(eventCache.end); + const to = new Date(eventCache.end.getTime() + PREFETCH_EXT); + api.get(`/caldav/events?start=${from.toISOString()}&end=${to.toISOString()}`) + .then(evs => { _mergeEvents(evs); eventCache.end = to; }) + .catch(() => {}) + .finally(() => { eventCache._fwdPending = false; }); + } + + if (!eventCache._bwdPending && (viewStart - eventCache.start) < PREFETCH_EDGE) { + eventCache._bwdPending = true; + const from = new Date(eventCache.start.getTime() - PREFETCH_EXT); + const to = new Date(eventCache.start); + api.get(`/caldav/events?start=${from.toISOString()}&end=${to.toISOString()}`) + .then(evs => { _mergeEvents(evs); eventCache.start = from; }) + .catch(() => {}) + .finally(() => { eventCache._bwdPending = false; }); + } } // ── Data fetching ───────────────────────────────────────── @@ -92,13 +134,13 @@ async function fetchAndRender(force = false) { renderView(); updateTitle(); renderMiniCal(); + prefetchIfNeeded(start, end); // extend cache in background if approaching an edge return; } // Cache miss: fetch a wider window (±8 weeks) so subsequent navigation is instant - const BUF = 56 * 86400000; // 8 weeks in ms - const fetchStart = new Date(start.getTime() - BUF); - const fetchEnd = new Date(end.getTime() + BUF); + const fetchStart = new Date(start.getTime() - CACHE_BUF); + const fetchEnd = new Date(end.getTime() + CACHE_BUF); showLoading(); try {