// api_v1/scanner/scanActivitiesUser.js const path = require('path'); const fsp = require('fs/promises'); const scanActivity = require('./scanActivity'); const { log } = require('./logger'); const { WEB_ROOT, SUPPORTED_ACTIVITY_EXTS } = require('../config'); const writeJsonAtomic = require('./writeJsonAtomic'); // --------------------------------------------------------- // ETA CALCULATOR // --------------------------------------------------------- function computeETA(startTime, current, total) { if (current === 0) return 'calcolo...'; const elapsed = (Date.now() - startTime) / 1000; const rate = current / elapsed; const remaining = (total - current) / rate; const m = Math.floor(remaining / 60); const s = Math.floor(remaining % 60); return `${m}m ${s}s`; } // --------------------------------------------------------- // PRIMA PASSATA: conta TUTTI i file reali ricorsivamente // --------------------------------------------------------- async function countActivityFilesUser(rootDir) { let count = 0; async function walk(dir) { let entries = []; try { entries = await fsp.readdir(dir, { withFileTypes: true }); } catch { return; } for (const e of entries) { const abs = path.join(dir, e.name); if (e.isDirectory()) { await walk(abs); } else { const ext = path.extname(e.name).toLowerCase(); if (SUPPORTED_ACTIVITY_EXTS.has(ext)) { count++; } } } } await walk(rootDir); return count; } // --------------------------------------------------------- // SECONDA PASSATA: scansiona SOLO le cartelle vere // --------------------------------------------------------- async function scanActivitiesUser(userName, db) { log(`🔵 Inizio scan ATTIVITÀ per user=${userName}`); const activitiesRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'activities'); const userDir = path.join(activitiesRoot, userName, 'original'); const statusPath = path.join(activitiesRoot, 'scan_status.json'); let entries = []; try { entries = await fsp.readdir(userDir, { withFileTypes: true }); } catch { log(`❌ Nessuna directory attività per utente ${userName}`); return []; } // Filtra SOLO cartelle vere dentro "original" const folders = entries .filter(e => e.isDirectory()) .map(e => e.name); // --------------------------------------------------------- // RIMOZIONE CARTELLE CANCELLATE DAL FILESYSTEM // --------------------------------------------------------- const createDeleteFolderFunctions = require('./deleteFolder'); const { deleteFolderForUser } = createDeleteFolderFunctions(db); const dbFolders = await db('activities') .where({ user: userName }) .distinct('cartella') .pluck('cartella'); for (const dbFolder of dbFolders) { if (!folders.includes(dbFolder)) { log(`🗑️ Cartella attività rimossa dal filesystem: ${dbFolder}`); await deleteFolderForUser(userName, dbFolder); } } // PRIMA PASSATA: conta i file reali const TOTAL_FILES = await countActivityFilesUser(userDir); const CURRENT = { value: 0 }; const start = Date.now(); log(`📊 Attività totali da scansionare: ${TOTAL_FILES}`); // Stato iniziale await writeJsonAtomic(statusPath, { current: 0, total: TOTAL_FILES, percent: 0, eta: 'calcolo...' }); const allNewActivities = []; // SECONDA PASSATA: UNA SOLA SCANSIONE PER CARTELLA for (const cartella of folders) { log(`📁 Scan cartella attività: ${cartella}`); const newActivities = await scanActivity( cartella, userName, db, CURRENT, TOTAL_FILES, start, async () => { await writeJsonAtomic(statusPath, { current: CURRENT.value, total: TOTAL_FILES, percent: TOTAL_FILES > 0 ? Math.round((CURRENT.value / TOTAL_FILES) * 100) : 100, eta: computeETA(start, CURRENT.value, TOTAL_FILES) }); } ); if (newActivities?.length) { allNewActivities.push(...newActivities); } } // Stato finale await writeJsonAtomic(statusPath, { current: TOTAL_FILES, total: TOTAL_FILES, percent: 100, eta: '0m 0s' }); log(`🟣 Scan ATTIVITÀ COMPLETATO per user=${userName}. Nuove attività: ${allNewActivities.length}`); return allNewActivities; } module.exports = scanActivitiesUser;