2026-09-16 09:58:55 +02:00
|
|
|
using System.Diagnostics;
|
|
|
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
|
|
|
|
|
|
namespace CadSearchGui;
|
|
|
|
|
|
|
|
|
|
public partial class Form1 : Form
|
|
|
|
|
{
|
|
|
|
|
private readonly Button _btnApriDb = new();
|
|
|
|
|
private readonly Button _btnAggiorna = new();
|
2026-09-16 10:59:16 +02:00
|
|
|
private readonly Button _btnCreaRaw = new();
|
2026-09-16 11:40:34 +02:00
|
|
|
private readonly Button _btnTrovaPrt = new();
|
2026-09-16 12:05:07 +02:00
|
|
|
private readonly Button _btnPulisci = new();
|
2026-09-16 09:58:55 +02:00
|
|
|
private readonly Label _lblStatus = new();
|
|
|
|
|
private readonly Label _lblDb = new();
|
2026-09-16 10:22:07 +02:00
|
|
|
private readonly DataGridView _gridImport = new();
|
|
|
|
|
private readonly Label _lblImportStatus = new();
|
2026-09-16 10:59:16 +02:00
|
|
|
private readonly ToolTip _tipImportStatus = new();
|
2026-09-16 09:58:55 +02:00
|
|
|
|
|
|
|
|
private static readonly string[] TipiPredefiniti = { "prt", "asm" };
|
|
|
|
|
private const string RadiceDiscoDefault = @"X:\";
|
|
|
|
|
|
|
|
|
|
private string? _dbPath;
|
2026-09-16 10:59:16 +02:00
|
|
|
private string? _percorsoInputCorrente;
|
|
|
|
|
private List<(string Componente, string Descrizione)>? _righeCorrenti;
|
2026-09-16 11:40:34 +02:00
|
|
|
private string? _ultimaCartellaRaw;
|
2026-09-16 09:58:55 +02:00
|
|
|
|
|
|
|
|
public Form1()
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
BuildUi();
|
|
|
|
|
Load += (_, _) => InizializzaDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
// Palette: blu primario per l'azione principale del flusso ("Crea cartella raw"),
|
|
|
|
|
// il resto resta ai colori di sistema per restare coerente col tema Windows dell'utente.
|
|
|
|
|
private static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177);
|
|
|
|
|
|
2026-09-16 09:58:55 +02:00
|
|
|
private void BuildUi()
|
|
|
|
|
{
|
2026-09-16 10:28:15 +02:00
|
|
|
AllowDrop = true;
|
|
|
|
|
Text = "Distinta Base -> CSV con percorsi CAD";
|
2026-09-16 10:59:16 +02:00
|
|
|
MinimumSize = new Size(760, 480);
|
|
|
|
|
Font = new Font("Segoe UI", 9.5f);
|
2026-09-16 10:28:15 +02:00
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
var contenutoTop = new TableLayoutPanel
|
2026-09-16 09:58:55 +02:00
|
|
|
{
|
|
|
|
|
Dock = DockStyle.Top,
|
2026-09-16 10:59:16 +02:00
|
|
|
AutoSize = true,
|
|
|
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
|
|
|
|
ColumnCount = 1,
|
|
|
|
|
Padding = new Padding(12, 12, 12, 0),
|
2026-09-16 09:58:55 +02:00
|
|
|
};
|
2026-09-16 10:59:16 +02:00
|
|
|
contenutoTop.Controls.Add(BuildGruppoDatabase());
|
|
|
|
|
contenutoTop.Controls.Add(BuildGruppoImporta());
|
2026-09-16 10:22:07 +02:00
|
|
|
|
|
|
|
|
_lblImportStatus.Dock = DockStyle.Top;
|
2026-09-16 10:59:16 +02:00
|
|
|
_lblImportStatus.AutoSize = false;
|
|
|
|
|
_lblImportStatus.AutoEllipsis = true;
|
|
|
|
|
_lblImportStatus.Height = 28;
|
|
|
|
|
_lblImportStatus.Padding = new Padding(12, 6, 12, 6);
|
2026-09-16 10:22:07 +02:00
|
|
|
_lblImportStatus.ForeColor = SystemColors.GrayText;
|
|
|
|
|
|
|
|
|
|
_gridImport.Dock = DockStyle.Fill;
|
|
|
|
|
_gridImport.ReadOnly = true;
|
2026-09-16 10:59:16 +02:00
|
|
|
_gridImport.BorderStyle = BorderStyle.None;
|
|
|
|
|
_gridImport.BackgroundColor = SystemColors.Window;
|
|
|
|
|
_gridImport.RowHeadersVisible = false;
|
2026-09-16 10:22:07 +02:00
|
|
|
_gridImport.AllowUserToAddRows = false;
|
|
|
|
|
_gridImport.AllowUserToDeleteRows = false;
|
2026-09-16 10:59:16 +02:00
|
|
|
_gridImport.AllowUserToResizeRows = false;
|
2026-09-16 10:28:15 +02:00
|
|
|
_gridImport.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
|
_gridImport.MultiSelect = false;
|
2026-09-16 10:22:07 +02:00
|
|
|
_gridImport.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
2026-09-16 10:59:16 +02:00
|
|
|
_gridImport.ColumnHeadersHeight = 32;
|
|
|
|
|
_gridImport.ColumnHeadersDefaultCellStyle.Font = new Font(Font, FontStyle.Bold);
|
|
|
|
|
_gridImport.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
|
|
|
|
_gridImport.RowTemplate.Height = 26;
|
|
|
|
|
_gridImport.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(245, 247, 250);
|
|
|
|
|
_gridImport.DefaultCellStyle.SelectionBackColor = ColorePrimario;
|
|
|
|
|
_gridImport.DefaultCellStyle.Padding = new Padding(4, 0, 4, 0);
|
2026-09-16 10:22:07 +02:00
|
|
|
_gridImport.Columns.Add("componente", "Componente");
|
|
|
|
|
_gridImport.Columns.Add("descrizione", "Descrizione");
|
2026-09-16 10:28:15 +02:00
|
|
|
_gridImport.Columns.Add("percorso", "Percorso CAD");
|
2026-09-16 10:59:16 +02:00
|
|
|
_gridImport.Columns["componente"]!.FillWeight = 20;
|
|
|
|
|
_gridImport.Columns["descrizione"]!.FillWeight = 40;
|
|
|
|
|
_gridImport.Columns["percorso"]!.FillWeight = 40;
|
2026-09-16 10:28:15 +02:00
|
|
|
_gridImport.CellDoubleClick += (_, _) => ApriCartellaFileSelezionato();
|
2026-09-16 10:22:07 +02:00
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight };
|
2026-09-16 10:28:15 +02:00
|
|
|
_lblStatus.Dock = DockStyle.Fill;
|
|
|
|
|
_lblStatus.TextAlign = ContentAlignment.MiddleLeft;
|
2026-09-16 10:59:16 +02:00
|
|
|
_lblStatus.Padding = new Padding(12, 0, 0, 0);
|
2026-09-16 10:28:15 +02:00
|
|
|
_lblStatus.Text = "Pronto.";
|
|
|
|
|
statusPanel.Controls.Add(_lblStatus);
|
2026-09-16 10:22:07 +02:00
|
|
|
|
2026-09-16 10:28:15 +02:00
|
|
|
Controls.Add(_gridImport);
|
|
|
|
|
Controls.Add(_lblImportStatus);
|
|
|
|
|
Controls.Add(statusPanel);
|
2026-09-16 10:59:16 +02:00
|
|
|
Controls.Add(contenutoTop);
|
2026-09-16 10:28:15 +02:00
|
|
|
|
|
|
|
|
DragEnter += (_, e) =>
|
2026-09-16 10:22:07 +02:00
|
|
|
e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
|
|
|
|
|
? DragDropEffects.Copy
|
|
|
|
|
: DragDropEffects.None;
|
2026-09-16 10:28:15 +02:00
|
|
|
DragDrop += (_, e) =>
|
2026-09-16 10:22:07 +02:00
|
|
|
{
|
|
|
|
|
if (e.Data?.GetData(DataFormats.FileDrop) is string[] { Length: > 0 } file)
|
|
|
|
|
ImportaDistinta(file[0]);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
private GroupBox BuildGruppoDatabase()
|
|
|
|
|
{
|
|
|
|
|
var contenuto = new TableLayoutPanel
|
|
|
|
|
{
|
|
|
|
|
Dock = DockStyle.Top,
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
|
|
|
|
ColumnCount = 1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_lblDb.AutoSize = true;
|
|
|
|
|
_lblDb.AutoEllipsis = true;
|
|
|
|
|
_lblDb.MaximumSize = new Size(900, 0);
|
|
|
|
|
_lblDb.ForeColor = SystemColors.GrayText;
|
|
|
|
|
_lblDb.Margin = new Padding(0, 0, 0, 8);
|
|
|
|
|
contenuto.Controls.Add(_lblDb);
|
|
|
|
|
|
|
|
|
|
var bottoni = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight };
|
|
|
|
|
|
|
|
|
|
_btnApriDb.Text = "Apri DB...";
|
|
|
|
|
_btnApriDb.Size = new Size(120, 32);
|
|
|
|
|
_btnApriDb.Margin = new Padding(0, 0, 8, 0);
|
|
|
|
|
_btnApriDb.Click += (_, _) => ScegliDatabase();
|
|
|
|
|
bottoni.Controls.Add(_btnApriDb);
|
|
|
|
|
|
|
|
|
|
_btnAggiorna.Text = "Aggiorna indice";
|
|
|
|
|
_btnAggiorna.Size = new Size(120, 32);
|
|
|
|
|
_btnAggiorna.Click += (_, _) => AggiornaIndice();
|
|
|
|
|
bottoni.Controls.Add(_btnAggiorna);
|
|
|
|
|
|
|
|
|
|
contenuto.Controls.Add(bottoni);
|
|
|
|
|
|
|
|
|
|
return new GroupBox
|
|
|
|
|
{
|
|
|
|
|
Text = "1. Indice CAD (caricato automaticamente all'avvio)",
|
|
|
|
|
Dock = DockStyle.Top,
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
|
|
|
|
Padding = new Padding(12, 8, 12, 12),
|
|
|
|
|
Margin = new Padding(0, 0, 0, 10),
|
|
|
|
|
Controls = { contenuto },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private GroupBox BuildGruppoImporta()
|
|
|
|
|
{
|
|
|
|
|
var contenuto = new TableLayoutPanel
|
|
|
|
|
{
|
|
|
|
|
Dock = DockStyle.Top,
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
|
|
|
|
ColumnCount = 1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var lblIstruzioni1 = new Label
|
|
|
|
|
{
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
ForeColor = SystemColors.GrayText,
|
|
|
|
|
Margin = new Padding(0, 0, 0, 0),
|
|
|
|
|
Text = "2. Trascina qui il file .xls/.xml esportato dal gestionale (foglio 'Distinta "
|
|
|
|
|
+ "Base'), oppure scegli il file col pulsante sotto: genera un CSV con Componente "
|
|
|
|
|
+ "e Descrizione, in anteprima nella griglia.",
|
|
|
|
|
};
|
|
|
|
|
contenuto.Controls.Add(lblIstruzioni1);
|
|
|
|
|
|
|
|
|
|
var btnSfoglia = new Button
|
|
|
|
|
{
|
|
|
|
|
Text = "Scegli file distinta...",
|
|
|
|
|
Size = new Size(220, 32),
|
|
|
|
|
Margin = new Padding(0, 8, 0, 16),
|
|
|
|
|
};
|
|
|
|
|
btnSfoglia.Click += (_, _) => ScegliFileDistinta();
|
|
|
|
|
contenuto.Controls.Add(btnSfoglia);
|
|
|
|
|
|
|
|
|
|
var lblIstruzioni2 = new Label
|
|
|
|
|
{
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
ForeColor = SystemColors.GrayText,
|
|
|
|
|
Text = "3. Premi qui sotto per cercare il percorso CAD di ogni componente "
|
|
|
|
|
+ "nell'indice e copiare i file trovati in una cartella 'raw'.",
|
|
|
|
|
};
|
|
|
|
|
contenuto.Controls.Add(lblIstruzioni2);
|
|
|
|
|
|
|
|
|
|
_btnCreaRaw.Text = "Crea cartella raw";
|
|
|
|
|
_btnCreaRaw.Size = new Size(220, 36);
|
|
|
|
|
_btnCreaRaw.Margin = new Padding(0, 8, 0, 0);
|
|
|
|
|
_btnCreaRaw.Enabled = false;
|
|
|
|
|
_btnCreaRaw.FlatStyle = FlatStyle.Flat;
|
|
|
|
|
_btnCreaRaw.FlatAppearance.BorderSize = 0;
|
|
|
|
|
_btnCreaRaw.BackColor = ColorePrimario;
|
|
|
|
|
_btnCreaRaw.ForeColor = Color.White;
|
|
|
|
|
_btnCreaRaw.Font = new Font(Font, FontStyle.Bold);
|
|
|
|
|
_btnCreaRaw.Click += (_, _) => CreaCartellaRaw();
|
|
|
|
|
contenuto.Controls.Add(_btnCreaRaw);
|
|
|
|
|
|
2026-09-16 11:40:34 +02:00
|
|
|
var lblIstruzioni3 = new Label
|
|
|
|
|
{
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
ForeColor = SystemColors.GrayText,
|
|
|
|
|
Margin = new Padding(0, 12, 0, 0),
|
|
|
|
|
Text = "4. Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi "
|
|
|
|
|
+ "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che "
|
|
|
|
|
+ "viene trovato nell'indice dentro 'raw'.",
|
|
|
|
|
};
|
|
|
|
|
contenuto.Controls.Add(lblIstruzioni3);
|
|
|
|
|
|
|
|
|
|
_btnTrovaPrt.Text = "Trova componenti referenziati";
|
|
|
|
|
_btnTrovaPrt.Size = new Size(220, 32);
|
|
|
|
|
_btnTrovaPrt.Margin = new Padding(0, 8, 0, 0);
|
|
|
|
|
_btnTrovaPrt.Enabled = false;
|
|
|
|
|
_btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati();
|
|
|
|
|
contenuto.Controls.Add(_btnTrovaPrt);
|
|
|
|
|
|
2026-09-16 12:05:07 +02:00
|
|
|
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);
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
return new GroupBox
|
|
|
|
|
{
|
2026-09-16 11:40:34 +02:00
|
|
|
Text = "2-4. Importa Distinta Base, crea la cartella raw e risolvi i componenti referenziati",
|
2026-09-16 10:59:16 +02:00
|
|
|
Dock = DockStyle.Top,
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
|
|
|
|
Padding = new Padding(12, 8, 12, 12),
|
|
|
|
|
Controls = { contenuto },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:22:07 +02:00
|
|
|
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);
|
2026-09-16 10:59:16 +02:00
|
|
|
_percorsoInputCorrente = percorso;
|
|
|
|
|
_righeCorrenti = righe;
|
2026-09-16 10:28:15 +02:00
|
|
|
|
2026-09-16 10:22:07 +02:00
|
|
|
_gridImport.Rows.Clear();
|
|
|
|
|
foreach (var (componente, descrizione) in righe)
|
|
|
|
|
{
|
2026-09-16 10:59:16 +02:00
|
|
|
_gridImport.Rows.Add(componente, descrizione, "");
|
2026-09-16 10:22:07 +02:00
|
|
|
}
|
2026-09-16 10:59:16 +02:00
|
|
|
_btnCreaRaw.Enabled = righe.Count > 0;
|
2026-09-16 10:28:15 +02:00
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
ImpostaStatoImport($"CSV generato ({righe.Count} righe): {csv}", Color.DarkGreen);
|
2026-09-16 10:22:07 +02:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-09-16 10:59:16 +02:00
|
|
|
_percorsoInputCorrente = null;
|
|
|
|
|
_righeCorrenti = null;
|
|
|
|
|
_btnCreaRaw.Enabled = false;
|
2026-09-16 10:22:07 +02:00
|
|
|
_gridImport.Rows.Clear();
|
2026-09-16 10:59:16 +02:00
|
|
|
ImpostaStatoImport($"Errore durante la conversione: {ex.Message}", Color.DarkRed);
|
2026-09-16 10:22:07 +02:00
|
|
|
}
|
2026-09-16 09:58:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
/// <summary>Imposta testo/colore dello stato import e il tooltip con il testo completo
|
|
|
|
|
/// (la label tronca con "..." quando il percorso non ci sta in una riga).</summary>
|
|
|
|
|
private void ImpostaStatoImport(string testo, Color colore)
|
|
|
|
|
{
|
|
|
|
|
_lblImportStatus.ForeColor = colore;
|
|
|
|
|
_lblImportStatus.Text = testo;
|
|
|
|
|
_tipImportStatus.SetToolTip(_lblImportStatus, testo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CreaCartellaRaw()
|
|
|
|
|
{
|
|
|
|
|
if (_percorsoInputCorrente is null || _righeCorrenti is null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var percorsiTrovati = TrovaPercorsiCad(_righeCorrenti.Select(r => r.Componente));
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _righeCorrenti.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
string percorsoCad = percorsiTrovati.TryGetValue(_righeCorrenti[i].Componente, out var lista)
|
|
|
|
|
? string.Join("; ", lista)
|
|
|
|
|
: "";
|
|
|
|
|
_gridImport.Rows[i].Cells[2].Value = percorsoCad;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cartellaRaw = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!, "raw");
|
|
|
|
|
int copiati = CopiaFileTrovati(percorsiTrovati.Values.SelectMany(p => p).Distinct(), cartellaRaw);
|
2026-09-16 11:40:34 +02:00
|
|
|
_ultimaCartellaRaw = cartellaRaw;
|
|
|
|
|
_btnTrovaPrt.Enabled = true;
|
2026-09-16 10:59:16 +02:00
|
|
|
|
|
|
|
|
ImpostaStatoImport(
|
|
|
|
|
$"{percorsiTrovati.Count} componenti con percorso CAD trovato su {_righeCorrenti.Count}. "
|
|
|
|
|
+ $"{copiati} file copiati in {cartellaRaw}",
|
|
|
|
|
Color.DarkGreen);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 12:05:07 +02:00
|
|
|
/// <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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 11:40:34 +02:00
|
|
|
/// <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
|
|
|
|
|
/// finché non restano solo PRT (foglie) o codici non presenti nell'indice.</summary>
|
|
|
|
|
private void TrovaPrtReferenziati()
|
|
|
|
|
{
|
|
|
|
|
if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw))
|
|
|
|
|
return;
|
|
|
|
|
|
2026-09-16 12:05:07 +02:00
|
|
|
if (_dbPath is null)
|
|
|
|
|
{
|
|
|
|
|
ImpostaStatoImport("Nessun database caricato: impossibile risolvere i componenti referenziati.", Color.DarkRed);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 11:40:34 +02:00
|
|
|
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]));
|
|
|
|
|
|
|
|
|
|
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
|
|
|
|
int codiciNonTrovati = 0;
|
|
|
|
|
|
2026-09-16 12:05:07 +02:00
|
|
|
using (var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly"))
|
2026-09-16 11:40:34 +02:00
|
|
|
{
|
|
|
|
|
connection.Open();
|
|
|
|
|
using var command = connection.CreateCommand();
|
|
|
|
|
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
|
|
|
|
+ "WHERE codice = @codice AND tipo_file IN ('prt', 'asm')";
|
|
|
|
|
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
|
|
|
|
|
|
|
|
while (daScansionare.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var asm = daScansionare.Dequeue();
|
|
|
|
|
foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm))
|
|
|
|
|
{
|
|
|
|
|
if (!codiciVisti.Add(codice))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
pCodice.Value = codice;
|
|
|
|
|
var componenti = new List<(string Tipo, string Percorso)>();
|
|
|
|
|
using (var reader = command.ExecuteReader())
|
|
|
|
|
{
|
|
|
|
|
while (reader.Read())
|
|
|
|
|
{
|
|
|
|
|
componenti.Add((reader.GetString(0), reader.GetString(1)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (componenti.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
codiciNonTrovati++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var (tipo, percorso) in componenti)
|
|
|
|
|
{
|
|
|
|
|
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw);
|
|
|
|
|
trovati.Add((codice, tipo, percorso));
|
|
|
|
|
if (tipo == "asm")
|
|
|
|
|
{
|
|
|
|
|
daScansionare.Enqueue(Path.Combine(_ultimaCartellaRaw, Path.GetFileName(percorso)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 12:05:07 +02:00
|
|
|
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);
|
|
|
|
|
|
2026-09-16 11:40:34 +02:00
|
|
|
using var dialog = new PrtMancantiForm(trovati, codiciNonTrovati, _ultimaCartellaRaw);
|
|
|
|
|
dialog.ShowDialog(this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
/// <summary>Copia i file trovati nella cartella "raw" indicata, creandola se necessario.
|
|
|
|
|
/// Ritorna il numero di file copiati con successo.</summary>
|
2026-09-16 11:40:34 +02:00
|
|
|
internal static int CopiaFileTrovati(IEnumerable<string> percorsi, string cartellaRaw)
|
2026-09-16 10:59:16 +02:00
|
|
|
{
|
|
|
|
|
int copiati = 0;
|
|
|
|
|
foreach (var percorso in percorsi)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(cartellaRaw);
|
|
|
|
|
File.Copy(percorso, Path.Combine(cartellaRaw, Path.GetFileName(percorso)), overwrite: true);
|
|
|
|
|
copiati++;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
|
|
|
|
{
|
|
|
|
|
// ponytail: file non raggiungibile (share offline, permessi) -> lo si salta, non blocca gli altri
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return copiati;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:28:15 +02:00
|
|
|
/// <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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
private static readonly string PercorsoImpostazioni = Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CadSearchGui", "ultimo_db.txt");
|
|
|
|
|
|
2026-09-16 09:58:55 +02:00
|
|
|
private void InizializzaDatabase()
|
|
|
|
|
{
|
|
|
|
|
var candidati = new[]
|
|
|
|
|
{
|
2026-09-16 10:59:16 +02:00
|
|
|
LeggiUltimoDbUsato(),
|
2026-09-16 09:58:55 +02:00
|
|
|
Path.Combine(AppContext.BaseDirectory, "cad_index.db"),
|
|
|
|
|
Path.Combine(Directory.GetCurrentDirectory(), "cad_index.db"),
|
|
|
|
|
};
|
2026-09-16 10:59:16 +02:00
|
|
|
_dbPath = candidati.FirstOrDefault(p => p is not null && File.Exists(p));
|
2026-09-16 09:58:55 +02:00
|
|
|
|
|
|
|
|
if (_dbPath is null)
|
|
|
|
|
{
|
2026-09-16 10:59:16 +02:00
|
|
|
_lblStatus.Text = "Database non trovato. Usa 'Apri DB...' per selezionarlo oppure 'Aggiorna indice' per crearlo.";
|
|
|
|
|
_lblDb.Text = "Nessun database caricato. Premi 'Aggiorna indice' per crearlo da X:\\, oppure 'Apri DB...' se ne hai gi\u00e0 uno.";
|
2026-09-16 09:58:55 +02:00
|
|
|
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.";
|
2026-09-16 10:59:16 +02:00
|
|
|
SalvaUltimoDbUsato(path);
|
2026-09-16 09:58:55 +02:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show(this, $"Impossibile aprire il database:\n{ex.Message}", "Errore",
|
|
|
|
|
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 10:59:16 +02:00
|
|
|
/// <summary>Ricorda l'ultimo DB caricato con successo, così al prossimo avvio si
|
|
|
|
|
/// ricarica automaticamente anche se l'eseguibile gira da una cartella diversa.</summary>
|
|
|
|
|
private static string? LeggiUltimoDbUsato()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return File.Exists(PercorsoImpostazioni) ? File.ReadAllText(PercorsoImpostazioni).Trim() : null;
|
|
|
|
|
}
|
|
|
|
|
catch (IOException)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SalvaUltimoDbUsato(string path)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(PercorsoImpostazioni)!);
|
|
|
|
|
File.WriteAllText(PercorsoImpostazioni, path);
|
|
|
|
|
}
|
|
|
|
|
catch (IOException)
|
|
|
|
|
{
|
|
|
|
|
// ponytail: non critico se non si riesce a salvare, si torna a chiedere "Apri DB..." la prossima volta
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 09:58:55 +02:00
|
|
|
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()
|
|
|
|
|
{
|
2026-09-16 10:28:15 +02:00
|
|
|
if (_gridImport.CurrentRow is null)
|
2026-09-16 09:58:55 +02:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-09-16 10:28:15 +02:00
|
|
|
string? percorsi = _gridImport.CurrentRow.Cells[2].Value as string;
|
|
|
|
|
if (string.IsNullOrEmpty(percorsi))
|
2026-09-16 09:58:55 +02:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-09-16 10:28:15 +02:00
|
|
|
string primoPercorso = percorsi.Split("; ", 2)[0];
|
2026-09-16 09:58:55 +02:00
|
|
|
try
|
|
|
|
|
{
|
2026-09-16 10:28:15 +02:00
|
|
|
Process.Start("explorer.exe", $"/select,\"{primoPercorso}\"");
|
2026-09-16 09:58:55 +02:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show(this, $"Impossibile aprire Esplora risorse:\n{ex.Message}", "Errore",
|
|
|
|
|
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|