Files
2025-12-16 07:31:47 +01:00

464 lines
19 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// <summary>
/// Host principale per l'applicazione Blazor su Windows Forms.
/// Questa classe rappresenta il punto di ingresso dell'applicazione desktop, integrando una vista Blazor WebAssembly
/// con servizi in background, monitoraggio della memoria e logica di riavvio automatico.
///
/// Responsabilità chiave:
/// - Inizializza e ospita una interfaccia Blazor WebAssembly tramite Windows Forms BlazorWebView.
/// - Gestionale eventi di vita: avvio, chiusura del form e arresto.
/// - Monitora l'uso della memoria .NET e della memoria sistema per prevenire perdita di memoria o crash a causa di esaurimento.
/// - Esegue verifiche periodiche per riavvio automatico in base alla configurazione e al comportamento del GC.
/// - Ascolta segnali esterni (ad esempio aggiornamenti di configurazione, richieste di riavvio o riavvio) da servizi.
/// - Arresta in modo graduale tutti i processi gestiti all'uscita o durante un riavvio.
/// </summary>
using IOB_MAN.Core.Services;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using System.Diagnostics;
using System.Reflection;
namespace IOB_MAN
{
public partial class BlazorForm : Form
{
#region Public Constructors
/// <summary>
/// Crea una nuova istanza della classe BlazorForm.
/// Imposta il form, inizializza i servizi, configura la vista Blazor e avvia timer in background.
/// </summary>
public BlazorForm()
{
InitializeComponent();
ServiceInit();
ConfInit();
InitBlazorView();
StartTimer();
CheckMemory();
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Forza il riavvio dell'applicazione con un ritardo opzionale.
///
/// Comportamento:
/// - Se 'force' è true, registra un riavvio forzato (ad esempio a causa di un errore critico).
/// - Altrimenti, attiva il riavvio a causa del superamento consecutivo di soglie di GC.
/// - Arresta tutti i processi gestiti tramite ACService.DoCloseAll().
/// - Introduce un ritardo di 5 secondi utilizzando cmd.exe per evitare un riavvio immediato.
/// - Utilizza Application.Exit() per terminare correttamente l'istanza corrente.
/// </summary>
/// <param name="force">Se true, esegue un riavvio forzato; altrimenti, attiva il riavvio a causa dell'esaurimento del GC.</param>
public static void RestartApplication(bool force)
{
if (force)
{
Log.Info($"Chiamata restart application forzato");
}
else
{
Log.Info($"Chiamata restart application x superamento soglia GC consecutivi");
}
string exePath = Application.ExecutablePath;
// Chiude tutti i servizi gestiti (ad esempio applicazioni esterne) prima del riavvio
ACService.DoCloseAll(false);
Log.Info("---------------------------------------");
Log.Info($"Completata chiusura processi x restart");
Log.Info("---------------------------------------");
// Ritardo di 10 secondi utilizzando cmd.exe per evitare un riavvio immediato
int restartDelay = 10;
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", $"/C timeout /t {restartDelay} & start \"\" \"{exePath}\"")
{
CreateNoWindow = true,
UseShellExecute = false
};
Process.Start(psi);
Log.Info("---------------------------------------");
Log.Info($"Avviato task di riavvio | delay {restartDelay}sec");
Log.Info("---------------------------------------");
// Termina l'istanza corrente dopo il ritardo
Application.Exit();
}
#endregion Public Methods
#region Protected Fields
/// <summary>
/// L'ora successiva prevista per il controllo automatico del riavvio (in base alla configurazione).
/// Utilizzata per determinare quando deve essere attivato il riavvio automatico.
/// </summary>
protected DateTime tOutAutocheck = DateTime.Now;
#endregion Protected Fields
#region Private Fields
/// <summary>
/// Istanza del logger per il logging strutturato durante l'intero ciclo di vita dell'applicazione.
/// </summary>
private static Logger Log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Contatore che segnala il numero di raccolte GC consecutive.
/// Utilizzato per rilevare pressione di memoria prolungata e attivare un riavvio forzato se il limite è superato.
/// </summary>
private int currSeqGC = 0;
/// <summary>
/// Memoria gestita allocata dal runtime .NET (in byte).
/// Monitorata per rilevare pressione di memoria e attivare GC o riavvio.
/// </summary>
private long dotNetMemUsed = 0;
/// <summary>
/// Ora della ultima raccolta GC forzata.
/// Utilizzata per garantire raccolte periodiche anche se la memoria non è criticamente elevata.
/// </summary>
private DateTime LastForcedGC = DateTime.Now;
/// <summary>
/// Numero massimo di raccolte GC consecutive ammesse prima dell'attivazione del riavvio.
/// Predefinito a 5 — se superato, l'applicazione si riavvia per evitare esaurimento della memoria.
/// </summary>
private int maxConsGC = 5;
/// <summary>
/// Limite massimo di consumo di memoria ammesso (in MB) per l'applicazione.
/// Predefinito a 128 MB — se la memoria gestita o totale supera la metà di questo valore, si attiva GC o riavvio.
/// </summary>
private double maxMemoryLimitMb = 128.0;
/// <summary>
/// Generatore di numeri casuali utilizzato per introdurre ritardi leggeri nell'avvio dei timer.
/// Utilizzato per prevenire che tutti i timer inizino simultaneamente.
/// </summary>
private Random rand = new Random();
/// <summary>
/// Memoria privata utilizzata dal processo corrente (in byte).
/// Utilizzata per rilevare pressione di memoria al livello del sistema.
/// </summary>
private long totalMemUsed = 0;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Servizio singleton per il controllo dell'applicazione (ad esempio gestione servizi esterni, configurazione, riavvii).
/// </summary>
private static AppControlService ACService { get; set; } = null!;
/// <summary>
/// Servizio singleton per la gestione delle richieste di log Flux e interazioni UI.
/// </summary>
private static FluxLogManService FLMService { get; set; } = null!;
/// <summary>
/// Converte la memoria gestita .NET (in byte) in MB per il logging e il display.
/// </summary>
private double dotNetMemUsedMb
{
get => dotNetMemUsed / 1024.0 / 1024.0;
}
/// <summary>
/// Salva la dimensione nota del form per mantenere una posizione coerente.
/// </summary>
private Size lastSize { get; set; } = new Size(1200, 700);
/// <summary>
/// Converte la memoria totale del processo (in byte) in MB per il logging e il display.
/// </summary>
private double totalMemUsedMb
{
get => totalMemUsed / 1024.0 / 1024.0;
}
#endregion Private Properties
#region Private Methods
/// <summary>
/// Gestisce un aggiornamento della configurazione fornito dal servizio AppControlService.
///
/// Azioni:
/// - Ferma e riavvia i timer per riflettere i nuovi intervalli di aggiornamento.
/// - Aggiorna il tempo previsto per il controllo del riavvio in base alla configurazione.
/// </summary>
private void ACService_EA_ConfigUpdated()
{
timerUI.Stop();
timerTask.Stop();
tOutAutocheck = ACService.VetoAutoCheck;
StartTimer();
}
/// <summary>
/// Gestisce una richiesta di riavvio fornita dal servizio AppControlService.
///
/// Azioni:
/// - Ferma tutti i timer e i servizi.
/// - Chiude tutti i processi esterni.
/// - Attiva un riavvio forzato.
/// </summary>
private void ACService_EA_RebootRequested()
{
Log.Info("---------------------------------------");
Log.Info("Starting Reboot Request");
Log.Info("---------------------------------------");
timerUI.Stop();
timerUI.Dispose();
timerTask.Stop();
timerTask.Dispose();
ACService.DoCloseAll(false);
RestartApplication(true);
}
/// <summary>
/// Pulisce gli event handler e le risorse quando il form viene chiuso.
///
/// Azioni:
/// - Rimuove le sottoscrizioni degli eventi dai servizi.
/// - Ferma e dismette i timer.
/// - Chiude tutti i processi esterni.
/// </summary>
private void BlazorForm_FormClosing(object sender, FormClosingEventArgs e)
{
ACService.EA_ConfigUpdated -= ACService_EA_ConfigUpdated;
ACService.EA_RebootRequested -= ACService_EA_RebootRequested;
FLMService.EA_FluxLogReq -= FLMService_EA_FluxLogReq;
timerUI.Stop();
timerUI.Dispose();
timerTask.Stop();
timerTask.Dispose();
ACService.DoCloseAll(false);
}
/// <summary>
/// Imposta la posizione del form al centro in basso dello spazio dello schermo.
/// Assicura un posizionamento coerente in diverse configurazioni di schermo.
/// </summary>
private void BlazorForm_Load(object sender, EventArgs e)
{
SetPosition();
}
/// <summary>
/// Controlla periodicamente l'uso della memoria e attiva la raccolta di rifiuti se i limiti sono superati.
///
/// Logica:
/// - Misura la memoria gestita e la memoria totale.
/// - Se la memoria gestita supera la metà del limite massimo (maxMemoryLimitMb) o la memoria totale supera maxMemoryLimitMb:
/// - Forza la raccolta di rifiuti (GC.Collect).
/// - Aumenta il contatore di raccolte consecutive.
/// - Se il contatore supera maxConsGC → attiva il riavvio dell'applicazione.
/// - Se più di un'ora è passata dal momento della precedente raccolta forzata → attiva una raccolta periodica.
/// - Altrimenti, decrementa il contatore per evitare sovrastima.
/// </summary>
private void CheckMemory()
{
dotNetMemUsed = GC.GetTotalMemory(false);
Process currentProcess = Process.GetCurrentProcess();
totalMemUsed = currentProcess.PrivateMemorySize64;
Log.Info($"Memory Usage | {dotNetMemUsedMb:F2} / {totalMemUsedMb:F2} MB | Managed / Total");
if (dotNetMemUsedMb > maxMemoryLimitMb / 2 || totalMemUsedMb > maxMemoryLimitMb)
{
currSeqGC++;
Log.Info($"Chiamato forzatamente GC.Collect per superamento limite memoria | count: {currSeqGC}");
GC.Collect();
LastForcedGC = DateTime.Now;
if (currSeqGC > maxConsGC)
{
Log.Info("---------------------------------------");
Log.Info($"Superato limite GC consecutivi | count: {currSeqGC}");
RestartApplication(false);
}
}
else if (DateTime.Now.Subtract(LastForcedGC).TotalHours > 1)
{
Log.Info($"Chiamato forzatamente GC.Collect per scadenza periodo");
GC.Collect();
LastForcedGC = DateTime.Now;
}
else
{
if (currSeqGC > 0)
{
currSeqGC--;
Log.Info($"Riduzione contatore GC consecutivi | count: {currSeqGC}");
}
}
}
/// <summary>
/// Inizializza i valori di configurazione dal servizio AppControlService.
///
/// In particolare: imposta maxMemoryLimitMb dal valore di configurazione (ad esempio MaxMemGc).
/// </summary>
private void ConfInit()
{
maxMemoryLimitMb = ACService.MaxMemGc;
}
/// <summary>
/// Gestisce una richiesta di apertura del form di editing dei log Flux.
///
/// Azione:
/// - Crea e mostra un nuovo form FluxLogData con il codice IOB fornito.
/// - Consente all'utente di modificare o visualizzare i log di flusso per un IOB specifico.
/// </summary>
/// <param name="codIOB">Il codice IOB associato al log richiesto.</param>
private void FLMService_EA_FluxLogReq(string codIOB)
{
FluxLogData FldForm = new FluxLogData(FLMService, codIOB);
FldForm.Show();
}
/// <summary>
/// Inizializza la vista Blazor WebView con i servizi necessari e i componenti principali.
///
/// Passi:
/// - Imposta una ServiceCollection con le dipendenze richieste.
/// - Registra AppControlService e FluxLogManService come servizi singleton.
/// - Configura la BlazorWebView per caricare la pagina index.html.
/// - Aggiunge il componente MainBlazor al nodo radice.
/// </summary>
private void InitBlazorView()
{
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
Log.Trace($"TrayMenu OK | {sw.ElapsedMilliseconds}ms");
var services = new ServiceCollection();
Log.Trace($"Add ServiceCollection | {sw.ElapsedMilliseconds}ms");
services.AddWindowsFormsBlazorWebView();
Log.Trace($"Add AddWindowsFormsBlazorWebView | {sw.ElapsedMilliseconds}ms");
services.AddSingleton<AppControlService>(ACService);
services.AddSingleton<FluxLogManService>(FLMService);
Log.Trace($"Add Singleton Services | {sw.ElapsedMilliseconds}ms");
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
blazorWebView1.RootComponents.Add<MainBlazor>("#app");
Log.Trace($"Add MainBlazor | {sw.ElapsedMilliseconds}ms");
}
catch (Exception exc)
{
Log.Error($"Exception during startup{Environment.NewLine}{exc}");
}
}
/// <summary>
/// Inizializza i servizi principali (AppControlService, FluxLogManService).
///
/// Registra eventi per:
/// - Aggiornamento della configurazione
/// - Richieste di riavvio, ricarica e riavvio
/// - Richieste di log Flux
/// </summary>
private void ServiceInit()
{
ACService = new AppControlService(true);
FLMService = new FluxLogManService();
ACService.EA_ConfigUpdated += ACService_EA_ConfigUpdated;
ACService.EA_RebootRequested += ACService_EA_RebootRequested;
FLMService.EA_FluxLogReq += FLMService_EA_FluxLogReq;
}
/// <summary>
/// Centra il form nella parte inferiore a sinistra dell'area di lavoro dello schermo.
/// Garantisce una posizione UI coerente indipendentemente dalla risoluzione dello schermo.
/// </summary>
private void SetPosition()
{
Rectangle workArea = Screen.GetWorkingArea(this);
this.Location = new Point((workArea.Right - lastSize.Width) / 2, (workArea.Bottom - lastSize.Height) / 2);
}
/// <summary>
/// Avvia i timer in background in base ai valori di configurazione forniti dal servizio AppControlService.
///
/// Timer:
/// - timerUI: esegue uno scansionamento dello stato dell'applicazione ogni intervallo (RefreshPeriod).
/// - timerTask: verifica le condizioni per il riavvio automatico ogni intervallo (CheckRestartPeriod).
/// - timerMemCheck: monitora l'uso della memoria ogni intervallo (CheckMemoryPeriod).
///
/// Nota: Introduce un ritardo di 2080 ms per evitare che tutti i timer inizino contemporaneamente.
/// </summary>
private void StartTimer()
{
timerUI.Interval = (ACService.RefreshPeriod);
timerTask.Interval = (ACService.CheckRestartPeriod);
timerMemCheck.Interval = (ACService.CheckMemoryPeriod);
timerUI.Start();
Thread.Sleep(rand.Next(20, 80));
timerTask.Start();
Thread.Sleep(rand.Next(20, 80));
timerMemCheck.Start();
}
/// <summary>
/// Evento del timer periodico per il controllo della memoria.
///
/// Attiva CheckMemory() per monitorare l'uso della memoria e forza la raccolta GC se necessario.
/// Inoltre, registra statistiche dei servizi per il debug.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timerMemCheck_Tick(object sender, EventArgs e)
{
CheckMemory();
ACService.PrintStats();
}
/// <summary>
/// Evento del timer periodico per le attività di verifica.
///
/// Azioni:
/// - Ferma il timer per evitare sovrapposizioni.
/// - Verifica le condizioni per un riavvio automatico dei servizi esterni.
/// - Riavvia il timer per mantenere il monitoraggio continuo.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timerTask_Tick(object sender, EventArgs e)
{
timerTask.Stop();
ACService.DoAutoRestart(false);
timerTask.Start();
}
/// <summary>
/// Evento del timer periodico per lo scansionamento dell'interfaccia utente.
///
/// Azioni:
/// - Ferma il timer per evitare sovrapposizioni.
/// - Esegue ACService.DoScan() per verificare lo stato dell'applicazione.
/// - Riattiva il timer per mantenere il monitoraggio continuo.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void timerUI_Tick(object sender, EventArgs e)
{
timerUI.Stop();
await ACService.DoScan();
timerUI.Start();
}
#endregion Private Methods
}
}