Index Unificati/Commerciali, merge cleanup buttons, report unmatched components

- Add 'Unificati' and 'Commerciali' as indexed root folders alongside the
  existing four, updating Indexer.CartelleRadice and the docs.
- Replace the separate "Pulisci cartella raw" / "Pulisci cartella step"
  buttons with a single "Pulisci raw e step" that clears both under one
  confirmation; falls back to a folder picker when no path is known yet
  in the session instead of staying disabled.
- "Trova componenti referenziati" now also writes a persistent summary
  (PRT/sub-assemblies found and copied, codes not found) to the main
  window's status line, not just the popup dialog, and reports clearly
  when no database is loaded instead of failing silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-16 12:05:07 +02:00
1 parent e56a3be5b4
commit 146ce78eae
4 files changed
+92 -8

No files matched your search

+4 -4
View File
@@ -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 `<codice>.<tipo>.<versione>` via regex, e fa un upsert incrementale
+86 -2
View File
@@ -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);
}
/// <summary>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.</summary>
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);
}
/// <summary>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<string>(
Directory.GetFiles(_ultimaCartellaRaw).Where(f => Path.GetFileName(f).Contains(".asm.")));
var codiciVisti = new HashSet<string>(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);
}
+1 -1
View File
@@ -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(@"^(?<codice>.+)\.(?<tipo>[A-Za-z0-9]+)\.(?<versione>\d+)$", RegexOptions.Compiled);
+1 -1
View File
@@ -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.