Files
prep-step/docs/db-cad-index.md
T
davideandClaude Opus 5.5 57020f9964 Aggiungi il setup per-utente e fissa il DB nella cartella di installazione
Nuovo installer/PrepStep.iss (Inno Setup 6): installa in
%LOCALAPPDATA%\Programs\PrepStep senza diritti di amministratore, con
collegamenti nel menu Start al programma e al disinstallatore. Se l'app
è già installata chiede se aggiornarla o disinstallarla (Inno da solo
aggiornerebbe sempre); con /VERYSILENT aggiorna senza chiedere.

Il DB è ora sempre cad_index.db accanto all'eseguibile, cioè nella
cartella di installazione: non si ricorda più l'ultimo DB aperto
(ultimo_db.txt) e "Apri DB..." vale solo per la sessione. Senza una
cartella di lavoro salvata si propone la cartella di installazione
invece di quella del DB.

Doc aggiornati nello stesso commit: README.md, CLAUDE.md,
docs/architettura.md, docs/db-cad-index.md, docs/guida-utente.md,
CHANGELOG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 08:29:03 +02:00

10 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, data_modifica DESC, percorso_completo). 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.

La vista è ricreata (DROP + CREATE, non CREATE IF NOT EXISTS) a ogni "Aggiorna indice", così un DB già esistente prende la definizione corrente senza dover reindicizzare la share.

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).

I criteri di pareggio non sono decorativi. Lo stesso codice può esistere nella stessa cartella radice, con la stessa versione, in due sottocartelle diverse (una copia in una cartella di studio, di lavoro o temporanea). Senza data_modifica nell'ORDER BY il pareggio veniva rotto in modo arbitrario, e poteva vincere la copia obsoleta — nascondendo del tutto quella buona, che non compariva più nella vista. Il file che finiva in raw/ era allora un modello diverso da quello che l'assieme si aspetta: Creo lo carica, i vincoli di accoppiamento non si risolvono e l'assieme non si apre affatto.

Il criterio deterministico però non basta a scegliere bene: è il minimo per non sbagliare a caso. Quale copia sia quella giusta dipende da chi la referenzia, non dalla data — vedi formato-asm-record.md §7.4 e Form1.ScegliPerCartella.

Come viene popolato: Indexer.ScanAsync

Chiamato da "Aggiorna indice" (fase 1) e riutilizza lo stesso dbPath già caricato.

  1. 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).
  2. Cancella subito tutte le righe con cartella_radice LIKE 'temp\_%': le cartelle temp_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 (vedi docs/formato-asm.md). Vengono escluse a monte, non solo filtrate nelle singole query.
  3. 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 modificare PrepStep), escludendo quelle che iniziano per temp_.
  4. Per ogni cartella di primo livello, cammina l'albero con uno stack manuale (Walk, in Indexer.cs) invece di Directory.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 un try/catch invece di far fallire l'intera scansione.
  5. Per ogni file trovato, estrae codice/tipo/versione dal nome (ParseFilename, vedi sotto) e fa un upsert riga per riga dentro una transazione per cartella: confronta dimensione_byte + data_modifica con quanto già salvato per lo stesso percorso_completo—
    • non esiste ancora → INSERT (contatore "nuovi")
    • esiste ma dimensione/data sono cambiate → UPDATE (contatore "aggiornati")
    • esiste e non è cambiato nulla → solo scansionato_il viene aggiornato (contatore "invariati"), per non riscrivere l'intera riga inutilmente
  6. Al termine di ogni cartella, cancella dal DB le righe di quella cartella_radice il cui scansionato_il non è 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.
  7. Il progresso (ScanProgress, con conteggi nuovi/aggiornati/invariati/rimossi, file fatti/previsti e tempo trascorso) viene riportato ogni 200 file tramite IProgress<T>, marshalato automaticamente sul thread UI, e mostrato in ProgressForm (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 carica sempre e solo cad_index.db accanto all'eseguibile (AppContext.BaseDirectory: con il setup è %LOCALAPPDATA%\Programs\PrepStep). Nessun percorso viene ricordato fra un avvio e l'altro.

Se non esiste, la fase 1 resta bloccata su "Database non trovato" finché l'utente non preme "Aggiorna indice" (Form1.AggiornaIndice, che crea cad_index.db da zero accanto all'eseguibile) o "Apri DB..." (Form1.ScegliDatabase, file picker libero: il DB scelto vale solo per la sessione corrente, e "Aggiorna indice" aggiorna quello).

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. L'app usa quindi la copia di cad_index.db in quella cartella (creandola lì al primo "Aggiorna indice"), non quella nella radice del repo né quella installata: per usare un altro DB in sviluppo va aperto con "Apri DB..." a ogni avvio. 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 sul codice è 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'esatto codice. In presenza di duplicati su più cartella_radice, tiene solo il primo per tipo_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 da scripts/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 (vedi docs/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).