gallery_server_client_s190js/api_v1/scanner/scanPhotosUser.js
2026-10-26 11:00:14 +01:00

143 lines
3.5 KiB
JavaScript

'use strict';
// Scansione completa: individua i file e li accoda.
// L'elaborazione effettiva è responsabilità di photoIngestWorker.
const path = require('path');
const fsp = require('fs/promises');
const crypto = require('crypto');
const { WEB_ROOT, SUPPORTED_EXTS } = require('../config');
const { log } = require('./logger');
const scanCartella = require('./scanCartella');
const {
ensurePhotoIngestQueue,
enqueuePhotoIngestJob,
} = require('./photoIngestQueue');
function sha256(value) {
return crypto.createHash('sha256').update(value).digest('hex');
}
async function thumbnailsExist(userName, cartella, relPath) {
const parsed = path.parse(relPath);
const thumbDir = path.resolve(
__dirname,
'..',
'..',
WEB_ROOT,
'photos',
userName,
'thumbs',
cartella,
parsed.dir
);
const base = path.join(thumbDir, parsed.name);
try {
await Promise.all([
fsp.access(`${base}_min.jpg`),
fsp.access(`${base}_avg.jpg`),
]);
return true;
} catch {
return false;
}
}
async function scanPhotosUser(userName, db) {
log(`🔵 Inizio scansione e accodamento per user=${userName}`);
const photosRoot = path.resolve(__dirname, '..', '..', WEB_ROOT, 'photos');
const userDir = path.join(photosRoot, userName, 'original');
let entries;
try {
entries = await fsp.readdir(userDir, { withFileTypes: true });
} catch {
log(`❌ Nessuna directory per utente ${userName}`);
return [];
}
const folders = entries
.filter(entry => entry.isDirectory())
.map(entry => entry.name);
// Mantiene la pulizia delle cartelle eliminate dal filesystem.
const { deleteFolderForUser } = require('./deleteFolder')(db);
const dbFolders = await db('photos')
.where({ user: userName })
.distinct('cartella')
.pluck('cartella');
for (const dbFolder of dbFolders) {
if (!folders.includes(dbFolder)) {
log(`🗑️ Cartella rimossa dal filesystem: ${dbFolder}`);
await deleteFolderForUser(userName, dbFolder);
}
}
await ensurePhotoIngestQueue(db);
let queued = 0;
let skipped = 0;
for (const cartella of folders) {
const absCartella = path.join(userDir, cartella);
log(`📁 Controllo cartella: ${cartella}`);
for await (const file of scanCartella(userName, cartella, absCartella, db)) {
const previous = await db('photos')
.select('id', 'size_bytes', 'mtimeMs', 'fast_hash', 'path')
.where({ id: file.id })
.first();
const expectedHash = sha256(`${file.stat.size}-${file.stat.mtimeMs}`);
const expectedPath = file.path;
let needsProcessing =
!previous ||
previous.path !== expectedPath ||
previous.fast_hash !== expectedHash;
if (!needsProcessing) {
needsProcessing = !(await thumbnailsExist(
userName,
cartella,
file.relPath
));
}
if (needsProcessing) {
await enqueuePhotoIngestJob(db, {
user: userName,
cartella,
relPath: file.relPath,
mtimeMs: file.stat.mtimeMs,
sizeBytes: file.stat.size,
});
queued++;
} else {
skipped++;
}
}
}
log(
`🟣 Scansione/accodamento terminato per ${userName}: ` +
`${queued} accodati, ${skipped} invariati`
);
// Restituisce un array per compatibilità con chi già chiama questa funzione.
// Non contiene risultati elaborati: quelli arriveranno dal worker.
return [];
}
module.exports = scanPhotosUser;