Files
Mimante/Mimante/Services/ApplicationStateService.cs
Alberto Balbo 690f7e636a Ottimizzazione RAM, UI e sistema di timing aste
- Ridotto consumo RAM: limiti log, pulizia e compattazione dati aste, timer periodico di cleanup
- UI più fluida: cache locale aste, throttling aggiornamenti, refresh log solo se necessario
- Nuovo sistema Ticker Loop: timing configurabile, strategie solo vicino alla scadenza, feedback puntate tardive
- Migliorato layout e splitter, log visivo, gestione cache HTML
- Aggiornata UI impostazioni e fix vari per performance e thread-safety
2026-02-07 19:28:30 +01:00

519 lines
14 KiB
C#

using AutoBidder.Models;
using AutoBidder.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AutoBidder.Services
{
/// <summary>
/// Servizio Singleton per mantenere lo stato dell'applicazione tra le navigazioni
/// Questo evita che i dati vengano azzerati quando si cambia pagina
/// </summary>
public class ApplicationStateService
{
// Lista aste (condivisa tra tutte le pagine)
private List<AuctionInfo> _auctions = new();
// Asta selezionata
private AuctionInfo? _selectedAuction;
// Log globale
private List<LogEntry> _globalLog = new();
// Stato monitoraggio
private bool _isMonitoringActive = false;
// Puntate manuali in corso
private HashSet<string> _manualBiddingAuctions = new();
// Session start time
private DateTime _sessionStart = DateTime.Now;
// Lock per thread-safety
private readonly object _lock = new();
// Eventi per notificare i componenti dei cambiamenti
// IMPORTANTE: Questi eventi vengono chiamati da thread in background
// I componenti DEVONO usare InvokeAsync quando gestiscono questi eventi
public event Func<Task>? OnStateChangedAsync;
public event Func<string, Task>? OnLogAddedAsync;
// === PROPRIETÀ PUBBLICHE ===
public IReadOnlyList<AuctionInfo> Auctions
{
get
{
lock (_lock)
{
return _auctions.ToList().AsReadOnly();
}
}
}
/// <summary>
/// Ottiene riferimento diretto alla lista per lettura veloce (NO COPY).
/// ATTENZIONE: Non modificare la lista, usare solo per lettura!
/// </summary>
public List<AuctionInfo> GetAuctionsDirectRef()
{
return _auctions; // Accesso diretto senza lock per velocità
}
/// <summary>
/// Ottiene riferimento diretto al log per lettura veloce (NO COPY).
/// </summary>
public List<LogEntry> GetLogDirectRef()
{
return _globalLog;
}
/// <summary>
/// Imposta l'asta selezionata SENZA notificare eventi async.
/// Usare per risposta UI immediata.
/// </summary>
public void SetSelectedAuctionDirect(AuctionInfo? auction)
{
_selectedAuction = auction;
}
/// <summary>
/// Ottiene la lista originale delle aste per il salvataggio.
/// ATTENZIONE: Usare solo per persistenza, non per iterazione durante modifiche!
/// </summary>
public List<AuctionInfo> GetAuctionsForPersistence()
{
lock (_lock)
{
return _auctions;
}
}
/// <summary>
/// Forza il salvataggio delle aste correnti su disco.
/// </summary>
public void PersistAuctions()
{
lock (_lock)
{
AutoBidder.Utilities.PersistenceManager.SaveAuctions(_auctions);
}
}
/// <summary>
/// Ottiene l'asta modificabile per ID.
/// IMPORTANTE: Dopo modifiche, chiamare PersistAuctions() per salvare!
/// </summary>
public AuctionInfo? GetAuctionById(string auctionId)
{
lock (_lock)
{
return _auctions.FirstOrDefault(a => a.AuctionId == auctionId);
}
}
public AuctionInfo? SelectedAuction
{
get
{
lock (_lock)
{
return _selectedAuction;
}
}
set
{
lock (_lock)
{
_selectedAuction = value;
}
_ = NotifyStateChangedAsync();
}
}
public IReadOnlyList<LogEntry> GlobalLog
{
get
{
lock (_lock)
{
return _globalLog.ToList().AsReadOnly();
}
}
}
public bool IsMonitoringActive
{
get
{
lock (_lock)
{
return _isMonitoringActive;
}
}
set
{
lock (_lock)
{
_isMonitoringActive = value;
}
_ = NotifyStateChangedAsync();
}
}
public DateTime SessionStart
{
get
{
lock (_lock)
{
return _sessionStart;
}
}
}
// === STATO AUCTION BROWSER ===
private int _browserCategoryIndex = 0;
private string _browserSearchQuery = "";
public int BrowserCategoryIndex
{
get
{
lock (_lock)
{
return _browserCategoryIndex;
}
}
set
{
lock (_lock)
{
_browserCategoryIndex = value;
}
}
}
public string BrowserSearchQuery
{
get
{
lock (_lock)
{
return _browserSearchQuery;
}
}
set
{
lock (_lock)
{
_browserSearchQuery = value;
}
}
}
// === METODI GESTIONE ASTE ===
public void SetAuctions(List<AuctionInfo> auctions)
{
lock (_lock)
{
_auctions = auctions.ToList();
}
_ = NotifyStateChangedAsync();
}
public void AddAuction(AuctionInfo auction)
{
bool added = false;
lock (_lock)
{
if (!_auctions.Any(a => a.AuctionId == auction.AuctionId))
{
_auctions.Add(auction);
added = true;
}
}
if (added)
{
_ = NotifyStateChangedAsync();
}
}
public void RemoveAuction(AuctionInfo auction)
{
bool removed = false;
lock (_lock)
{
removed = _auctions.Remove(auction);
if (removed && _selectedAuction?.AuctionId == auction.AuctionId)
{
_selectedAuction = null;
}
}
if (removed)
{
_ = NotifyStateChangedAsync();
}
}
public void UpdateAuction(AuctionInfo auction)
{
bool updated = false;
lock (_lock)
{
var existing = _auctions.FirstOrDefault(a => a.AuctionId == auction.AuctionId);
if (existing != null)
{
var index = _auctions.IndexOf(existing);
_auctions[index] = auction;
if (_selectedAuction?.AuctionId == auction.AuctionId)
{
_selectedAuction = auction;
}
updated = true;
}
}
if (updated)
{
_ = NotifyStateChangedAsync();
}
}
// === METODI GESTIONE LOG ===
public void AddLog(string message, LogLevel level = LogLevel.Info)
{
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Message = message,
Level = level
};
lock (_lock)
{
_globalLog.Add(entry);
// Mantieni solo gli ultimi 500 log (ridotto da 1000 per RAM)
if (_globalLog.Count > 500)
{
_globalLog.RemoveRange(0, _globalLog.Count - 500);
_globalLog.TrimExcess();
}
}
// RIMOSSO: NotifyStateChangedAsync qui causava troppi re-render
// I log vengono visualizzati al prossimo refresh naturale
}
public void ClearLog()
{
lock (_lock)
{
_globalLog.Clear();
}
_ = NotifyStateChangedAsync();
}
// === METODI GESTIONE PUNTATE MANUALI ===
public bool IsManualBidding(string auctionId)
{
lock (_lock)
{
return _manualBiddingAuctions.Contains(auctionId);
}
}
public void StartManualBidding(string auctionId)
{
bool added = false;
lock (_lock)
{
added = _manualBiddingAuctions.Add(auctionId);
}
if (added)
{
_ = NotifyStateChangedAsync();
}
}
public void StopManualBidding(string auctionId)
{
bool removed = false;
lock (_lock)
{
removed = _manualBiddingAuctions.Remove(auctionId);
}
if (removed)
{
_ = NotifyStateChangedAsync();
}
}
// === NOTIFICHE (THREAD-SAFE) ===
private async Task NotifyStateChangedAsync()
{
var handler = OnStateChangedAsync;
if (handler != null)
{
// Invoca tutti i delegate in parallelo
var invocationList = handler.GetInvocationList();
var tasks = invocationList
.Cast<Func<Task>>()
.Select(d => Task.Run(async () =>
{
try
{
await d();
}
catch (Exception ex)
{
// Log dell'errore ma non bloccare altri handler
Console.WriteLine($"[AppState] Error in OnStateChangedAsync: {ex.Message}");
}
}));
await Task.WhenAll(tasks);
}
}
private async Task NotifyLogAddedAsync(string message)
{
var handler = OnLogAddedAsync;
if (handler != null)
{
var invocationList = handler.GetInvocationList();
var tasks = invocationList
.Cast<Func<string, Task>>()
.Select(d => Task.Run(async () =>
{
try
{
await d(message);
}
catch (Exception ex)
{
Console.WriteLine($"[AppState] Error in OnLogAddedAsync: {ex.Message}");
}
}));
await Task.WhenAll(tasks);
}
}
public void ForceUpdate()
{
_ = NotifyStateChangedAsync();
}
// ???????????????????????????????????????????????????????????????????
// GESTIONE MEMORIA
// ???????????????????????????????????????????????????????????????????
/// <summary>
/// Compatta i dati di tutte le aste per ridurre il consumo RAM
/// </summary>
public void CompactAllAuctions()
{
lock (_lock)
{
foreach (var auction in _auctions)
{
try
{
auction.CompactData();
}
catch { /* Ignora errori */ }
}
}
Console.WriteLine($"[AppState] Compattati dati di {_auctions.Count} aste");
}
/// <summary>
/// Pulisce i dati delle aste terminate dalla memoria
/// </summary>
public void CleanupCompletedAuctions()
{
lock (_lock)
{
foreach (var auction in _auctions.Where(a => !a.IsActive))
{
try
{
// Per le aste terminate, mantieni solo dati essenziali
auction.CompactData(maxBidHistory: 20, maxRecentBids: 10, maxLogLines: 50);
}
catch { }
}
}
}
/// <summary>
/// Ritorna statistiche sull'uso della memoria
/// </summary>
public MemoryStats GetMemoryStats()
{
lock (_lock)
{
return new MemoryStats
{
AuctionsCount = _auctions.Count,
ActiveAuctionsCount = _auctions.Count(a => a.IsActive),
TotalBidHistoryEntries = _auctions.Sum(a => a.BidHistory?.Count ?? 0),
TotalRecentBidsEntries = _auctions.Sum(a => a.RecentBids?.Count ?? 0),
TotalLogEntries = _auctions.Sum(a => a.AuctionLog?.Count ?? 0),
GlobalLogEntries = _globalLog.Count
};
}
}
}
/// <summary>
/// Statistiche memoria per debug
/// </summary>
public class MemoryStats
{
public int AuctionsCount { get; set; }
public int ActiveAuctionsCount { get; set; }
public int TotalBidHistoryEntries { get; set; }
public int TotalRecentBidsEntries { get; set; }
public int TotalLogEntries { get; set; }
public int GlobalLogEntries { get; set; }
}
/// <summary>
/// Entry del log globale
/// </summary>
public class LogEntry
{
public DateTime Timestamp { get; set; }
public string Message { get; set; } = "";
public LogLevel Level { get; set; } = LogLevel.Info;
}
/// <summary>
/// Livelli di log
/// </summary>
public enum LogLevel
{
Info,
Success,
Warning,
Error,
Debug
}
}