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}"; } } /// Cerca, per ogni codice componente, i percorsi dei file CAD (ultima versione, /// solo prt/asm) corrispondenti nell'indice. private Dictionary> TrovaPercorsiCad(IEnumerable codici) { var risultato = new Dictionary>(); 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(); 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(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); } } }