super
5
.env
|
|
@ -5,3 +5,8 @@ PASSWORD=master66
|
||||||
JWT_SECRET=123456789
|
JWT_SECRET=123456789
|
||||||
JWT_EXPIRES=1h
|
JWT_EXPIRES=1h
|
||||||
PATH_FULL=true
|
PATH_FULL=true
|
||||||
|
LOG_MODE=both # console | file | both
|
||||||
|
LOG_FILE=scan.log # nome file log
|
||||||
|
LOG_DIR=public
|
||||||
|
LOG_VERBOSE=true
|
||||||
|
|
||||||
|
|
|
||||||
22
api_v1/scanner/elevation.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// elevation.js
|
||||||
|
async function getElevation(lat, lon) {
|
||||||
|
console.log("OpenElevation request:", lat, lon);
|
||||||
|
|
||||||
|
const url = `https://api.open-elevation.com/api/v1/lookup?locations=${lat},${lon}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
console.log("OpenElevation status:", res.status);
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
console.log("OpenElevation response:", json);
|
||||||
|
|
||||||
|
return json?.results?.[0]?.elevation ?? null;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Errore OpenElevation:", err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getElevation };
|
||||||
|
|
@ -8,35 +8,16 @@ const absIndexTmp = absIndexPath + '.tmp';
|
||||||
|
|
||||||
const PRETTY = process.env.INDEX_PRETTY === 'true';
|
const PRETTY = process.env.INDEX_PRETTY === 'true';
|
||||||
|
|
||||||
/**
|
|
||||||
* Carica l'indice strutturato:
|
|
||||||
* {
|
|
||||||
* user: {
|
|
||||||
* cartella: {
|
|
||||||
* id: { id, user, cartella, path, hash }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
async function loadPreviousIndex() {
|
async function loadPreviousIndex() {
|
||||||
try {
|
try {
|
||||||
const raw = await fsp.readFile(absIndexPath, 'utf8');
|
const raw = await fsp.readFile(absIndexPath, 'utf8');
|
||||||
const json = JSON.parse(raw);
|
const json = JSON.parse(raw);
|
||||||
|
return (json && typeof json === 'object') ? json : {};
|
||||||
if (json && typeof json === 'object' && !Array.isArray(json)) {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[INDEX] Formato vecchio → forzo rescan completo");
|
|
||||||
return {};
|
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Salva l'indice in modo atomico.
|
|
||||||
*/
|
|
||||||
async function saveIndex(indexTree) {
|
async function saveIndex(indexTree) {
|
||||||
const dir = path.dirname(absIndexPath);
|
const dir = path.dirname(absIndexPath);
|
||||||
await fsp.mkdir(dir, { recursive: true });
|
await fsp.mkdir(dir, { recursive: true });
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
// scanner/indexStore.js
|
|
||||||
const path = require('path');
|
|
||||||
const fsp = require('fs/promises');
|
|
||||||
const { WEB_ROOT, INDEX_PATH } = require('../config');
|
|
||||||
|
|
||||||
const absIndexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
|
||||||
const absIndexTmp = absIndexPath + '.tmp';
|
|
||||||
|
|
||||||
const PRETTY = process.env.INDEX_PRETTY === 'true';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Carica l'indice strutturato:
|
|
||||||
* {
|
|
||||||
* user: {
|
|
||||||
* cartella: {
|
|
||||||
* id: { id, user, cartella, path, hash }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
async function loadPreviousIndex() {
|
|
||||||
try {
|
|
||||||
const raw = await fsp.readFile(absIndexPath, 'utf8');
|
|
||||||
const json = JSON.parse(raw);
|
|
||||||
|
|
||||||
if (json && typeof json === 'object' && !Array.isArray(json)) {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[INDEX] Formato vecchio → forzo rescan completo");
|
|
||||||
return {};
|
|
||||||
} catch {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Salva l'indice in modo atomico.
|
|
||||||
*/
|
|
||||||
async function saveIndex(indexTree) {
|
|
||||||
const dir = path.dirname(absIndexPath);
|
|
||||||
await fsp.mkdir(dir, { recursive: true });
|
|
||||||
|
|
||||||
const data = PRETTY
|
|
||||||
? JSON.stringify(indexTree, null, 2)
|
|
||||||
: JSON.stringify(indexTree);
|
|
||||||
|
|
||||||
await fsp.writeFile(absIndexTmp, data, 'utf8');
|
|
||||||
await fsp.rename(absIndexTmp, absIndexPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { loadPreviousIndex, saveIndex, absIndexPath };
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// tools/listFolderIds.js
|
|
||||||
const fs = require('fs');
|
|
||||||
const fsp = require('fs/promises');
|
|
||||||
const path = require('path');
|
|
||||||
const readline = require('readline');
|
|
||||||
const { WEB_ROOT, INDEX_PATH } = require('../config');
|
|
||||||
|
|
||||||
async function buildIdsListForFolder(userName, cartella) {
|
|
||||||
const indexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
|
||||||
const idsIndex = [];
|
|
||||||
|
|
||||||
console.log("\n[LIST] buildIdsListForFolder()");
|
|
||||||
console.log("[LIST] user =", userName);
|
|
||||||
console.log("[LIST] cartella=", cartella);
|
|
||||||
console.log("[LIST] indexPath =", indexPath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) {
|
|
||||||
console.log("[LIST] index.json non trovato");
|
|
||||||
return idsIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const raw = await fsp.readFile(indexPath, 'utf8');
|
|
||||||
const index = JSON.parse(raw);
|
|
||||||
|
|
||||||
const userObj = index[userName];
|
|
||||||
if (!userObj) {
|
|
||||||
console.log(`[LIST] Nessuna entry per user=${userName}`);
|
|
||||||
return idsIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const folder = userObj[cartella];
|
|
||||||
if (!folder) {
|
|
||||||
console.log(`[LIST] Nessuna cartella "${cartella}" per user=${userName}`);
|
|
||||||
return idsIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids = Object.keys(folder).filter(k => k !== "_folderHash");
|
|
||||||
idsIndex.push(...ids);
|
|
||||||
|
|
||||||
console.log(`\n[LIST] ID trovati in ${userName}/${cartella}: ${idsIndex.length}`);
|
|
||||||
idsIndex.forEach(id => console.log(" -", id));
|
|
||||||
console.log("");
|
|
||||||
|
|
||||||
return idsIndex;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[LIST] Errore leggendo/parsing index.json:", err.message);
|
|
||||||
return idsIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
const ask = (q) => new Promise(res => rl.question(q, res));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await ask('User: ');
|
|
||||||
const cartella = await ask('Cartella: ');
|
|
||||||
|
|
||||||
await buildIdsListForFolder(user.trim(), cartella.trim());
|
|
||||||
} finally {
|
|
||||||
rl.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(err => {
|
|
||||||
console.error("Errore:", err);
|
|
||||||
});
|
|
||||||
43
api_v1/scanner/logger.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// scanner/logger.js
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const LOG_MODE = process.env.LOG_MODE || "console"; // console | file | both
|
||||||
|
const LOG_DIR = process.env.LOG_DIR || null;
|
||||||
|
const LOG_FILE = process.env.LOG_FILE || "scan.log";
|
||||||
|
|
||||||
|
let stream = null;
|
||||||
|
|
||||||
|
function resolveLogPath() {
|
||||||
|
if (LOG_DIR) {
|
||||||
|
return path.resolve(__dirname, "..", "..", LOG_DIR, LOG_FILE);
|
||||||
|
}
|
||||||
|
return path.resolve(__dirname, "..", "..", LOG_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_MODE === "file" || LOG_MODE === "both") {
|
||||||
|
const logPath = resolveLogPath();
|
||||||
|
|
||||||
|
// crea la directory se non esiste
|
||||||
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
||||||
|
|
||||||
|
stream = fs.createWriteStream(logPath, { flags: "a" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function ts() {
|
||||||
|
return new Date().toISOString().replace("T", " ").split(".")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(message) {
|
||||||
|
const line = `${ts()} ${message}\n`;
|
||||||
|
|
||||||
|
if (LOG_MODE === "console" || LOG_MODE === "both") {
|
||||||
|
process.stdout.write(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_MODE === "file" || LOG_MODE === "both") {
|
||||||
|
stream.write(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { log };
|
||||||
31
api_v1/scanner/logger.js.ok
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// scanner/logger.js
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const LOG_MODE = process.env.LOG_MODE || "console"; // console | file | both
|
||||||
|
const LOG_FILE = process.env.LOG_FILE || "scan.log";
|
||||||
|
|
||||||
|
let stream = null;
|
||||||
|
|
||||||
|
if (LOG_MODE === "file" || LOG_MODE === "both") {
|
||||||
|
const logPath = path.resolve(__dirname, "..", LOG_FILE);
|
||||||
|
stream = fs.createWriteStream(logPath, { flags: "a" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function ts() {
|
||||||
|
return new Date().toISOString().replace("T", " ").split(".")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(message) {
|
||||||
|
const line = `${ts()} ${message}\n`;
|
||||||
|
|
||||||
|
if (LOG_MODE === "console" || LOG_MODE === "both") {
|
||||||
|
process.stdout.write(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_MODE === "file" || LOG_MODE === "both") {
|
||||||
|
stream.write(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { log };
|
||||||
|
|
@ -10,92 +10,29 @@ module.exports = function createCleanupFunctions(db) {
|
||||||
const indexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
const indexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
||||||
const idsIndex = [];
|
const idsIndex = [];
|
||||||
|
|
||||||
console.log("\n[ORPHAN] buildIdsListForFolder()");
|
if (!fs.existsSync(indexPath)) return idsIndex;
|
||||||
console.log("[ORPHAN] user =", userName);
|
|
||||||
console.log("[ORPHAN] cartella =", cartella);
|
|
||||||
console.log("[ORPHAN] indexPath =", indexPath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) {
|
|
||||||
console.log("[ORPHAN] index.json non trovato");
|
|
||||||
return idsIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = await fsp.readFile(indexPath, 'utf8');
|
const raw = await fsp.readFile(indexPath, 'utf8');
|
||||||
const index = JSON.parse(raw);
|
const index = JSON.parse(raw);
|
||||||
|
|
||||||
const userObj = index[userName];
|
const folder = index?.[userName]?.[cartella];
|
||||||
if (!userObj) return idsIndex;
|
|
||||||
|
|
||||||
const folder = userObj[cartella];
|
|
||||||
if (!folder) return idsIndex;
|
if (!folder) return idsIndex;
|
||||||
|
|
||||||
const ids = Object.keys(folder).filter(k => k !== "_folderHash");
|
return Object.keys(folder).filter(k => k !== "_folderHash");
|
||||||
idsIndex.push(...ids);
|
} catch {
|
||||||
|
|
||||||
console.log(`[ORPHAN] ID trovati in ${userName}/${cartella}: ${idsIndex.length}`);
|
|
||||||
idsIndex.forEach(id => console.log(" -", id));
|
|
||||||
|
|
||||||
return idsIndex;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.log("[ORPHAN] Errore leggendo index.json:", err.message);
|
|
||||||
return idsIndex;
|
return idsIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeIdFromList(idsIndex, id) {
|
function removeIdFromList(idsIndex, id) {
|
||||||
const newList = idsIndex.filter(x => x !== id);
|
return idsIndex.filter(x => x !== id);
|
||||||
|
|
||||||
if (newList.length !== idsIndex.length) {
|
|
||||||
console.log(`[ORPHAN] Rimosso ID dalla lista: ${id}`);
|
|
||||||
} else {
|
|
||||||
console.log(`[ORPHAN] ID NON presente nella lista: ${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[ORPHAN] ID rimanenti (${newList.length}):`);
|
|
||||||
newList.forEach(x => console.log(" -", x));
|
|
||||||
console.log("");
|
|
||||||
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteFromIndexById(id) {
|
|
||||||
const indexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
|
||||||
|
|
||||||
if (!fs.existsSync(indexPath)) return false;
|
|
||||||
|
|
||||||
const raw = await fsp.readFile(indexPath, 'utf8');
|
|
||||||
const index = JSON.parse(raw);
|
|
||||||
|
|
||||||
let deleted = false;
|
|
||||||
|
|
||||||
for (const user of Object.keys(index)) {
|
|
||||||
for (const cartella of Object.keys(index[user])) {
|
|
||||||
const folder = index[user][cartella];
|
|
||||||
if (folder[id]) {
|
|
||||||
delete folder[id];
|
|
||||||
deleted = true;
|
|
||||||
console.log(`✔ Eliminato ID ${id} da ${user}/${cartella}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleted) {
|
|
||||||
await fsp.writeFile(indexPath, JSON.stringify(index, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
return deleted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteThumbsById(id) {
|
async function deleteThumbsById(id) {
|
||||||
const col = db.get('photos');
|
const col = db.get('photos');
|
||||||
const rec = col.find({ id }).value();
|
const rec = col.find({ id }).value();
|
||||||
|
if (!rec) return false;
|
||||||
if (!rec) {
|
|
||||||
console.log(`[ORPHAN] Nessun record DB per ID ${id}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thumbs = [rec.thub1, rec.thub2].filter(Boolean);
|
const thumbs = [rec.thub1, rec.thub2].filter(Boolean);
|
||||||
let deleted = false;
|
let deleted = false;
|
||||||
|
|
@ -104,11 +41,10 @@ module.exports = function createCleanupFunctions(db) {
|
||||||
const abs = path.resolve(__dirname, '../public' + t);
|
const abs = path.resolve(__dirname, '../public' + t);
|
||||||
if (fs.existsSync(abs)) {
|
if (fs.existsSync(abs)) {
|
||||||
await fsp.rm(abs, { force: true });
|
await fsp.rm(abs, { force: true });
|
||||||
console.log(`✔ Eliminato thumb: ${abs}`);
|
console.log(` 🔴 Thumb eliminato: ${abs}`);
|
||||||
deleted = true;
|
deleted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,18 +54,15 @@ module.exports = function createCleanupFunctions(db) {
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
col.remove({ id }).write();
|
col.remove({ id }).write();
|
||||||
console.log(`✔ Eliminato ID ${id} dal DB`);
|
console.log(` 🔴 Eliminato dal DB: ${id}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ORPHAN] ID ${id} non presente nel DB`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildIdsListForFolder,
|
buildIdsListForFolder,
|
||||||
removeIdFromList,
|
removeIdFromList,
|
||||||
deleteFromIndexById,
|
|
||||||
deleteThumbsById,
|
deleteThumbsById,
|
||||||
deleteFromDB
|
deleteFromDB
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,12 @@ const { createVideoThumbnail, createThumbnails } = require('./thumbs');
|
||||||
const { probeVideo } = require('./video');
|
const { probeVideo } = require('./video');
|
||||||
const loc = require('../geo.js');
|
const loc = require('../geo.js');
|
||||||
const { WEB_ROOT, PATH_FULL } = require('../config');
|
const { WEB_ROOT, PATH_FULL } = require('../config');
|
||||||
|
const { getElevation } = require('./elevation');
|
||||||
|
|
||||||
|
|
||||||
async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
|
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
|
||||||
|
|
||||||
console.log(
|
|
||||||
`PROCESSING → user=${userName} | cartella=${cartella} | file=${fileRelPath}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const thumbBase = path.join(
|
const thumbBase = path.join(
|
||||||
WEB_ROOT,
|
WEB_ROOT,
|
||||||
'photos',
|
'photos',
|
||||||
|
|
@ -27,7 +25,10 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
|
|
||||||
await fsp.mkdir(thumbBase, { recursive: true });
|
await fsp.mkdir(thumbBase, { recursive: true });
|
||||||
|
|
||||||
const baseName = path.parse(fileRelPath).name;
|
// --- Nome file + estensione (dal path ASSOLUTO, sempre corretto) ---
|
||||||
|
const parsed = path.parse(absPath);
|
||||||
|
const baseName = parsed.name; // IMG_0249
|
||||||
|
const extName = parsed.ext; // .JPG
|
||||||
|
|
||||||
const absThumbMin = path.join(thumbBase, `${baseName}_min.jpg`);
|
const absThumbMin = path.join(thumbBase, `${baseName}_min.jpg`);
|
||||||
const absThumbAvg = path.join(thumbBase, `${baseName}_avg.jpg`);
|
const absThumbAvg = path.join(thumbBase, `${baseName}_avg.jpg`);
|
||||||
|
|
@ -44,54 +45,93 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
tags = await ExifReader.load(absPath, { expanded: true });
|
tags = await ExifReader.load(absPath, { expanded: true });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
let timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
||||||
const takenAtIso = parseExifDateUtc(timeRaw);
|
let takenAtIso = parseExifDateUtc(timeRaw);
|
||||||
|
|
||||||
// --- GPS ---
|
// Fallback per i video
|
||||||
let gps = isVideo
|
if (isVideo) {
|
||||||
? await extractGpsWithExiftool(absPath)
|
const fallback = new Date(st.mtimeMs).toISOString();
|
||||||
: extractGpsFromExif(tags);
|
takenAtIso = fallback;
|
||||||
|
timeRaw = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
// --- DIMENSIONI ---
|
|
||||||
let width = null, height = null, duration = null;
|
// --- GPS ---
|
||||||
|
let gps = null;
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
// i video usano exiftool
|
||||||
|
gps = await extractGpsWithExiftool(absPath);
|
||||||
|
} else {
|
||||||
|
// le foto usano exifreader
|
||||||
|
gps = extractGpsFromExif(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ALTITUDINE DA SERVIZIO ESTERNO SE MANCANTE ---
|
||||||
|
if (gps && gps.lat && gps.lng) {
|
||||||
|
if (gps.alt == null) {
|
||||||
|
gps.alt = await getElevation(gps.lat, gps.lng);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- DIMENSIONI & ROTAZIONE ---
|
||||||
|
let width = null, height = null, duration = null, duration_ms = null, rotation = 0;
|
||||||
|
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
const info = await probeVideo(absPath);
|
const info = await probeVideo(absPath);
|
||||||
const stream = info.streams?.find(s => s.width && s.height);
|
const stream = info.streams?.find(s => s.width && s.height);
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
width = stream.width;
|
width = stream.width;
|
||||||
height = stream.height;
|
height = stream.height;
|
||||||
|
|
||||||
|
rotation = 0;
|
||||||
|
|
||||||
|
if (stream?.tags?.rotate) {
|
||||||
|
rotation = Number(stream.tags.rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdl = stream?.side_data_list;
|
||||||
|
if (sdl && Array.isArray(sdl)) {
|
||||||
|
const rotEntry = sdl.find(d => d.rotation !== undefined);
|
||||||
|
if (rotEntry) rotation = Number(rotEntry.rotation);
|
||||||
|
|
||||||
|
const matrixEntry = sdl.find(d => typeof d.displaymatrix === 'string');
|
||||||
|
if (matrixEntry) {
|
||||||
|
const match = matrixEntry.displaymatrix.match(/rotation of ([\-0-9]+) degrees/i);
|
||||||
|
if (match) rotation = Number(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation = ((rotation % 360) + 360) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
duration = info.format?.duration || null;
|
duration = info.format?.duration || null;
|
||||||
|
duration_ms = duration ? Math.round(duration * 1000) : null;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const meta = await sharp(absPath).metadata();
|
const meta = await sharp(absPath).metadata();
|
||||||
width = meta.width || null;
|
width = meta.width || null;
|
||||||
height = meta.height || null;
|
height = meta.height || null;
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const raw =
|
||||||
|
tags?.exif?.Orientation?.value ??
|
||||||
|
tags?.image?.Orientation?.value ??
|
||||||
|
tags?.ifd0?.Orientation?.value ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const val = Array.isArray(raw) ? raw[0] : raw;
|
||||||
|
|
||||||
|
const map = { 1: 0, 3: 180, 6: 90, 8: 270 };
|
||||||
|
rotation = map[val] ?? 0;
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ROTAZIONE EXIF → GRADI REALI ---
|
|
||||||
let rotation = null;
|
|
||||||
try {
|
|
||||||
const raw =
|
|
||||||
tags?.exif?.Orientation?.value ??
|
|
||||||
tags?.image?.Orientation?.value ??
|
|
||||||
tags?.ifd0?.Orientation?.value ??
|
|
||||||
null;
|
|
||||||
|
|
||||||
const val = Array.isArray(raw) ? raw[0] : raw;
|
|
||||||
|
|
||||||
const map = {
|
|
||||||
1: 0,
|
|
||||||
3: 180,
|
|
||||||
6: 90,
|
|
||||||
8: 270
|
|
||||||
};
|
|
||||||
|
|
||||||
rotation = map[val] ?? null;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const mime_type = inferMimeFromExt(ext);
|
const mime_type = inferMimeFromExt(ext);
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
|
@ -121,6 +161,7 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
id,
|
id,
|
||||||
user: userName,
|
user: userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
name: baseName + extName, // 👈 SEMPRE CORRETTO
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
thub1: fullThub1,
|
thub1: fullThub1,
|
||||||
thub2: fullThub2,
|
thub2: fullThub2,
|
||||||
|
|
@ -130,10 +171,10 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
mime_type,
|
mime_type,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
rotation, // <── ROTAZIONE IN GRADI REALI
|
rotation,
|
||||||
size_bytes: st.size,
|
size_bytes: st.size,
|
||||||
mtimeMs: st.mtimeMs,
|
mtimeMs: st.mtimeMs,
|
||||||
duration: isVideo ? duration : null,
|
duration_ms: isVideo ? duration_ms : null,
|
||||||
location
|
location
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
171
api_v1/scanner/processFile.js.ok
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const ExifReader = require('exifreader');
|
||||||
|
const sharp = require('sharp');
|
||||||
|
const { sha256, inferMimeFromExt, parseExifDateUtc } = require('./utils');
|
||||||
|
const { extractGpsFromExif, extractGpsWithExiftool } = require('./gps');
|
||||||
|
const { createVideoThumbnail, createThumbnails } = require('./thumbs');
|
||||||
|
const { probeVideo } = require('./video');
|
||||||
|
const loc = require('../geo.js');
|
||||||
|
const { WEB_ROOT, PATH_FULL } = require('../config');
|
||||||
|
|
||||||
|
async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
|
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
|
||||||
|
|
||||||
|
const thumbBase = path.join(
|
||||||
|
WEB_ROOT,
|
||||||
|
'photos',
|
||||||
|
userName,
|
||||||
|
'thumbs',
|
||||||
|
cartella,
|
||||||
|
path.dirname(fileRelPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
await fsp.mkdir(thumbBase, { recursive: true });
|
||||||
|
|
||||||
|
const baseName = path.parse(fileRelPath).name;
|
||||||
|
|
||||||
|
const absThumbMin = path.join(thumbBase, `${baseName}_min.jpg`);
|
||||||
|
const absThumbAvg = path.join(thumbBase, `${baseName}_avg.jpg`);
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
await createVideoThumbnail(absPath, absThumbMin, absThumbAvg);
|
||||||
|
} else {
|
||||||
|
await createThumbnails(absPath, absThumbMin, absThumbAvg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EXIF ---
|
||||||
|
let tags = {};
|
||||||
|
try {
|
||||||
|
tags = await ExifReader.load(absPath, { expanded: true });
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
||||||
|
const takenAtIso = parseExifDateUtc(timeRaw);
|
||||||
|
|
||||||
|
// --- GPS ---
|
||||||
|
let gps = isVideo
|
||||||
|
? await extractGpsWithExiftool(absPath)
|
||||||
|
: extractGpsFromExif(tags);
|
||||||
|
|
||||||
|
// --- DIMENSIONI & ROTAZIONE ---
|
||||||
|
let width = null, height = null, duration = null, rotation = 0;
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
const info = await probeVideo(absPath);
|
||||||
|
const stream = info.streams?.find(s => s.width && s.height);
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
width = stream.width;
|
||||||
|
height = stream.height;
|
||||||
|
|
||||||
|
// --- ROTAZIONE VIDEO (robusta, compatibile Xiaomi) ---
|
||||||
|
rotation = 0;
|
||||||
|
|
||||||
|
// 1) rotate standard
|
||||||
|
if (stream?.tags?.rotate) {
|
||||||
|
rotation = Number(stream.tags.rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) side_data_list rotation
|
||||||
|
const sdl = stream?.side_data_list;
|
||||||
|
if (sdl && Array.isArray(sdl)) {
|
||||||
|
const rotEntry = sdl.find(d => d.rotation !== undefined);
|
||||||
|
if (rotEntry) {
|
||||||
|
rotation = Number(rotEntry.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Xiaomi: displaymatrix string
|
||||||
|
const matrixEntry = sdl.find(d => typeof d.displaymatrix === 'string');
|
||||||
|
if (matrixEntry) {
|
||||||
|
const match = matrixEntry.displaymatrix.match(/rotation of ([\-0-9]+) degrees/i);
|
||||||
|
if (match) {
|
||||||
|
rotation = Number(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalizza (Xiaomi usa -90 → 270)
|
||||||
|
rotation = ((rotation % 360) + 360) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = info.format?.duration || null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// FOTO
|
||||||
|
try {
|
||||||
|
const meta = await sharp(absPath).metadata();
|
||||||
|
width = meta.width || null;
|
||||||
|
height = meta.height || null;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// --- ROTAZIONE EXIF FOTO ---
|
||||||
|
try {
|
||||||
|
const raw =
|
||||||
|
tags?.exif?.Orientation?.value ??
|
||||||
|
tags?.image?.Orientation?.value ??
|
||||||
|
tags?.ifd0?.Orientation?.value ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const val = Array.isArray(raw) ? raw[0] : raw;
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
1: 0,
|
||||||
|
3: 180,
|
||||||
|
6: 90,
|
||||||
|
8: 270
|
||||||
|
};
|
||||||
|
|
||||||
|
rotation = map[val] ?? 0;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mime_type = inferMimeFromExt(ext);
|
||||||
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
const location = gps ? await loc(gps.lng, gps.lat) : null;
|
||||||
|
|
||||||
|
//
|
||||||
|
// --- GESTIONE PATH FULL / RELATIVI ---
|
||||||
|
//
|
||||||
|
|
||||||
|
const relPath = fileRelPath;
|
||||||
|
const relThub1 = fileRelPath.replace(/\.[^.]+$/, '_min.jpg');
|
||||||
|
const relThub2 = fileRelPath.replace(/\.[^.]+$/, '_avg.jpg');
|
||||||
|
|
||||||
|
const fullPath = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, cartella, fileRelPath)
|
||||||
|
: relPath;
|
||||||
|
|
||||||
|
const fullThub1 = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, 'thumbs', cartella, relThub1)
|
||||||
|
: relThub1;
|
||||||
|
|
||||||
|
const fullThub2 = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, 'thumbs', cartella, relThub2)
|
||||||
|
: relThub2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: fullPath,
|
||||||
|
thub1: fullThub1,
|
||||||
|
thub2: fullThub2,
|
||||||
|
gps,
|
||||||
|
data: timeRaw,
|
||||||
|
taken_at: takenAtIso,
|
||||||
|
mime_type,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
rotation,
|
||||||
|
size_bytes: st.size,
|
||||||
|
mtimeMs: st.mtimeMs,
|
||||||
|
duration: isVideo ? duration : null,
|
||||||
|
location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processFile;
|
||||||
|
|
||||||
|
|
||||||
171
api_v1/scanner/processFile.js.ok2
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const ExifReader = require('exifreader');
|
||||||
|
const sharp = require('sharp');
|
||||||
|
const { sha256, inferMimeFromExt, parseExifDateUtc } = require('./utils');
|
||||||
|
const { extractGpsFromExif, extractGpsWithExiftool } = require('./gps');
|
||||||
|
const { createVideoThumbnail, createThumbnails } = require('./thumbs');
|
||||||
|
const { probeVideo } = require('./video');
|
||||||
|
const loc = require('../geo.js');
|
||||||
|
const { WEB_ROOT, PATH_FULL } = require('../config');
|
||||||
|
|
||||||
|
async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
|
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
|
||||||
|
|
||||||
|
const thumbBase = path.join(
|
||||||
|
WEB_ROOT,
|
||||||
|
'photos',
|
||||||
|
userName,
|
||||||
|
'thumbs',
|
||||||
|
cartella,
|
||||||
|
path.dirname(fileRelPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
await fsp.mkdir(thumbBase, { recursive: true });
|
||||||
|
|
||||||
|
const baseName = path.parse(fileRelPath).name;
|
||||||
|
|
||||||
|
const absThumbMin = path.join(thumbBase, `${baseName}_min.jpg`);
|
||||||
|
const absThumbAvg = path.join(thumbBase, `${baseName}_avg.jpg`);
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
await createVideoThumbnail(absPath, absThumbMin, absThumbAvg);
|
||||||
|
} else {
|
||||||
|
await createThumbnails(absPath, absThumbMin, absThumbAvg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EXIF ---
|
||||||
|
let tags = {};
|
||||||
|
try {
|
||||||
|
tags = await ExifReader.load(absPath, { expanded: true });
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
||||||
|
const takenAtIso = parseExifDateUtc(timeRaw);
|
||||||
|
|
||||||
|
// --- GPS ---
|
||||||
|
let gps = isVideo
|
||||||
|
? await extractGpsWithExiftool(absPath)
|
||||||
|
: extractGpsFromExif(tags);
|
||||||
|
|
||||||
|
// --- DIMENSIONI & ROTAZIONE ---
|
||||||
|
let width = null, height = null, duration = null, duration_ms = null, rotation = 0;
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
const info = await probeVideo(absPath);
|
||||||
|
const stream = info.streams?.find(s => s.width && s.height);
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
width = stream.width;
|
||||||
|
height = stream.height;
|
||||||
|
|
||||||
|
// --- ROTAZIONE VIDEO (robusta, compatibile Xiaomi) ---
|
||||||
|
rotation = 0;
|
||||||
|
|
||||||
|
// 1) rotate standard
|
||||||
|
if (stream?.tags?.rotate) {
|
||||||
|
rotation = Number(stream.tags.rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) side_data_list rotation
|
||||||
|
const sdl = stream?.side_data_list;
|
||||||
|
if (sdl && Array.isArray(sdl)) {
|
||||||
|
const rotEntry = sdl.find(d => d.rotation !== undefined);
|
||||||
|
if (rotEntry) {
|
||||||
|
rotation = Number(rotEntry.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Xiaomi: displaymatrix string
|
||||||
|
const matrixEntry = sdl.find(d => typeof d.displaymatrix === 'string');
|
||||||
|
if (matrixEntry) {
|
||||||
|
const match = matrixEntry.displaymatrix.match(/rotation of ([\-0-9]+) degrees/i);
|
||||||
|
if (match) {
|
||||||
|
rotation = Number(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalizza (Xiaomi usa -90 → 270)
|
||||||
|
rotation = ((rotation % 360) + 360) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = info.format?.duration || null;
|
||||||
|
duration_ms = duration ? Math.round(duration * 1000) : null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// FOTO
|
||||||
|
try {
|
||||||
|
const meta = await sharp(absPath).metadata();
|
||||||
|
width = meta.width || null;
|
||||||
|
height = meta.height || null;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// --- ROTAZIONE EXIF FOTO ---
|
||||||
|
try {
|
||||||
|
const raw =
|
||||||
|
tags?.exif?.Orientation?.value ??
|
||||||
|
tags?.image?.Orientation?.value ??
|
||||||
|
tags?.ifd0?.Orientation?.value ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const val = Array.isArray(raw) ? raw[0] : raw;
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
1: 0,
|
||||||
|
3: 180,
|
||||||
|
6: 90,
|
||||||
|
8: 270
|
||||||
|
};
|
||||||
|
|
||||||
|
rotation = map[val] ?? 0;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mime_type = inferMimeFromExt(ext);
|
||||||
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
const location = gps ? await loc(gps.lng, gps.lat) : null;
|
||||||
|
|
||||||
|
//
|
||||||
|
// --- GESTIONE PATH FULL / RELATIVI ---
|
||||||
|
//
|
||||||
|
|
||||||
|
const relPath = fileRelPath;
|
||||||
|
const relThub1 = fileRelPath.replace(/\.[^.]+$/, '_min.jpg');
|
||||||
|
const relThub2 = fileRelPath.replace(/\.[^.]+$/, '_avg.jpg');
|
||||||
|
|
||||||
|
const fullPath = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, cartella, fileRelPath)
|
||||||
|
: relPath;
|
||||||
|
|
||||||
|
const fullThub1 = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, 'thumbs', cartella, relThub1)
|
||||||
|
: relThub1;
|
||||||
|
|
||||||
|
const fullThub2 = PATH_FULL
|
||||||
|
? path.posix.join('/photos', userName, 'thumbs', cartella, relThub2)
|
||||||
|
: relThub2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: fullPath,
|
||||||
|
thub1: fullThub1,
|
||||||
|
thub2: fullThub2,
|
||||||
|
gps,
|
||||||
|
data: timeRaw,
|
||||||
|
taken_at: takenAtIso,
|
||||||
|
mime_type,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
rotation,
|
||||||
|
size_bytes: st.size,
|
||||||
|
mtimeMs: st.mtimeMs,
|
||||||
|
// duration: isVideo ? duration : null,
|
||||||
|
duration_ms: isVideo ? duration_ms : null,
|
||||||
|
location
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processFile;
|
||||||
|
|
@ -5,17 +5,13 @@ const processFile = require('./processFile');
|
||||||
const { sha256 } = require('./utils');
|
const { sha256 } = require('./utils');
|
||||||
const { SUPPORTED_EXTS } = require('../config');
|
const { SUPPORTED_EXTS } = require('../config');
|
||||||
|
|
||||||
async function scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
async function* scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
||||||
const changes = [];
|
|
||||||
|
|
||||||
async function walk(currentAbs, relPath = '') {
|
|
||||||
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
|
||||||
|
|
||||||
|
async function* walk(currentAbs, relPath = '') {
|
||||||
let entries = [];
|
let entries = [];
|
||||||
try {
|
try {
|
||||||
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +19,7 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
const absPath = path.join(currentAbs, e.name);
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
if (e.isDirectory()) {
|
if (e.isDirectory()) {
|
||||||
await walk(absPath, path.join(relPath, e.name));
|
yield* walk(absPath, path.join(relPath, e.name));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,29 +27,16 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
if (!SUPPORTED_EXTS.has(ext)) continue;
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
||||||
|
|
||||||
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
||||||
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
|
||||||
|
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
let st;
|
let st;
|
||||||
try {
|
try { st = await fsp.stat(absPath); } catch { continue; }
|
||||||
st = await fsp.stat(absPath);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
|
|
||||||
console.log(`[SCAN] CHECK → id=${id}, hash=${hash}, prevHash=${prev?.hash}`);
|
|
||||||
|
|
||||||
// 🔥 NON facciamo più SKIP qui.
|
|
||||||
// Anche se il file è invariato, lo passiamo a scanPhoto.js
|
|
||||||
// così può rimuovere l'ID da idsIndex.
|
|
||||||
if (prev && prev.hash === hash) {
|
if (prev && prev.hash === hash) {
|
||||||
console.log(`[SCAN] FILE INVARIATO (passato a scanPhoto) → ${fileRelPath}`);
|
yield {
|
||||||
|
|
||||||
const meta = {
|
|
||||||
id,
|
id,
|
||||||
user: userName,
|
user: userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
|
@ -61,13 +44,9 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
_indexHash: hash,
|
_indexHash: hash,
|
||||||
unchanged: true
|
unchanged: true
|
||||||
};
|
};
|
||||||
|
|
||||||
changes.push(meta);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
|
||||||
|
|
||||||
const meta = await processFile(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
|
@ -81,12 +60,11 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
meta._indexHash = hash;
|
meta._indexHash = hash;
|
||||||
|
|
||||||
changes.push(meta);
|
yield meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await walk(absCartella);
|
yield* walk(absCartella);
|
||||||
return changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = scanCartella;
|
module.exports = scanCartella;
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,13 @@ const processFile = require('./processFile');
|
||||||
const { sha256 } = require('./utils');
|
const { sha256 } = require('./utils');
|
||||||
const { SUPPORTED_EXTS } = require('../config');
|
const { SUPPORTED_EXTS } = require('../config');
|
||||||
|
|
||||||
async function scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
async function* scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
||||||
const changes = [];
|
|
||||||
|
|
||||||
async function walk(currentAbs, relPath = '') {
|
|
||||||
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
|
||||||
|
|
||||||
|
async function* walk(currentAbs, relPath = '') {
|
||||||
let entries = [];
|
let entries = [];
|
||||||
try {
|
try {
|
||||||
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +19,7 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
const absPath = path.join(currentAbs, e.name);
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
if (e.isDirectory()) {
|
if (e.isDirectory()) {
|
||||||
await walk(absPath, path.join(relPath, e.name));
|
yield* walk(absPath, path.join(relPath, e.name));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,29 +27,28 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
if (!SUPPORTED_EXTS.has(ext)) continue;
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
||||||
|
|
||||||
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
||||||
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
|
||||||
|
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
let st;
|
let st;
|
||||||
try {
|
try { st = await fsp.stat(absPath); } catch { continue; }
|
||||||
st = await fsp.stat(absPath);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
|
|
||||||
console.log(`[SCAN] CHECK → id=${id}, hash=${hash}, prevHash=${prev?.hash}`);
|
// FILE INVARIATO
|
||||||
|
|
||||||
if (prev && prev.hash === hash) {
|
if (prev && prev.hash === hash) {
|
||||||
console.log(`[SCAN] SKIP (unchanged) → ${fileRelPath}`);
|
yield {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: `/photos/${userName}/original/${cartella}/${fileRelPath}`,
|
||||||
|
_indexHash: hash,
|
||||||
|
unchanged: true
|
||||||
|
};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
// FILE NUOVO/MODIFICATO
|
||||||
|
|
||||||
const meta = await processFile(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
|
@ -67,12 +62,11 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
meta._indexHash = hash;
|
meta._indexHash = hash;
|
||||||
|
|
||||||
changes.push(meta);
|
yield meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await walk(absCartella);
|
yield* walk(absCartella);
|
||||||
return changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = scanCartella;
|
module.exports = scanCartella;
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@ const fsp = require('fs/promises');
|
||||||
const processFile = require('./processFile');
|
const processFile = require('./processFile');
|
||||||
const { sha256 } = require('./utils');
|
const { sha256 } = require('./utils');
|
||||||
const { SUPPORTED_EXTS } = require('../config');
|
const { SUPPORTED_EXTS } = require('../config');
|
||||||
|
const { log } = require('./logger');
|
||||||
|
|
||||||
async function scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
async function* scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
||||||
const changes = [];
|
|
||||||
|
|
||||||
async function walk(currentAbs, relPath = '') {
|
|
||||||
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
|
||||||
|
|
||||||
|
async function* walk(currentAbs, relPath = '') {
|
||||||
let entries = [];
|
let entries = [];
|
||||||
try {
|
try {
|
||||||
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +20,7 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
const absPath = path.join(currentAbs, e.name);
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
if (e.isDirectory()) {
|
if (e.isDirectory()) {
|
||||||
await walk(absPath, path.join(relPath, e.name));
|
yield* walk(absPath, path.join(relPath, e.name));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,29 +28,26 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
if (!SUPPORTED_EXTS.has(ext)) continue;
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
||||||
|
|
||||||
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
||||||
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
|
||||||
|
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
let st;
|
let st;
|
||||||
try {
|
try { st = await fsp.stat(absPath); } catch { continue; }
|
||||||
st = await fsp.stat(absPath);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
|
|
||||||
console.log(`[SCAN] CHECK → id=${id}, hash=${hash}, prevHash=${prev?.hash}`);
|
|
||||||
|
|
||||||
if (prev && prev.hash === hash) {
|
if (prev && prev.hash === hash) {
|
||||||
console.log(`[SCAN] SKIP (unchanged) → ${fileRelPath}`);
|
yield {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: `/photos/${userName}/original/${cartella}/${fileRelPath}`,
|
||||||
|
_indexHash: hash,
|
||||||
|
unchanged: true
|
||||||
|
};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
|
||||||
|
|
||||||
const meta = await processFile(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
|
@ -67,12 +61,11 @@ async function scanCartella(userName, cartella, absCartella, previousIndexTree)
|
||||||
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
meta._indexHash = hash;
|
meta._indexHash = hash;
|
||||||
|
|
||||||
changes.push(meta);
|
yield meta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await walk(absCartella);
|
yield* walk(absCartella);
|
||||||
return changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = scanCartella;
|
module.exports = scanCartella;
|
||||||
70
api_v1/scanner/scanCartella.js.ok.senza_log_invariati
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// scanner/scanCartella.js
|
||||||
|
const path = require('path');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const processFile = require('./processFile');
|
||||||
|
const { sha256 } = require('./utils');
|
||||||
|
const { SUPPORTED_EXTS } = require('../config');
|
||||||
|
|
||||||
|
async function* scanCartella(userName, cartella, absCartella, previousIndexTree) {
|
||||||
|
|
||||||
|
async function* walk(currentAbs, relPath = '') {
|
||||||
|
let entries = [];
|
||||||
|
try {
|
||||||
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of entries) {
|
||||||
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
|
if (e.isDirectory()) {
|
||||||
|
yield* walk(absPath, path.join(relPath, e.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = path.extname(e.name).toLowerCase();
|
||||||
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
||||||
|
|
||||||
|
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
||||||
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
let st;
|
||||||
|
try { st = await fsp.stat(absPath); } catch { continue; }
|
||||||
|
|
||||||
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
|
|
||||||
|
if (prev && prev.hash === hash) {
|
||||||
|
yield {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: `/photos/${userName}/original/${cartella}/${fileRelPath}`,
|
||||||
|
_indexHash: hash,
|
||||||
|
unchanged: true
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = await processFile(
|
||||||
|
userName,
|
||||||
|
cartella,
|
||||||
|
fileRelPath,
|
||||||
|
absPath,
|
||||||
|
ext,
|
||||||
|
st
|
||||||
|
);
|
||||||
|
|
||||||
|
meta.id = id;
|
||||||
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
|
meta._indexHash = hash;
|
||||||
|
|
||||||
|
yield meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield* walk(absCartella);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = scanCartella;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// scanner/scanPhoto.js
|
// scanner/scanPhoto.js
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
|
||||||
const fsp = require('fs/promises');
|
const fsp = require('fs/promises');
|
||||||
|
|
||||||
|
const { log } = require('./logger');
|
||||||
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
||||||
const scanCartella = require('./scanCartella');
|
const scanCartella = require('./scanCartella');
|
||||||
const postWithAuth = require('./postWithAuth');
|
const postWithAuth = require('./postWithAuth');
|
||||||
|
|
@ -11,13 +11,59 @@ const {
|
||||||
WEB_ROOT,
|
WEB_ROOT,
|
||||||
SEND_PHOTOS,
|
SEND_PHOTOS,
|
||||||
BASE_URL,
|
BASE_URL,
|
||||||
WRITE_INDEX
|
WRITE_INDEX,
|
||||||
|
SUPPORTED_EXTS
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
|
|
||||||
const createCleanupFunctions = require('./orphanCleanup');
|
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) {
|
async function scanPhoto(dir, userName, db) {
|
||||||
console.log(`[SCAN] Avvio scanPhoto per user=${userName}`);
|
const start = Date.now();
|
||||||
|
log(`🔵 Inizio scan globale per user=${userName}`);
|
||||||
|
|
||||||
const previousIndexTree = await loadPreviousIndex();
|
const previousIndexTree = await loadPreviousIndex();
|
||||||
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
||||||
|
|
@ -32,73 +78,80 @@ async function scanPhoto(dir, userName, db) {
|
||||||
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
||||||
const userDir = path.join(photosRoot, userName, 'original');
|
const userDir = path.join(photosRoot, userName, 'original');
|
||||||
|
|
||||||
let changes = [];
|
let totalNew = 0;
|
||||||
|
let totalDeleted = 0;
|
||||||
|
let totalUnchanged = 0;
|
||||||
|
|
||||||
|
let newFiles = [];
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// SCAN DI UNA SINGOLA CARTELLA
|
// 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 dall’HTML
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
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) {
|
async function scanSingleFolder(user, cartella, absCartella) {
|
||||||
console.log(`\n==============================================`);
|
log(`📁 Inizio cartella: ${cartella}`);
|
||||||
console.log(`[SCAN] CARTELLA → ${user}/${cartella}`);
|
|
||||||
console.log(`[DEBUG] SCANSIONE CARTELLA PATH: ${absCartella}`);
|
|
||||||
console.log("==============================================");
|
|
||||||
|
|
||||||
let idsIndex = await buildIdsListForFolder(user, cartella);
|
let idsIndex = await buildIdsListForFolder(user, cartella);
|
||||||
|
|
||||||
const folderFiles = await scanCartella(
|
let newCount = 0;
|
||||||
user,
|
let unchangedCount = 0;
|
||||||
cartella,
|
let deletedCount = 0;
|
||||||
absCartella,
|
|
||||||
previousIndexTree
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const m of folderFiles) {
|
for await (const m of scanCartella(user, cartella, absCartella, previousIndexTree)) {
|
||||||
const prev = previousIndexTree?.[m.user]?.[m.cartella]?.[m.id];
|
CURRENT++;
|
||||||
|
const fileName = m.path.split('/').pop();
|
||||||
|
const prefix = `[${CURRENT}/${TOTAL_FILES}]`;
|
||||||
|
|
||||||
// 🔥 FILE INVARIATO
|
// Aggiorna file statico per l’HTML
|
||||||
if (prev && prev.hash === m._indexHash) {
|
await updateStatusFile();
|
||||||
|
|
||||||
console.log("\n================ FILE INVARIATO ================");
|
// ⚪ FILE INVARIATO
|
||||||
console.log("[FILE]:", m.path);
|
if (m.unchanged) {
|
||||||
|
if (LOG_VERBOSE) {
|
||||||
console.log("[ID OGGI]:", JSON.stringify(m.id));
|
log(`${prefix} ⚪ Invariato: ${fileName}`);
|
||||||
console.log("[ID INDEX]:", JSON.stringify(prev.id));
|
|
||||||
console.log("[UGUALI?]:", m.id === prev.id);
|
|
||||||
|
|
||||||
console.log("[LEN OGGI]:", m.id.length);
|
|
||||||
console.log("[LEN INDEX]:", prev.id.length);
|
|
||||||
|
|
||||||
console.log("----- BYTE PER BYTE -----");
|
|
||||||
const max = Math.min(m.id.length, prev.id.length, 64);
|
|
||||||
for (let i = 0; i < max; i++) {
|
|
||||||
const a = m.id[i];
|
|
||||||
const b = prev.id[i];
|
|
||||||
console.log(
|
|
||||||
i.toString().padStart(2, "0"),
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
a === b ? "==" : "!="
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
console.log("----- idsIndex PRIMA -----");
|
unchangedCount++;
|
||||||
console.log(idsIndex.map(x => JSON.stringify(x)));
|
|
||||||
|
|
||||||
const found = idsIndex.includes(m.id);
|
|
||||||
console.log("[ID PRESENTE IN idsIndex?]:", found);
|
|
||||||
|
|
||||||
const newList = idsIndex.filter(x => x !== m.id);
|
|
||||||
|
|
||||||
console.log("----- idsIndex DOPO -----");
|
|
||||||
console.log(newList.map(x => JSON.stringify(x)));
|
|
||||||
|
|
||||||
idsIndex = newList;
|
|
||||||
|
|
||||||
console.log("=================================================\n");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 FILE NUOVO O MODIFICATO
|
// 🟢 FILE NUOVO O MODIFICATO
|
||||||
|
log(`${prefix} 🟢 Nuovo/Modificato: ${fileName}`);
|
||||||
|
|
||||||
nextIndexTree[m.user] ??= {};
|
nextIndexTree[m.user] ??= {};
|
||||||
nextIndexTree[m.user][m.cartella] ??= {};
|
nextIndexTree[m.user][m.cartella] ??= {};
|
||||||
nextIndexTree[m.user][m.cartella][m.id] = {
|
nextIndexTree[m.user][m.cartella][m.id] = {
|
||||||
|
|
@ -110,38 +163,33 @@ async function scanPhoto(dir, userName, db) {
|
||||||
};
|
};
|
||||||
|
|
||||||
idsIndex = removeIdFromList(idsIndex, m.id);
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
changes.push(m);
|
newCount++;
|
||||||
}
|
newFiles.push(m);
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// ORFANI
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (idsIndex.length > 0) {
|
|
||||||
console.log(`[ORPHAN] ID orfani trovati nella cartella ${cartella}:`);
|
|
||||||
idsIndex.forEach(id => console.log(" -", id));
|
|
||||||
} else {
|
|
||||||
console.log(`[ORPHAN] Nessun ID orfano nella cartella ${cartella}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔴 ORFANI
|
||||||
for (const orphanId of idsIndex) {
|
for (const orphanId of idsIndex) {
|
||||||
console.log(`\n[ORPHAN] Eliminazione ID → ${orphanId}`);
|
const old = previousIndexTree?.[user]?.[cartella]?.[orphanId];
|
||||||
|
const fileName = old?.path?.split('/').pop() || orphanId;
|
||||||
|
|
||||||
const thumbsDeleted = await deleteThumbsById(orphanId);
|
log(` 🔴 Eliminato: ${fileName}`);
|
||||||
console.log(` → thumbsDeleted = ${thumbsDeleted}`);
|
|
||||||
|
|
||||||
const dbDeleted = deleteFromDB(orphanId);
|
await deleteThumbsById(orphanId);
|
||||||
console.log(` → dbDeleted = ${dbDeleted}`);
|
deleteFromDB(orphanId);
|
||||||
|
|
||||||
const userTree = nextIndexTree[user];
|
const userTree = nextIndexTree[user];
|
||||||
if (userTree && userTree[cartella] && userTree[cartella][orphanId]) {
|
if (userTree?.[cartella]?.[orphanId]) {
|
||||||
delete userTree[cartella][orphanId];
|
delete userTree[cartella][orphanId];
|
||||||
console.log(` → nextIndexTree updated (removed ${orphanId})`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ORPHAN] COMPLETATO → ${orphanId}`);
|
deletedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`==============================================\n`);
|
log(`📊 Fine cartella ${cartella}: invariati=${unchangedCount}, nuovi=${newCount}, cancellati=${deletedCount}`);
|
||||||
|
|
||||||
|
totalNew += newCount;
|
||||||
|
totalDeleted += deletedCount;
|
||||||
|
totalUnchanged += unchangedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|
@ -151,7 +199,7 @@ async function scanPhoto(dir, userName, db) {
|
||||||
try {
|
try {
|
||||||
entries = await fsp.readdir(userDir, { withFileTypes: true });
|
entries = await fsp.readdir(userDir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
console.log(`[SCAN] Nessuna directory per utente ${userName}`);
|
log(`Nessuna directory per utente ${userName}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,20 +218,30 @@ async function scanPhoto(dir, userName, db) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// INVIO AL SERVER
|
// INVIO AL SERVER / POPOLAZIONE DB
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
if (SEND_PHOTOS && BASE_URL && changes.length) {
|
if (SEND_PHOTOS && BASE_URL && newFiles.length > 0) {
|
||||||
for (const p of changes) {
|
for (const p of newFiles) {
|
||||||
|
const fileName = p.path.split('/').pop();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await postWithAuth(`${BASE_URL}/photos`, p);
|
await postWithAuth(`${BASE_URL}/photos`, p);
|
||||||
|
log(`📤 Inviato al server: ${fileName}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Errore invio:', err.message);
|
log(`Errore invio ${fileName}: ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`SCAN COMPLETATA: ${changes.length} file aggiornati`);
|
// ---------------------------------------------------------
|
||||||
return changes;
|
// 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;
|
module.exports = scanPhoto;
|
||||||
|
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
// scanner/scanPhoto.js
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const fsp = require('fs/promises');
|
|
||||||
|
|
||||||
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
|
||||||
const scanCartella = require('./scanCartella');
|
|
||||||
const postWithAuth = require('./postWithAuth');
|
|
||||||
|
|
||||||
const {
|
|
||||||
WEB_ROOT,
|
|
||||||
SEND_PHOTOS,
|
|
||||||
BASE_URL,
|
|
||||||
WRITE_INDEX
|
|
||||||
} = require('../config');
|
|
||||||
|
|
||||||
const createCleanupFunctions = require('./orphanCleanup');
|
|
||||||
|
|
||||||
async function scanPhoto(dir, userName, db) {
|
|
||||||
console.log(`[SCAN] Avvio scanPhoto per user=${userName}`);
|
|
||||||
|
|
||||||
// Log di configurazione (utile per diagnosi)
|
|
||||||
console.log('[SCAN] Config:', {
|
|
||||||
WEB_ROOT,
|
|
||||||
SEND_PHOTOS,
|
|
||||||
BASE_URL,
|
|
||||||
WRITE_INDEX
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1) Carico l’indice precedente (fonte unica durante la run)
|
|
||||||
const previousIndexTree = await loadPreviousIndex(); // <— RAM source
|
|
||||||
// 2) Clono l’indice per costruire il nuovo
|
|
||||||
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
|
||||||
|
|
||||||
// Funzioni di cleanup (solo thumbs + DB)
|
|
||||||
// NOTA: con Opzione A NON leggiamo più l'indice da file e NON aggiorniamo index.json "in itinere"
|
|
||||||
const {
|
|
||||||
// buildIdsListForFolder, // <— NON usato in Opzione A
|
|
||||||
removeIdFromList,
|
|
||||||
deleteThumbsById,
|
|
||||||
deleteFromDB
|
|
||||||
// deleteFromIndexById // <— NON usato in Opzione A
|
|
||||||
} = createCleanupFunctions(db); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
|
|
||||||
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
|
||||||
const userDir = path.join(photosRoot, userName, 'original');
|
|
||||||
|
|
||||||
let changes = [];
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// SCAN DI UNA SINGOLA CARTELLA
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
async function scanSingleFolder(user, cartella, absCartella) {
|
|
||||||
console.log(`\n==============================================`);
|
|
||||||
console.log(`[SCAN] CARTELLA → ${user}/${cartella}`);
|
|
||||||
console.log("==============================================");
|
|
||||||
|
|
||||||
// Costruisci idsIndex dall'indice in RAM (previousIndexTree)
|
|
||||||
// Fonte unica di verità per questa run → evita drift con il file su disco.
|
|
||||||
let idsIndex = Object.keys(previousIndexTree?.[user]?.[cartella] || {})
|
|
||||||
.filter(k => k !== '_folderHash'); // <— niente metadati
|
|
||||||
console.log(`[ORPHAN] ID iniziali in RAM (${idsIndex.length}):`);
|
|
||||||
idsIndex.forEach(id => console.log(" -", id));
|
|
||||||
|
|
||||||
// File reali trovati nella cartella
|
|
||||||
const folderFiles = await scanCartella(
|
|
||||||
user,
|
|
||||||
cartella,
|
|
||||||
absCartella,
|
|
||||||
previousIndexTree
|
|
||||||
); // [2](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/scanCartella.js.txt)
|
|
||||||
|
|
||||||
for (const m of folderFiles) {
|
|
||||||
const prev = previousIndexTree?.[m.user]?.[m.cartella]?.[m.id];
|
|
||||||
|
|
||||||
// 🔥 FILE INVARIATO
|
|
||||||
if (prev && prev.hash === m._indexHash) {
|
|
||||||
console.log(`[SCAN] SKIP (unchanged) → ${m.path}`);
|
|
||||||
// Rimuovo SOLO dalla lista orfani (dalla RAM)
|
|
||||||
idsIndex = removeIdFromList(idsIndex, m.id); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔥 FILE NUOVO O MODIFICATO → aggiorno nextIndexTree
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rimuovo dalla lista orfani (in RAM)
|
|
||||||
idsIndex = removeIdFromList(idsIndex, m.id); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
changes.push(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// ORFANI → rimuovo SOLO da nextIndexTree + thumbs + DB
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (idsIndex.length > 0) {
|
|
||||||
console.log(`[ORPHAN] ID orfani trovati nella cartella ${cartella}:`);
|
|
||||||
idsIndex.forEach(id => console.log(" -", id));
|
|
||||||
} else {
|
|
||||||
console.log(`[ORPHAN] Nessun ID orfano nella cartella ${cartella}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const orphanId of idsIndex) {
|
|
||||||
console.log(`\n[ORPHAN] Eliminazione ID → ${orphanId}`);
|
|
||||||
|
|
||||||
// 1) Cancello thumbs
|
|
||||||
const thumbsDeleted = await deleteThumbsById(orphanId); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
console.log(` → thumbsDeleted = ${thumbsDeleted}`);
|
|
||||||
|
|
||||||
// 2) Cancello dal DB
|
|
||||||
const dbDeleted = deleteFromDB(orphanId); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
console.log(` → dbDeleted = ${dbDeleted}`);
|
|
||||||
|
|
||||||
// 3) Cancello SOLO da nextIndexTree (in RAM)
|
|
||||||
const userTree = nextIndexTree[user];
|
|
||||||
if (userTree && userTree[cartella] && userTree[cartella][orphanId]) {
|
|
||||||
delete userTree[cartella][orphanId];
|
|
||||||
console.log(` → nextIndexTree updated (removed ${orphanId})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[ORPHAN] COMPLETATO → ${orphanId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`==============================================\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// SCAN SOLO SOTTOCARTELLE — niente _root
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
let entries = [];
|
|
||||||
try {
|
|
||||||
entries = await fsp.readdir(userDir, { withFileTypes: true });
|
|
||||||
} catch {
|
|
||||||
console.log(`[SCAN] 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 SOLO nextIndexTree (scrittura unica a fine run)
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
if (WRITE_INDEX) {
|
|
||||||
await saveIndex(nextIndexTree); // [1](https://electrolux-my.sharepoint.com/personal/fabio_micheluz_electrolux_com/Documents/File%20di%20Microsoft%20Copilot%20Chat/orphanCleanup.js.txt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// INVIO AL SERVER
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
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: ${changes.length} file aggiornati`);
|
|
||||||
return changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = scanPhoto;
|
|
||||||
|
|
@ -1,153 +1,165 @@
|
||||||
// scanner/scanPhoto.js
|
// scanner/scanPhoto.js
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fsp = require('fs/promises');
|
const fsp = require('fs/promises');
|
||||||
|
|
||||||
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
||||||
const scanUserRoot = require('./scanUser');
|
const scanCartella = require('./scanCartella');
|
||||||
const postWithAuth = require('./postWithAuth');
|
const postWithAuth = require('./postWithAuth');
|
||||||
const { WEB_ROOT, SEND_PHOTOS, BASE_URL, WRITE_INDEX } = require('../config');
|
|
||||||
|
|
||||||
const COMMON = 'Common';
|
const {
|
||||||
|
WEB_ROOT,
|
||||||
|
SEND_PHOTOS,
|
||||||
|
BASE_URL,
|
||||||
|
WRITE_INDEX
|
||||||
|
} = require('../config');
|
||||||
|
|
||||||
async function scanPhoto(dir, userName) {
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
console.log(`[SCAN] Avvio scanPhoto per user=${userName}`);
|
|
||||||
|
async function scanPhoto(dir, userName, db) {
|
||||||
|
const start = Date.now();
|
||||||
|
console.log(`\n🔵 Inizio scan globale per user=${userName}`);
|
||||||
|
|
||||||
const previousIndexTree = await loadPreviousIndex();
|
const previousIndexTree = await loadPreviousIndex();
|
||||||
console.log(`[SCAN] previousIndexTree keys:`, Object.keys(previousIndexTree));
|
|
||||||
|
|
||||||
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
||||||
|
|
||||||
|
const {
|
||||||
|
buildIdsListForFolder,
|
||||||
|
removeIdFromList,
|
||||||
|
deleteThumbsById,
|
||||||
|
deleteFromDB
|
||||||
|
} = createCleanupFunctions(db);
|
||||||
|
|
||||||
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
||||||
|
const userDir = path.join(photosRoot, userName, 'original');
|
||||||
|
|
||||||
console.log(`[SCAN] photosRoot = ${photosRoot}`);
|
let totalNew = 0;
|
||||||
|
let totalDeleted = 0;
|
||||||
|
let totalUnchanged = 0;
|
||||||
|
|
||||||
let changes = [];
|
// 🔥 Array dei file nuovi/modificati (per DB e server)
|
||||||
|
let newFiles = [];
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// 1) ADMIN → scansiona tutti gli utenti
|
// SCAN DI UNA SINGOLA CARTELLA
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
if (userName === 'Admin') {
|
async function scanSingleFolder(user, cartella, absCartella) {
|
||||||
let entries = [];
|
console.log(`\n📁 Inizio cartella: ${cartella}`);
|
||||||
try {
|
|
||||||
entries = await fsp.readdir(photosRoot, { withFileTypes: true });
|
|
||||||
} catch {
|
|
||||||
console.error(`[SCAN] photosRoot non accessibile`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[SCAN] Trovati ${entries.length} utenti/cartelle`);
|
let idsIndex = await buildIdsListForFolder(user, cartella);
|
||||||
|
|
||||||
for (const e of entries) {
|
let newCount = 0;
|
||||||
if (!e.isDirectory()) continue;
|
let unchangedCount = 0;
|
||||||
|
let deletedCount = 0;
|
||||||
|
|
||||||
const user = e.name;
|
// STREAMING DEI FILE
|
||||||
if (user.toLowerCase() === COMMON.toLowerCase()) continue;
|
for await (const m of scanCartella(user, cartella, absCartella, previousIndexTree)) {
|
||||||
|
const fileName = m.path.split('/').pop();
|
||||||
|
|
||||||
const userDir = path.join(photosRoot, user, 'original');
|
// ⚪ FILE INVARIATO
|
||||||
|
if (m.unchanged) {
|
||||||
try {
|
console.log(` ⚪ Invariato: ${fileName}`);
|
||||||
const st = await fsp.stat(userDir);
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
if (!st.isDirectory()) continue;
|
unchangedCount++;
|
||||||
} catch {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[SCAN] → Scansiono utente: ${user}`);
|
// 🟢 FILE NUOVO O MODIFICATO
|
||||||
|
console.log(` 🟢 Nuovo/Modificato: ${fileName}`);
|
||||||
|
|
||||||
const userChanges = await scanUserRoot(user, userDir, previousIndexTree);
|
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
|
||||||
|
};
|
||||||
|
|
||||||
for (const m of userChanges) {
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
nextIndexTree[m.user] ??= {};
|
newCount++;
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.push(...userChanges);
|
// Aggiungiamo alla lista dei file nuovi
|
||||||
|
newFiles.push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common
|
// 🔴 ORFANI (FILE CANCELLATI)
|
||||||
const commonDir = path.join(photosRoot, COMMON, 'original');
|
for (const orphanId of idsIndex) {
|
||||||
try {
|
const old = previousIndexTree?.[user]?.[cartella]?.[orphanId];
|
||||||
const st = await fsp.stat(commonDir);
|
const fileName = old?.path?.split('/').pop() || orphanId;
|
||||||
if (st.isDirectory()) {
|
|
||||||
console.log(`[SCAN] → Scansiono Common`);
|
|
||||||
const commonChanges = await scanUserRoot(COMMON, commonDir, previousIndexTree);
|
|
||||||
|
|
||||||
for (const m of commonChanges) {
|
console.log(` 🔴 Eliminato: ${fileName}`);
|
||||||
nextIndexTree[COMMON] ??= {};
|
|
||||||
nextIndexTree[COMMON][m.cartella] ??= {};
|
|
||||||
nextIndexTree[COMMON][m.cartella][m.id] = {
|
|
||||||
id: m.id,
|
|
||||||
user: m.user,
|
|
||||||
cartella: m.cartella,
|
|
||||||
path: m.path,
|
|
||||||
hash: m._indexHash
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.push(...commonChanges);
|
await deleteThumbsById(orphanId);
|
||||||
|
deleteFromDB(orphanId);
|
||||||
|
|
||||||
|
const userTree = nextIndexTree[user];
|
||||||
|
if (userTree?.[cartella]?.[orphanId]) {
|
||||||
|
delete userTree[cartella][orphanId];
|
||||||
}
|
}
|
||||||
} catch {}
|
|
||||||
|
deletedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`📊 Fine cartella ${cartella}: invariati=${unchangedCount}, nuovi=${newCount}, cancellati=${deletedCount}`
|
||||||
|
);
|
||||||
|
|
||||||
|
totalNew += newCount;
|
||||||
|
totalDeleted += deletedCount;
|
||||||
|
totalUnchanged += unchangedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// 2) NON-ADMIN → scansiona SOLO l’utente richiesto
|
// SCAN SOTTOCARTELLE
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
else {
|
let entries = [];
|
||||||
const userDir = path.join(photosRoot, userName, 'original');
|
try {
|
||||||
|
entries = await fsp.readdir(userDir, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
console.log(`Nessuna directory per utente ${userName}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
for (const e of entries) {
|
||||||
const st = await fsp.stat(userDir);
|
if (!e.isDirectory()) continue;
|
||||||
if (st.isDirectory()) {
|
const cartella = e.name;
|
||||||
console.log(`[SCAN] → Scansiono utente singolo: ${userName}`);
|
const absCartella = path.join(userDir, cartella);
|
||||||
|
await scanSingleFolder(userName, cartella, absCartella);
|
||||||
const userChanges = await scanUserRoot(userName, userDir, previousIndexTree);
|
|
||||||
|
|
||||||
for (const m of userChanges) {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.push(...userChanges);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
console.log(`[SCAN] Nessuna directory per utente ${userName}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// SALVA INDEX
|
// SALVO INDEX
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
if (WRITE_INDEX) {
|
if (WRITE_INDEX) {
|
||||||
await saveIndex(nextIndexTree);
|
await saveIndex(nextIndexTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// INVIA AL SERVER
|
// INVIO AL SERVER / POPOLAZIONE DB
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
if (SEND_PHOTOS && BASE_URL && changes.length) {
|
if (SEND_PHOTOS && BASE_URL && newFiles.length > 0) {
|
||||||
for (const p of changes) {
|
for (const p of newFiles) {
|
||||||
|
const fileName = p.path.split('/').pop();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await postWithAuth(`${BASE_URL}/photos`, p);
|
await postWithAuth(`${BASE_URL}/photos`, p);
|
||||||
|
console.log(`📤 Inviato al server: ${fileName}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Errore invio:', err.message);
|
console.error(`Errore invio ${fileName}:`, err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`SCAN COMPLETATA: ${changes.length} file aggiornati`);
|
// ---------------------------------------------------------
|
||||||
return changes;
|
// FINE SCAN
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
const elapsed = ((Date.now() - start) / 1000).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`\n🟣 Scan COMPLETATO: nuovi=${totalNew}, cancellati=${totalDeleted}, invariati=${totalUnchanged}`);
|
||||||
|
console.log(`⏱ Tempo totale: ${elapsed}s\n`);
|
||||||
|
|
||||||
|
return newFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = scanPhoto;
|
module.exports = scanPhoto;
|
||||||
|
|
|
||||||
142
api_v1/scanner/scanPhoto.js.ok.con_log_invariati
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
// 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
|
||||||
|
} = require('../config');
|
||||||
|
|
||||||
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
const fileName = m.path.split('/').pop();
|
||||||
|
|
||||||
|
if (m.unchanged) {
|
||||||
|
log(` ⚪ Invariato: ${fileName}`);
|
||||||
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
|
unchangedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(` 🟢 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WRITE_INDEX) {
|
||||||
|
await saveIndex(nextIndexTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
144
api_v1/scanner/scanPhoto.js.ok.senza_log_invariati
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
// 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
|
||||||
|
} = require('../config');
|
||||||
|
|
||||||
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
const fileName = m.path.split('/').pop();
|
||||||
|
|
||||||
|
// ⚪ FILE INVARIATO → NON LOGGARE
|
||||||
|
if (m.unchanged) {
|
||||||
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
|
unchangedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🟢 FILE NUOVO O MODIFICATO
|
||||||
|
log(` 🟢 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WRITE_INDEX) {
|
||||||
|
await saveIndex(nextIndexTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
165
api_v1/scanner/scanPhoto.js.super_ok
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
// 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
|
||||||
|
} = require('../config');
|
||||||
|
|
||||||
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
|
|
||||||
|
// Variabile per log completo o ridotto
|
||||||
|
const LOG_VERBOSE = (process.env.LOG_VERBOSE === "true");
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// SCAN DI UNA SINGOLA CARTELLA
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
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)) {
|
||||||
|
const fileName = m.path.split('/').pop();
|
||||||
|
|
||||||
|
// ⚪ FILE INVARIATO
|
||||||
|
if (m.unchanged) {
|
||||||
|
if (LOG_VERBOSE) {
|
||||||
|
log(` ⚪ Invariato: ${fileName}`);
|
||||||
|
}
|
||||||
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
|
unchangedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🟢 FILE NUOVO O MODIFICATO
|
||||||
|
log(` 🟢 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 (FILE CANCELLATI)
|
||||||
|
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;
|
||||||
192
api_v1/scanner/scanPhoto.js.super_ok2
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
// 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
|
||||||
|
} = require('../config');
|
||||||
|
|
||||||
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
|
|
||||||
|
// Variabile per log completo o ridotto
|
||||||
|
const LOG_VERBOSE = (process.env.LOG_VERBOSE === "true");
|
||||||
|
|
||||||
|
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) CALCOLO TOTALE FILE (contatore globale)
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
let TOTAL_FILES = 0;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
for await (const _ of scanCartella(userName, cartella, absCartella, previousIndexTree)) {
|
||||||
|
TOTAL_FILES++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TOTAL_FILES === 0) {
|
||||||
|
log(`Nessun file trovato per user=${userName}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`📦 File totali da processare: ${TOTAL_FILES}`);
|
||||||
|
|
||||||
|
// contatore progressivo
|
||||||
|
let CURRENT = 0;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 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}]`;
|
||||||
|
|
||||||
|
// ⚪ 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 (REPLAY)
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
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;
|
||||||
|
|
@ -7,12 +7,12 @@ const { sha256 } = require('./utils');
|
||||||
const { SUPPORTED_EXTS } = require('../config');
|
const { SUPPORTED_EXTS } = require('../config');
|
||||||
|
|
||||||
async function scanUserRoot(userName, userDir, previousIndexTree) {
|
async function scanUserRoot(userName, userDir, previousIndexTree) {
|
||||||
console.log(`[SCAN] scanUserRoot → user=${userName}, dir=${userDir}`);
|
console.log(`\n🔵 Inizio scan user: ${userName}`);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
const entries = await fsp.readdir(userDir, { withFileTypes: true });
|
const entries = await fsp.readdir(userDir, { withFileTypes: true });
|
||||||
|
|
||||||
// 1) File nella root
|
// ROOT FILES
|
||||||
for (const e of entries) {
|
for (const e of entries) {
|
||||||
if (e.isDirectory()) continue;
|
if (e.isDirectory()) continue;
|
||||||
|
|
||||||
|
|
@ -29,14 +29,7 @@ async function scanUserRoot(userName, userDir, previousIndexTree) {
|
||||||
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
|
|
||||||
console.log(`[SCAN] ROOT CHECK → ${fileRelPath}, hash=${hash}, prevHash=${prev?.hash}`);
|
if (prev && prev.hash === hash) continue;
|
||||||
|
|
||||||
if (prev && prev.hash === hash) {
|
|
||||||
console.log(`[SCAN] SKIP ROOT (unchanged) → ${fileRelPath}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[SCAN] PROCESSING ROOT → ${fileRelPath}`);
|
|
||||||
|
|
||||||
const meta = await processFile(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
|
|
@ -54,14 +47,14 @@ async function scanUserRoot(userName, userDir, previousIndexTree) {
|
||||||
results.push(meta);
|
results.push(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Sottocartelle
|
// SUBFOLDERS
|
||||||
for (const e of entries) {
|
for (const e of entries) {
|
||||||
if (!e.isDirectory()) continue;
|
if (!e.isDirectory()) continue;
|
||||||
|
|
||||||
const cartella = e.name;
|
const cartella = e.name;
|
||||||
const absCartella = path.join(userDir, cartella);
|
const absCartella = path.join(userDir, cartella);
|
||||||
|
|
||||||
console.log(`[SCAN] → Scansiono cartella: ${cartella}`);
|
console.log(` 📁 Cartella: ${cartella}`);
|
||||||
|
|
||||||
const files = await scanCartella(userName, cartella, absCartella, previousIndexTree);
|
const files = await scanCartella(userName, cartella, absCartella, previousIndexTree);
|
||||||
results.push(...files);
|
results.push(...files);
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// scanner/scanUser.js
|
|
||||||
const path = require('path');
|
|
||||||
const fsp = require('fs/promises');
|
|
||||||
const scanCartella = require('./scanCartella');
|
|
||||||
const processFile = require('./processFile');
|
|
||||||
const { sha256 } = require('./utils');
|
|
||||||
const { SUPPORTED_EXTS } = require('../config');
|
|
||||||
|
|
||||||
async function scanUserRoot(userName, userDir, previousIndexTree) {
|
|
||||||
console.log(`[SCAN] scanUserRoot → user=${userName}, dir=${userDir}`);
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
const entries = await fsp.readdir(userDir, { withFileTypes: true });
|
|
||||||
|
|
||||||
// 1) File nella root
|
|
||||||
for (const e of entries) {
|
|
||||||
if (e.isDirectory()) continue;
|
|
||||||
|
|
||||||
const ext = path.extname(e.name).toLowerCase();
|
|
||||||
if (!SUPPORTED_EXTS.has(ext)) continue;
|
|
||||||
|
|
||||||
const absPath = path.join(userDir, e.name);
|
|
||||||
const st = await fsp.stat(absPath);
|
|
||||||
|
|
||||||
const fileRelPath = e.name;
|
|
||||||
const cartella = '_root';
|
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
|
||||||
|
|
||||||
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
|
||||||
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
|
||||||
|
|
||||||
console.log(`[SCAN] ROOT CHECK → ${fileRelPath}, hash=${hash}, prevHash=${prev?.hash}`);
|
|
||||||
|
|
||||||
if (prev && prev.hash === hash) {
|
|
||||||
console.log(`[SCAN] SKIP ROOT (unchanged) → ${fileRelPath}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[SCAN] PROCESSING ROOT → ${fileRelPath}`);
|
|
||||||
|
|
||||||
const meta = await processFile(
|
|
||||||
userName,
|
|
||||||
cartella,
|
|
||||||
fileRelPath,
|
|
||||||
absPath,
|
|
||||||
ext,
|
|
||||||
st
|
|
||||||
);
|
|
||||||
|
|
||||||
meta.id = id;
|
|
||||||
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
|
||||||
meta._indexHash = hash;
|
|
||||||
|
|
||||||
results.push(meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Sottocartelle
|
|
||||||
for (const e of entries) {
|
|
||||||
if (!e.isDirectory()) continue;
|
|
||||||
|
|
||||||
const cartella = e.name;
|
|
||||||
const absCartella = path.join(userDir, cartella);
|
|
||||||
|
|
||||||
console.log(`[SCAN] → Scansiono cartella: ${cartella}`);
|
|
||||||
|
|
||||||
const files = await scanCartella(userName, cartella, absCartella, previousIndexTree);
|
|
||||||
results.push(...files);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = scanUserRoot;
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// idsIndex-test.js
|
|
||||||
|
|
||||||
// Questa è la stessa funzione che usi nel tuo programma
|
|
||||||
function removeIdFromList(idsIndex, id) {
|
|
||||||
return idsIndex.filter(x => x !== id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simuliamo idsIndex come lo costruisce il tuo programma:
|
|
||||||
// un array di stringhe (ID)
|
|
||||||
const idsIndex = [
|
|
||||||
"b3cda38a2fdee7ba9bdd2ea07d936b2fa5361baa21be2b2c29f1ad8ebcc4c31e",
|
|
||||||
"09ef919ff2a105ad75d2e7631b9c02228c1784df5048c98276fd379f1533360b",
|
|
||||||
"dba1de0e187650e62118279beb2d83e8759a6d4fb8a5412a6a18cdc4e01a33d7"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Prendiamo il primo ID
|
|
||||||
const idToRemove = idsIndex[0];
|
|
||||||
|
|
||||||
// LOG iniziali
|
|
||||||
console.log("=== TEST RIMOZIONE ID ===");
|
|
||||||
console.log("idsIndex iniziale:", idsIndex);
|
|
||||||
console.log("ID da rimuovere:", idToRemove);
|
|
||||||
console.log("--------------------------");
|
|
||||||
|
|
||||||
// Rimozione
|
|
||||||
const newList = removeIdFromList(idsIndex, idToRemove);
|
|
||||||
|
|
||||||
// LOG finali
|
|
||||||
console.log("idsIndex dopo removeIdFromList:", newList);
|
|
||||||
|
|
||||||
// Verifica manuale
|
|
||||||
if (newList.includes(idToRemove)) {
|
|
||||||
console.log("❌ ERRORE: l'ID NON è stato rimosso!");
|
|
||||||
} else {
|
|
||||||
console.log("✅ OK: l'ID è stato rimosso correttamente.");
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,32 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Photo Manager</title>
|
<title>Photo Manager</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#progressContainer {
|
||||||
|
width: 100%;
|
||||||
|
background: #ddd;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progressBar {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
background: #4caf50;
|
||||||
|
transition: width 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scanInfo {
|
||||||
|
font-family: monospace;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app" style="padding:20px;">
|
<div id="app" style="padding:20px;">
|
||||||
|
|
@ -17,26 +42,40 @@
|
||||||
<button onclick="resetDBuser()">Reset DB Utente</button>
|
<button onclick="resetDBuser()">Reset DB Utente</button>
|
||||||
<button onclick="window.location.href='index.html'">Torna alla galleria</button>
|
<button onclick="window.location.href='index.html'">Torna alla galleria</button>
|
||||||
|
|
||||||
|
<!-- Barra di avanzamento -->
|
||||||
|
<div id="progressContainer">
|
||||||
|
<div id="progressBar"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info avanzamento -->
|
||||||
|
<div id="scanInfo">
|
||||||
|
<div id="scanProgress"></div>
|
||||||
|
<div id="scanEta"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<pre id="out"></pre>
|
<pre id="out"></pre>
|
||||||
</div>
|
</div>
|
||||||
<!-- Eruda Debug Console -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
|
<!-- Eruda Debug Console -->
|
||||||
<script>
|
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
|
||||||
eruda.init();
|
<script>
|
||||||
console.log("Eruda inizializzato");
|
eruda.init();
|
||||||
</script>
|
console.log("Eruda inizializzato");
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let BASE_URL = null;
|
let BASE_URL = null;
|
||||||
let token = localStorage.getItem("token");
|
let token = localStorage.getItem("token");
|
||||||
let db = [];
|
let db = [];
|
||||||
|
|
||||||
// Se non c'è token → torna alla galleria (login avviene lì)
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
window.location.href = "index.html";
|
window.location.href = "index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// FUNZIONI ESISTENTI
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
async function deletePhoto() {
|
async function deletePhoto() {
|
||||||
const id = prompt("Inserisci l'ID della foto da cancellare:");
|
const id = prompt("Inserisci l'ID della foto da cancellare:");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
@ -69,7 +108,6 @@ async function findIdIndex() {
|
||||||
async function resetDBuser() {
|
async function resetDBuser() {
|
||||||
let url = `${BASE_URL}/initDBuser`;
|
let url = `${BASE_URL}/initDBuser`;
|
||||||
|
|
||||||
// Se Admin → chiedi quale utente cancellare
|
|
||||||
const payload = parseJwt(token);
|
const payload = parseJwt(token);
|
||||||
if (payload.name === "Admin") {
|
if (payload.name === "Admin") {
|
||||||
const user = prompt("Inserisci il nome dell'utente da cancellare:");
|
const user = prompt("Inserisci il nome dell'utente da cancellare:");
|
||||||
|
|
@ -84,7 +122,6 @@ async function resetDBuser() {
|
||||||
await readDB();
|
await readDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility per leggere il token JWT
|
|
||||||
function parseJwt(t) {
|
function parseJwt(t) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(atob(t.split('.')[1]));
|
return JSON.parse(atob(t.split('.')[1]));
|
||||||
|
|
@ -93,7 +130,6 @@ function parseJwt(t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
const res = await fetch('/config');
|
const res = await fetch('/config');
|
||||||
const cfg = await res.json();
|
const cfg = await res.json();
|
||||||
|
|
@ -109,13 +145,6 @@ async function readDB() {
|
||||||
document.getElementById("out").textContent = JSON.stringify(db, null, 2);
|
document.getElementById("out").textContent = JSON.stringify(db, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scan() {
|
|
||||||
await fetch(`${BASE_URL}/scan`, {
|
|
||||||
headers: { "Authorization": "Bearer " + token }
|
|
||||||
});
|
|
||||||
await readDB();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resetDB() {
|
async function resetDB() {
|
||||||
await fetch(`${BASE_URL}/initDB`, {
|
await fetch(`${BASE_URL}/initDB`, {
|
||||||
headers: { "Authorization": "Bearer " + token }
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
|
@ -123,6 +152,56 @@ async function resetDB() {
|
||||||
await readDB();
|
await readDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// NUOVA PARTE: SCAN + BARRA
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
let scanInterval = null;
|
||||||
|
|
||||||
|
async function scan() {
|
||||||
|
// Avvia lo scan sul backend
|
||||||
|
fetch(`${BASE_URL}/scan`, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Avvia il polling dello stato
|
||||||
|
startScanStatusPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startScanStatusPolling() {
|
||||||
|
if (scanInterval) clearInterval(scanInterval);
|
||||||
|
|
||||||
|
scanInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
// PERCORSO CORRETTO
|
||||||
|
const res = await fetch(`/photos/scan_status.json?ts=` + Date.now());
|
||||||
|
if (!res.ok) return;
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Aggiorna testo
|
||||||
|
document.getElementById("scanProgress").textContent =
|
||||||
|
`Progresso: ${data.current}/${data.total} (${data.percent}%)`;
|
||||||
|
|
||||||
|
document.getElementById("scanEta").textContent =
|
||||||
|
`Tempo stimato rimanente: ${data.eta}`;
|
||||||
|
|
||||||
|
// Aggiorna barra
|
||||||
|
document.getElementById("progressBar").style.width = data.percent + "%";
|
||||||
|
|
||||||
|
// Fine scan
|
||||||
|
if (data.current >= data.total && data.total > 0) {
|
||||||
|
clearInterval(scanInterval);
|
||||||
|
scanInterval = null;
|
||||||
|
await readDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Errore polling scan:", err);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
await loadConfig();
|
await loadConfig();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
132
public/admin.html.ok
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Photo Manager</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app" style="padding:20px;">
|
||||||
|
<h2>Gestione Foto</h2>
|
||||||
|
|
||||||
|
<button onclick="scan()">Scansiona Foto</button>
|
||||||
|
<button onclick="resetDB()">Reset DB</button>
|
||||||
|
<button onclick="readDB()">Leggi DB</button>
|
||||||
|
<button onclick="deletePhoto()">Cancella Foto per ID</button>
|
||||||
|
<button onclick="findIdIndex()">Cerca ID in index.json</button>
|
||||||
|
<button onclick="resetDBuser()">Reset DB Utente</button>
|
||||||
|
<button onclick="window.location.href='index.html'">Torna alla galleria</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre id="out"></pre>
|
||||||
|
</div>
|
||||||
|
<!-- Eruda Debug Console -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
|
||||||
|
<script>
|
||||||
|
eruda.init();
|
||||||
|
console.log("Eruda inizializzato");
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
let BASE_URL = null;
|
||||||
|
let token = localStorage.getItem("token");
|
||||||
|
let db = [];
|
||||||
|
|
||||||
|
// Se non c'è token → torna alla galleria (login avviene lì)
|
||||||
|
if (!token) {
|
||||||
|
window.location.href = "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePhoto() {
|
||||||
|
const id = prompt("Inserisci l'ID della foto da cancellare:");
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const res = await fetch(`${BASE_URL}/delphoto/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
const out = await res.json();
|
||||||
|
document.getElementById("out").textContent =
|
||||||
|
JSON.stringify(out, null, 2);
|
||||||
|
|
||||||
|
await readDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findIdIndex() {
|
||||||
|
const id = prompt("Inserisci l'ID da cercare in index.json:");
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const res = await fetch(`${BASE_URL}/findIdIndex/${id}`, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
const out = await res.json();
|
||||||
|
document.getElementById("out").textContent =
|
||||||
|
JSON.stringify(out, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetDBuser() {
|
||||||
|
let url = `${BASE_URL}/initDBuser`;
|
||||||
|
|
||||||
|
// Se Admin → chiedi quale utente cancellare
|
||||||
|
const payload = parseJwt(token);
|
||||||
|
if (payload.name === "Admin") {
|
||||||
|
const user = prompt("Inserisci il nome dell'utente da cancellare:");
|
||||||
|
if (!user) return;
|
||||||
|
url += `?user=${encodeURIComponent(user)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(url, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
await readDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility per leggere il token JWT
|
||||||
|
function parseJwt(t) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(atob(t.split('.')[1]));
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
const res = await fetch('/config');
|
||||||
|
const cfg = await res.json();
|
||||||
|
BASE_URL = cfg.baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDB() {
|
||||||
|
const res = await fetch(`${BASE_URL}/photos`, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
|
||||||
|
db = await res.json();
|
||||||
|
document.getElementById("out").textContent = JSON.stringify(db, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scan() {
|
||||||
|
await fetch(`${BASE_URL}/scan`, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
await readDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetDB() {
|
||||||
|
await fetch(`${BASE_URL}/initDB`, {
|
||||||
|
headers: { "Authorization": "Bearer " + token }
|
||||||
|
});
|
||||||
|
await readDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = async () => {
|
||||||
|
await loadConfig();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -45,7 +45,8 @@ function renderInfo(photo) {
|
||||||
<div class="info-row"><b>Peso:</b> ${photo.size_bytes ? (photo.size_bytes / 1024 / 1024).toFixed(2) + ' MB' : '-'}</div>
|
<div class="info-row"><b>Peso:</b> ${photo.size_bytes ? (photo.size_bytes / 1024 / 1024).toFixed(2) + ' MB' : '-'}</div>
|
||||||
<div class="info-row"><b>Tipo:</b> ${photo.mime_type ?? '-'}</div>
|
<div class="info-row"><b>Tipo:</b> ${photo.mime_type ?? '-'}</div>
|
||||||
|
|
||||||
<div class="info-row"><b>Cartella:</b> ${folder}</div>
|
<div class="info-row"><b>User:</b> ${photo.user}</div>
|
||||||
|
<div class="info-row"><b>Cartella:</b> ${photo.cartella}</div>
|
||||||
|
|
||||||
<div class="info-spacer"></div>
|
<div class="info-spacer"></div>
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 3.3 MiB |
BIN
public/photos/Fabio/original/2017Irlanda19-29ago/IMG_0100.JPG
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
public/photos/Fabio/original/2017Irlanda19-29ago/IMG_0102.JPG
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
public/photos/Fabio/original/2017Irlanda19-29ago/IMG_0104.JPG
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 4.2 MiB |
|
Before Width: | Height: | Size: 4.3 MiB |
|
Before Width: | Height: | Size: 4.5 MiB |
1
public/photos/scan_status.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"current":101,"total":101,"percent":100,"eta":"00:00:00","elapsed":"00:01:09"}
|
||||||