Add CadSearchGui: WinForms app to index and search the CAD archive
Indicizza le cartelle __Accessori, __B.end, __Corpi, __Fondi in un DB SQLite locale ed espone una GUI di ricerca (codice, tipo, cartella, solo ultima versione) con aggiornamento incrementale dell'indice e stima del tempo rimanente. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
82a69e0d20
8 files changed
+956
No files matched your search
@@ -0,0 +1,277 @@
|
||||
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" };
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user