- 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
519 lines
14 KiB
C#
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
|
|
}
|
|
}
|