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>
98 lines
3.7 KiB
C#
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;
|
|
}
|
|
}
|