diff --git a/CLAUDE.md b/CLAUDE.md index 1efdb3f..7f9cbbe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,9 +5,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Cos'è questo progetto `CadSearchGui` è un'app Windows Forms (.NET 10, WinForms) che indicizza e permette di -cercare i file CAD Pro-ENGINEER/Creo contenuti in quattro cartelle di un disco/share -di rete (`__Accessori`, `__B.end`, `__Corpi`, `__Fondi` sotto una radice, di default -`X:\`), salvando i metadati in un DB SQLite locale (`cad_index.db`). +cercare i file CAD Pro-ENGINEER/Creo contenuti in sei cartelle di un disco/share +di rete (`__Accessori`, `__B.end`, `__Corpi`, `__Fondi`, `Unificati`, `Commerciali` sotto una radice, +di default `X:\`), salvando i metadati in un DB SQLite locale (`cad_index.db`). ## Comandi @@ -39,7 +39,7 @@ entrambi rimossi: ora l'unico programma è questo, con GUI e indicizzazione inte `_grid`, ecc.). `Form1.Designer.cs` contiene solo lo scheletro minimo (`InitializeComponent`/`Dispose`) generato dal template. - **Indexer.cs** — logica di scansione del disco e scrittura/aggiornamento del DB - SQLite (`Indexer.ScanAsync`). Cammina le 4 cartelle radice (`Indexer.CartelleRadice`) + SQLite (`Indexer.ScanAsync`). Cammina le 6 cartelle radice (`Indexer.CartelleRadice`) con un walk manuale a stack (`Walk`, più veloce di `EnumerateFiles(..., AllDirectories)` su share di rete con centinaia di migliaia di file), estrae da ogni nome file il pattern Pro-E `..` via regex, e fa un upsert incrementale diff --git a/CadSearchGui/Form1.cs b/CadSearchGui/Form1.cs index 2bb444d..36a5475 100644 --- a/CadSearchGui/Form1.cs +++ b/CadSearchGui/Form1.cs @@ -9,6 +9,7 @@ public partial class Form1 : Form private readonly Button _btnAggiorna = new(); private readonly Button _btnCreaRaw = new(); private readonly Button _btnTrovaPrt = new(); + private readonly Button _btnPulisci = new(); private readonly Label _lblStatus = new(); private readonly Label _lblDb = new(); private readonly DataGridView _gridImport = new(); @@ -221,6 +222,22 @@ public partial class Form1 : Form _btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati(); contenuto.Controls.Add(_btnTrovaPrt); + var lblManutenzione = new Label + { + AutoSize = true, + ForeColor = SystemColors.GrayText, + Margin = new Padding(0, 16, 0, 0), + Text = "Manutenzione: svuota le cartelle 'raw' e 'step' nella root prima di un " + + "nuovo giro (non tocca l'indice, solo le copie locali).", + }; + contenuto.Controls.Add(lblManutenzione); + + _btnPulisci.Text = "Pulisci raw e step"; + _btnPulisci.Size = new Size(200, 30); + _btnPulisci.Margin = new Padding(0, 6, 0, 0); + _btnPulisci.Click += (_, _) => PulisciRawEStep(); + contenuto.Controls.Add(_btnPulisci); + return new GroupBox { Text = "2-4. Importa Distinta Base, crea la cartella raw e risolvi i componenti referenziati", @@ -305,6 +322,61 @@ public partial class Form1 : Form Color.DarkGreen); } + /// Svuota in un colpo solo le cartelle "raw" e "step" nella root (stessa cartella + /// base del file distinta importato), previa un'unica conferma. Non tocca l'indice CAD: solo + /// le copie locali generate dalla pipeline. + private void PulisciRawEStep() + { + string radice; + if (_ultimaCartellaRaw is not null) + { + radice = Path.GetDirectoryName(_ultimaCartellaRaw)!; + } + else + { + // Non abbiamo ancora una cartella nota in questa sessione (es. app appena riaperta): + // si chiede all'utente di indicare la root invece di tenere il pulsante disabilitato. + using var dialog = new FolderBrowserDialog { Description = "Seleziona la cartella root (contiene 'raw' e 'step')" }; + if (dialog.ShowDialog(this) != DialogResult.OK) + return; + radice = dialog.SelectedPath; + } + + var cartelle = new[] { Path.Combine(radice, "raw"), Path.Combine(radice, "step") } + .Where(Directory.Exists) + .ToList(); + if (cartelle.Count == 0) + { + ImpostaStatoImport($"Nessuna cartella 'raw' o 'step' trovata in {radice}.", Color.DarkRed); + return; + } + + var conferma = MessageBox.Show(this, + $"Eliminare tutti i file in:\n{string.Join("\n", cartelle)}\n\nL'operazione non è reversibile.", + "Pulisci raw e step", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + if (conferma != DialogResult.Yes) + return; + + int eliminati = 0; + foreach (var cartella in cartelle) + { + foreach (var file in Directory.GetFiles(cartella)) + { + try + { + File.Delete(file); + eliminati++; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + // ponytail: file in uso (es. aperto in Creo) -> si salta, non blocca gli altri + } + } + } + + ImpostaStatoImport($"{eliminati} file eliminati da {string.Join(" e ", cartelle)}.", Color.DarkGreen); + } + /// Risolve ricorsivamente l'albero dei componenti a partire dagli .asm in "raw": /// per ogni assieme legge i codici referenziati al suo interno, li cerca nell'indice, copia /// in "raw" quelli trovati e, se sono a loro volta assiemi, li accoda per essere scansionati @@ -314,6 +386,12 @@ public partial class Form1 : Form if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw)) return; + if (_dbPath is null) + { + ImpostaStatoImport("Nessun database caricato: impossibile risolvere i componenti referenziati.", Color.DarkRed); + return; + } + var daScansionare = new Queue( Directory.GetFiles(_ultimaCartellaRaw).Where(f => Path.GetFileName(f).Contains(".asm."))); var codiciVisti = new HashSet(daScansionare.Select(f => Path.GetFileName(f).Split('.')[0])); @@ -321,9 +399,8 @@ public partial class Form1 : Form var trovati = new List<(string Codice, string Tipo, string Percorso)>(); int codiciNonTrovati = 0; - if (_dbPath is not null) + using (var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly")) { - using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly"); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione " @@ -367,6 +444,13 @@ public partial class Form1 : Form } } + int numPrt = trovati.Count(r => r.Tipo == "prt"); + int numAsm = trovati.Count(r => r.Tipo == "asm"); + ImpostaStatoImport( + $"Componenti referenziati: {numPrt} PRT e {numAsm} sotto-assiemi trovati e copiati in raw, " + + $"{codiciNonTrovati} codici referenziati non trovati nell'indice.", + Color.DarkGreen); + using var dialog = new PrtMancantiForm(trovati, codiciNonTrovati, _ultimaCartellaRaw); dialog.ShowDialog(this); } diff --git a/CadSearchGui/Indexer.cs b/CadSearchGui/Indexer.cs index ccbc4e3..11c9bf2 100644 --- a/CadSearchGui/Indexer.cs +++ b/CadSearchGui/Indexer.cs @@ -18,7 +18,7 @@ public sealed record ScanProgress( public static class Indexer { - public static readonly string[] CartelleRadice = { "__Accessori", "__B.end", "__Corpi", "__Fondi" }; + public static readonly string[] CartelleRadice = { "__Accessori", "__B.end", "__Corpi", "__Fondi", "Unificati", "Commerciali" }; private static readonly Regex FilenameRegex = new(@"^(?.+)\.(?[A-Za-z0-9]+)\.(?\d+)$", RegexOptions.Compiled); diff --git a/README.md b/README.md index 60c6c42..a2a3183 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ CAD di ogni componente. presente accanto all'eseguibile). Se manca, si può selezionare un DB esistente con **Apri DB...** o generarne uno nuovo con **Aggiorna indice**, che scansiona il disco (di default `X:\`, cartelle - `__Accessori`, `__B.end`, `__Corpi`, `__Fondi`) e indicizza tutti i file. + `__Accessori`, `__B.end`, `__Corpi`, `__Fondi`, `Unificati`, `Commerciali`) e indicizza tutti i file. 2. **Importa Distinta Base** — trascina (o scegli da file) l'estrazione `.xls`/`.xml` del gestionale (foglio "Distinta Base", colonne Componente/Descrizione): viene generato un CSV con anteprima in griglia.