gallery_server_client_s190js/api_v1/scanner/scan.md
2026-10-26 11:00:14 +01:00

249 lines
7.2 KiB
Markdown

scanPhotosUser(userName, db)
│
├── individua:
│ photos/<user>/original/
│
├── legge le entry dentro original/
│
├── prende SOLO le directory
│
├── countFilesUser(userDir)
│ └── ricorsivamente guarda tutti i file
│ └── conta solo quelli con estensione in SUPPORTED_EXTS
│
└── per ogni cartella:
scanPhoto(cartella, userName, db, ...)
│
└── QUI viene probabilmente
deciso cosa fare col singolo file
scanPhotosUser
│
│ per ogni cartella
▼
scanPhoto()
│
▼
scanPhotoCartella()
│
└── QUI probabilmente avviene la decisione sul file
Quindi scanPhoto():
costruisce il percorso:
const absCartella = path.join(userDir, cartella);
passa la cartella a scanPhotoCartella().
scanPhotoCartella() riempie newFiles.
solo dopo, scanPhoto() manda al server i file presenti in newFiles:
scanCartella.js non decide se la foto è già stata scansionata: decide solo quali file sono candidati alla scansione.
cartella
↓
walk() ricorsivo
↓
è directory?
├─ sì → entra dentro
└─ no
↓
estensione supportata?
├─ no → SKIP
└─ sì
↓
crea ID deterministico
↓
stat() del file
↓
yield metadata
Il valore di SUPPORTED_EXTS è quindi importante. Lo trovi in:
api_v1/config.js
La cosa ancora più interessante: l'ID
Per ogni file accettato crea:
const id = sha256(`${userName}/${cartella}/${fileRelPath}`);
Quindi l'ID non dipende dal contenuto della foto.
Dipende da:
user
+
cartella
+
percorso relativo
scanPhotoUser
↓
scanPhoto
↓
scanPhotoCartella
↓
scanCartella
↓
per ogni file con estensione supportata
↓
scanPhotoSingle(...)
↓
QUI viene probabilmente deciso:
├── già presente → non fare certe operazioni
├── nuovo → processa
└── modificato → eventualmente aggiorna
Quindi non è scanPhotoCartella a decidere se processare o meno la foto.
È scanPhotoSingle.
4. Ma c'è un altro meccanismo importante: gli orfani
Dopo scanPhotoSingle trovi:
idsSet.delete(f.id);
Questo significa:
"Ho trovato fisicamente questo file durante la scansione, quindi il suo ID non è un orfano."
Alla fine:
for (const orphanId of idsSet) {
log(`🔴 Orfano: ${orphanId}`);
await deleteThumbsById(orphanId);
await deleteFromDB(orphanId, user);
}
DB all'inizio
│
├── A
├── B
└── C
↓
scansione filesystem
↓
trovo A → idsSet.delete(A)
trovo C → idsSet.delete(C)
↓
rimane B
↓
B non esiste più sul filesystem
↓
B = ORFANO
↓
cancella thumbnail + DB
Una foto viene presa in considerazione se:
si trova dentro una cartella sotto original;
è un file, non una directory;
la sua estensione è presente in SUPPORTED_EXTS.
Poi viene generato un ID:
sha256(`${userName}/${cartella}/${fileRelPath}`)
e il file viene passato a:
scanPhotoSingle(...)
La decisione è tutta qui
Quando trova un file, fa:
let prev = await db('photos')
.select('id', 'size_bytes', 'mtimeMs', 'fast_hash', 'path')
.where({ id })
.first();
Quindi cerca nel DB una foto con lo stesso ID.
L'ID, come abbiamo visto prima, è:
sha256(`${userName}/${cartella}/${fileRelPath}`)
Quindi normalmente identifica quel preciso percorso del file.
Poi calcola:
const fastHash = sha256(`${st.size}-${st.mtimeMs}`);
Quindi il contenuto non viene hashato: il "fast hash" dipende solo da:
dimensione file + data modifica
Le 3 situazioni
1. Foto nuova
Se:
if (!prev)
cioè non esiste nel DB un record con quell'ID, fa:
const meta = await processFile(...)
Questa è la parte che legge/elabora veramente la foto.
Poi la inserisce nel DB:
await db('photos').insert(...)
e soprattutto:
newFiles.push(meta);
Quindi una foto nuova viene processata e considerata newFile.
2. Foto già presente e invariata
Questa è la situazione che probabilmente ti interessava:
if (prev.fast_hash === fastHash) {
In questo caso NON chiama processFile().
Fa solamente:
await db('photos')
.where({ id })
.update({
path: f.path,
size_bytes: st.size,
mtimeMs: st.mtimeMs,
fast_hash: fastHash,
updated_at: new Date().toISOString(),
});
return null;
Quindi:
Se l'ID esiste e size + mtimeMs sono uguali, la foto viene saltata.
Non viene riletta, non vengono rigenerate thumbnail, non viene ricalcolato GPS/location, ecc.
Questo è il vero "skip".
3. Foto modificata
Se esiste prev, ma:
prev.fast_hash !== fastHash
allora:
log(`${prefix} 🟠 Modificato: ${fileName}`);
e viene nuovamente chiamato:
const meta = await processFile(...)
Quindi la foto viene riprocessata completamente.
C'è però un caso speciale: path cambiato
Prima del controllo fast_hash c'è:
if (prev && prev.path !== f.path) {
log(`${prefix} 🟢 Nuovo/Modificato (path changed): ${fileName}`);
prev = null;
}
Questo significa:
se trovo lo stesso id ma il path nel DB è diverso da quello attuale, tratto il file come nuovo.
E quindi finisce qui:
if (!prev) {
...
await processFile(...)
...
await db('photos').insert(...)
}
Ma qui vedo una possibile anomalia
Se prev esiste già nel DB e fai:
prev = null;
poi fai:
await db('photos').insert({
id: meta.id,
...
});
Se id è una chiave unica, potresti avere un errore di duplicate key invece di un vero aggiornamento.
Però per capire se questo caso è realmente possibile bisogna vedere come viene costruito f.path e come viene gestito il DB.
TROVO FILE
│
▼
estensione supportata?
│ │
NO SI
│ │
SKIP ▼
calcolo ID
│
▼
cerco ID nel DB
│
┌────────────┴────────────┐
│ │
non esiste esiste
│ │
▼ ▼
processFile() path diverso?
│ │ │
│ SI NO
│ │ │
│ ▼ ▼
│ prev=null confronto
│ fastHash
│ │ │
│ UGUALE DIVERSO
│ │ │
│ ▼ ▼
│ SKIP processFile()
│ │
└──────────────────────────────────────┘