Files
prep-step/CadSearchGui/Form1.cs
T
davideandClaude Sonnet 5 98c513dd64 Replace CAD search tab with CAD-path lookup for imported components
Drop the free-text search UI; the single remaining view imports the
Distinta Base, generates the CSV, and looks up each Componente code in
the indexed DB (files_ultima_versione, prt/asm) to show its CAD file
path directly in the results grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 10:28:15 +02:00

327 lines
11 KiB
C#

using System.Diagnostics;
using Microsoft.Data.Sqlite;
namespace CadSearchGui;
public partial class Form1 : Form
{
private readonly Button _btnApriDb = new();
private readonly Button _btnAggiorna = new();
private readonly Label _lblStatus = new();
private readonly Label _lblDb = new();
private readonly DataGridView _gridImport = new();
private readonly Label _lblImportStatus = new();
private static readonly string[] TipiPredefiniti = { "prt", "asm" };
private const string RadiceDiscoDefault = @"X:\";
private string? _dbPath;
public Form1()
{
InitializeComponent();
BuildUi();
Load += (_, _) => InizializzaDatabase();
}
private void BuildUi()
{
AllowDrop = true;
Text = "Distinta Base -> CSV con percorsi CAD";
var topPanel = new TableLayoutPanel
{
Dock = DockStyle.Top,
Height = 108,
ColumnCount = 3,
RowCount = 3,
Padding = new Padding(8),
};
topPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
topPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
topPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
var lblIstruzioni = new Label
{
Dock = DockStyle.Fill,
AutoSize = false,
Text = "Trascina qui il file .xls/.xml esportato dal gestionale (foglio 'Distinta "
+ "Base'), oppure scegli il file col pulsante 'Scegli file...'. Viene generato "
+ "un CSV con Componente/Descrizione e, per ogni componente, il percorso del "
+ "file CAD corrispondente trovato nell'indice.",
};
topPanel.SetColumnSpan(lblIstruzioni, 3);
topPanel.Controls.Add(lblIstruzioni, 0, 0);
var btnSfoglia = new Button { Text = "Scegli file distinta...", Dock = DockStyle.Fill };
btnSfoglia.Click += (_, _) => ScegliFileDistinta();
topPanel.Controls.Add(btnSfoglia, 0, 1);
_btnApriDb.Text = "Apri DB...";
_btnApriDb.Dock = DockStyle.Fill;
_btnApriDb.Click += (_, _) => ScegliDatabase();
topPanel.Controls.Add(_btnApriDb, 1, 1);
_btnAggiorna.Text = "Aggiorna indice";
_btnAggiorna.Dock = DockStyle.Fill;
_btnAggiorna.Click += (_, _) => AggiornaIndice();
topPanel.Controls.Add(_btnAggiorna, 2, 1);
_lblDb.Dock = DockStyle.Fill;
_lblDb.AutoSize = false;
_lblDb.ForeColor = SystemColors.GrayText;
_lblDb.TextAlign = ContentAlignment.MiddleLeft;
topPanel.SetColumnSpan(_lblDb, 3);
topPanel.Controls.Add(_lblDb, 0, 2);
_lblImportStatus.Dock = DockStyle.Top;
_lblImportStatus.Height = 24;
_lblImportStatus.Padding = new Padding(8, 4, 8, 4);
_lblImportStatus.ForeColor = SystemColors.GrayText;
_gridImport.Dock = DockStyle.Fill;
_gridImport.ReadOnly = true;
_gridImport.AllowUserToAddRows = false;
_gridImport.AllowUserToDeleteRows = false;
_gridImport.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
_gridImport.MultiSelect = false;
_gridImport.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
_gridImport.Columns.Add("componente", "Componente");
_gridImport.Columns.Add("descrizione", "Descrizione");
_gridImport.Columns.Add("percorso", "Percorso CAD");
_gridImport.CellDoubleClick += (_, _) => ApriCartellaFileSelezionato();
var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28 };
_lblStatus.Dock = DockStyle.Fill;
_lblStatus.TextAlign = ContentAlignment.MiddleLeft;
_lblStatus.Padding = new Padding(8, 0, 0, 0);
_lblStatus.Text = "Pronto.";
statusPanel.Controls.Add(_lblStatus);
Controls.Add(_gridImport);
Controls.Add(_lblImportStatus);
Controls.Add(statusPanel);
Controls.Add(topPanel);
DragEnter += (_, e) =>
e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
? DragDropEffects.Copy
: DragDropEffects.None;
DragDrop += (_, e) =>
{
if (e.Data?.GetData(DataFormats.FileDrop) is string[] { Length: > 0 } file)
ImportaDistinta(file[0]);
};
}
private void ScegliFileDistinta()
{
using var dialog = new OpenFileDialog
{
Filter = "File distinta base (*.xls;*.xml)|*.xls;*.xml|Tutti i file|*.*",
Title = "Seleziona il file Distinta Base",
};
if (dialog.ShowDialog(this) == DialogResult.OK)
ImportaDistinta(dialog.FileName);
}
private void ImportaDistinta(string percorso)
{
try
{
var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
var percorsiTrovati = TrovaPercorsiCad(righe.Select(r => r.Componente));
_gridImport.Rows.Clear();
int trovati = 0;
foreach (var (componente, descrizione) in righe)
{
string percorsoCad = percorsiTrovati.TryGetValue(componente, out var lista)
? string.Join("; ", lista)
: "";
if (percorsoCad.Length > 0)
trovati++;
_gridImport.Rows.Add(componente, descrizione, percorsoCad);
}
_lblImportStatus.ForeColor = Color.DarkGreen;
_lblImportStatus.Text = $"CSV generato ({righe.Count} righe, {trovati} con percorso CAD trovato): {csv}";
}
catch (Exception ex)
{
_gridImport.Rows.Clear();
_lblImportStatus.ForeColor = Color.DarkRed;
_lblImportStatus.Text = $"Errore durante la conversione: {ex.Message}";
}
}
/// <summary>Cerca, per ogni codice componente, i percorsi dei file CAD (ultima versione,
/// solo prt/asm) corrispondenti nell'indice.</summary>
private Dictionary<string, List<string>> TrovaPercorsiCad(IEnumerable<string> codici)
{
var risultato = new Dictionary<string, List<string>>();
if (_dbPath is null)
return risultato;
using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly");
connection.Open();
var nomiParametri = TipiPredefiniti.Select((_, i) => $"@tipo{i}");
using var command = connection.CreateCommand();
command.CommandText = $"""
SELECT percorso_completo FROM files_ultima_versione
WHERE codice = @codice AND tipo_file IN ({string.Join(",", nomiParametri)})
ORDER BY tipo_file
""";
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
for (int i = 0; i < TipiPredefiniti.Length; i++)
{
command.Parameters.AddWithValue($"@tipo{i}", TipiPredefiniti[i]);
}
foreach (var codice in codici.Distinct())
{
pCodice.Value = codice;
using var reader = command.ExecuteReader();
var percorsi = new List<string>();
while (reader.Read())
{
percorsi.Add(reader.GetString(0));
}
if (percorsi.Count > 0)
risultato[codice] = percorsi;
}
return risultato;
}
private void InizializzaDatabase()
{
var candidati = new[]
{
Path.Combine(AppContext.BaseDirectory, "cad_index.db"),
Path.Combine(Directory.GetCurrentDirectory(), "cad_index.db"),
};
_dbPath = candidati.FirstOrDefault(File.Exists);
if (_dbPath is null)
{
_lblStatus.Text = "Database non trovato. Usa 'Apri DB...' per selezionarlo.";
_lblDb.Text = "Nessun database caricato.";
return;
}
CaricaDatabase(_dbPath);
}
private void ScegliDatabase()
{
using var dialog = new OpenFileDialog
{
Filter = "Database SQLite (*.db)|*.db|Tutti i file (*.*)|*.*",
Title = "Seleziona cad_index.db",
};
if (dialog.ShowDialog(this) == DialogResult.OK)
{
CaricaDatabase(dialog.FileName);
}
}
private void CaricaDatabase(string path)
{
try
{
using var connection = new SqliteConnection($"Data Source={path};Mode=ReadOnly");
connection.Open();
long totale = (long)(new SqliteCommand("SELECT COUNT(*) FROM files", connection).ExecuteScalar() ?? 0L);
_dbPath = path;
_lblDb.Text = $"Database: {path} ({totale:N0} file indicizzati)";
_lblStatus.Text = "Pronto.";
}
catch (Exception ex)
{
MessageBox.Show(this, $"Impossibile aprire il database:\n{ex.Message}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void AggiornaIndice()
{
string dbPath = _dbPath ?? Path.Combine(AppContext.BaseDirectory, "cad_index.db");
if (!Directory.Exists(RadiceDiscoDefault))
{
MessageBox.Show(this, $"Disco non raggiungibile: {RadiceDiscoDefault}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
_btnAggiorna.Enabled = false;
_lblStatus.Text = "Aggiornamento indice in corso...";
using var cts = new CancellationTokenSource();
using var dialog = new ProgressForm();
var progress = new Progress<ScanProgress>(dialog.Aggiorna);
dialog.Annullato += (_, _) => cts.Cancel();
var scanTask = Indexer.ScanAsync(RadiceDiscoDefault, dbPath, progress, cts.Token);
// Chiude automaticamente il dialog quando lo scan termina (successo, errore o annullamento)
_ = scanTask.ContinueWith(_ =>
{
if (dialog.Visible)
{
dialog.BeginInvoke(dialog.Close);
}
}, TaskScheduler.Default);
dialog.ShowDialog(this);
try
{
await scanTask;
_lblStatus.Text = "Indice aggiornato.";
CaricaDatabase(dbPath);
}
catch (OperationCanceledException)
{
_lblStatus.Text = "Aggiornamento annullato.";
}
catch (Exception ex)
{
MessageBox.Show(this, $"Errore durante l'aggiornamento dell'indice:\n{ex.Message}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
_lblStatus.Text = "Aggiornamento fallito.";
}
finally
{
_btnAggiorna.Enabled = true;
}
}
private void ApriCartellaFileSelezionato()
{
if (_gridImport.CurrentRow is null)
{
return;
}
string? percorsi = _gridImport.CurrentRow.Cells[2].Value as string;
if (string.IsNullOrEmpty(percorsi))
{
return;
}
string primoPercorso = percorsi.Split("; ", 2)[0];
try
{
Process.Start("explorer.exe", $"/select,\"{primoPercorso}\"");
}
catch (Exception ex)
{
MessageBox.Show(this, $"Impossibile aprire Esplora risorse:\n{ex.Message}", "Errore",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}