Copia in raw i file che servono a Creo, non solo quelli della distinta
Creo Distributed Batch falliva su 8 dei 22 assiemi con "[-4] Dati di input non completi", un messaggio che non nomina il file mancante. Erano due cause distinte, entrambe emerse aprendo gli assiemi da raw/ e cercando nell'albero il componente con l'icona rossa. Copia sbagliata del componente. Lo stesso codice esiste spesso in piu' cartelle della share con pari versione e contenuto diverso (13% dei codici): non sono revisioni, sono istantanee parallele lasciate da studi, allestimenti fiera e cartelle temporanee. La vista dell'indice rompeva il pareggio in modo arbitrario, e poteva vincere una copia obsoleta - nascondendo del tutto quella buona. Creo poi caricava un modello diverso da quello atteso, non risolveva i vincoli e non apriva l'assieme. Il .asm non dice quale copia usa (ne' percorso, ne' UOBJ_ID del figlio, ne' revisione attesa), quindi si replica la regola di Creo: vince la copia nella stessa cartella dell'assieme che la referenzia. La vista ora rompe il pareggio in modo deterministico e viene ricreata a ogni scan, cosi' un DB esistente prende la definizione nuova senza reindicizzare. Componenti soppressi. Spariscono da "members" ma restano citati dalla feature che li posiziona, e il loro file serve comunque per aprire l'assieme. Da quando la lettura usa il parser esatto - che legge solo "members", ed e' la scelta giusta per la BOM - non venivano piu' copiati. Ora si copiano a parte, senza entrare nel conteggio dei "non trovati": non sono componenti della distinta, segnalarli come mancanti sarebbe un falso allarme. Documentate entrambe in docs/formato-asm-record.md §7.3 e §7.4, insieme alla distinzione che le accomuna: la lista della distinta e la lista dei file necessari a Creo non coincidono, e la seconda e' piu' lunga. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0ae2a5e7cf
commit
54f267b508
8 files changed
+316
-18
No files matched your search
+132
-6
@@ -743,6 +743,64 @@ public partial class Form1 : Form
|
||||
return codiciNoti;
|
||||
}
|
||||
|
||||
/// <summary>Tutti i file prt/asm dell'indice raggruppati per codice, tenendo per ogni
|
||||
/// cartella la sola versione più alta. A differenza di <see cref="CaricaCodiciNoti"/> non
|
||||
/// sceglie un vincitore fra le cartelle: lo stesso codice può esistere in più punti della
|
||||
/// share (copie in cartelle di studio, di lavoro, temporanee) con contenuto diverso, e
|
||||
/// quale sia quello giusto dipende da chi lo referenzia - vedi
|
||||
/// <see cref="ScegliPerCartella"/>.</summary>
|
||||
private static Dictionary<string, List<(string Tipo, string Percorso, int Versione, string Data)>> CaricaCandidati(string dbPath)
|
||||
{
|
||||
var candidati = new Dictionary<string, List<(string Tipo, string Percorso, int Versione, string Data)>>(StringComparer.OrdinalIgnoreCase);
|
||||
using var connection = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT codice, tipo_file, percorso_completo, versione, data_modifica FROM files "
|
||||
+ "WHERE LOWER(tipo_file) IN ('prt', 'asm')";
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
string codice = reader.GetString(0);
|
||||
string tipo = reader.GetString(1).ToLowerInvariant();
|
||||
string percorso = reader.GetString(2);
|
||||
int versione = reader.GetInt32(3);
|
||||
var lista = candidati.TryGetValue(codice, out var l) ? l : candidati[codice] = new();
|
||||
|
||||
string cartella = Path.GetDirectoryName(percorso) ?? "";
|
||||
int esistente = lista.FindIndex(x => x.Tipo == tipo
|
||||
&& string.Equals(Path.GetDirectoryName(x.Percorso), cartella, StringComparison.OrdinalIgnoreCase));
|
||||
if (esistente < 0)
|
||||
lista.Add((tipo, percorso, versione, reader.GetString(4)));
|
||||
else if (versione > lista[esistente].Versione)
|
||||
lista[esistente] = (tipo, percorso, versione, reader.GetString(4));
|
||||
}
|
||||
return candidati;
|
||||
}
|
||||
|
||||
/// <summary>Fra più copie dello stesso codice sulla share sceglie quella che userebbe Creo:
|
||||
/// prima cerca nella cartella dell'assieme che la referenzia (Creo risolve i componenti
|
||||
/// partendo da lì), poi ripiega sulla versione più alta e, a parità, sulla più recente.
|
||||
/// Senza questo criterio si finisce per copiare in "raw" una copia omonima ma di un altro
|
||||
/// modello - Creo la carica e i vincoli di accoppiamento non si risolvono, quindi l'assieme
|
||||
/// non si apre affatto.</summary>
|
||||
private static List<(string Tipo, string Percorso)> ScegliPerCartella(
|
||||
List<(string Tipo, string Percorso, int Versione, string Data)> candidati, string? cartellaPadre)
|
||||
{
|
||||
return candidati
|
||||
.GroupBy(c => c.Tipo)
|
||||
.Select(perTipo =>
|
||||
{
|
||||
var scelto = perTipo
|
||||
.OrderByDescending(c => cartellaPadre is not null
|
||||
&& string.Equals(Path.GetDirectoryName(c.Percorso), cartellaPadre, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenByDescending(c => c.Versione)
|
||||
.ThenByDescending(c => c.Data)
|
||||
.First();
|
||||
return (scelto.Tipo, scelto.Percorso);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>Codici referenziati direttamente in un assieme: prova prima il parser esatto
|
||||
/// dei record binari (<see cref="AsmRecordReader"/>, vedi docs/formato-asm-record.md), che
|
||||
/// non ha i falsi positivi/negativi noti dell'euristica testuale (docs/formato-asm.md); se
|
||||
@@ -763,6 +821,50 @@ public partial class Form1 : Form
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Componenti soppressi di un assieme (<see cref="AsmRecordReader.EstraiCodiciSoppressi"/>):
|
||||
/// non sono nella distinta, ma senza il loro file Creo non apre l'assieme. Vuoto per i file
|
||||
/// che il parser esatto non sa leggere: lì si usa l'euristica testuale, che i piazzamenti li
|
||||
/// riconosce già per conto suo (<see cref="AssemblyComponentScanner"/>).</summary>
|
||||
private static IEnumerable<string> CodiciSoppressi(string percorsoAsm)
|
||||
{
|
||||
try
|
||||
{
|
||||
return AsmRecordReader.EstraiCodiciSoppressi(percorsoAsm);
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Cartella della share da cui proviene un file già copiato in "raw": fra le copie
|
||||
/// omonime indicizzate è quella di pari dimensione. Serve per gli assiemi di partenza, che
|
||||
/// arrivano dalla fase precedente senza portarsi dietro l'origine. Null se non si riesce a
|
||||
/// stabilirla (il file non è nell'indice, o la share non risponde): in quel caso la scelta
|
||||
/// del componente ricade sul criterio generale.</summary>
|
||||
private static string? CartellaDiOrigine(string fileInRaw,
|
||||
Dictionary<string, List<(string Tipo, string Percorso, int Versione, string Data)>> candidati)
|
||||
{
|
||||
string nome = Path.GetFileName(fileInRaw);
|
||||
if (!candidati.TryGetValue(nome.Split('.')[0], out var perCodice))
|
||||
return null;
|
||||
try
|
||||
{
|
||||
long dimensione = new FileInfo(fileInRaw).Length;
|
||||
foreach (var c in perCodice.Where(c => string.Equals(Path.GetFileName(c.Percorso), nome, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var info = new FileInfo(c.Percorso);
|
||||
if (info.Exists && info.Length == dimensione)
|
||||
return Path.GetDirectoryName(c.Percorso);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
// share non raggiungibile: si prosegue col criterio generale
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Risolve ricorsivamente l'albero dei componenti a partire dai file .asm indicati
|
||||
/// (già copiati 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
|
||||
@@ -771,45 +873,69 @@ public partial class Form1 : Form
|
||||
private (List<(string Codice, string Tipo, string Percorso)> Trovati, List<string> NonTrovati) RisolviAlberoComponenti(
|
||||
IEnumerable<string> asmIniziali, IProgress<(int Fatti, int Totale)>? progress = null)
|
||||
{
|
||||
var daScansionare = new Queue<string>(asmIniziali);
|
||||
var codiciIniziali = daScansionare.Select(f => Path.GetFileName(f).Split('.')[0]);
|
||||
// Ogni assieme viaggia con la cartella della share da cui proviene: e' li' che Creo
|
||||
// cerca per primi i suoi componenti, quindi e' li' che li cerchiamo anche noi.
|
||||
var daScansionare = new Queue<(string File, string? CartellaOrigine)>(
|
||||
asmIniziali.Select(f => (f, (string?)null)));
|
||||
var codiciIniziali = asmIniziali.Select(f => Path.GetFileName(f).Split('.')[0]).ToList();
|
||||
var codiciVisti = new HashSet<string>(
|
||||
codiciIniziali.Concat(codiciIniziali.Select(AssemblyComponentScanner.CodiceRadice)),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var codiciSoppressiVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
||||
var codiciNonTrovati = new List<string>();
|
||||
if (daScansionare.Count == 0 || _dbPath is null)
|
||||
return (trovati, codiciNonTrovati);
|
||||
|
||||
var codiciNoti = CaricaCodiciNoti(_dbPath);
|
||||
var candidati = CaricaCandidati(_dbPath);
|
||||
|
||||
int processati = 0;
|
||||
while (daScansionare.Count > 0)
|
||||
{
|
||||
var asm = daScansionare.Dequeue();
|
||||
var (asm, cartellaOrigine) = daScansionare.Dequeue();
|
||||
cartellaOrigine ??= CartellaDiOrigine(asm, candidati);
|
||||
processati++;
|
||||
foreach (var codice in EstraiCodiciReferenziati(asm, codiciNoti.Keys))
|
||||
{
|
||||
if (!codiciVisti.Add(codice))
|
||||
continue;
|
||||
|
||||
if (!codiciNoti.TryGetValue(codice, out var componenti))
|
||||
if (!candidati.TryGetValue(codice, out var perCodice))
|
||||
{
|
||||
codiciNonTrovati.Add(codice);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (tipo, percorso) in componenti)
|
||||
foreach (var (tipo, percorso) in ScegliPerCartella(perCodice, cartellaOrigine))
|
||||
{
|
||||
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw!);
|
||||
trovati.Add((codice, tipo, percorso));
|
||||
if (tipo == "asm")
|
||||
{
|
||||
daScansionare.Enqueue(Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)));
|
||||
daScansionare.Enqueue((Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)),
|
||||
Path.GetDirectoryName(percorso)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I componenti soppressi non compaiono nella BOM, ma Creo ne vuole comunque il file
|
||||
// per aprire l'assieme: si copiano senza entrare nel conteggio dei "non trovati"
|
||||
// (non essendo componenti della distinta, segnalarli come mancanti sarebbe un falso
|
||||
// allarme) e senza ricorrervi dentro.
|
||||
foreach (var codice in CodiciSoppressi(asm))
|
||||
{
|
||||
if (!codiciSoppressiVisti.Add(codice) || codiciVisti.Contains(codice))
|
||||
continue;
|
||||
if (!candidati.TryGetValue(codice, out var perCodice))
|
||||
continue;
|
||||
foreach (var (tipo, percorso) in ScegliPerCartella(perCodice, cartellaOrigine))
|
||||
{
|
||||
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw!);
|
||||
trovati.Add((codice, tipo, percorso));
|
||||
}
|
||||
}
|
||||
progress?.Report((processati, processati + daScansionare.Count));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user