// scanner/scanPhoto.js const path = require('path'); const fsp = require('fs/promises'); const { loadPreviousIndex, saveIndex } = require('./indexStore'); const scanUserRoot = require('./scanUser'); const postWithAuth = require('./postWithAuth'); const { WEB_ROOT, SEND_PHOTOS, BASE_URL, WRITE_INDEX, } = require('../config'); const COMMON = 'Common'; // Nome canonico e case-sensitive per la shared folder /** * Restituisce la prima directory esistente tra le candidate * @param {string[]} candidates * @returns {Promise} */ async function firstExistingDir(candidates) { for (const dir of candidates) { try { const st = await fsp.stat(dir); if (st.isDirectory()) return dir; } catch { /* ignore */ } } return null; } /** * Scansione foto con indice separato: * - Carica indice precedente (mappa { id: meta }) * - Produce SOLO i cambiamenti (nuovi/modificati) * - Merge e salvataggio atomico dell'indice * - POST verso /photos solo del delta (se abilitato) * - Admin: include la cartella "Common" * * Nota path: usiamo un path ASSOLUTO per /public/photos * partendo da .../api_v1/scanner -> '..', '..' per salire alla root del progetto. */ async function scanPhoto(dir, userName) { try { const previousIndexMap = await loadPreviousIndex(); const nextIndexMap = { ...previousIndexMap }; // Path assoluto alla radice photos (es: /public/photos) const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos'); let changes = []; if (userName === 'Admin') { // 1) Scansiona TUTTI gli utenti tranne la/e cartella/e common let entries = []; try { entries = await fsp.readdir(photosRoot, { withFileTypes: true }); } catch (e) { console.error(`[SCAN] photosRoot non accessibile: ${photosRoot}`, e.message); entries = []; } for (const e of entries) { if (!e.isDirectory()) continue; // salta qualunque "common" (qualunque casing) per non doppiare if (e.name.toLowerCase() === COMMON.toLowerCase()) continue; const userDir = path.join(photosRoot, e.name, 'original'); try { const st = await fsp.stat(userDir); if (!st.isDirectory()) continue; } catch { continue; } const userChanges = await scanUserRoot(e.name, userDir, previousIndexMap); for (const m of userChanges) nextIndexMap[m.id] = m; changes.push(...userChanges); } // 2) Scansiona la cartella COMMON (case strict), con fallback legacy (solo lettura) a 'common' const commonPreferred = path.join(photosRoot, COMMON, 'original'); // .../photos/Common/original const commonLegacy = path.join(photosRoot, 'common', 'original'); // .../photos/common/original (legacy) const commonDir = await firstExistingDir([commonPreferred, commonLegacy]); if (commonDir) { // Forziamo SEMPRE userName = 'Common' per ID/thumbnails coerenti in 'Common' const commonChanges = await scanUserRoot(COMMON, commonDir, previousIndexMap); for (const m of commonChanges) nextIndexMap[m.id] = m; changes.push(...commonChanges); console.log(`[SCAN] Common indicizzati da ${commonDir}: +${commonChanges.length}`); } else { console.log(`[SCAN] Nessuna cartella "${COMMON}" trovata sotto ${photosRoot} (atteso: ${commonPreferred})`); } } else { // Non-Admin (per completezza; in server.js /scan รจ Admin-only) const userDir = path.join(photosRoot, userName, 'original'); try { const st = await fsp.stat(userDir); if (st.isDirectory()) { const userChanges = await scanUserRoot(userName, userDir, previousIndexMap); for (const m of userChanges) nextIndexMap[m.id] = m; changes.push(...userChanges); } } catch { // utente senza dir 'original' -> nessuna modifica } } // Salva indice (mappa) in modo atomico if (WRITE_INDEX) { await saveIndex(nextIndexMap); } // POST solo dei cambiamenti (delta) if (SEND_PHOTOS && BASE_URL && changes.length) { for (const p of changes) { try { await postWithAuth(`${BASE_URL}/photos`, p); } catch (err) { console.error('Errore invio:', err.message); } } } console.log(`SCAN COMPLETATA per utente ${userName}: ${changes.length} file aggiornati`); return changes; } catch (e) { console.error('Errore generale scanPhoto:', e); throw e; } } module.exports = scanPhoto;