12 KiB
📌 1. Avvio dello scan
- Parte con un log: "Inizio scan globale per user=X".
- Calcola il percorso della cartella photos//original.
- Conta velocemente quanti file supportati esistono (con countFilesFast).
- Se non ci sono file → termina.
📊 2. Gestione dello stato di avanzamento Durante lo scan:
- Tiene traccia di:
- CURRENT = quanti file ha già processato
- TOTAL_FILES = quanti file totali ci sono
- Aggiorna un file JSON pubblico:
public/photos/scan_status.json
Questo file contiene:
- current
- total
- percent
- elapsed
- eta
Serve per mostrare una barra di avanzamento in tempo reale.
📁 3. Scansione delle cartelle dell’utente Per ogni sottocartella dentro original/:
- Chiama scanSingleFolder(user, cartella, absCartella).
Dentro questa funzione succede il grosso del lavoro.
🔍 4. Per ogni file trovato: scanCartella() restituisce oggetti con:
- id (deterministico)
- name
- relPath
- absPath
- ext
- stat (size, mtime, ecc.)
- path normalizzato
user=Fabio cartella=2017Irlanda19-29ago relPath=VID_20260317_115145.mp4 id=51a7e1861de8da5147bbbc35d70a0128fcd2a0451c7121a8bf2412cb2903d469
Per ogni file:
🟦 4.1 Recupera eventuale record esistente dal DB
js prev = await db("photos").where({ id }).first();
🟩 4.2 Se il path è cambiato → considerato nuovo
js if (prev && prev.path !== f.path) prev = null;
🟢 4.3 Se è un nuovo file
- Log dettagliato
- Chiama processFile() per generare metadati, thumbnails, EXIF, ecc.
- Inserisce nel DB
- Registra un evento in photo_changes
- Aggiunge a newFiles
🔵 4.4 Fast-skip (file invariato) Due livelli:
-
FAST-SIZE-SKIP Se la size è identica → aggiorna solo path/mtime/hash.
-
FAST-HASH-SKIP Se l’hash veloce (sha256(size-mtime)) è identico → aggiorna solo path/mtime.
🟠 4.5 File modificato Se non passa i fast-skip:
- Rielabora il file con processFile()
- Aggiorna il DB con .insert().onConflict().merge()
- Registra evento updated
- Aggiunge a newFiles
🗑️ 5. Gestione degli orfani Alla fine della cartella:
- Tutti gli ID presenti nel DB ma non trovati sul disco vengono:
- rimossi dal DB
- rimossi i thumbnails
- loggati come cancellati
🌐 6. Invio al server remoto (opzionale) Se SENDPHOTOS=true e BASEURL è configurato:
- Ogni nuovo file viene inviato via postWithAuth() al server remoto.
Se non configurato:
- Logga [NO SEND].
🟣 7. Fine scan Logga il tempo totale e restituisce l’array newFiles.
🧩 In sintesi scanPhoto.js è un motore di sincronizzazione locale → database → server remoto.
Fa tutto questo:
| Funzione | Descrizione |
|---|---|
| Scansione ricorsiva | Trova tutti i file supportati nelle cartelle dell’utente |
| Confronto con DB | Determina se un file è nuovo, modificato o invariato |
| Fast-skip | Evita elaborazioni inutili usando size e hash veloce |
| Elaborazione file | Chiama processFile() per EXIF, thumbnails, metadati |
| Aggiornamento DB | Inserisce/aggiorna record in photos |
| Rilevamento orfani | Cancella dal DB file non più presenti sul disco |
| Log avanzamento | Scrive un file JSON con percentuale, ETA, ecc. |
| Invio remoto | Invia i nuovi file a un server remoto (se abilitato) |
functio countFilesFast(rootDir) conta i file nelle cartelle photos//original
function formatTime(ms) trasforma ms in hⓂ️s
async function scanPhoto(dir, userName, db)
const photosRoot è il path assoluto /public/photos
const userDir è il path assoluto /public/photos//original
function updateStatusFile() fa l'aggiornamento dello stato di scansione dei file
function scanSingleFolder(user, cartella, absCartella) user: Fabio cartella: 2017Irlanda19-29ago absCartella: /home/nvme/dockerdata/prove/s15j/public/photos/Fabio/original/2017Irlanda19-29ago
🧩 Cosa fa esattamente questa riga
const { deleteThumbsById, deleteFromDB } = createCleanupFunctions(db);
Quella riga fa una cosa molto semplice… ma potentissima.
E soprattutto è il cuore della pulizia degli orfani.
📌 0. definita da
const createCleanupFunctions = require('./orphanCleanup');
🔍 1. Chiama createCleanupFunctions(db) createCleanupFunctions è una funzione che riceve il database (db) e restituisce un oggetto contenente due funzioni:
- deleteThumbsById(id)
- deleteFromDB(id, user)
In pratica, è un factory: crea funzioni già configurate con il DB.
📌 2. Cosa fanno le due funzioni restituite
🗑️ deleteThumbsById(id) Cancella i thumbnails associati a una foto:
- thub1
- thub2
- eventuali altre miniature generate
Serve quando un file non esiste più sul disco → quindi i suoi thumbnails diventano spazzatura.
🗄️ deleteFromDB(id, user) Cancella dal database:
- la riga nella tabella photos
- eventuali metadati collegati
- eventuali record in photo_changes (dipende da come l’hai implementato)
È la parte che rimuove l’entry dal DB quando il file è sparito dal filesystem.
🔴 3. Perché serve? Durante lo scan, alla fine di ogni cartella, hai:
js for (const orphanId of idsSet) { log( 🔴 Cancellato ${orphanId}); await deleteThumbsById(orphanId); await deleteFromDB(orphanId, user); }
idsSet contiene gli ID presenti nel DB ma non trovati sul disco.
Quindi:
- deleteThumbsById() → pulisce i file fisici
- deleteFromDB() → pulisce il database
È la logica che mantiene il sistema coerente.
🧠 In sintesi Quella riga:
js const { deleteThumbsById, deleteFromDB } = createCleanupFunctions(db); Eccoti una spiegazione cristallina e completa di cosa fa:
js async function scanSingleFolder(user, cartella, absCartella)
È una funzione fondamentale: gestisce tutta la sincronizzazione tra filesystem e database per una singola cartella dell’utente.
📁 Obiettivo della funzione Scansionare una cartella (es. DCIM, Vacanze2023, WhatsApp Images) e:
- Trovare i file presenti sul disco
- Confrontarli con il database
- Capire cosa è nuovo, cosa è modificato, cosa è invariato
- Aggiornare il DB
- Elaborare i nuovi file (EXIF, thumbnails, metadati)
- Rimuovere dal DB i file orfani (presenti nel DB ma non più sul disco)
È il cuore della logica di sincronizzazione.
🧠 Funzionamento passo per passo
1️⃣ Log di inizio
js log(📁 Inizio cartella: ${cartella});
2️⃣ Recupera dal DB tutti gli ID già conosciuti `js const rows = await db('photos') .where({ user, cartella }) .select('id');
const idsSet = new Set(rows.map(r => r.id)); `
idsSet serve per capire quali file non verranno trovati sul disco → saranno orfani.
3️⃣ Itera su tutti i file trovati sul disco
js for await (const f of scanCartella(...)) {
scanCartella() restituisce per ogni file:
- id deterministico
- name
- path normalizzato
- relPath, absPath
- ext
- stat (size, mtime, ecc.)
4️⃣ Aggiorna lo stato di avanzamento Incrementa CURRENT e aggiorna il file JSON con percentuale, ETA, ecc.
5️⃣ Recupera eventuale record esistente dal DB
js let prev = await db("photos") .select(...) .where({ id }) .first();
6️⃣ Calcola il fast-hash
js const fastHash = sha256(${st.size}-${st.mtimeMs});
Serve per capire velocemente se il file è cambiato.
🔍 7️⃣ Logica decisionale per ogni file
🟢 Caso A — File nuovo Se prev è null:
- Log
- Chiama processFile() per estrarre metadati, EXIF, thumbnails
- Inserisce nel DB
- Registra evento added
- Aggiunge a newFiles
- Rimuove l’ID da idsSet
🔵 Caso B — File invariato (fast-skip)
-
FAST-SIZE-SKIP Se la size è identica → file invariato.
-
FAST-HASH-SKIP Se l’hash veloce è identico → invariato.
In entrambi i casi:
- Aggiorna solo path/mtime/hash nel DB
- Rimuove l’ID da idsSet
🟠 Caso C — File modificato Se non passa i fast-skip:
- Log
- Chiama processFile()
- Aggiorna il DB con .insert().onConflict().merge()
- Registra evento updated
- Aggiunge a newFiles
- Rimuove l’ID da idsSet
🔴 8️⃣ Gestione degli orfani Alla fine della cartella:
js for (const orphanId of idsSet) { await deleteThumbsById(orphanId); await deleteFromDB(orphanId, user); }
Questi sono file che:
- esistono nel DB
- non sono stati trovati sul disco
Quindi vengono eliminati.
🧩 In sintesi scanSingleFolder():
| Step | Cosa fa |
|---|---|
| 1 | Carica dal DB gli ID della cartella |
| 2 | Scansiona i file reali sul disco |
| 3 | Per ogni file decide: nuovo, modificato, invariato |
| 4 | Elabora i nuovi/modificati con processFile() |
| 5 | Aggiorna o inserisce nel DB |
| 6 | Registra gli eventi in photo_changes |
| 7 | Rimuove gli orfani (DB → disco) |
| 8 | Restituisce i nuovi file |
È la funzione che mantiene coerenza totale tra filesystem e database.
Se vuoi, posso anche:
- disegnarti un diagramma di flusso
- riscrivere la funzione in modo più modulare
- aggiungere logging più leggibile
- ottimizzare la parte di fast-skip
- aggiungere batching per il DB
Dimmi tu come vuoi procedere.
fa questo:
- chiama una funzione factory
- che crea due funzioni già configurate con il DB
- che servono per cancellare foto orfane (thumbnails + DB)
È un modo elegante per incapsulare la logica di cleanup.
Eccoti la spiegazione chiara, completa e tecnica di cosa fa scanCartella.js e cosa restituisce.
📌 Cos’è scanCartella? È una funzione asincrona generator (async function*) che:
- scansiona ricorsivamente una cartella sul filesystem
- filtra solo i file con estensioni supportate
- calcola un ID deterministico per ogni file
- raccoglie informazioni minime (stat, path, nome, estensione…)
- NON elabora il file (niente EXIF, niente thumbnails)
- NON scrive sul database
👉 Il suo unico scopo è enumerare i file reali presenti sul disco e restituire un oggetto per ciascuno.
È volutamente “leggera”: tutta la logica pesante (processFile, DB, confronto) è in scanPhoto.
🧠 Cosa fa, passo per passo
1️⃣ Legge la cartella ricorsivamente La funzione interna walk():
- usa fsp.readdir() con { withFileTypes: true }
- entra nelle sottocartelle
- ignora file non supportati
2️⃣ Filtra per estensioni supportate
js if (!SUPPORTED_EXTS.has(ext)) continue;
3️⃣ Costruisce il percorso relativo
js const fileRelPath = relPath ? ${relPath}/${e.name} : e.name;
4️⃣ Genera un ID deterministico
js const id = sha256(${userName}/${cartella}/${fileRelPath});
Questo ID è:
- stabile
- identico tra scansioni
- basato su user/cartella/percorso relativo
È fondamentale per confrontare file reali ↔ DB.
5️⃣ Legge lo stat del file
js st = await fsp.stat(absPath);
Serve per:
- size
- mtimeMs
- fast-hash
- confronto rapido in scanPhoto
6️⃣ Logga il file trovato
js log(📂 scanCartella → user=${userName} cartella=${cartella} relPath=${fileRelPath} id=${id});
7️⃣ Restituisce un oggetto per ogni file Usa yield per restituire uno stream di risultati, non un array.
📤 Cosa restituisce esattamente?
Per ogni file valido, restituisce un oggetto:
js { id, // ID deterministico user: userName, // nome utente cartella, // nome cartella (es. DCIM) name: e.name, // nome file relPath: fileRelPath, // percorso relativo dentro la cartella absPath, // percorso assoluto sul disco ext, // estensione (es. .jpg) stat: st, // fs.stat() → size, mtimeMs, ecc. path: /photos/${userName}/original/${cartella}/${fileRelPath} }
🧩 In sintesi
✔️ Cosa fa
- Scansiona ricorsivamente una cartella
- Filtra solo i file supportati
- Genera ID deterministici
- Legge stat del file
- Costruisce percorsi relativi/assoluti
- Restituisce un oggetto per ogni file
❌ Cosa NON fa
- Non elabora il file (niente EXIF, niente thumbnails)
- Non scrive nel DB
- Non decide se il file è nuovo/modificato
- Non cancella orfani
🎯 A cosa serve?
È il motore di enumerazione dei file.
scanPhoto() usa i dati prodotti da scanCartella() per:
- confrontare con il DB
- decidere se un file è nuovo/modificato/invariato
- elaborarlo con processFile()
- aggiornare il DB
- eliminare orfani
Se vuoi, posso anche:
- disegnarti un diagramma di flusso
- riscrivere scanCartella in modo più modulare
- aggiungere caching o parallelizzazione
- aggiungere controlli per file corrotti
Dimmi tu come vuoi procedere.