add disk-based metadata cache + refresh endpoint

This commit is contained in:
Steven (Zen) 2026-08-15 12:37:47 -06:00
parent 8aa50dd5e2
commit ad45d9a6b9

View File

@ -4,8 +4,8 @@
* *
* Strategy: * Strategy:
* - SSH (server-side find+cat) for all NAS access — metadata and thumbnails * - SSH (server-side find+cat) for all NAS access — metadata and thumbnails
* - Disk-based thumbnail cache at CACHE_DIR (survives container restarts) * - Disk-based cache at CACHE_DIR for both metadata and thumbnails (survives container restarts)
* - In-memory library metadata cache (reset on restart, cheap to rebuild) * - In-memory library metadata cache (hot path after disk cache is warm)
*/ */
const express = require('express'); const express = require('express');
@ -95,13 +95,16 @@ function listLibraries() {
.map(e => e.replace('.library', '')); .map(e => e.replace('.library', ''));
} }
// --- In-memory library metadata cache --- // --- In-memory library metadata cache (populated from disk cache on hit) ---
let libraryCache = {}; let libraryCache = {};
// --- Thumbnail: disk cache path helper --- // --- Disk cache path helpers ---
function thumbCachePath(library, imageId) { function thumbCachePath(library, imageId) {
return path.join(CACHE_DIR, `${library}__${imageId}.png`); return path.join(CACHE_DIR, `${library}__${imageId}.png`);
} }
function metaCachePath(libName) {
return path.join(CACHE_DIR, `meta__${libName}.json`);
}
// --- Routes --- // --- Routes ---
@ -113,15 +116,56 @@ app.get('/api/libraries', (req, res) => {
} }
}); });
app.get('/api/library/:name', (req, res) => { app.get('/api/library/:name', async (req, res) => {
try { try {
const name = req.params.name + '.library'; const name = req.params.name + '.library';
console.log(`Loading library: ${name} …`);
// 1. In-memory cache (fastest — already loaded this session)
if (libraryCache[name]) {
const data = libraryCache[name];
return res.json({ folders: data.meta.folders, imageCount: data.images.length, images: data.images, cached: 'memory' });
}
// 2. Disk cache (fast — survives restarts)
const diskPath = metaCachePath(name);
try {
const raw = await fsp.readFile(diskPath, 'utf8');
const data = JSON.parse(raw);
libraryCache[name] = data; // also warm the in-memory cache
console.log(`Library ${name} loaded from disk cache (${data.images.length} images)`);
return res.json({ folders: data.meta.folders, imageCount: data.images.length, images: data.images, cached: 'disk' });
} catch { /* cache miss — fall through to SSH */ }
// 3. SSH fetch from NAS (slow — first time only)
console.log(`Loading library: ${name} from NAS via SSH…`);
const t0 = Date.now(); const t0 = Date.now();
const data = loadLibrary(name); const data = loadLibrary(name);
libraryCache[name] = data; libraryCache[name] = data;
console.log(`Loaded ${data.images.length} images in ${Date.now() - t0}ms`); console.log(`Loaded ${data.images.length} images in ${Date.now() - t0}ms — writing to disk cache`);
res.json({ folders: data.meta.folders, imageCount: data.images.length, images: data.images });
// Write to disk cache (fire-and-forget)
fsp.writeFile(diskPath, JSON.stringify(data)).catch(err =>
console.warn(`Failed to write metadata cache for ${name}:`, err.message)
);
res.json({ folders: data.meta.folders, imageCount: data.images.length, images: data.images, cached: 'miss' });
} catch (e) {
console.error(e);
res.status(500).json({ error: e.message });
}
});
// Force a fresh SSH fetch and update the disk cache (call this when library contents change)
app.post('/api/library/:name/refresh', async (req, res) => {
try {
const name = req.params.name + '.library';
console.log(`Refreshing library: ${name} …`);
const t0 = Date.now();
const data = loadLibrary(name);
libraryCache[name] = data;
await fsp.writeFile(metaCachePath(name), JSON.stringify(data));
console.log(`Refreshed ${data.images.length} images in ${Date.now() - t0}ms`);
res.json({ ok: true, imageCount: data.images.length, ms: Date.now() - t0 });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
res.status(500).json({ error: e.message }); res.status(500).json({ error: e.message });