Files
prep-step/CadSearchGui/Indexer.cs
T
davideandClaude Sonnet 5 146ce78eae 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>
2026-09-16 12:05:07 +02:00

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;
}
}
}
}
}