Schema, logica di scansione/upsert incrementale di Indexer.ScanAsync, come l'app individua il DB all'avvio (incluso il caveat sulla cartella di build in dev) e le query piu' usate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.0 KiB
Il database cad_index.db
Documentazione tecnica del DB SQLite che indicizza i file CAD: schema, come viene
popolato/aggiornato (Indexer.ScanAsync), come l'app lo trova all'avvio e i problemi più
comuni quando i risultati sembrano "mancanti".
Schema
Tabella files (schema SQL in Indexer.Schema, PrepStep/Indexer.cs): un record per ogni
file trovato sotto la radice del disco (default X:\, Form1.RadiceDiscoDefault).
| Colonna | Contenuto |
|---|---|
id |
chiave primaria autoincrementale |
percorso_completo |
percorso assoluto del file — chiave univoca, usata per upsert |
percorso_relativo |
percorso relativo alla cartella di primo livello (cartella_radice) |
nome_file |
solo il nome del file |
cartella_radice |
prima cartella sotto la radice del disco (es. __B.end, Commerciali, ...) |
famiglia |
primo livello di sottocartella dentro cartella_radice (null se il file è direttamente dentro cartella_radice) |
codice |
estratto dal nome file col pattern Pro-E <codice>.<tipo>.<versione> |
tipo_file |
es. prt, asm, o altro se il nome non rispetta il pattern |
versione |
numero di versione Pro-E (intero), null se non rispetta il pattern |
dimensione_byte |
dimensione del file |
data_modifica |
LastWriteTimeUtc del file, formato ISO 8601 |
scansionato_il |
timestamp UTC dell'ultima scansione in cui il file è stato visto |
Indici su codice, tipo_file, cartella_radice (le colonne usate nelle query di ricerca
componenti e nel filtro per cartella).
Vista files_ultima_versione: stessa struttura di files, filtrata alla sola riga con
versione più alta per ogni (cartella_radice, codice, tipo_file), tramite ROW_NUMBER() OVER (PARTITION BY ... ORDER BY versione DESC). Tutte le ricerche di componenti (Form1. TrovaPercorsiCad, Form1.CaricaCodiciNoti) interrogano questa vista, non files
direttamente, così un componente con più versioni sulla share risolve sempre all'ultima.
Deliberatamente una ROW_NUMBER() invece di un self-join con GROUP BY: su ~200k righe un
self-join degenera in tempi quasi quadratici, ROW_NUMBER() resta lineare.
Nota: cartella_radice fa parte della partition, quindi se lo stesso codice+tipo esiste
in due cartelle radice diverse (duplicati sulla share), la vista restituisce una riga per
ciascuna — non una sola in assoluto. TrovaPercorsiCad gestisce questo caso deduplicando per
tipo_file lato C# (vedi sotto).
Come viene popolato: Indexer.ScanAsync
Chiamato da "Aggiorna indice" (fase 1) e riutilizza lo stesso dbPath già caricato.
- Crea lo schema se non esiste (
CREATE TABLE/VIEW IF NOT EXISTS) — quindi funziona sia su un DB vuoto (prima esecuzione) sia su uno esistente (aggiornamento incrementale). - Cancella subito tutte le righe con
cartella_radice LIKE 'temp\_%': le cartelletemp_paris,temp_davide,temp_studi esterni, ecc. sono lavori personali/temporanei che possono contenere copie datate con lo stesso codice numerico di file "ufficiali" altrove sulla share, causando falsi positivi nella risoluzione dei sotto-componenti (vedidocs/formato-asm.md). Vengono escluse a monte, non solo filtrate nelle singole query. - Elenca le cartelle di primo livello sotto la radice con
Directory.GetDirectories(non un elenco fisso in codice: una nuova cartella sulla share entra nell'indice al prossimo aggiornamento senza bisogno di modificarePrepStep), escludendo quelle che iniziano pertemp_. - Per ogni cartella di primo livello, cammina l'albero con uno stack manuale (
Walk, inIndexer.cs) invece diDirectory.EnumerateFiles(..., SearchOption.AllDirectories): più veloce su una share di rete con centinaia di migliaia di file, e ogni sottocartella irraggiungibile (permessi, link rotti) viene saltata con untry/catchinvece di far fallire l'intera scansione. - Per ogni file trovato, estrae
codice/tipo/versionedal nome (ParseFilename, vedi sotto) e fa un upsert riga per riga dentro una transazione per cartella: confrontadimensione_byte+data_modificacon quanto già salvato per lo stessopercorso_completo—- non esiste ancora →
INSERT(contatore "nuovi") - esiste ma dimensione/data sono cambiate →
UPDATE(contatore "aggiornati") - esiste e non è cambiato nulla → solo
scansionato_ilviene aggiornato (contatore "invariati"), per non riscrivere l'intera riga inutilmente
- non esiste ancora →
- Al termine di ogni cartella, cancella dal DB le righe di quella
cartella_radiceil cuiscansionato_ilnon è quello di questa scansione (DELETE ... WHERE scansionato_il != @now): sono file rimossi dal disco dall'ultima scansione. Il conteggio finisce nel contatore "rimossi". Questo avviene per cartella, non a scansione intera completata: se annulli a metà, le cartelle già scansionate hanno comunque gli eliminati ripuliti. - Il progresso (
ScanProgress, con conteggi nuovi/aggiornati/invariati/rimossi, file fatti/previsti e tempo trascorso) viene riportato ogni 200 file tramiteIProgress<T>, marshalato automaticamente sul thread UI, e mostrato inProgressForm(che stima anche il tempo rimanente dal ritmo file/secondo rispetto al totale atteso, preso dal conteggio della scansione precedente salvato nel DB).
Estrazione codice/tipo/versione: ParseFilename
Regex: ^(?<codice>.+)\.(?<tipo>[A-Za-z0-9]+)\.(?<versione>\d+)$ applicata al nome file
(non al percorso). Esempio: 9251208030c.asm.5 → codice="9251208030c", tipo="asm",
versione=5. Il gruppo codice è greedy, quindi cattura tutto quello che precede l'ultimo
.tipo.versione — se un componente ha una lettera di revisione attaccata nel nome del file
(es. 9248908002a.asm.3), quella lettera fa parte del codice, non è un gruppo separato.
Se il nome file non rispetta il pattern (niente .tipo.numero finale), l'intero nome finisce
in codice e tipo_file diventa "altro" (usato per capire quanti file "strani" ci sono
sulla share, non tipicamente cercato dalla UI).
Come l'app trova il DB all'avvio
Form1.InizializzaDatabase prova, in ordine, i seguenti candidati e usa il primo che esiste
su disco:
- l'ultimo DB caricato con successo, salvato in
%AppData%/PrepStep/ultimo_db.txt(Form1.LeggiUltimoDbUsato/SalvaUltimoDbUsato) cad_index.dbaccanto all'eseguibile (AppContext.BaseDirectory)cad_index.dbnella working directory corrente
Se nessuno dei tre esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente
non preme "Apri DB..." (Form1.ScegliDatabase, file picker libero) o "Aggiorna indice"
(Form1.AggiornaIndice, che crea cad_index.db da zero accanto all'eseguibile se non c'è
ancora un _dbPath).
Attenzione in sviluppo (dotnet run / IDE): AppContext.BaseDirectory è la cartella di
build (PrepStep/bin/Release/net10.0-windows o Debug/...), non la radice del repo. Se in
quella cartella finisce per errore una copia di cad_index.db (es. creata lì da una vecchia
sessione), l'app la userà invece di quella nella radice del repo, e le due copie divergeranno
a ogni "Aggiorna indice" fatto nell'uno o nell'altro ambiente. Se i risultati di una ricerca
sembrano incoerenti con quello che ti aspetti di vedere sul DB "giusto", controlla con "Apri
DB..." quale file è effettivamente caricato (mostrato in fase 1 accanto al conteggio dei file
indicizzati) prima di sospettare un bug nella ricerca.
Query più usate
- Verifica completezza / crea raw (
Form1.TrovaPercorsiCad): per ogni codice del CSV,SELECT tipo_file, percorso_completo FROM files_ultima_versione WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt','asm') ORDER BY tipo_file, data_modifica DESC. Il match sulcodiceè esatto (case-insensitive, non un LIKE): se il codice del CSV ha una lettera di revisione in più o in meno rispetto al nome del file su disco, non c'è match e la cella "Percorso CAD" resta vuota — non è un errore della query, è che il DB non ha nessuna riga con quell'esattocodice. In presenza di duplicati su piùcartella_radice, tiene solo il primo pertipo_file(il più recente, dato l'ORDER BY data_modifica DESC) e scarta gli altri. - Risoluzione codici noti per lo scanner assiemi (
Form1.CaricaCodiciNoti, usata sia dalla GUI in fase 4 sia dascripts/BomViaDb.cs): carica in un'unica query tutti i codici prt/asm noti con i relativi percorsi, invece di una query per codice — usata per validare i token generici trovati nei binari.asm(vedidocs/formato-asm.md).
Debug ad-hoc
Per interrogare il DB direttamente da riga di comando senza aprire un tool SQLite esterno:
dotnet run scripts/Query.cs -- cad_index.db "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione WHERE codice LIKE '925%' LIMIT 20"
Utile per capire in fretta se un codice mancante nella UI non è nell'indice affatto (nessuna
riga, nemmeno con un LIKE più permissivo) oppure c'è ma con un codice leggermente diverso
da quello atteso (es. lettera di revisione, maiuscole/minuscole, spazi).