From e3c3aaf558fb448615dc2be3b13b33102b6e062d Mon Sep 17 00:00:00 2001 From: Hiren Kangad Date: Tue, 10 Mar 2026 12:19:43 +0530 Subject: [PATCH] fix(refboard): server posters respect culling budget + fix temp dir leak 1. Server posters no longer auto-load in VideoSprite constructor. loadServerPoster() is now public and called by the culling system only when within MAX_POSTER_VIDEOS budget. Videos outside the budget stay as placeholders regardless of server poster availability. 2. video-utils cleanup() now uses statSync to distinguish files from directories, unlinks files first, then rmdirs. Previously tmpDir was passed through path.dirname() which resolved to /tmp instead of the created temp directory. --- backend/video-utils.js | 29 ++++++++++++++-------- frontend/src/canvas/PixiCanvas.tsx | 9 +++++-- frontend/src/canvas/sprites/VideoSprite.ts | 13 +++++----- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/backend/video-utils.js b/backend/video-utils.js index 91c93f9..5112ea5 100644 --- a/backend/video-utils.js +++ b/backend/video-utils.js @@ -112,18 +112,27 @@ function extractPoster(buffer) { }); } -function cleanup(...files) { - for (const f of files) { - try { unlinkSync(f); } catch { /* ignore */ } - } - // Try removing parent dirs (they're temp dirs we created) - for (const f of files) { +/** + * Clean up temp files and directories. + * Pass file paths first, then the temp directory last. + * Files are unlinked, then the directory is removed. + */ +function cleanup(...paths) { + const dirs = []; + // First pass: unlink files, collect directories + for (const p of paths) { try { - const dir = path.dirname(f); - if (dir.includes('refboard-')) { - require('fs').rmdirSync(dir); + const stat = require('fs').statSync(p); + if (stat.isDirectory()) { + dirs.push(p); + } else { + unlinkSync(p); } - } catch { /* ignore — dir may not be empty or already removed */ } + } catch { /* already gone */ } + } + // Second pass: remove directories (now empty) + for (const d of dirs) { + try { require('fs').rmdirSync(d); } catch { /* ignore */ } } } diff --git a/frontend/src/canvas/PixiCanvas.tsx b/frontend/src/canvas/PixiCanvas.tsx index 40a5fe5..7e024e9 100644 --- a/frontend/src/canvas/PixiCanvas.tsx +++ b/frontend/src/canvas/PixiCanvas.tsx @@ -248,9 +248,14 @@ const PixiCanvas = forwardRef( } } - // Capture posters for nearest subset (only if already initialized) + // Load posters for nearest subset within budget for (const { item, sprite } of videoItems) { - if (shouldPoster.has(item.id) && sprite.isInitialized && !sprite.hasPoster) { + if (!shouldPoster.has(item.id) || sprite.hasPoster) continue; + if (sprite.hasServerPoster) { + // Server poster: load as image texture (no