Files
Titano/Titano/Diagnostics/CrashGuard.cs
T
Alby96andClaude Opus 5 15735a7b11 I comandi entrano nella scheda e la scheda prende tutta la finestra
I pulsanti contestuali stavano sulla riga del marchio, fuori dal riquadro della
scheda a cui appartenevano: per trovare il comando che riguardava una tabella
bisognava risalire fino al bordo della finestra. Ora stanno dentro la sezione,
sopra il contenuto su cui agiscono, e ciascuno spiega a comparsa cosa fa. Il
marchio scende nella barra delle sezioni, che diventa alta quanto la finestra e
si riduce ai soli simboli; a destra non resta altro che la scheda, dall'alto al
basso. Lo spazio liberato in cima ospita l'avanzamento delle operazioni di massa
— una barra sola, perche' la scheda Esportazione ne aveva una seconda che diceva
la stessa cosa piu' in basso — che passa anche sull'icona nella barra delle
applicazioni tramite ITaskbarList3 dichiarata a mano.

Durante un'operazione di massa i controlli che la disturberebbero sono spenti: il
progetto viene letto da un altro thread a ogni fotogramma, e cambiare risoluzione
o ritaglio a meta' significa cambiare le regole a partita iniziata.

L'anteprima guadagna due letture. La maschera del ritaglio mostra il fotogramma
intero scurendo cio' che il formato d'uscita taglia via, perche' il rapporto
d'immagine si sceglie guardando cosa si perde; e lo schermo intero apre il
fotogramma in una finestra a parte alla risoluzione nativa, con i dati di scatto
in un pannello che si chiude, perche' decidere se una stella e' puntiforme si fa
al cento per cento.

La scheda Esportazione si apre su una preimpostazione. I parametri di codifica
non sono indipendenti — il bitrate abbondante a 1080p lascia gradini sul cielo a
4K — e sceglierli bene significa muoverli insieme; restano tutti modificabili, e
toccarne uno riporta la voce a «Personalizzata».

Sull'annullamento: non sono riuscito a riprodurre un crollo sull'albero di
partenza in dodici scenari automatici, ma ho chiuso quattro strade per cui
poteva passare. Cancel() veniva chiamato nudo dentro il gestore del clic, dove
un'eccezione di una registrazione avrebbe ucciso la finestra. FrameWindow non
tollerava ObjectDisposedException quando il file di parcheggio veniva chiuso
sotto le decodifiche in volo, non impediva l'avvio di nuove decodifiche dopo la
chiusura, e lasciava non osservate le eccezioni dei compiti falliti. Soprattutto,
la coda di un'esportazione annullata tocca i controlli dopo che la finestra puo'
essere gia' stata chiusa — ed e' proprio la cosa piu' naturale da fare dopo aver
annullato: quel percorso ora si ferma prima. Resta una rete: le eccezioni non
gestite finiscono in Documenti\Titano\errori.txt invece di portarsi via il lavoro.

I dati dell'applicazione si spostano da AppData a Documenti, perche' sono file
fatti per essere aperti; le preferenze nella vecchia collocazione vengono
migrate. Tre nuove verifiche coprono l'annullamento con il parcheggio su disco
attivo: 63 su 63 passano, provate anche sulle sequenze reali in K:\Time Lapse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 17:09:40 +02:00

98 lines
3.7 KiB
C#

using System.Text;
using Titano.Pipeline;
namespace Titano.Diagnostics;
/// <summary>
/// Rete di sicurezza per le eccezioni che sfuggono ai gestori.
///
/// Un programma a finestre che incontra un'eccezione non gestita sul thread dell'interfaccia
/// muore mostrando la finestra di errore di sistema, e con lui muore il lavoro in corso: la
/// sequenza caricata, l'analisi appena fatta, le impostazioni non ancora salvate. È un
/// prezzo sproporzionato per un difetto che nella maggior parte dei casi riguarda un
/// dettaglio del disegno o un compito che stava chiudendo.
///
/// Qui l'eccezione viene invece scritta in un file accanto alle impostazioni, con la data e
/// la traccia completa, e mostrata in una finestra che dice dove è finita. Il programma
/// resta aperto. Se il difetto è grave se ne accorgerà comunque; se non lo è, non ha portato
/// via niente — e il file resta da leggere per capire cos'era.
/// </summary>
public static class CrashGuard
{
private static readonly object Gate = new();
private static bool _installed;
public static string LogPath => Path.Combine(AppSettings.DataDirectory, "errori.txt");
public static void Install()
{
if (_installed) return;
_installed = true;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += (_, e) => Handle(e.Exception, "interfaccia");
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
// Qui non si può impedire la chiusura: resta da lasciarne traccia.
if (e.ExceptionObject is Exception exception) Record(exception, "dominio");
};
// Un compito il cui esito nessuno ha guardato non deve far cadere il processo, ma
// vale la pena saperlo: quasi sempre è una decodifica interrotta a metà.
TaskScheduler.UnobservedTaskException += (_, e) =>
{
e.SetObserved();
Record(e.Exception, "compito non osservato");
};
}
private static void Handle(Exception exception, string origin)
{
string path = Record(exception, origin);
try
{
MessageBox.Show(
$"Si è verificato un errore imprevisto e Titano lo ha annotato invece di chiudersi.\n\n" +
$"{exception.GetType().Name}: {exception.Message}\n\n" +
$"La traccia completa è in:\n{path}\n\n" +
"Il lavoro in corso è ancora aperto. Conviene comunque salvare l'esportazione " +
"e riavviare quando possibile.",
"Titano", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
catch (Exception)
{
// Se non si riesce nemmeno a mostrare un messaggio non c'è altro da tentare.
}
}
private static string Record(Exception exception, string origin)
{
string path = LogPath;
try
{
lock (Gate)
{
Directory.CreateDirectory(AppSettings.DataDirectory);
var text = new StringBuilder();
text.AppendLine(new string('-', 78));
text.AppendLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} origine: {origin}");
text.AppendLine($"versione .NET {Environment.Version}, {Environment.OSVersion}");
text.AppendLine();
text.AppendLine(exception.ToString());
text.AppendLine();
File.AppendAllText(path, text.ToString(), Encoding.UTF8);
}
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
// Non poter annotare l'errore non è motivo per generarne un secondo.
}
return path;
}
}