Require painting thumb regen on every picture change and document the invariant.
Fix disk sync, ensure, and preload paths that previously reused the full image as a thumb. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
67594ea4d3
commit
bfa21989c9
+86
-15
@@ -96,10 +96,15 @@ function localFileExists(relPath) {
|
||||
return fs.existsSync(path.join(IMAGE_DIR, relPath));
|
||||
}
|
||||
|
||||
function syncPaintingFromDisk(row) {
|
||||
const safeBase = `${row.artist_name}_${row.title}`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
let imagePath = row.image_path;
|
||||
let thumbPath = row.thumbnail_path;
|
||||
function isPaintingThumbRel(rel) {
|
||||
if (!rel) return false;
|
||||
return rel.replace(/\\/g, '/').startsWith('paintings/thumbs/');
|
||||
}
|
||||
|
||||
/** Discover full/thumb files on disk for a painting basename. */
|
||||
function discoverPaintingFilesOnDisk(safeBase) {
|
||||
let imagePath = null;
|
||||
let thumbPath = null;
|
||||
|
||||
for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.JPG']) {
|
||||
const full = path.join(IMAGE_DIR, 'paintings', safeBase + ext);
|
||||
@@ -111,11 +116,50 @@ function syncPaintingFromDisk(row) {
|
||||
thumbPath = `paintings/thumbs/${safeBase}_thumb${ext}`;
|
||||
}
|
||||
}
|
||||
if (!thumbPath && imagePath) thumbPath = imagePath;
|
||||
const jpgThumb = path.join(IMAGE_DIR, 'paintings', 'thumbs', `${safeBase}_thumb.jpg`);
|
||||
if (!thumbPath && fs.existsSync(jpgThumb)) {
|
||||
thumbPath = `paintings/thumbs/${safeBase}_thumb.jpg`;
|
||||
}
|
||||
return { imagePath, thumbPath };
|
||||
}
|
||||
|
||||
/** Fast preload: link local files only, no external API calls */
|
||||
/**
|
||||
* Ensure a dedicated thumbs/ file exists for a full painting image.
|
||||
* Regenerates from the full file when missing.
|
||||
*/
|
||||
async function ensurePaintingThumbFromFull(fullRel, safeBase) {
|
||||
if (!fullRel || !localFileExists(fullRel)) return null;
|
||||
const expectedThumb = `paintings/thumbs/${safeBase}_thumb.jpg`;
|
||||
if (localFileExists(expectedThumb)) return expectedThumb;
|
||||
try {
|
||||
return await writePaintingThumb(path.join(IMAGE_DIR, fullRel), safeBase);
|
||||
} catch (err) {
|
||||
console.warn(`Painting thumb generation failed for ${safeBase}:`, err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sync/link paths from disk; generate thumbnail when full exists without a thumbs/ file. */
|
||||
async function syncPaintingFromDisk(row) {
|
||||
const safeBase = safePaintingBase(row.artist_name, row.title);
|
||||
let imagePath = localFileExists(row.image_path) ? row.image_path : null;
|
||||
let thumbPath =
|
||||
localFileExists(row.thumbnail_path) && isPaintingThumbRel(row.thumbnail_path)
|
||||
? row.thumbnail_path
|
||||
: null;
|
||||
|
||||
const discovered = discoverPaintingFilesOnDisk(safeBase);
|
||||
if (!imagePath && discovered.imagePath) imagePath = discovered.imagePath;
|
||||
if (!thumbPath && discovered.thumbPath) thumbPath = discovered.thumbPath;
|
||||
|
||||
if (!thumbPath && imagePath) {
|
||||
thumbPath = (await ensurePaintingThumbFromFull(imagePath, safeBase)) || null;
|
||||
}
|
||||
|
||||
return { imagePath, thumbPath };
|
||||
}
|
||||
|
||||
/** Fast preload: link local files only, no external API calls; regenerate missing thumbs from full files. */
|
||||
async function preloadArtistImagesLocal(artistId) {
|
||||
const rows = await pool.query(
|
||||
`SELECT p.id, p.title, p.image_path, p.thumbnail_path, a.name AS artist_name
|
||||
@@ -127,14 +171,14 @@ async function preloadArtistImagesLocal(artistId) {
|
||||
|
||||
let linked = 0;
|
||||
for (const row of rows.rows) {
|
||||
const hasLocal =
|
||||
localFileExists(row.thumbnail_path) || localFileExists(row.image_path);
|
||||
if (hasLocal) {
|
||||
const hasFull = localFileExists(row.image_path);
|
||||
const hasThumb = localFileExists(row.thumbnail_path) && isPaintingThumbRel(row.thumbnail_path);
|
||||
if (hasFull && hasThumb) {
|
||||
linked++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const synced = syncPaintingFromDisk(row);
|
||||
const synced = await syncPaintingFromDisk(row);
|
||||
if (synced.imagePath || synced.thumbPath) {
|
||||
await pool.query(
|
||||
`UPDATE paintings SET image_path = COALESCE($1, image_path), thumbnail_path = COALESCE($2, thumbnail_path) WHERE id = $3`,
|
||||
@@ -163,12 +207,27 @@ async function ensurePaintingImages(paintingId, size = 'thumb') {
|
||||
|
||||
const row = result.rows[0];
|
||||
const wantThumb = size !== 'full';
|
||||
const safeBase = safePaintingBase(row.artist_name, row.title);
|
||||
|
||||
if (wantThumb && localFileExists(row.thumbnail_path)) return row.thumbnail_path;
|
||||
if (wantThumb && localFileExists(row.thumbnail_path) && isPaintingThumbRel(row.thumbnail_path)) {
|
||||
return row.thumbnail_path;
|
||||
}
|
||||
if (!wantThumb && localFileExists(row.image_path)) return row.image_path;
|
||||
if (wantThumb && localFileExists(row.image_path)) return row.image_path;
|
||||
|
||||
const synced = syncPaintingFromDisk(row);
|
||||
// Full on disk but no dedicated thumb — regenerate before falling back to full file.
|
||||
if (wantThumb && localFileExists(row.image_path)) {
|
||||
const thumbRel = await ensurePaintingThumbFromFull(row.image_path, safeBase);
|
||||
if (thumbRel) {
|
||||
await pool.query(`UPDATE paintings SET thumbnail_path = $1 WHERE id = $2`, [
|
||||
thumbRel,
|
||||
paintingId,
|
||||
]);
|
||||
return thumbRel;
|
||||
}
|
||||
return row.image_path;
|
||||
}
|
||||
|
||||
const synced = await syncPaintingFromDisk(row);
|
||||
if (synced.imagePath || synced.thumbPath) {
|
||||
await pool.query(
|
||||
`UPDATE paintings
|
||||
@@ -177,8 +236,20 @@ async function ensurePaintingImages(paintingId, size = 'thumb') {
|
||||
WHERE id = $3`,
|
||||
[synced.imagePath, synced.thumbPath, paintingId]
|
||||
);
|
||||
if (wantThumb && localFileExists(synced.thumbPath)) return synced.thumbPath;
|
||||
if (wantThumb && localFileExists(synced.imagePath)) return synced.imagePath;
|
||||
if (wantThumb && localFileExists(synced.thumbPath) && isPaintingThumbRel(synced.thumbPath)) {
|
||||
return synced.thumbPath;
|
||||
}
|
||||
if (wantThumb && localFileExists(synced.imagePath)) {
|
||||
const thumbRel = await ensurePaintingThumbFromFull(synced.imagePath, safeBase);
|
||||
if (thumbRel) {
|
||||
await pool.query(`UPDATE paintings SET thumbnail_path = $1 WHERE id = $2`, [
|
||||
thumbRel,
|
||||
paintingId,
|
||||
]);
|
||||
return thumbRel;
|
||||
}
|
||||
return synced.imagePath;
|
||||
}
|
||||
if (!wantThumb && localFileExists(synced.imagePath)) return synced.imagePath;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user