photo_server_json_flutter_c.../api_v1/scanner/scanPhoto.js
2026-03-18 09:34:41 +01:00

247 lines
7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scanner/scanPhoto.js
const path = require('path');
const fsp = require('fs/promises');
const { log } = require('./logger');
const { loadPreviousIndex, saveIndex } = require('./indexStore');
const scanCartella = require('./scanCartella');
const postWithAuth = require('./postWithAuth');
const {
WEB_ROOT,
SEND_PHOTOS,
BASE_URL,
WRITE_INDEX,
SUPPORTED_EXTS
} = require('../config');
const createCleanupFunctions = require('./orphanCleanup');
// Variabile per log completo o ridotto
const LOG_VERBOSE = (process.env.LOG_VERBOSE === "true");
// Formatta ms → HH:MM:SS
function formatTime(ms) {
const sec = Math.floor(ms / 1000);
const h = String(Math.floor(sec / 3600)).padStart(2, '0');
const m = String(Math.floor((sec % 3600) / 60)).padStart(2, '0');
const s = String(sec % 60).padStart(2, '0');
return `${h}:${m}:${s}`;
}
// ---------------------------------------------------------
// ⚡ CONTEGGIO FILE VELOCE (senza hashing, EXIF, sharp, ecc.)
// ---------------------------------------------------------
async function countFilesFast(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_EXTS.has(ext)) {
count++;
}
}
}
}
await walk(rootDir);
return count;
}
async function scanPhoto(dir, userName, db) {
const start = Date.now();
log(`🔵 Inizio scan globale per user=${userName}`);
const previousIndexTree = await loadPreviousIndex();
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
const {
buildIdsListForFolder,
removeIdFromList,
deleteThumbsById,
deleteFromDB
} = createCleanupFunctions(db);
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
const userDir = path.join(photosRoot, userName, 'original');
let totalNew = 0;
let totalDeleted = 0;
let totalUnchanged = 0;
let newFiles = [];
// ---------------------------------------------------------
// 1) ⚡ CONTEGGIO FILE SUPER VELOCE
// ---------------------------------------------------------
const TOTAL_FILES = await countFilesFast(userDir);
if (TOTAL_FILES === 0) {
log(`Nessun file trovato per user=${userName}`);
return [];
}
log(`📦 File totali da processare: ${TOTAL_FILES}`);
let CURRENT = 0;
// ---------------------------------------------------------
// Funzione per aggiornare il file statico leggibile dallHTML
// ---------------------------------------------------------
async function updateStatusFile() {
const now = Date.now();
const elapsedMs = now - start;
const avg = elapsedMs / CURRENT;
const remainingMs = (TOTAL_FILES - CURRENT) * avg;
const status = {
current: CURRENT,
total: TOTAL_FILES,
percent: Number((CURRENT / TOTAL_FILES * 100).toFixed(2)),
eta: formatTime(remainingMs),
elapsed: formatTime(elapsedMs)
};
const statusPath = path.resolve(__dirname, "..", "..", "public/photos/scan_status.json");
await fsp.writeFile(statusPath, JSON.stringify(status));
}
// ---------------------------------------------------------
// 2) SCAN REALE DELLE CARTELLE
// ---------------------------------------------------------
async function scanSingleFolder(user, cartella, absCartella) {
log(`📁 Inizio cartella: ${cartella}`);
let idsIndex = await buildIdsListForFolder(user, cartella);
let newCount = 0;
let unchangedCount = 0;
let deletedCount = 0;
for await (const m of scanCartella(user, cartella, absCartella, previousIndexTree)) {
CURRENT++;
const fileName = m.path.split('/').pop();
const prefix = `[${CURRENT}/${TOTAL_FILES}]`;
// Aggiorna file statico per lHTML
await updateStatusFile();
// ⚪ FILE INVARIATO
if (m.unchanged) {
if (LOG_VERBOSE) {
log(`${prefix} ⚪ Invariato: ${fileName}`);
}
idsIndex = removeIdFromList(idsIndex, m.id);
unchangedCount++;
continue;
}
// 🟢 FILE NUOVO O MODIFICATO
log(`${prefix} 🟢 Nuovo/Modificato: ${fileName}`);
nextIndexTree[m.user] ??= {};
nextIndexTree[m.user][m.cartella] ??= {};
nextIndexTree[m.user][m.cartella][m.id] = {
id: m.id,
user: m.user,
cartella: m.cartella,
path: m.path,
hash: m._indexHash
};
idsIndex = removeIdFromList(idsIndex, m.id);
newCount++;
newFiles.push(m);
}
// 🔴 ORFANI
for (const orphanId of idsIndex) {
const old = previousIndexTree?.[user]?.[cartella]?.[orphanId];
const fileName = old?.path?.split('/').pop() || orphanId;
log(` 🔴 Eliminato: ${fileName}`);
await deleteThumbsById(orphanId);
deleteFromDB(orphanId);
const userTree = nextIndexTree[user];
if (userTree?.[cartella]?.[orphanId]) {
delete userTree[cartella][orphanId];
}
deletedCount++;
}
log(`📊 Fine cartella ${cartella}: invariati=${unchangedCount}, nuovi=${newCount}, cancellati=${deletedCount}`);
totalNew += newCount;
totalDeleted += deletedCount;
totalUnchanged += unchangedCount;
}
// ---------------------------------------------------------
// SCAN SOTTOCARTELLE
// ---------------------------------------------------------
let entries = [];
try {
entries = await fsp.readdir(userDir, { withFileTypes: true });
} catch {
log(`Nessuna directory per utente ${userName}`);
return [];
}
for (const e of entries) {
if (!e.isDirectory()) continue;
const cartella = e.name;
const absCartella = path.join(userDir, cartella);
await scanSingleFolder(userName, cartella, absCartella);
}
// ---------------------------------------------------------
// SALVO INDEX
// ---------------------------------------------------------
if (WRITE_INDEX) {
await saveIndex(nextIndexTree);
}
// ---------------------------------------------------------
// INVIO AL SERVER / POPOLAZIONE DB
// ---------------------------------------------------------
if (SEND_PHOTOS && BASE_URL && newFiles.length > 0) {
for (const p of newFiles) {
const fileName = p.path.split('/').pop();
try {
await postWithAuth(`${BASE_URL}/photos`, p);
log(`📤 Inviato al server: ${fileName}`);
} catch (err) {
log(`Errore invio ${fileName}: ${err.message}`);
}
}
}
// ---------------------------------------------------------
// FINE SCAN
// ---------------------------------------------------------
const elapsed = ((Date.now() - start) / 1000).toFixed(2);
log(`🟣 Scan COMPLETATO: nuovi=${totalNew}, cancellati=${totalDeleted}, invariati=${totalUnchanged}`);
log(`⏱ Tempo totale: ${elapsed}s`);
return newFiles;
}
module.exports = scanPhoto;