- 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>
278 lines
11 KiB
C#
278 lines
11 KiB
C#
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.Data.Sqlite;
|
|
|
|
namespace CadSearchGui;
|
|
|
|
public sealed record ScanProgress(
|
|
string Cartella,
|
|
long FattiCartella,
|
|
long PrevistiCartella,
|
|
long FattiTotali,
|
|
long PrevistiTotali,
|
|
int Nuovi,
|
|
int Aggiornati,
|
|
int Invariati,
|
|
int Rimossi,
|
|
TimeSpan Trascorso);
|
|
|
|
public static class Indexer
|
|
{
|
|
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);
|
|
|
|
private const string Schema = """
|
|
CREATE TABLE IF NOT EXISTS files (
|
|
id INTEGER PRIMARY KEY,
|
|
percorso_completo TEXT UNIQUE NOT NULL,
|
|
percorso_relativo TEXT,
|
|
nome_file TEXT,
|
|
cartella_radice TEXT,
|
|
famiglia TEXT,
|
|
codice TEXT,
|
|
tipo_file TEXT,
|
|
versione INTEGER,
|
|
dimensione_byte INTEGER,
|
|
data_modifica TEXT,
|
|
scansionato_il TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_files_codice ON files(codice);
|
|
CREATE INDEX IF NOT EXISTS idx_files_tipo ON files(tipo_file);
|
|
CREATE INDEX IF NOT EXISTS idx_files_cartella ON files(cartella_radice);
|
|
|
|
CREATE VIEW IF NOT EXISTS files_ultima_versione AS
|
|
SELECT percorso_completo, percorso_relativo, nome_file, cartella_radice, famiglia,
|
|
codice, tipo_file, versione, dimensione_byte, data_modifica, scansionato_il
|
|
FROM (
|
|
SELECT f.*, ROW_NUMBER() OVER (
|
|
PARTITION BY cartella_radice, codice, tipo_file
|
|
ORDER BY versione DESC
|
|
) AS rn
|
|
FROM files f
|
|
) WHERE rn = 1;
|
|
""";
|
|
|
|
public static Task ScanAsync(string root, string dbPath, IProgress<ScanProgress> progress, CancellationToken ct)
|
|
=> Task.Run(() => Scan(root, dbPath, progress, ct), ct);
|
|
|
|
private static void Scan(string root, string dbPath, IProgress<ScanProgress> progress, CancellationToken ct)
|
|
{
|
|
using var connection = new SqliteConnection($"Data Source={dbPath}");
|
|
connection.Open();
|
|
using (var schemaCmd = connection.CreateCommand())
|
|
{
|
|
schemaCmd.CommandText = Schema;
|
|
schemaCmd.ExecuteNonQuery();
|
|
}
|
|
|
|
var previsti = new Dictionary<string, long>();
|
|
foreach (var cartella in CartelleRadice)
|
|
{
|
|
using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = "SELECT COUNT(*) FROM files WHERE cartella_radice = @c";
|
|
cmd.Parameters.AddWithValue("@c", cartella);
|
|
previsti[cartella] = (long)(cmd.ExecuteScalar() ?? 0L);
|
|
}
|
|
long previstiTotali = previsti.Values.Sum();
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
long fattiTotali = 0;
|
|
|
|
foreach (var cartella in CartelleRadice)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
string rootPath = Path.Combine(root, cartella);
|
|
if (!Directory.Exists(rootPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string now = DateTime.UtcNow.ToString("o");
|
|
int nuovi = 0, aggiornati = 0, invariati = 0;
|
|
long fattiCartella = 0;
|
|
|
|
using var transaction = connection.BeginTransaction();
|
|
|
|
using var cmdSelect = connection.CreateCommand();
|
|
cmdSelect.Transaction = transaction;
|
|
cmdSelect.CommandText = "SELECT dimensione_byte, data_modifica FROM files WHERE percorso_completo = @p";
|
|
var pSelect = cmdSelect.Parameters.Add("@p", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
|
|
using var cmdInsert = connection.CreateCommand();
|
|
cmdInsert.Transaction = transaction;
|
|
cmdInsert.CommandText = """
|
|
INSERT INTO files
|
|
(percorso_completo, percorso_relativo, nome_file, cartella_radice, famiglia,
|
|
codice, tipo_file, versione, dimensione_byte, data_modifica, scansionato_il)
|
|
VALUES (@percorso, @relativo, @nome, @cartella, @famiglia, @codice, @tipo, @versione,
|
|
@dimensione, @modifica, @scansionato)
|
|
""";
|
|
AggiungiParametriRecord(cmdInsert);
|
|
cmdInsert.Parameters.AddWithValue("@scansionato", now);
|
|
|
|
using var cmdUpdate = connection.CreateCommand();
|
|
cmdUpdate.Transaction = transaction;
|
|
cmdUpdate.CommandText = """
|
|
UPDATE files SET percorso_relativo=@relativo, nome_file=@nome, cartella_radice=@cartella,
|
|
famiglia=@famiglia, codice=@codice, tipo_file=@tipo, versione=@versione,
|
|
dimensione_byte=@dimensione, data_modifica=@modifica, scansionato_il=@scansionato
|
|
WHERE percorso_completo=@percorso
|
|
""";
|
|
AggiungiParametriRecord(cmdUpdate);
|
|
cmdUpdate.Parameters.AddWithValue("@scansionato", now);
|
|
|
|
using var cmdTouch = connection.CreateCommand();
|
|
cmdTouch.Transaction = transaction;
|
|
cmdTouch.CommandText = "UPDATE files SET scansionato_il=@scansionato WHERE percorso_completo=@percorso";
|
|
cmdTouch.Parameters.AddWithValue("@scansionato", now);
|
|
var pTouchPercorso = cmdTouch.Parameters.Add("@percorso", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
|
|
foreach (var file in Walk(rootPath))
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
string percorsoCompleto = file.FullName;
|
|
string percorsoRelativo = Path.GetRelativePath(rootPath, percorsoCompleto);
|
|
string nomeFile = file.Name;
|
|
string[] parti = percorsoRelativo.Split(Path.DirectorySeparatorChar);
|
|
string? famiglia = parti.Length > 1 ? parti[0] : null;
|
|
var (codice, tipo, versione) = ParseFilename(nomeFile);
|
|
long dimensione = file.Length;
|
|
string modifica = file.LastWriteTimeUtc.ToString("o");
|
|
|
|
pSelect.Value = percorsoCompleto;
|
|
object? esistenteDim = null;
|
|
string? esistenteMod = null;
|
|
bool esiste = false;
|
|
using (var reader = cmdSelect.ExecuteReader())
|
|
{
|
|
if (reader.Read())
|
|
{
|
|
esiste = true;
|
|
esistenteDim = reader.IsDBNull(0) ? null : reader.GetInt64(0);
|
|
esistenteMod = reader.IsDBNull(1) ? null : reader.GetString(1);
|
|
}
|
|
}
|
|
|
|
if (!esiste)
|
|
{
|
|
ImpostaParametriRecord(cmdInsert, percorsoCompleto, percorsoRelativo, nomeFile, cartella,
|
|
famiglia, codice, tipo, versione, dimensione, modifica);
|
|
cmdInsert.ExecuteNonQuery();
|
|
nuovi++;
|
|
}
|
|
else if (!(esistenteDim is long d && d == dimensione && esistenteMod == modifica))
|
|
{
|
|
ImpostaParametriRecord(cmdUpdate, percorsoCompleto, percorsoRelativo, nomeFile, cartella,
|
|
famiglia, codice, tipo, versione, dimensione, modifica);
|
|
cmdUpdate.ExecuteNonQuery();
|
|
aggiornati++;
|
|
}
|
|
else
|
|
{
|
|
pTouchPercorso.Value = percorsoCompleto;
|
|
cmdTouch.ExecuteNonQuery();
|
|
invariati++;
|
|
}
|
|
|
|
fattiCartella++;
|
|
fattiTotali++;
|
|
|
|
if (fattiTotali % 200 == 0)
|
|
{
|
|
progress.Report(new ScanProgress(cartella, fattiCartella, previsti[cartella],
|
|
fattiTotali, previstiTotali, nuovi, aggiornati, invariati, 0, sw.Elapsed));
|
|
}
|
|
}
|
|
|
|
using (var cmdDelete = connection.CreateCommand())
|
|
{
|
|
cmdDelete.Transaction = transaction;
|
|
cmdDelete.CommandText = "DELETE FROM files WHERE cartella_radice = @c AND scansionato_il != @now";
|
|
cmdDelete.Parameters.AddWithValue("@c", cartella);
|
|
cmdDelete.Parameters.AddWithValue("@now", now);
|
|
int rimossi = cmdDelete.ExecuteNonQuery();
|
|
|
|
transaction.Commit();
|
|
|
|
progress.Report(new ScanProgress(cartella, fattiCartella, previsti[cartella],
|
|
fattiTotali, previstiTotali, nuovi, aggiornati, invariati, rimossi, sw.Elapsed));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AggiungiParametriRecord(SqliteCommand cmd)
|
|
{
|
|
cmd.Parameters.Add("@percorso", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@relativo", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@nome", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@cartella", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@famiglia", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@tipo", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
cmd.Parameters.Add("@versione", Microsoft.Data.Sqlite.SqliteType.Integer);
|
|
cmd.Parameters.Add("@dimensione", Microsoft.Data.Sqlite.SqliteType.Integer);
|
|
cmd.Parameters.Add("@modifica", Microsoft.Data.Sqlite.SqliteType.Text);
|
|
}
|
|
|
|
private static void ImpostaParametriRecord(SqliteCommand cmd, string percorso, string relativo, string nome,
|
|
string cartella, string? famiglia, string codice, string tipo, int? versione, long dimensione, string modifica)
|
|
{
|
|
cmd.Parameters["@percorso"].Value = percorso;
|
|
cmd.Parameters["@relativo"].Value = relativo;
|
|
cmd.Parameters["@nome"].Value = nome;
|
|
cmd.Parameters["@cartella"].Value = cartella;
|
|
cmd.Parameters["@famiglia"].Value = (object?)famiglia ?? DBNull.Value;
|
|
cmd.Parameters["@codice"].Value = codice;
|
|
cmd.Parameters["@tipo"].Value = tipo;
|
|
cmd.Parameters["@versione"].Value = (object?)versione ?? DBNull.Value;
|
|
cmd.Parameters["@dimensione"].Value = dimensione;
|
|
cmd.Parameters["@modifica"].Value = modifica;
|
|
}
|
|
|
|
private static (string codice, string tipo, int? versione) ParseFilename(string nomeFile)
|
|
{
|
|
var match = FilenameRegex.Match(nomeFile);
|
|
if (match.Success)
|
|
{
|
|
return (match.Groups["codice"].Value, match.Groups["tipo"].Value,
|
|
int.Parse(match.Groups["versione"].Value));
|
|
}
|
|
return (nomeFile, "altro", null);
|
|
}
|
|
|
|
private static IEnumerable<FileInfo> Walk(string root)
|
|
{
|
|
var stack = new Stack<string>();
|
|
stack.Push(root);
|
|
while (stack.Count > 0)
|
|
{
|
|
string current = stack.Pop();
|
|
IEnumerable<FileSystemInfo> entries;
|
|
try
|
|
{
|
|
entries = new DirectoryInfo(current).EnumerateFileSystemInfos();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var entry in entries)
|
|
{
|
|
if (entry is DirectoryInfo dir)
|
|
{
|
|
stack.Push(dir.FullName);
|
|
}
|
|
else if (entry is FileInfo file)
|
|
{
|
|
yield return file;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|