scan intelligente ok
19
api_v1/scanner/deleteWithAuth.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// scanner/deleteWithAuth.js
|
||||||
|
const { API_KEY } = require('../config');
|
||||||
|
|
||||||
|
module.exports = async function deleteWithAuth(url) {
|
||||||
|
// import dinamico compatibile con CommonJS
|
||||||
|
const fetch = (await import('node-fetch')).default;
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'x-api-key': API_KEY,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`DELETE failed: ${res.status}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,42 +3,47 @@ const path = require('path');
|
||||||
const fsp = require('fs/promises');
|
const fsp = require('fs/promises');
|
||||||
const { WEB_ROOT, INDEX_PATH } = require('../config');
|
const { WEB_ROOT, INDEX_PATH } = require('../config');
|
||||||
|
|
||||||
// NOTE: siamo in .../api_v1/scanner → per arrivare alla root progetto servono due '..'
|
|
||||||
const absIndexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
const absIndexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
||||||
const absIndexTmp = absIndexPath + '.tmp';
|
const absIndexTmp = absIndexPath + '.tmp';
|
||||||
|
|
||||||
const PRETTY = process.env.INDEX_PRETTY === 'true';
|
const PRETTY = process.env.INDEX_PRETTY === 'true';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Carica l'indice. Formato canonico: mappa { [id]: meta }.
|
* Carica l'indice strutturato:
|
||||||
* Retro-compat: se il file è un array, convertilo in mappa.
|
* {
|
||||||
|
* 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);
|
||||||
|
|
||||||
if (Array.isArray(json)) {
|
if (json && typeof json === 'object' && !Array.isArray(json)) {
|
||||||
const map = {};
|
return json;
|
||||||
for (const p of json) {
|
|
||||||
if (p && p.id) map[p.id] = p;
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return json && typeof json === 'object' ? json : {};
|
console.log("[INDEX] Formato vecchio → forzo rescan completo");
|
||||||
|
return {};
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Salva l'indice (mappa) in modo atomico: scrive su .tmp e poi rename.
|
* Salva l'indice in modo atomico.
|
||||||
*/
|
*/
|
||||||
async function saveIndex(indexMap) {
|
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 });
|
||||||
const data = PRETTY ? JSON.stringify(indexMap, null, 2) : JSON.stringify(indexMap);
|
|
||||||
|
const data = PRETTY
|
||||||
|
? JSON.stringify(indexTree, null, 2)
|
||||||
|
: JSON.stringify(indexTree);
|
||||||
|
|
||||||
await fsp.writeFile(absIndexTmp, data, 'utf8');
|
await fsp.writeFile(absIndexTmp, data, 'utf8');
|
||||||
await fsp.rename(absIndexTmp, absIndexPath);
|
await fsp.rename(absIndexTmp, absIndexPath);
|
||||||
|
|
|
||||||
52
api_v1/scanner/indexStore.js.ok
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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 };
|
||||||
73
api_v1/scanner/l.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
136
api_v1/scanner/orphanCleanup.js
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
// scanner/orphanCleanup.js
|
||||||
|
const fs = require('fs');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const path = require('path');
|
||||||
|
const { WEB_ROOT, INDEX_PATH } = require('../config');
|
||||||
|
|
||||||
|
module.exports = function createCleanupFunctions(db) {
|
||||||
|
|
||||||
|
async function buildIdsListForFolder(userName, cartella) {
|
||||||
|
const indexPath = path.resolve(__dirname, '..', '..', WEB_ROOT, INDEX_PATH);
|
||||||
|
const idsIndex = [];
|
||||||
|
|
||||||
|
console.log("\n[ORPHAN] buildIdsListForFolder()");
|
||||||
|
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 {
|
||||||
|
const raw = await fsp.readFile(indexPath, 'utf8');
|
||||||
|
const index = JSON.parse(raw);
|
||||||
|
|
||||||
|
const userObj = index[userName];
|
||||||
|
if (!userObj) return idsIndex;
|
||||||
|
|
||||||
|
const folder = userObj[cartella];
|
||||||
|
if (!folder) return idsIndex;
|
||||||
|
|
||||||
|
const ids = Object.keys(folder).filter(k => k !== "_folderHash");
|
||||||
|
idsIndex.push(...ids);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeIdFromList(idsIndex, id) {
|
||||||
|
const newList = 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) {
|
||||||
|
const col = db.get('photos');
|
||||||
|
const rec = col.find({ id }).value();
|
||||||
|
|
||||||
|
if (!rec) {
|
||||||
|
console.log(`[ORPHAN] Nessun record DB per ID ${id}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbs = [rec.thub1, rec.thub2].filter(Boolean);
|
||||||
|
let deleted = false;
|
||||||
|
|
||||||
|
for (const t of thumbs) {
|
||||||
|
const abs = path.resolve(__dirname, '../public' + t);
|
||||||
|
if (fs.existsSync(abs)) {
|
||||||
|
await fsp.rm(abs, { force: true });
|
||||||
|
console.log(`✔ Eliminato thumb: ${abs}`);
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFromDB(id) {
|
||||||
|
const col = db.get('photos');
|
||||||
|
const exists = col.find({ id }).value();
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
col.remove({ id }).write();
|
||||||
|
console.log(`✔ Eliminato ID ${id} dal DB`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[ORPHAN] ID ${id} non presente nel DB`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildIdsListForFolder,
|
||||||
|
removeIdFromList,
|
||||||
|
deleteFromIndexById,
|
||||||
|
deleteThumbsById,
|
||||||
|
deleteFromDB
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,6 @@ const path = require('path');
|
||||||
const fsp = require('fs/promises');
|
const fsp = require('fs/promises');
|
||||||
const ExifReader = require('exifreader');
|
const ExifReader = require('exifreader');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
|
|
||||||
const { sha256, inferMimeFromExt, parseExifDateUtc } = require('./utils');
|
const { sha256, inferMimeFromExt, parseExifDateUtc } = require('./utils');
|
||||||
const { extractGpsFromExif, extractGpsWithExiftool } = require('./gps');
|
const { extractGpsFromExif, extractGpsWithExiftool } = require('./gps');
|
||||||
const { createVideoThumbnail, createThumbnails } = require('./thumbs');
|
const { createVideoThumbnail, createThumbnails } = require('./thumbs');
|
||||||
|
|
@ -39,16 +38,21 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
await createThumbnails(absPath, absThumbMin, absThumbAvg);
|
await createThumbnails(absPath, absThumbMin, absThumbAvg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- EXIF ---
|
||||||
let tags = {};
|
let tags = {};
|
||||||
try { tags = await ExifReader.load(absPath, { expanded: true }); } catch {}
|
try {
|
||||||
|
tags = await ExifReader.load(absPath, { expanded: true });
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
|
||||||
const takenAtIso = parseExifDateUtc(timeRaw);
|
const takenAtIso = parseExifDateUtc(timeRaw);
|
||||||
|
|
||||||
|
// --- GPS ---
|
||||||
let gps = isVideo
|
let gps = isVideo
|
||||||
? await extractGpsWithExiftool(absPath)
|
? await extractGpsWithExiftool(absPath)
|
||||||
: extractGpsFromExif(tags);
|
: extractGpsFromExif(tags);
|
||||||
|
|
||||||
|
// --- DIMENSIONI ---
|
||||||
let width = null, height = null, duration = null;
|
let width = null, height = null, duration = null;
|
||||||
|
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
|
|
@ -67,6 +71,27 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
} catch {}
|
} 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}`);
|
||||||
|
|
||||||
|
|
@ -76,12 +101,10 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
// --- GESTIONE PATH FULL / RELATIVI ---
|
// --- GESTIONE PATH FULL / RELATIVI ---
|
||||||
//
|
//
|
||||||
|
|
||||||
// relativi (comportamento attuale)
|
|
||||||
const relPath = fileRelPath;
|
const relPath = fileRelPath;
|
||||||
const relThub1 = fileRelPath.replace(/\.[^.]+$/, '_min.jpg');
|
const relThub1 = fileRelPath.replace(/\.[^.]+$/, '_min.jpg');
|
||||||
const relThub2 = fileRelPath.replace(/\.[^.]+$/, '_avg.jpg');
|
const relThub2 = fileRelPath.replace(/\.[^.]+$/, '_avg.jpg');
|
||||||
|
|
||||||
// completi (solo se PATH_FULL = true)
|
|
||||||
const fullPath = PATH_FULL
|
const fullPath = PATH_FULL
|
||||||
? path.posix.join('/photos', userName, cartella, fileRelPath)
|
? path.posix.join('/photos', userName, cartella, fileRelPath)
|
||||||
: relPath;
|
: relPath;
|
||||||
|
|
@ -107,6 +130,7 @@ async function processFile(userName, cartella, fileRelPath, absPath, ext, st) {
|
||||||
mime_type,
|
mime_type,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
rotation, // <── ROTAZIONE IN GRADI REALI
|
||||||
size_bytes: st.size,
|
size_bytes: st.size,
|
||||||
mtimeMs: st.mtimeMs,
|
mtimeMs: st.mtimeMs,
|
||||||
duration: isVideo ? duration : null,
|
duration: isVideo ? duration : null,
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,16 @@ 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) {
|
||||||
* Scansiona ricorsivamente una cartella e ritorna SOLO i cambiamenti
|
|
||||||
* (nuovi/modificati) rispetto a previousIndex (mappa { id: meta }).
|
|
||||||
*
|
|
||||||
* @param {string} userName - Nome utente (es. "Fabio" o "Common")
|
|
||||||
* @param {string} cartella - Nome della cartella logica sotto "original" (es. "2017Irlanda19-29ago")
|
|
||||||
* @param {string} absCartella - Percorso assoluto della cartella da scansionare (p.es. .../photos/<Utente>/original/<cartella>)
|
|
||||||
* @param {Object} previousIndex- Mappa dell'indice precedente: { <id>: meta }
|
|
||||||
* @returns {Promise<Array<Object>>} changes - Lista dei soli meta cambiati o nuovi
|
|
||||||
*/
|
|
||||||
async function scanCartella(userName, cartella, absCartella, previousIndex) {
|
|
||||||
const changes = [];
|
const changes = [];
|
||||||
|
|
||||||
async function walk(currentAbs, relPath = '') {
|
async function walk(currentAbs, relPath = '') {
|
||||||
|
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
||||||
|
|
||||||
let entries = [];
|
let entries = [];
|
||||||
try {
|
try {
|
||||||
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Cartella non leggibile: logga e prosegui (non bloccare la scansione globale)
|
|
||||||
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -36,35 +27,47 @@ async function scanCartella(userName, cartella, absCartella, previousIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtra estensioni non supportate
|
|
||||||
const ext = path.extname(e.name).toLowerCase();
|
const ext = path.extname(e.name).toLowerCase();
|
||||||
if (!SUPPORTED_EXTS.has(ext)) continue;
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
||||||
|
|
||||||
// Percorso relativo POSIX (usato per mantenere slash '/')
|
|
||||||
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
const fileRelPath = relPath ? `${relPath}/${e.name}` : e.name;
|
||||||
|
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
||||||
|
|
||||||
// ID deterministico: user/cartella/relPath
|
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
// Confronto con indice precedente per saltare "unchanged"
|
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = await fsp.stat(absPath);
|
st = await fsp.stat(absPath);
|
||||||
} catch (err) {
|
} catch {
|
||||||
// File sparito o non accessibile: salta
|
|
||||||
console.warn(`[SCAN] stat fallita: ${absPath} - ${err.message}`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prev = previousIndex[id];
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const unchanged =
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
prev &&
|
|
||||||
prev.size_bytes === st.size &&
|
|
||||||
prev.mtimeMs === st.mtimeMs;
|
|
||||||
|
|
||||||
if (unchanged) continue; // nulla da aggiornare
|
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) {
|
||||||
|
console.log(`[SCAN] FILE INVARIATO (passato a scanPhoto) → ${fileRelPath}`);
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
id,
|
||||||
|
user: userName,
|
||||||
|
cartella,
|
||||||
|
path: `/photos/${userName}/original/${cartella}/${fileRelPath}`,
|
||||||
|
_indexHash: hash,
|
||||||
|
unchanged: true
|
||||||
|
};
|
||||||
|
|
||||||
|
changes.push(meta);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
// Estrae metadati (EXIF, mime, dimensioni, thumbs, ecc.)
|
|
||||||
const meta = await processFile(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
cartella,
|
cartella,
|
||||||
|
|
@ -74,19 +77,9 @@ async function scanCartella(userName, cartella, absCartella, previousIndex) {
|
||||||
st
|
st
|
||||||
);
|
);
|
||||||
|
|
||||||
// ID sempre presente
|
|
||||||
meta.id = id;
|
meta.id = id;
|
||||||
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
// 🔧 Canonicalizza SEMPRE il path pubblicato nell’index:
|
meta._indexHash = hash;
|
||||||
// /photos/<Utente>/original/<cartella>/<fileRelPath>
|
|
||||||
{
|
|
||||||
// Normalizza separatori e rimuove eventuali leading-slash multipli
|
|
||||||
const relPosix = String(fileRelPath).replace(/\\/g, '/').replace(/^\/+/, '');
|
|
||||||
// Costruisci il path canonico e comprimi eventuali '//' in '/'
|
|
||||||
const canonical = `/photos/${userName}/original/${cartella}/${relPosix}`.replace(/\/+/g, '/');
|
|
||||||
meta.path = canonical;
|
|
||||||
// NB: thub1/thub2 rimangono quelli prodotti da processFile (ok per immagini e video)
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.push(meta);
|
changes.push(meta);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
api_v1/scanner/scanCartella.js.new
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// 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) {
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
async function walk(currentAbs, relPath = '') {
|
||||||
|
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
||||||
|
|
||||||
|
let entries = [];
|
||||||
|
try {
|
||||||
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of entries) {
|
||||||
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
|
if (e.isDirectory()) {
|
||||||
|
await 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;
|
||||||
|
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
console.log(`[SCAN] CHECK → id=${id}, hash=${hash}, prevHash=${prev?.hash}`);
|
||||||
|
|
||||||
|
if (prev && prev.hash === hash) {
|
||||||
|
console.log(`[SCAN] SKIP (unchanged) → ${fileRelPath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
const meta = await processFile(
|
||||||
|
userName,
|
||||||
|
cartella,
|
||||||
|
fileRelPath,
|
||||||
|
absPath,
|
||||||
|
ext,
|
||||||
|
st
|
||||||
|
);
|
||||||
|
|
||||||
|
meta.id = id;
|
||||||
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
|
meta._indexHash = hash;
|
||||||
|
|
||||||
|
changes.push(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await walk(absCartella);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = scanCartella;
|
||||||
78
api_v1/scanner/scanCartella.js.ok
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// 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) {
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
async function walk(currentAbs, relPath = '') {
|
||||||
|
console.log(`[SCAN] Entrato in cartella: ${currentAbs}`);
|
||||||
|
|
||||||
|
let entries = [];
|
||||||
|
try {
|
||||||
|
entries = await fsp.readdir(currentAbs, { withFileTypes: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[SCAN] Impossibile leggere: ${currentAbs} - ${err.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of entries) {
|
||||||
|
const absPath = path.join(currentAbs, e.name);
|
||||||
|
|
||||||
|
if (e.isDirectory()) {
|
||||||
|
await 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;
|
||||||
|
console.log(`[SCAN] FILE TROVATO → ${fileRelPath}`);
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
console.log(`[SCAN] CHECK → id=${id}, hash=${hash}, prevHash=${prev?.hash}`);
|
||||||
|
|
||||||
|
if (prev && prev.hash === hash) {
|
||||||
|
console.log(`[SCAN] SKIP (unchanged) → ${fileRelPath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SCAN] PROCESSING → ${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
|
const meta = await processFile(
|
||||||
|
userName,
|
||||||
|
cartella,
|
||||||
|
fileRelPath,
|
||||||
|
absPath,
|
||||||
|
ext,
|
||||||
|
st
|
||||||
|
);
|
||||||
|
|
||||||
|
meta.id = id;
|
||||||
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
|
meta._indexHash = hash;
|
||||||
|
|
||||||
|
changes.push(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await walk(absCartella);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = scanCartella;
|
||||||
|
|
@ -1,134 +1,189 @@
|
||||||
// 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 { 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 {
|
const {
|
||||||
WEB_ROOT,
|
WEB_ROOT,
|
||||||
SEND_PHOTOS,
|
SEND_PHOTOS,
|
||||||
BASE_URL,
|
BASE_URL,
|
||||||
WRITE_INDEX,
|
WRITE_INDEX
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
|
|
||||||
const COMMON = 'Common'; // Nome canonico e case-sensitive per la shared folder
|
const createCleanupFunctions = require('./orphanCleanup');
|
||||||
|
|
||||||
/**
|
async function scanPhoto(dir, userName, db) {
|
||||||
* Restituisce la prima directory esistente tra le candidate
|
console.log(`[SCAN] Avvio scanPhoto per user=${userName}`);
|
||||||
* @param {string[]} candidates
|
|
||||||
* @returns {Promise<string|null>}
|
|
||||||
*/
|
|
||||||
async function firstExistingDir(candidates) {
|
|
||||||
for (const dir of candidates) {
|
|
||||||
try {
|
|
||||||
const st = await fsp.stat(dir);
|
|
||||||
if (st.isDirectory()) return dir;
|
|
||||||
} catch { /* ignore */ }
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const previousIndexTree = await loadPreviousIndex();
|
||||||
* Scansione foto con indice separato:
|
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
||||||
* - Carica indice precedente (mappa { id: meta })
|
|
||||||
* - Produce SOLO i cambiamenti (nuovi/modificati)
|
|
||||||
* - Merge e salvataggio atomico dell'indice
|
|
||||||
* - POST verso /photos solo del delta (se abilitato)
|
|
||||||
* - Admin: include la cartella "Common"
|
|
||||||
*
|
|
||||||
* Nota path: usiamo un path ASSOLUTO per <root>/public/photos
|
|
||||||
* partendo da .../api_v1/scanner -> '..', '..' per salire alla root del progetto.
|
|
||||||
*/
|
|
||||||
async function scanPhoto(dir, userName) {
|
|
||||||
try {
|
|
||||||
const previousIndexMap = await loadPreviousIndex();
|
|
||||||
const nextIndexMap = { ...previousIndexMap };
|
|
||||||
|
|
||||||
// Path assoluto alla radice photos (es: <root>/public/photos)
|
const {
|
||||||
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
buildIdsListForFolder,
|
||||||
|
removeIdFromList,
|
||||||
|
deleteThumbsById,
|
||||||
|
deleteFromDB
|
||||||
|
} = createCleanupFunctions(db);
|
||||||
|
|
||||||
let changes = [];
|
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
||||||
|
const userDir = path.join(photosRoot, userName, 'original');
|
||||||
|
|
||||||
if (userName === 'Admin') {
|
let changes = [];
|
||||||
// 1) Scansiona TUTTI gli utenti tranne la/e cartella/e common
|
|
||||||
let entries = [];
|
|
||||||
try {
|
|
||||||
entries = await fsp.readdir(photosRoot, { withFileTypes: true });
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[SCAN] photosRoot non accessibile: ${photosRoot}`, e.message);
|
|
||||||
entries = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const e of entries) {
|
// ---------------------------------------------------------
|
||||||
if (!e.isDirectory()) continue;
|
// SCAN DI UNA SINGOLA CARTELLA
|
||||||
// salta qualunque "common" (qualunque casing) per non doppiare
|
// ---------------------------------------------------------
|
||||||
if (e.name.toLowerCase() === COMMON.toLowerCase()) continue;
|
async function scanSingleFolder(user, cartella, absCartella) {
|
||||||
|
console.log(`\n==============================================`);
|
||||||
|
console.log(`[SCAN] CARTELLA → ${user}/${cartella}`);
|
||||||
|
console.log(`[DEBUG] SCANSIONE CARTELLA PATH: ${absCartella}`);
|
||||||
|
console.log("==============================================");
|
||||||
|
|
||||||
const userDir = path.join(photosRoot, e.name, 'original');
|
let idsIndex = await buildIdsListForFolder(user, cartella);
|
||||||
try {
|
|
||||||
const st = await fsp.stat(userDir);
|
const folderFiles = await scanCartella(
|
||||||
if (!st.isDirectory()) continue;
|
user,
|
||||||
} catch {
|
cartella,
|
||||||
continue;
|
absCartella,
|
||||||
|
previousIndexTree
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const m of folderFiles) {
|
||||||
|
const prev = previousIndexTree?.[m.user]?.[m.cartella]?.[m.id];
|
||||||
|
|
||||||
|
// 🔥 FILE INVARIATO
|
||||||
|
if (prev && prev.hash === m._indexHash) {
|
||||||
|
|
||||||
|
console.log("\n================ FILE INVARIATO ================");
|
||||||
|
console.log("[FILE]:", m.path);
|
||||||
|
|
||||||
|
console.log("[ID OGGI]:", JSON.stringify(m.id));
|
||||||
|
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 ? "==" : "!="
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userChanges = await scanUserRoot(e.name, userDir, previousIndexMap);
|
console.log("----- idsIndex PRIMA -----");
|
||||||
for (const m of userChanges) nextIndexMap[m.id] = m;
|
console.log(idsIndex.map(x => JSON.stringify(x)));
|
||||||
changes.push(...userChanges);
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Scansiona la cartella COMMON (case strict), con fallback legacy (solo lettura) a 'common'
|
// 🔥 FILE NUOVO O MODIFICATO
|
||||||
const commonPreferred = path.join(photosRoot, COMMON, 'original'); // .../photos/Common/original
|
nextIndexTree[m.user] ??= {};
|
||||||
const commonLegacy = path.join(photosRoot, 'common', 'original'); // .../photos/common/original (legacy)
|
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
|
||||||
|
};
|
||||||
|
|
||||||
const commonDir = await firstExistingDir([commonPreferred, commonLegacy]);
|
idsIndex = removeIdFromList(idsIndex, m.id);
|
||||||
if (commonDir) {
|
changes.push(m);
|
||||||
// Forziamo SEMPRE userName = 'Common' per ID/thumbnails coerenti in 'Common'
|
}
|
||||||
const commonChanges = await scanUserRoot(COMMON, commonDir, previousIndexMap);
|
|
||||||
for (const m of commonChanges) nextIndexMap[m.id] = m;
|
|
||||||
changes.push(...commonChanges);
|
|
||||||
console.log(`[SCAN] Common indicizzati da ${commonDir}: +${commonChanges.length}`);
|
|
||||||
} else {
|
|
||||||
console.log(`[SCAN] Nessuna cartella "${COMMON}" trovata sotto ${photosRoot} (atteso: ${commonPreferred})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// ORFANI
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
if (idsIndex.length > 0) {
|
||||||
|
console.log(`[ORPHAN] ID orfani trovati nella cartella ${cartella}:`);
|
||||||
|
idsIndex.forEach(id => console.log(" -", id));
|
||||||
} else {
|
} else {
|
||||||
// Non-Admin (per completezza; in server.js /scan è Admin-only)
|
console.log(`[ORPHAN] Nessun ID orfano nella cartella ${cartella}`);
|
||||||
const userDir = path.join(photosRoot, userName, 'original');
|
}
|
||||||
try {
|
|
||||||
const st = await fsp.stat(userDir);
|
for (const orphanId of idsIndex) {
|
||||||
if (st.isDirectory()) {
|
console.log(`\n[ORPHAN] Eliminazione ID → ${orphanId}`);
|
||||||
const userChanges = await scanUserRoot(userName, userDir, previousIndexMap);
|
|
||||||
for (const m of userChanges) nextIndexMap[m.id] = m;
|
const thumbsDeleted = await deleteThumbsById(orphanId);
|
||||||
changes.push(...userChanges);
|
console.log(` → thumbsDeleted = ${thumbsDeleted}`);
|
||||||
}
|
|
||||||
} catch {
|
const dbDeleted = deleteFromDB(orphanId);
|
||||||
// utente senza dir 'original' -> nessuna modifica
|
console.log(` → dbDeleted = ${dbDeleted}`);
|
||||||
|
|
||||||
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salva indice (mappa) in modo atomico
|
console.log(`==============================================\n`);
|
||||||
if (WRITE_INDEX) {
|
|
||||||
await saveIndex(nextIndexMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST solo dei cambiamenti (delta)
|
|
||||||
if (SEND_PHOTOS && BASE_URL && changes.length) {
|
|
||||||
for (const p of changes) {
|
|
||||||
try {
|
|
||||||
await postWithAuth(`${BASE_URL}/photos`, p);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Errore invio:', err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`SCAN COMPLETATA per utente ${userName}: ${changes.length} file aggiornati`);
|
|
||||||
return changes;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Errore generale scanPhoto:', e);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// SCAN SOTTOCARTELLE
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
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 INDEX
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
if (WRITE_INDEX) {
|
||||||
|
await saveIndex(nextIndexTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 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;
|
module.exports = scanPhoto;
|
||||||
176
api_v1/scanner/scanPhoto.js.new
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
// 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;
|
||||||
153
api_v1/scanner/scanPhoto.js.ok
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
// scanner/scanPhoto.js
|
||||||
|
const path = require('path');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const { loadPreviousIndex, saveIndex } = require('./indexStore');
|
||||||
|
const scanUserRoot = require('./scanUser');
|
||||||
|
const postWithAuth = require('./postWithAuth');
|
||||||
|
const { WEB_ROOT, SEND_PHOTOS, BASE_URL, WRITE_INDEX } = require('../config');
|
||||||
|
|
||||||
|
const COMMON = 'Common';
|
||||||
|
|
||||||
|
async function scanPhoto(dir, userName) {
|
||||||
|
console.log(`[SCAN] Avvio scanPhoto per user=${userName}`);
|
||||||
|
|
||||||
|
const previousIndexTree = await loadPreviousIndex();
|
||||||
|
console.log(`[SCAN] previousIndexTree keys:`, Object.keys(previousIndexTree));
|
||||||
|
|
||||||
|
const nextIndexTree = JSON.parse(JSON.stringify(previousIndexTree || {}));
|
||||||
|
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
|
||||||
|
|
||||||
|
console.log(`[SCAN] photosRoot = ${photosRoot}`);
|
||||||
|
|
||||||
|
let changes = [];
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 1) ADMIN → scansiona tutti gli utenti
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
if (userName === 'Admin') {
|
||||||
|
let entries = [];
|
||||||
|
try {
|
||||||
|
entries = await fsp.readdir(photosRoot, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
console.error(`[SCAN] photosRoot non accessibile`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SCAN] Trovati ${entries.length} utenti/cartelle`);
|
||||||
|
|
||||||
|
for (const e of entries) {
|
||||||
|
if (!e.isDirectory()) continue;
|
||||||
|
|
||||||
|
const user = e.name;
|
||||||
|
if (user.toLowerCase() === COMMON.toLowerCase()) continue;
|
||||||
|
|
||||||
|
const userDir = path.join(photosRoot, user, 'original');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const st = await fsp.stat(userDir);
|
||||||
|
if (!st.isDirectory()) continue;
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[SCAN] → Scansiono utente: ${user}`);
|
||||||
|
|
||||||
|
const userChanges = await scanUserRoot(user, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common
|
||||||
|
const commonDir = path.join(photosRoot, COMMON, 'original');
|
||||||
|
try {
|
||||||
|
const st = await fsp.stat(commonDir);
|
||||||
|
if (st.isDirectory()) {
|
||||||
|
console.log(`[SCAN] → Scansiono Common`);
|
||||||
|
const commonChanges = await scanUserRoot(COMMON, commonDir, previousIndexTree);
|
||||||
|
|
||||||
|
for (const m of commonChanges) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// 2) NON-ADMIN → scansiona SOLO l’utente richiesto
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
else {
|
||||||
|
const userDir = path.join(photosRoot, userName, 'original');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const st = await fsp.stat(userDir);
|
||||||
|
if (st.isDirectory()) {
|
||||||
|
console.log(`[SCAN] → Scansiono utente singolo: ${userName}`);
|
||||||
|
|
||||||
|
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
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
if (WRITE_INDEX) {
|
||||||
|
await saveIndex(nextIndexTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// INVIA 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;
|
||||||
|
|
@ -6,16 +6,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 scanUserRoot(userName, userDir, previousIndexTree) {
|
||||||
* Scansiona la root dell'utente (p.es. .../<user>/original) e:
|
console.log(`[SCAN] scanUserRoot → user=${userName}, dir=${userDir}`);
|
||||||
* - indicizza i file direttamente al root (cartella virtuale "_root")
|
|
||||||
* - per ogni sottocartella, chiama scanCartella
|
|
||||||
*/
|
|
||||||
async function scanUserRoot(userName, userDir, previousIndex) {
|
|
||||||
const results = [];
|
const results = [];
|
||||||
const entries = await fsp.readdir(userDir, { withFileTypes: true });
|
const entries = await fsp.readdir(userDir, { withFileTypes: true });
|
||||||
|
|
||||||
// 1) File direttamente al root (virtual folder "_root")
|
// 1) File nella root
|
||||||
for (const e of entries) {
|
for (const e of entries) {
|
||||||
if (e.isDirectory()) continue;
|
if (e.isDirectory()) continue;
|
||||||
|
|
||||||
|
|
@ -29,13 +26,17 @@ async function scanUserRoot(userName, userDir, previousIndex) {
|
||||||
const cartella = '_root';
|
const cartella = '_root';
|
||||||
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
|
||||||
|
|
||||||
const prev = previousIndex[id];
|
const hash = sha256(`${st.size}-${st.mtimeMs}`);
|
||||||
const unchanged =
|
const prev = previousIndexTree?.[userName]?.[cartella]?.[id];
|
||||||
prev &&
|
|
||||||
prev.size_bytes === st.size &&
|
|
||||||
prev.mtimeMs === st.mtimeMs;
|
|
||||||
|
|
||||||
if (unchanged) continue;
|
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(
|
const meta = await processFile(
|
||||||
userName,
|
userName,
|
||||||
|
|
@ -45,16 +46,24 @@ async function scanUserRoot(userName, userDir, previousIndex) {
|
||||||
ext,
|
ext,
|
||||||
st
|
st
|
||||||
);
|
);
|
||||||
|
|
||||||
meta.id = id;
|
meta.id = id;
|
||||||
|
meta.path = `/photos/${userName}/original/${cartella}/${fileRelPath}`;
|
||||||
|
meta._indexHash = hash;
|
||||||
|
|
||||||
results.push(meta);
|
results.push(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Sottocartelle (comportamento classico)
|
// 2) Sottocartelle
|
||||||
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);
|
||||||
const files = await scanCartella(userName, cartella, absCartella, previousIndex);
|
|
||||||
|
console.log(`[SCAN] → Scansiono cartella: ${cartella}`);
|
||||||
|
|
||||||
|
const files = await scanCartella(userName, cartella, absCartella, previousIndexTree);
|
||||||
results.push(...files);
|
results.push(...files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
73
api_v1/scanner/scanUser.js.ok
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// 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;
|
||||||
36
api_v1/scanner/t.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
353
api_v1/server.js
|
|
@ -105,6 +105,183 @@ function resetDB() {
|
||||||
console.log('DB resettato');
|
console.log('DB resettato');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// Rimuove tutte le directories thumbs se user = Admin altrimenti solo quella dello user (Public/photos/<user>/thumbs)
|
||||||
|
// -----------------------------------------------------
|
||||||
|
|
||||||
|
async function removeAllThumbs(user) {
|
||||||
|
const photosRoot = path.resolve(__dirname, '../public/photos');
|
||||||
|
|
||||||
|
if (!fs.existsSync(photosRoot)) {
|
||||||
|
console.log('Nessuna cartella photos trovata, niente thumbs da cancellare');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se NON è Admin → cancella solo la sua cartella
|
||||||
|
if (user !== 'Admin') {
|
||||||
|
const thumbsDir = path.join(photosRoot, user, 'thumbs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fsp.rm(thumbsDir, { recursive: true, force: true });
|
||||||
|
console.log(`✔ thumbs rimosse per utente "${user}": ${thumbsDir}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`✖ errore rimuovendo thumbs per "${user}":`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🎉 Cancellazione thumbs completata per utente "${user}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se è Admin → cancella TUTTE le thumbs
|
||||||
|
const entries = await fsp.readdir(photosRoot, { withFileTypes: true });
|
||||||
|
|
||||||
|
let removed = 0;
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory()) continue;
|
||||||
|
|
||||||
|
const userDir = path.join(photosRoot, entry.name);
|
||||||
|
const thumbsDir = path.join(userDir, 'thumbs');
|
||||||
|
|
||||||
|
total++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fsp.rm(thumbsDir, { recursive: true, force: true });
|
||||||
|
removed++;
|
||||||
|
console.log(`✔ thumbs rimossa: ${thumbsDir}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`✖ errore rimuovendo ${thumbsDir}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG FINALE ADMIN
|
||||||
|
if (removed === total) {
|
||||||
|
console.log(`🎉 Tutte le cartelle thumbs (${removed}) sono state cancellate`);
|
||||||
|
} else {
|
||||||
|
console.log(`⚠ Cancellate ${removed} cartelle thumbs su ${total}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// Cancella una foto da index.json usando l'id
|
||||||
|
// -----------------------------------------------------
|
||||||
|
|
||||||
|
async function deleteFromIndexById(id) {
|
||||||
|
const indexPath = path.resolve(__dirname, '..', WEB_ROOT, INDEX_PATH);
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.log("index.json non trovato");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await fsp.readFile(indexPath, 'utf8');
|
||||||
|
const index = JSON.parse(raw);
|
||||||
|
|
||||||
|
let deleted = false;
|
||||||
|
|
||||||
|
for (const user of Object.keys(index)) {
|
||||||
|
const userObj = index[user];
|
||||||
|
if (!userObj || typeof userObj !== 'object') continue;
|
||||||
|
|
||||||
|
for (const cartella of Object.keys(userObj)) {
|
||||||
|
const folder = userObj[cartella];
|
||||||
|
if (!folder || typeof folder !== 'object') continue;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// Cancella i thumbs usando l'id
|
||||||
|
// -----------------------------------------------------
|
||||||
|
async function deleteThumbsById(id) {
|
||||||
|
console.log(`\n=== DELETE THUMBS FOR ID: ${id} ===`);
|
||||||
|
|
||||||
|
const db = router.db;
|
||||||
|
const col = db.get('photos');
|
||||||
|
const rec = col.find({ id }).value();
|
||||||
|
|
||||||
|
if (!rec) {
|
||||||
|
console.log("Record non trovato nel DB → impossibile cancellare thumbs");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumb1 = rec.thub1;
|
||||||
|
const thumb2 = rec.thub2;
|
||||||
|
|
||||||
|
if (!thumb1 && !thumb2) {
|
||||||
|
console.log("Nessun thumb registrato nel DB");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Costruzione corretta del percorso assoluto
|
||||||
|
const absThumb1 = thumb1 ? path.resolve(__dirname, '../public' + thumb1) : null;
|
||||||
|
const absThumb2 = thumb2 ? path.resolve(__dirname, '../public' + thumb2) : null;
|
||||||
|
|
||||||
|
console.log(`Thumb1 path: ${absThumb1}`);
|
||||||
|
console.log(`Thumb2 path: ${absThumb2}`);
|
||||||
|
|
||||||
|
let deleted = false;
|
||||||
|
|
||||||
|
if (absThumb1) {
|
||||||
|
const exists1 = fs.existsSync(absThumb1);
|
||||||
|
console.log(`Thumb1 exists: ${exists1}`);
|
||||||
|
|
||||||
|
if (exists1) {
|
||||||
|
await fsp.rm(absThumb1, { force: true });
|
||||||
|
console.log("✔ Eliminato thumb1");
|
||||||
|
deleted = true;
|
||||||
|
} else {
|
||||||
|
console.log("✖ thumb1 NON trovato");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absThumb2) {
|
||||||
|
const exists2 = fs.existsSync(absThumb2);
|
||||||
|
console.log(`Thumb2 exists: ${exists2}`);
|
||||||
|
|
||||||
|
if (exists2) {
|
||||||
|
await fsp.rm(absThumb2, { force: true });
|
||||||
|
console.log("✔ Eliminato thumb2");
|
||||||
|
deleted = true;
|
||||||
|
} else {
|
||||||
|
console.log("✖ thumb2 NON trovato");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`=== FINE DELETE THUMBS ID: ${id} ===\n`);
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// Elimina uno user dal DB e lo salva
|
||||||
|
// -----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
function deleteUserRecords(username) {
|
||||||
|
const db = router.db; // lowdb instance
|
||||||
|
const col = db.get('photos');
|
||||||
|
|
||||||
|
// Rimuove i record e salva su disco
|
||||||
|
const removed = col.remove({ user: username }).write();
|
||||||
|
|
||||||
|
console.log(`Eliminati ${removed.length} record per user "${username}"`);
|
||||||
|
return removed.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// HOME
|
// HOME
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
|
|
@ -192,7 +369,7 @@ server.use((req, res, next) => {
|
||||||
// - Admin: scansiona tutti gli utenti + Common
|
// - Admin: scansiona tutti gli utenti + Common
|
||||||
// - Non-Admin: scansiona solo la propria area (NO Common)
|
// - Non-Admin: scansiona solo la propria area (NO Common)
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
server.get('/scan', async (req, res) => {
|
server.get('/scanold', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (req.user && req.user.name === 'Admin') {
|
if (req.user && req.user.name === 'Admin') {
|
||||||
await scanPhoto(undefined, 'Admin');
|
await scanPhoto(undefined, 'Admin');
|
||||||
|
|
@ -216,6 +393,30 @@ server.get('/scan', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.get('/scan', async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.user && req.user.name === 'Admin') {
|
||||||
|
await scanPhoto(undefined, 'Admin', router.db);
|
||||||
|
return res.send({
|
||||||
|
status: 'Scansione completata',
|
||||||
|
user: 'Admin',
|
||||||
|
scope: 'tutti gli utenti + Common',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-Admin → solo la sua area (niente Common)
|
||||||
|
await scanPhoto(undefined, req.user.name, router.db);
|
||||||
|
res.send({
|
||||||
|
status: 'Scansione completata',
|
||||||
|
user: req.user.name,
|
||||||
|
scope: 'utente corrente',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Errore scan:', err);
|
||||||
|
res.status(500).json({ error: 'Errore durante lo scan', details: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// FILE STATICI
|
// FILE STATICI
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
|
|
@ -240,27 +441,167 @@ server.get('/initDB', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
resetDB();
|
resetDB();
|
||||||
|
|
||||||
// <root>/public/photos/index.json (coerente con indexStore.js)
|
// Rimuove index.json
|
||||||
const absIndexPath = path.resolve(__dirname, '..', WEB_ROOT, INDEX_PATH);
|
const absIndexPath = path.resolve(__dirname, '..', WEB_ROOT, INDEX_PATH);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fsp.unlink(absIndexPath);
|
await fsp.unlink(absIndexPath);
|
||||||
console.log('initDB: index.json rimosso ->', absIndexPath);
|
console.log('initDB: index.json rimosso ->', absIndexPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err && err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
console.log('initDB: index.json non trovato, niente da cancellare:', absIndexPath);
|
console.log('initDB: index.json non trovato:', absIndexPath);
|
||||||
} else {
|
} else {
|
||||||
console.error('initDB: errore cancellando index.json:', err);
|
console.error('initDB: errore cancellando index.json:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ status: 'DB resettato', indexRemoved: true, indexPath: absIndexPath });
|
// rimuove tutte le cartelle thumbs
|
||||||
|
await removeAllThumbs('Admin');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: 'DB resettato',
|
||||||
|
indexRemoved: true,
|
||||||
|
thumbsRemoved: true,
|
||||||
|
indexPath: absIndexPath
|
||||||
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('initDB: errore generale:', err);
|
console.error('initDB: errore generale:', err);
|
||||||
res.status(500).json({ status: 'errore', message: err.message });
|
res.status(500).json({ status: 'errore', message: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// DELETE FOTO da DB + index.json + thumbs
|
||||||
|
// -----------------------------------------------------
|
||||||
|
server.delete('/delphoto/:id', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = router.db;
|
||||||
|
const col = db.get('photos');
|
||||||
|
|
||||||
|
const existing = col.find({ id }).value();
|
||||||
|
|
||||||
|
// 1) Cancella thumbs PRIMA DI TUTTO
|
||||||
|
const deletedThumbs = await deleteThumbsById(id);
|
||||||
|
|
||||||
|
let deletedDB = false;
|
||||||
|
|
||||||
|
// 2) Cancella dal DB
|
||||||
|
if (existing) {
|
||||||
|
col.remove({ id }).write();
|
||||||
|
deletedDB = true;
|
||||||
|
console.log(`DELPHOTO → foto cancellata dal DB: ${id}`);
|
||||||
|
} else {
|
||||||
|
console.log(`DELPHOTO → foto NON trovata nel DB: ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Cancella da index.json
|
||||||
|
const deletedIndex = await deleteFromIndexById(id);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
id,
|
||||||
|
deletedThumbs,
|
||||||
|
deletedDB,
|
||||||
|
deletedIndex
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('DELPHOTO errore:', err);
|
||||||
|
return res.status(500).json({ ok: false, error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// RESET DB SOLO PER UN UTENTE
|
||||||
|
// - Admin: deve specificare ?user=<nome>
|
||||||
|
// - Non-Admin: cancella solo i propri dati
|
||||||
|
// -----------------------------------------------------
|
||||||
|
server.get('/initDBuser', async (req, res) => {
|
||||||
|
try {
|
||||||
|
let targetUser = req.user.name;
|
||||||
|
|
||||||
|
// Admin può specificare chi cancellare
|
||||||
|
if (req.user.name === 'Admin') {
|
||||||
|
targetUser = req.query.user;
|
||||||
|
if (!targetUser) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "Admin deve specificare ?user=<nome>"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Cancella record DB
|
||||||
|
const deleted = deleteUserRecords(targetUser);
|
||||||
|
|
||||||
|
// 2) Cancella thumbs
|
||||||
|
await removeAllThumbs(targetUser);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: "OK",
|
||||||
|
user: targetUser,
|
||||||
|
deletedRecords: deleted,
|
||||||
|
thumbsRemoved: true
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("initDBuser errore:", err);
|
||||||
|
res.status(500).json({ error: "Errore durante initDBuser", details: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// FIND ID IN INDEX.JSON + RETURN RECORD (SOLO LETTURA)
|
||||||
|
// -----------------------------------------------------
|
||||||
|
server.get('/findIdIndex/:id', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const indexPath = path.resolve(__dirname, '..', WEB_ROOT, INDEX_PATH);
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
return res.json({ ok: false, found: false, message: "index.json non trovato" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await fsp.readFile(indexPath, 'utf8');
|
||||||
|
const index = JSON.parse(raw);
|
||||||
|
|
||||||
|
for (const user of Object.keys(index)) {
|
||||||
|
const userObj = index[user];
|
||||||
|
if (!userObj || typeof userObj !== 'object') continue;
|
||||||
|
|
||||||
|
for (const cartella of Object.keys(userObj)) {
|
||||||
|
const folder = userObj[cartella];
|
||||||
|
if (!folder || typeof folder !== 'object') continue;
|
||||||
|
|
||||||
|
// dentro la cartella: chiavi = id, più _folderHash
|
||||||
|
if (folder[id]) {
|
||||||
|
return res.json({
|
||||||
|
ok: true,
|
||||||
|
found: true,
|
||||||
|
user,
|
||||||
|
cartella,
|
||||||
|
record: folder[id]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ ok: true, found: false });
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Errore findIdIndex:", err);
|
||||||
|
return res.status(500).json({ ok: false, error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// UPSERT anti-duplicato per /photos (prima del router)
|
// UPSERT anti-duplicato per /photos (prima del router)
|
||||||
// Se id esiste -> aggiorna; altrimenti crea
|
// Se id esiste -> aggiorna; altrimenti crea
|
||||||
|
|
|
||||||
10
package-lock.json
generated
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "ssj",
|
"name": "gallery-jwt-json-server",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ssj",
|
"name": "gallery-jwt-json-server",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^3.2.6",
|
"async": "^3.2.6",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,13 @@
|
||||||
<button onclick="scan()">Scansiona Foto</button>
|
<button onclick="scan()">Scansiona Foto</button>
|
||||||
<button onclick="resetDB()">Reset DB</button>
|
<button onclick="resetDB()">Reset DB</button>
|
||||||
<button onclick="readDB()">Leggi 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>
|
<button onclick="window.location.href='index.html'">Torna alla galleria</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<pre id="out"></pre>
|
<pre id="out"></pre>
|
||||||
</div>
|
</div>
|
||||||
<!-- Eruda Debug Console -->
|
<!-- Eruda Debug Console -->
|
||||||
|
|
@ -32,6 +37,63 @@ if (!token) {
|
||||||
window.location.href = "index.html";
|
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() {
|
async function loadConfig() {
|
||||||
const res = await fetch('/config');
|
const res = await fetch('/config');
|
||||||
const cfg = await res.json();
|
const cfg = await res.json();
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 3.9 MiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
|
@ -1,9 +0,0 @@
|
||||||
const { exec } = require('child_process');
|
|
||||||
const video = process.argv[2];
|
|
||||||
const cmd = `exiftool -n -G1 -a -gps:all -quicktime:all -user:all "${video}"`;
|
|
||||||
console.log("Eseguo:", cmd);
|
|
||||||
exec(cmd, (err, stdout, stderr) => {
|
|
||||||
console.log("err:", err);
|
|
||||||
console.log("stderr:", stderr ? stderr.slice(0,1000) : "<empty>");
|
|
||||||
console.log("stdout (head):", stdout ? stdout.slice(0,2000) : "<empty>");
|
|
||||||
});
|
|
||||||