Rimuovi export settings, aggiungi filtro livello log

- Rimossa la sezione "Impostazioni Export" dalla UI e dal code-behind, inclusi controlli, eventi e file legacy di export.
- Aggiunta configurazione del livello minimo di log (ErrorOnly, Normal, Informational, Debug, Trace) con guida e legenda colori.
- La funzione di log ora filtra i messaggi in base al livello selezionato.
- Aggiornati modelli di impostazioni e enum LogLevel per supportare i nuovi livelli.
- Refactoring commenti e uniformità sezioni impostazioni.
- Migliorata la chiarezza del log di avvio applicazione.
This commit is contained in:
2025-12-11 14:20:05 +01:00
parent 79756d878d
commit 7b405ed78e
19 changed files with 497 additions and 894 deletions

View File

@@ -1,4 +1,4 @@
using System.Configuration; using System.Configuration;
using System.Data; using System.Data;
using System.Windows; using System.Windows;

View File

@@ -89,79 +89,7 @@
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="30,20"> <StackPanel Margin="30,20">
<!-- SEZIONE 1: Impostazioni Export --> <!-- SEZIONE 1: Impostazioni Predefinite Aste -->
<Border Background="#252526"
BorderBrush="#3E3E42"
BorderThickness="1"
CornerRadius="4"
Padding="20"
Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="Impostazioni Export"
Style="{StaticResource SectionHeader}"/>
<TextBlock Text="Percorso di Export"
Style="{StaticResource FieldLabel}"/>
<Grid Margin="0,0,0,20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
x:Name="ExportPathTextBox"
VerticalAlignment="Center"
Margin="0,0,10,0"/>
<Button Grid.Column="1"
x:Name="ExportBrowseButton"
Content="Sfoglia"
Background="#007ACC"
Style="{StaticResource ModernButton}"
Click="ExportBrowseButton_Click"/>
</Grid>
<TextBlock Text="Formato File"
Style="{StaticResource FieldLabel}"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,20">
<RadioButton x:Name="ExtCsv"
Content="CSV"
GroupName="ExportFormat"
IsChecked="True"/>
<RadioButton x:Name="ExtJson"
Content="JSON"
GroupName="ExportFormat"/>
<RadioButton x:Name="ExtXml"
Content="XML"
GroupName="ExportFormat"/>
</StackPanel>
<TextBlock Text="Opzioni di Export"
Style="{StaticResource FieldLabel}"/>
<StackPanel>
<CheckBox x:Name="IncludeUsedBids"
Content="Includi solo puntate utilizzate"
IsChecked="True"/>
<CheckBox x:Name="IncludeLogs"
Content="Includi log delle aste"/>
<CheckBox x:Name="IncludeUserBids"
Content="Includi storico puntate utenti"
IsChecked="True"/>
<CheckBox x:Name="IncludeMetadata"
Content="Includi metadata delle aste"
IsChecked="True"/>
<CheckBox x:Name="RemoveAfterExport"
Content="Rimuovi aste dopo l'export"/>
<CheckBox x:Name="OverwriteExisting"
Content="Sovrascrivi file esistenti"/>
</StackPanel>
</StackPanel>
</Border>
<!-- SEZIONE 2: Impostazioni Predefinite Aste -->
<Border Background="#252526" <Border Background="#252526"
BorderBrush="#3E3E42" BorderBrush="#3E3E42"
BorderThickness="1" BorderThickness="1"
@@ -194,7 +122,7 @@
<TextBlock Grid.Row="0" Grid.Column="0" Text="Anticipo Puntata (millisecondi)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Millisecondi prima della scadenza per puntare"/> <TextBlock Grid.Row="0" Grid.Column="0" Text="Anticipo Puntata (millisecondi)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Millisecondi prima della scadenza per puntare"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="DefaultBidBeforeDeadlineMsTextBox" Text="200" Margin="10,10"/> <TextBox Grid.Row="0" Grid.Column="1" x:Name="DefaultBidBeforeDeadlineMsTextBox" Text="200" Margin="10,10"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Verifica Stato Prima di Puntare" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Controlla che l'asta sia ancora aperta prima di puntare"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="Verifica Stato Prima Di Puntare" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Controlla che l'asta sia ancora aperta prima di puntare"/>
<CheckBox Grid.Row="1" Grid.Column="1" x:Name="DefaultCheckAuctionOpenCheckBox" Margin="10,10" VerticalAlignment="Center"/> <CheckBox Grid.Row="1" Grid.Column="1" x:Name="DefaultCheckAuctionOpenCheckBox" Margin="10,10" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Prezzo Minimo (€)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="Prezzo Minimo (€)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/>
@@ -209,7 +137,7 @@
</StackPanel> </StackPanel>
</Border> </Border>
<!-- SEZIONE 3: Stato Iniziale Aste --> <!-- SEZIONE 2: Stato Iniziale Aste -->
<Border Background="#252526" <Border Background="#252526"
BorderBrush="#3E3E42" BorderBrush="#3E3E42"
BorderThickness="1" BorderThickness="1"
@@ -294,7 +222,7 @@
</StackPanel> </StackPanel>
</Border> </Border>
<!-- SEZIONE 4: Protezione Account --> <!-- SEZIONE 3: Protezione Account -->
<Border Background="#252526" <Border Background="#252526"
BorderBrush="#3E3E42" BorderBrush="#3E3E42"
BorderThickness="1" BorderThickness="1"
@@ -357,7 +285,7 @@
</StackPanel> </StackPanel>
</Border> </Border>
<!-- SEZIONE 5: Limiti Log --> <!-- SEZIONE 4: Limiti Log -->
<Border Background="#252526" <Border Background="#252526"
BorderBrush="#3E3E42" BorderBrush="#3E3E42"
BorderThickness="1" BorderThickness="1"
@@ -439,6 +367,131 @@
</Border> </Border>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- SEZIONE 5: Livello di Dettaglio Log -->
<Border Background="#252526"
BorderBrush="#3E3E42"
BorderThickness="1"
CornerRadius="4"
Padding="20"
Margin="0,20,0,0">
<StackPanel>
<TextBlock Text="Livello di Dettaglio Log"
Style="{StaticResource SectionHeader}"/>
<TextBlock Text="Configura il livello minimo dei messaggi da visualizzare nel log. Livelli più bassi mostrano solo messaggi critici, livelli più alti mostrano tutti i dettagli (utile per debug)."
Foreground="#999999"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,20"/>
<!-- Radio Buttons per livello log -->
<StackPanel>
<RadioButton x:Name="LogLevelErrorOnly"
Content="Solo Errori"
GroupName="LogLevel"
Margin="0,5"
ToolTip="Mostra solo errori critici (uso minimo per produzione)"/>
<RadioButton x:Name="LogLevelNormal"
Content="Normale (Errori + Avvisi)"
GroupName="LogLevel"
IsChecked="True"
Margin="0,5"
ToolTip="Mostra errori e avvisi (uso giornaliero raccomandato)"/>
<RadioButton x:Name="LogLevelInformational"
Content="Informativo (Include operazioni completate)"
GroupName="LogLevel"
Margin="0,5"
ToolTip="Mostra anche messaggi informativi e conferme (uso dettagliato)"/>
<RadioButton x:Name="LogLevelDebug"
Content="Debug (Include dettagli tecnici)"
GroupName="LogLevel"
Margin="0,5"
ToolTip="Mostra anche messaggi di debug per sviluppo"/>
<RadioButton x:Name="LogLevelTrace"
Content="Trace (Tutto - molto verboso)"
GroupName="LogLevel"
Margin="0,5"
ToolTip="Mostra ogni singola operazione (debug avanzato)"/>
</StackPanel>
<!-- Info Box -->
<Border Style="{StaticResource InfoBox}" Margin="0,15,0,0">
<StackPanel>
<TextBlock Text="🔍 Guida alla Scelta"
FontWeight="Bold"
Foreground="#FFB700"
Margin="0,0,0,10"/>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,5">
<Run>Solo Errori: Usa in produzione per vedere solo problemi critici.</Run>
</TextBlock>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,5">
<Run>Normale: Raccomandato per uso giornaliero. Mostra errori e avvisi importanti.</Run>
</TextBlock>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,5">
<Run>Informativo: Utile per seguire le operazioni principali (aggiunte aste, puntate).</Run>
</TextBlock>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,5">
<Run>Debug: Per sviluppo. Mostra parametri chiamate API e valori interni.</Run>
</TextBlock>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
TextWrapping="Wrap"
Margin="0,0,0,10">
<Run>Trace: Debug avanzato. Mostra ogni singola chiamata (molto verboso).</Run>
</TextBlock>
<TextBlock Foreground="#CCCCCC"
FontSize="12"
FontWeight="Bold"
Margin="0,5,0,5">Legenda colori log:</TextBlock>
<StackPanel>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#E81123">■ ROSSO</Run>
<Run Foreground="#CCCCCC"> = Errori critici</Run>
</TextBlock>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#FFB700">■ ARANCIONE</Run>
<Run Foreground="#CCCCCC"> = Avvisi</Run>
</TextBlock>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#64B4FF">■ BLU</Run>
<Run Foreground="#CCCCCC"> = Informazioni</Run>
</TextBlock>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#00D800">■ VERDE</Run>
<Run Foreground="#CCCCCC"> = Operazioni riuscite</Run>
</TextBlock>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#FF8CFF">■ MAGENTA</Run>
<Run Foreground="#CCCCCC"> = Debug</Run>
</TextBlock>
<TextBlock FontSize="11" Margin="0,2">
<Run Foreground="#A0A0A0">■ GRIGIO</Run>
<Run Foreground="#CCCCCC"> = Trace</Run>
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -25,26 +25,6 @@ namespace AutoBidder.Controls
// ?? NUOVO: Proprietà per limite storia puntate // ?? NUOVO: Proprietà per limite storia puntate
public TextBox MaxBidHistoryEntries => MaxBidHistoryEntriesTextBox; public TextBox MaxBidHistoryEntries => MaxBidHistoryEntriesTextBox;
// ========================================
// NOTA: Eventi cookie RIMOSSI
// Gestione automatica tramite browser
// ========================================
private void ExportBrowseButton_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(ExportBrowseClickedEvent, this));
}
private void SaveSettingsButton_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(SaveSettingsClickedEvent, this));
}
private void CancelSettingsButton_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CancelSettingsClickedEvent, this));
}
private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e) private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e)
{ {
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this)); RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
@@ -60,10 +40,7 @@ namespace AutoBidder.Controls
{ {
try try
{ {
// 1. Salva impostazioni export // Salva impostazioni predefinite aste (export rimosso)
RaiseEvent(new RoutedEventArgs(SaveSettingsClickedEvent, this));
// 2. Salva impostazioni predefinite aste
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this)); RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
// UNICO MessageBox di conferma // UNICO MessageBox di conferma
@@ -88,44 +65,16 @@ namespace AutoBidder.Controls
private void CancelAllSettings_Click(object sender, RoutedEventArgs e) private void CancelAllSettings_Click(object sender, RoutedEventArgs e)
{ {
// Annulla tutte le modifiche // Annulla tutte le modifiche
RaiseEvent(new RoutedEventArgs(CancelSettingsClickedEvent, this));
RaiseEvent(new RoutedEventArgs(CancelDefaultsClickedEvent, this)); RaiseEvent(new RoutedEventArgs(CancelDefaultsClickedEvent, this));
} }
// Routed Events (cookie events RIMOSSI) // Routed Events
public static readonly RoutedEvent ExportBrowseClickedEvent = EventManager.RegisterRoutedEvent(
"ExportBrowseClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public static readonly RoutedEvent SaveSettingsClickedEvent = EventManager.RegisterRoutedEvent(
"SaveSettingsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public static readonly RoutedEvent CancelSettingsClickedEvent = EventManager.RegisterRoutedEvent(
"CancelSettingsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public static readonly RoutedEvent SaveDefaultsClickedEvent = EventManager.RegisterRoutedEvent( public static readonly RoutedEvent SaveDefaultsClickedEvent = EventManager.RegisterRoutedEvent(
"SaveDefaultsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl)); "SaveDefaultsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public static readonly RoutedEvent CancelDefaultsClickedEvent = EventManager.RegisterRoutedEvent( public static readonly RoutedEvent CancelDefaultsClickedEvent = EventManager.RegisterRoutedEvent(
"CancelDefaultsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl)); "CancelDefaultsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public event RoutedEventHandler ExportBrowseClicked
{
add { AddHandler(ExportBrowseClickedEvent, value); }
remove { RemoveHandler(ExportBrowseClickedEvent, value); }
}
public event RoutedEventHandler SaveSettingsClicked
{
add { AddHandler(SaveSettingsClickedEvent, value); }
remove { RemoveHandler(SaveSettingsClickedEvent, value); }
}
public event RoutedEventHandler CancelSettingsClicked
{
add { AddHandler(CancelSettingsClickedEvent, value); }
remove { RemoveHandler(CancelSettingsClickedEvent, value); }
}
public event RoutedEventHandler SaveDefaultsClicked public event RoutedEventHandler SaveDefaultsClicked
{ {
add { AddHandler(SaveDefaultsClickedEvent, value); } add { AddHandler(SaveDefaultsClickedEvent, value); }

View File

@@ -1,351 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Linq;
using AutoBidder.Utilities;
namespace AutoBidder
{
/// <summary>
/// Export functionality event handlers
/// </summary>
public partial class MainWindow
{
private CancellationTokenSource? _exportCts;
private void LoadExportSettings()
{
try
{
var s = SettingsManager.Load();
if (s != null)
{
ExportPathTextBox.Text = s.ExportPath ?? string.Empty;
if (!string.IsNullOrEmpty(s.LastExportExt))
{
var ext = s.LastExportExt.ToLowerInvariant();
if (ext == ".json") ExtJson.IsChecked = true;
else if (ext == ".xml") ExtXml.IsChecked = true;
else ExtCsv.IsChecked = true;
}
else
{
ExtCsv.IsChecked = true;
}
try { var cbOpen = this.FindName("ExportOpenToolbar") as System.Windows.Controls.CheckBox; if (cbOpen != null) cbOpen.IsChecked = s.ExportOpen; } catch { }
try { var cbClosed = this.FindName("ExportClosedToolbar") as System.Windows.Controls.CheckBox; if (cbClosed != null) cbClosed.IsChecked = s.ExportClosed; } catch { }
try { var cbUnknown = this.FindName("ExportUnknownToolbar") as System.Windows.Controls.CheckBox; if (cbUnknown != null) cbUnknown.IsChecked = s.ExportUnknown; } catch { }
try { IncludeUsedBids.IsChecked = s.IncludeOnlyUsedBids; } catch { }
try { IncludeLogs.IsChecked = s.IncludeLogs; } catch { }
try { IncludeUserBids.IsChecked = s.IncludeUserBids; } catch { }
try { IncludeMetadata.IsChecked = s.IncludeMetadata; } catch { }
try { RemoveAfterExport.IsChecked = s.RemoveAfterExport; } catch { }
try { OverwriteExisting.IsChecked = s.OverwriteExisting; } catch { }
}
}
catch { }
}
private async void ExportAllButton_Click(object sender, RoutedEventArgs e)
{
try
{
var settings = SettingsManager.Load();
string ext = ExtJson.IsChecked == true ? ".json" : ExtXml.IsChecked == true ? ".xml" : ".csv";
var dlg = new Microsoft.Win32.SaveFileDialog() { FileName = "auctions_export" + ext, Filter = "CSV files|*.csv|JSON files|*.json|XML files|*.xml|All files|*.*" };
if (dlg.ShowDialog(this) != true) return;
var path = dlg.FileName;
var all = _auctionMonitor.GetAuctions();
var includeOpen = (this.FindName("ExportOpenToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var includeClosed = (this.FindName("ExportClosedToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var includeUnknown = (this.FindName("ExportUnknownToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var selection = all.Where(a =>
(includeOpen && a.IsActive) ||
(includeClosed && !a.IsActive) ||
(includeUnknown && ((a.BidHistory == null || a.BidHistory.Count == 0) && (a.BidderStats == null || a.BidderStats.Count == 0)))
).ToList();
if (selection.Count == 0)
{
MessageBox.Show(this, "Nessuna asta da esportare.", "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
Log("[INFO] Esportazione in corso...", LogLevel.Info);
await Task.Run(() =>
{
if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
var json = System.Text.Json.JsonSerializer.Serialize(selection, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(path, json, Encoding.UTF8);
}
else if (path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
var doc = new XDocument(new XElement("Auctions",
from a in selection
select new XElement("Auction",
new XElement("AuctionId", a.AuctionId),
new XElement("Name", a.Name),
new XElement("OriginalUrl", a.OriginalUrl ?? string.Empty)
)
));
doc.Save(path);
}
else
{
CsvExporter.ExportAllAuctions(selection, path);
}
});
try { ExportPreferences.SaveLastExportExtension(Path.GetExtension(path)); } catch { }
MessageBox.Show(this, "Esportazione completata.", "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Information);
Log($"[EXPORT] Aste esportate -> {path}", LogLevel.Success);
}
catch (Exception ex)
{
Log($"[ERRORE] Esportazione massiva: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante esportazione: " + ex.Message, "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void ExportToolbarButton_Click(object sender, RoutedEventArgs e)
{
try
{
var settings = SettingsManager.Load();
var chosenExt = ExtJson.IsChecked == true ? ".json" : ExtXml.IsChecked == true ? ".xml" : ".csv";
var includeOpen = (this.FindName("ExportOpenToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var includeClosed = (this.FindName("ExportClosedToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var includeUnknown = (this.FindName("ExportUnknownToolbar") as System.Windows.Controls.CheckBox)?.IsChecked == true;
var all = _auctionMonitor.GetAuctions();
var selection = all.Where(a =>
(includeOpen && a.IsActive) ||
(includeClosed && !a.IsActive) ||
(includeUnknown && ((a.BidHistory == null || a.BidHistory.Count == 0) && (a.BidderStats == null || a.BidderStats.Count == 0)))
).ToList();
if (selection.Count == 0)
{
MessageBox.Show(this, "Nessuna asta da esportare.", "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
string folder;
if (!string.IsNullOrWhiteSpace(settings?.ExportPath) && Directory.Exists(settings.ExportPath))
{
folder = settings.ExportPath!;
}
else
{
MessageBox.Show(this, "Percorso export non configurato o non valido.\nConfigura il percorso nelle Impostazioni.", "Percorso Export", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var confirm = MessageBox.Show(this, $"Esportare {selection.Count} asta/e in:\n{folder}\n\nFormato: {chosenExt.ToUpperInvariant()}\n(Un file separato per ogni asta)", "Conferma Esportazione", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (confirm != MessageBoxResult.Yes) return;
Log("[INFO] Esportazione in corso...", LogLevel.Info);
int exported = 0;
int skipped = 0;
await Task.Run(() =>
{
foreach (var a in selection)
{
try
{
var filename = $"auction_{a.AuctionId}{chosenExt}";
var path = Path.Combine(folder, filename);
if (File.Exists(path) && settings != null && settings.OverwriteExisting != true)
{
skipped++;
Log($"[SKIP] File già esistente: {filename}", LogLevel.Warn);
continue;
}
if (chosenExt.Equals(".json", StringComparison.OrdinalIgnoreCase))
{
// JSON EXPORT - AGGIORNATO
var obj = new
{
AuctionId = a.AuctionId,
Name = a.Name,
OriginalUrl = a.OriginalUrl,
MinPrice = a.MinPrice,
MaxPrice = a.MaxPrice,
BidBeforeDeadlineMs = a.BidBeforeDeadlineMs,
CheckAuctionOpenBeforeBid = a.CheckAuctionOpenBeforeBid,
IsActive = a.IsActive,
IsPaused = a.IsPaused,
BidHistory = a.BidHistory,
Bidders = a.BidderStats.Values.ToList(),
AuctionLog = a.AuctionLog.ToList()
};
var json = System.Text.Json.JsonSerializer.Serialize(obj, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(path, json, Encoding.UTF8);
}
else if (chosenExt.Equals(".xml", StringComparison.OrdinalIgnoreCase))
{
// XML EXPORT - AGGIORNATO
var doc = new XDocument(
new XElement("AuctionExport",
new XElement("Metadata",
new XElement("AuctionId", a.AuctionId),
new XElement("Name", a.Name ?? string.Empty),
new XElement("OriginalUrl", a.OriginalUrl ?? string.Empty),
new XElement("MinPrice", a.MinPrice),
new XElement("MaxPrice", a.MaxPrice),
new XElement("BidBeforeDeadlineMs", a.BidBeforeDeadlineMs),
new XElement("CheckAuctionOpenBeforeBid", a.CheckAuctionOpenBeforeBid),
new XElement("IsActive", a.IsActive),
new XElement("IsPaused", a.IsPaused)
),
new XElement("FinalPrice", a.BidHistory?.LastOrDefault()?.Price.ToString("F2", CultureInfo.InvariantCulture) ?? string.Empty),
new XElement("TotalBids", a.BidHistory?.Count ?? 0),
new XElement("Bidders",
from b in a.BidderStats.Values.Where(x => x.BidCount > 0)
select new XElement("Bidder",
new XAttribute("Username", b.Username ?? string.Empty),
new XAttribute("BidCount", b.BidCount),
new XElement("LastBidTime", b.LastBidTimeDisplay ?? string.Empty)
)
),
new XElement("AuctionLog",
from l in a.AuctionLog
select new XElement("Entry", l)
),
new XElement("BidHistory",
from bh in a.BidHistory
select new XElement("Entry",
new XElement("Timestamp", bh.Timestamp.ToString("o")),
new XElement("EventType", bh.EventType),
new XElement("Bidder", bh.Bidder),
new XElement("Price", bh.Price.ToString("F2", CultureInfo.InvariantCulture)),
new XElement("Timer", bh.Timer.ToString("F2", CultureInfo.InvariantCulture)),
new XElement("LatencyMs", bh.LatencyMs),
new XElement("Success", bh.Success),
new XElement("Notes", bh.Notes)
)
)
)
);
doc.Save(path);
}
else
{
// CSV EXPORT - AGGIORNATO
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("Field,Value");
sw.WriteLine($"AuctionId,{a.AuctionId}");
sw.WriteLine($"Name,\"{EscapeCsv(a.Name)}\"");
sw.WriteLine($"OriginalUrl,\"{EscapeCsv(a.OriginalUrl)}\"");
sw.WriteLine($"MinPrice,{a.MinPrice}");
sw.WriteLine($"MaxPrice,{a.MaxPrice}");
sw.WriteLine($"BidBeforeDeadlineMs,{a.BidBeforeDeadlineMs}");
sw.WriteLine($"CheckAuctionOpenBeforeBid,{a.CheckAuctionOpenBeforeBid}");
sw.WriteLine($"IsActive,{a.IsActive}");
sw.WriteLine($"IsPaused,{a.IsPaused}");
sw.WriteLine();
sw.WriteLine("--Auction Log--");
sw.WriteLine("Message");
foreach (var l in a.AuctionLog)
{
sw.WriteLine($"\"{EscapeCsv(l)}\"");
}
sw.WriteLine();
sw.WriteLine("--Bidders--");
sw.WriteLine("Username,BidCount,LastBidTime");
foreach (var b in a.BidderStats.Values)
{
sw.WriteLine($"\"{EscapeCsv(b.Username)}\",{b.BidCount},\"{EscapeCsv(b.LastBidTimeDisplay)}\"");
}
sw.WriteLine();
sw.WriteLine("--BidHistory--");
sw.WriteLine("Timestamp,EventType,Bidder,Price,Timer,LatencyMs,Success,Notes");
foreach (var bh in a.BidHistory)
{
sw.WriteLine($"\"{EscapeCsv(bh.Timestamp.ToString("o"))}\",{bh.EventType},\"{EscapeCsv(bh.Bidder)}\",{bh.Price:F2},{bh.Timer:F2},{bh.LatencyMs},{bh.Success},\"{EscapeCsv(bh.Notes)}\"");
}
}
exported++;
Log($"[EXPORT] Asta esportata -> {path}", LogLevel.Success);
}
catch (Exception ex)
{
Log($"[ERRORE] Export asta {a.AuctionId}: {ex.Message}", LogLevel.Error);
skipped++;
}
}
});
try { ExportPreferences.SaveLastExportExtension(chosenExt); } catch { }
MessageBox.Show(this, $"Esportazione completata.\n\nEsportate: {exported}\nIgnorate: {skipped}\nPercorso: {folder}", "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Information);
Log($"[EXPORT] Completato: {exported} esportate, {skipped} ignorate -> {folder}", LogLevel.Success);
if ((this.FindName("RemoveAfterExport") as System.Windows.Controls.CheckBox)?.IsChecked == true && selection.Count > 0)
{
Dispatcher.Invoke(() =>
{
foreach (var a in selection)
{
try
{
_auctionMonitor.RemoveAuction(a.AuctionId);
var vm = _auctionViewModels.FirstOrDefault(x => x.AuctionId == a.AuctionId);
if (vm != null)
{
_auctionViewModels.Remove(vm);
}
}
catch (Exception ex)
{
Log($"[WARN] Errore rimozione asta {a.AuctionId}: {ex.Message}", LogLevel.Warn);
}
}
SaveAuctions();
UpdateTotalCount();
});
}
}
catch (Exception ex)
{
Log($"[ERRORE] Esportazione toolbar: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante esportazione: " + ex.Message, "Esporta Aste", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ExportBrowseButton_Click(object sender, RoutedEventArgs e)
{
var dlg = new Microsoft.Win32.SaveFileDialog() { FileName = "export.csv", Filter = "CSV files|*.csv|All files|*.*" };
if (dlg.ShowDialog(this) == true)
{
ExportPathTextBox.Text = Path.GetDirectoryName(dlg.FileName) ?? string.Empty;
}
}
private string EscapeCsv(string? value)
{
if (string.IsNullOrEmpty(value)) return string.Empty;
return value.Replace("\"", "\"\"");
}
}
}

View File

@@ -36,6 +36,33 @@ namespace AutoBidder
// ?? NUOVO: Carica limite minimo puntate // ?? NUOVO: Carica limite minimo puntate
MinimumRemainingBidsTextBox.Text = settings.MinimumRemainingBids.ToString(); MinimumRemainingBidsTextBox.Text = settings.MinimumRemainingBids.ToString();
// ?? NUOVO: Carica livello log
var logLevelErrorOnly = Settings.FindName("LogLevelErrorOnly") as System.Windows.Controls.RadioButton;
var logLevelNormal = Settings.FindName("LogLevelNormal") as System.Windows.Controls.RadioButton;
var logLevelInformational = Settings.FindName("LogLevelInformational") as System.Windows.Controls.RadioButton;
var logLevelDebug = Settings.FindName("LogLevelDebug") as System.Windows.Controls.RadioButton;
var logLevelTrace = Settings.FindName("LogLevelTrace") as System.Windows.Controls.RadioButton;
switch (settings.MinLogLevel)
{
case "ErrorOnly":
if (logLevelErrorOnly != null) logLevelErrorOnly.IsChecked = true;
break;
case "Informational":
if (logLevelInformational != null) logLevelInformational.IsChecked = true;
break;
case "Debug":
if (logLevelDebug != null) logLevelDebug.IsChecked = true;
break;
case "Trace":
if (logLevelTrace != null) logLevelTrace.IsChecked = true;
break;
case "Normal":
default:
if (logLevelNormal != null) logLevelNormal.IsChecked = true;
break;
}
// Aggiorna indicatore visivo // Aggiorna indicatore visivo
UpdateMinBidsIndicator(settings.MinimumRemainingBids); UpdateMinBidsIndicator(settings.MinimumRemainingBids);
@@ -85,67 +112,6 @@ namespace AutoBidder
} }
} }
private void SaveSettingsButton_Click(object sender, RoutedEventArgs e)
{
try
{
// ? Carica le impostazioni esistenti per non perdere gli altri valori
var settings = Utilities.SettingsManager.Load() ?? new Utilities.AppSettings();
// === SEZIONE EXPORT: Percorso e Formato ===
settings.ExportPath = ExportPathTextBox.Text;
settings.LastExportExt = ExtJson.IsChecked == true ? ".json" : ExtXml.IsChecked == true ? ".xml" : ".csv";
// === SEZIONE EXPORT: Scope (Aste da esportare) ===
var cbClosed = this.FindName("ExportClosedToolbar") as System.Windows.Controls.CheckBox;
var cbUnknown = this.FindName("ExportUnknownToolbar") as System.Windows.Controls.CheckBox;
var cbOpen = this.FindName("ExportOpenToolbar") as System.Windows.Controls.CheckBox;
var scope = "All";
if (cbClosed != null && cbClosed.IsChecked == true) scope = "Closed";
else if (cbUnknown != null && cbUnknown.IsChecked == true) scope = "Unknown";
else if (cbOpen != null && cbOpen.IsChecked == true) scope = "Open";
settings.ExportScope = scope;
settings.ExportOpen = cbOpen?.IsChecked ?? true;
settings.ExportClosed = cbClosed?.IsChecked ?? true;
settings.ExportUnknown = cbUnknown?.IsChecked ?? true;
// === SEZIONE EXPORT: Opzioni ? FIX: Aggiunte le 3 checkbox mancanti ===
settings.IncludeOnlyUsedBids = IncludeUsedBids.IsChecked == true;
settings.IncludeLogs = IncludeLogs.IsChecked == true;
settings.IncludeUserBids = IncludeUserBids.IsChecked == true;
settings.IncludeMetadata = IncludeMetadata.IsChecked == true; // ? AGGIUNTO
settings.RemoveAfterExport = RemoveAfterExport.IsChecked == true; // ? AGGIUNTO
settings.OverwriteExisting = OverwriteExisting.IsChecked == true; // ? AGGIUNTO
SettingsManager.Save(settings);
ExportPreferences.SaveLastExportExtension(settings.LastExportExt);
}
catch (Exception ex)
{
Log($"[ERRORE] Salvataggio impostazioni export: {ex.Message}", LogLevel.Error);
}
}
private void CancelSettingsButton_Click(object sender, RoutedEventArgs e)
{
try
{
// Ricarica impostazioni export
LoadExportSettings();
// NOTA: Reload cookie RIMOSSO - ora automatico tramite browser
MessageBox.Show(this, "Impostazioni ripristinate alle ultime salvate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log($"[ERRORE] Ripristino impostazioni: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e) private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e)
{ {
try try
@@ -239,6 +205,29 @@ namespace AutoBidder
Log("[ERRORE] Valore limite minimo puntate non valido (deve essere >= 0)", LogLevel.Error); Log("[ERRORE] Valore limite minimo puntate non valido (deve essere >= 0)", LogLevel.Error);
} }
// ?? NUOVO: Salva livello log
var logLevelErrorOnly = Settings.FindName("LogLevelErrorOnly") as System.Windows.Controls.RadioButton;
var logLevelNormal = Settings.FindName("LogLevelNormal") as System.Windows.Controls.RadioButton;
var logLevelInformational = Settings.FindName("LogLevelInformational") as System.Windows.Controls.RadioButton;
var logLevelDebug = Settings.FindName("LogLevelDebug") as System.Windows.Controls.RadioButton;
var logLevelTrace = Settings.FindName("LogLevelTrace") as System.Windows.Controls.RadioButton;
string selectedLogLevel = "Normal"; // Default
if (logLevelErrorOnly?.IsChecked == true)
selectedLogLevel = "ErrorOnly";
else if (logLevelInformational?.IsChecked == true)
selectedLogLevel = "Informational";
else if (logLevelDebug?.IsChecked == true)
selectedLogLevel = "Debug";
else if (logLevelTrace?.IsChecked == true)
selectedLogLevel = "Trace";
else if (logLevelNormal?.IsChecked == true)
selectedLogLevel = "Normal";
settings.MinLogLevel = selectedLogLevel;
Log($"[LOG] Livello log impostato: {selectedLogLevel}", LogLevel.Info);
// === SEZIONE DEFAULTS: Stati Iniziali Aste === // === SEZIONE DEFAULTS: Stati Iniziali Aste ===
var loadAuctionsRemember = Settings.FindName("LoadAuctionsRemember") as System.Windows.Controls.RadioButton; var loadAuctionsRemember = Settings.FindName("LoadAuctionsRemember") as System.Windows.Controls.RadioButton;
var loadAuctionsActive = Settings.FindName("LoadAuctionsActive") as System.Windows.Controls.RadioButton; var loadAuctionsActive = Settings.FindName("LoadAuctionsActive") as System.Windows.Controls.RadioButton;
@@ -290,5 +279,43 @@ namespace AutoBidder
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
// === HANDLER PER PULSANTI UNIFICATI ===
private void SaveAllSettings_Click(object sender, RoutedEventArgs e)
{
try
{
// Salva tutte le impostazioni (ora solo defaults, export rimosso)
SaveDefaultsButton_Click(sender, e);
MessageBox.Show(
"Tutte le impostazioni sono state salvate con successo.\n\nLe nuove impostazioni verranno applicate alle aste future.",
"Impostazioni Salvate",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
catch (Exception ex)
{
Log($"[ERRORE] Salvataggio impostazioni: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante salvataggio: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void CancelAllSettings_Click(object sender, RoutedEventArgs e)
{
try
{
// Annulla tutte le modifiche
LoadDefaultSettings();
MessageBox.Show(this, "Impostazioni ripristinate alle ultime salvate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log($"[ERRORE] Ripristino impostazioni: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
} }
} }

View File

@@ -1,11 +1,11 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using AutoBidder.Models; using AutoBidder.Models;
using AutoBidder.ViewModels; using AutoBidder.ViewModels;
using AutoBidder.Utilities; using AutoBidder.Utilities;
using AutoBidder.Services; // AGGIUNTO per RequestPriority e HtmlResponse using AutoBidder.Services; // ? AGGIUNTO per RequestPriority e HtmlResponse
namespace AutoBidder namespace AutoBidder
{ {
@@ -28,10 +28,10 @@ namespace AutoBidder
string? productName = null; string? productName = null;
string originalUrl; string originalUrl;
// Verifica se è un URL o solo un ID // Verifica se è un URL o solo un ID
if (input.Contains("bidoo.com") || input.Contains("http")) if (input.Contains("bidoo.com") || input.Contains("http"))
{ {
// È un URL - estrai ID e nome prodotto dall'URL stesso // È un URL - estrai ID e nome prodotto dall'URL stesso
originalUrl = input.Trim(); originalUrl = input.Trim();
auctionId = ExtractAuctionId(originalUrl); auctionId = ExtractAuctionId(originalUrl);
if (string.IsNullOrEmpty(auctionId)) if (string.IsNullOrEmpty(auctionId))
@@ -44,7 +44,7 @@ namespace AutoBidder
} }
else else
{ {
// È solo un ID numerico - costruisci URL generico // È solo un ID numerico - costruisci URL generico
auctionId = input.Trim(); auctionId = input.Trim();
originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}"; originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
} }
@@ -52,11 +52,11 @@ namespace AutoBidder
// Verifica duplicati // Verifica duplicati
if (_auctionViewModels.Any(a => a.AuctionId == auctionId)) if (_auctionViewModels.Any(a => a.AuctionId == auctionId))
{ {
MessageBox.Show("Asta già monitorata!", "Duplicato", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Asta già monitorata!", "Duplicato", MessageBoxButton.OK, MessageBoxImage.Information);
return; return;
} }
// MODIFICATO: Nome senza ID (già nella colonna separata) // ? MODIFICATO: Nome senza ID (già nella colonna separata)
var displayName = string.IsNullOrEmpty(productName) var displayName = string.IsNullOrEmpty(productName)
? $"Asta {auctionId}" ? $"Asta {auctionId}"
: DecodeAllHtmlEntities(productName); : DecodeAllHtmlEntities(productName);
@@ -64,7 +64,7 @@ namespace AutoBidder
// CARICA IMPOSTAZIONI PREDEFINITE SALVATE // CARICA IMPOSTAZIONI PREDEFINITE SALVATE
var settings = Utilities.SettingsManager.Load(); var settings = Utilities.SettingsManager.Load();
// Determina stato iniziale dalla configurazione // ? Determina stato iniziale dalla configurazione
bool isActive = false; bool isActive = false;
bool isPaused = false; bool isPaused = false;
@@ -109,7 +109,7 @@ namespace AutoBidder
}; };
_auctionViewModels.Add(vm); _auctionViewModels.Add(vm);
// Auto-start del monitoraggio se l'asta è attiva e il monitoraggio è fermo // ? Auto-start del monitoraggio se l'asta è attiva e il monitoraggio è fermo
if (isActive && !_isAutomationActive) if (isActive && !_isAutomationActive)
{ {
_auctionMonitor.Start(); _auctionMonitor.Start();
@@ -124,7 +124,7 @@ namespace AutoBidder
var stateText = isActive ? (isPaused ? "Paused" : "Active") : "Stopped"; var stateText = isActive ? (isPaused ? "Paused" : "Active") : "Stopped";
Log($"[ADD] Asta aggiunta con stato={stateText}, Anticipo={settings.DefaultBidBeforeDeadlineMs}ms", Utilities.LogLevel.Info); Log($"[ADD] Asta aggiunta con stato={stateText}, Anticipo={settings.DefaultBidBeforeDeadlineMs}ms", Utilities.LogLevel.Info);
// NUOVO: Se il nome non è stato estratto, recuperalo in background DOPO l'aggiunta // ? NUOVO: Se il nome non è stato estratto, recuperalo in background DOPO l'aggiunta
if (string.IsNullOrEmpty(productName)) if (string.IsNullOrEmpty(productName))
{ {
_ = FetchAuctionNameInBackgroundAsync(auction, vm); _ = FetchAuctionNameInBackgroundAsync(auction, vm);
@@ -144,7 +144,7 @@ namespace AutoBidder
{ {
try try
{ {
// USA IL SERVIZIO CENTRALIZZATO invece di HttpClient diretto // ? USA IL SERVIZIO CENTRALIZZATO invece di HttpClient diretto
var response = await _htmlCacheService.GetHtmlAsync( var response = await _htmlCacheService.GetHtmlAsync(
auction.OriginalUrl, auction.OriginalUrl,
RequestPriority.Normal, RequestPriority.Normal,
@@ -153,7 +153,7 @@ namespace AutoBidder
if (!response.Success) if (!response.Success)
{ {
Log($"[WARN] Impossibile recuperare nome per asta {auction.AuctionId}: {response.Error}", LogLevel.Warn); Log($"[WARN] Impossibile recuperare nome per asta {auction.AuctionId}: {response.Error}", LogLevel.Warning);
return; return;
} }
@@ -163,9 +163,9 @@ namespace AutoBidder
if (match.Success) if (match.Success)
{ {
var productName = match.Groups[1].Value.Trim().Replace(" - Bidoo", ""); var productName = match.Groups[1].Value.Trim().Replace(" - Bidoo", "");
// Decodifica entity HTML (incluse quelle non standard) // ? Decodifica entity HTML (incluse quelle non standard)
productName = DecodeAllHtmlEntities(productName); productName = DecodeAllHtmlEntities(productName);
// MODIFICATO: Nome senza ID // ? MODIFICATO: Nome senza ID
var newName = productName; var newName = productName;
// Aggiorna il nome su thread UI // Aggiorna il nome su thread UI
@@ -182,12 +182,12 @@ namespace AutoBidder
} }
else else
{ {
Log($"[WARN] Nome non trovato nell'HTML per asta {auction.AuctionId}", LogLevel.Warn); Log($"[WARN] Nome non trovato nell'HTML per asta {auction.AuctionId}", LogLevel.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[WARN] Errore recupero nome per asta {auction.AuctionId}: {ex.Message}", LogLevel.Warn); Log($"[WARN] Errore recupero nome per asta {auction.AuctionId}: {ex.Message}", LogLevel.Warning);
} }
} }
@@ -202,16 +202,16 @@ namespace AutoBidder
// Prima decodifica entity standard // Prima decodifica entity standard
var decoded = System.Net.WebUtility.HtmlDecode(text); var decoded = System.Net.WebUtility.HtmlDecode(text);
// Poi sostituisci entity non standard che WebUtility.HtmlDecode non gestisce // ? Poi sostituisci entity non standard che WebUtility.HtmlDecode non gestisce
decoded = decoded.Replace("&plus;", "+"); decoded = decoded.Replace("&plus;", "+");
decoded = decoded.Replace("&equals;", "="); decoded = decoded.Replace("&equals;", "=");
decoded = decoded.Replace("&minus;", "-"); decoded = decoded.Replace("&minus;", "-");
decoded = decoded.Replace("&times;", "×"); decoded = decoded.Replace("&times;", "×");
decoded = decoded.Replace("&divide;", "÷"); decoded = decoded.Replace("&divide;", "÷");
decoded = decoded.Replace("&percnt;", "%"); decoded = decoded.Replace("&percnt;", "%");
decoded = decoded.Replace("&dollar;", "$"); decoded = decoded.Replace("&dollar;", "$");
decoded = decoded.Replace("&euro;", "€"); decoded = decoded.Replace("&euro;", "€");
decoded = decoded.Replace("&pound;", "£"); decoded = decoded.Replace("&pound;", "£");
return decoded; return decoded;
} }
@@ -236,7 +236,7 @@ namespace AutoBidder
// Verifica duplicati // Verifica duplicati
if (_auctionViewModels.Any(a => a.AuctionId == auctionId)) if (_auctionViewModels.Any(a => a.AuctionId == auctionId))
{ {
MessageBox.Show("Asta già monitorata!", "Duplicato", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Asta già monitorata!", "Duplicato", MessageBoxButton.OK, MessageBoxImage.Information);
return; return;
} }
@@ -244,7 +244,7 @@ namespace AutoBidder
var name = $"Asta {auctionId}"; var name = $"Asta {auctionId}";
try try
{ {
// USA IL SERVIZIO CENTRALIZZATO // ? USA IL SERVIZIO CENTRALIZZATO
var response = await _htmlCacheService.GetHtmlAsync(url, RequestPriority.Normal); var response = await _htmlCacheService.GetHtmlAsync(url, RequestPriority.Normal);
if (response.Success) if (response.Success)
@@ -261,7 +261,7 @@ namespace AutoBidder
// CARICA IMPOSTAZIONI PREDEFINITE SALVATE // CARICA IMPOSTAZIONI PREDEFINITE SALVATE
var settings = Utilities.SettingsManager.Load(); var settings = Utilities.SettingsManager.Load();
// Determina stato iniziale dalla configurazione // ? Determina stato iniziale dalla configurazione
bool isActive = false; bool isActive = false;
bool isPaused = false; bool isPaused = false;
@@ -306,7 +306,7 @@ namespace AutoBidder
}; };
_auctionViewModels.Add(vm); _auctionViewModels.Add(vm);
// Auto-start del monitoraggio se l'asta è attiva e il monitoraggio è fermo // ? Auto-start del monitoraggio se l'asta è attiva e il monitoraggio è fermo
if (isActive && !_isAutomationActive) if (isActive && !_isAutomationActive)
{ {
_auctionMonitor.Start(); _auctionMonitor.Start();
@@ -353,12 +353,12 @@ namespace AutoBidder
{ {
try try
{ {
// Aspetta 30 secondi prima di ritentare (dà tempo alle altre richieste di completare) // Aspetta 30 secondi prima di ritentare (dà tempo alle altre richieste di completare)
await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(30)); await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(30));
// Trova aste con nomi generici "Asta XXXX" // Trova aste con nomi generici "Asta XXXX"
var auctionsWithGenericNames = _auctionViewModels var auctionsWithGenericNames = _auctionViewModels
.Where(vm => vm.Name.StartsWith("Asta ") && !vm.Name.Contains("Shop") && !vm.Name.Contains("€")) .Where(vm => vm.Name.StartsWith("Asta ") && !vm.Name.Contains("Shop") && !vm.Name.Contains("€"))
.ToList(); .ToList();
if (auctionsWithGenericNames.Count > 0) if (auctionsWithGenericNames.Count > 0)
@@ -375,7 +375,7 @@ namespace AutoBidder
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[WARN] Errore retry nomi aste: {ex.Message}", LogLevel.Warn); Log($"[WARN] Errore retry nomi aste: {ex.Message}", LogLevel.Warning);
} }
} }
@@ -396,7 +396,7 @@ namespace AutoBidder
{ {
try try
{ {
// Carica impostazioni // ? Carica impostazioni
var settings = Utilities.SettingsManager.Load(); var settings = Utilities.SettingsManager.Load();
// Ottieni username corrente dalla sessione per ripristinare IsMyBid // Ottieni username corrente dalla sessione per ripristinare IsMyBid
@@ -409,10 +409,10 @@ namespace AutoBidder
// Protezione: rimuovi eventuali BidHistory null // Protezione: rimuovi eventuali BidHistory null
auction.BidHistory = auction.BidHistory?.Where(b => b != null).ToList() ?? new System.Collections.Generic.List<BidHistory>(); auction.BidHistory = auction.BidHistory?.Where(b => b != null).ToList() ?? new System.Collections.Generic.List<BidHistory>();
// Decode HTML entities (incluse quelle non standard) // ? Decode HTML entities (incluse quelle non standard)
try { auction.Name = DecodeAllHtmlEntities(auction.Name ?? string.Empty); } catch { } try { auction.Name = DecodeAllHtmlEntities(auction.Name ?? string.Empty); } catch { }
// Ripristina IsMyBid per tutte le puntate in RecentBids // ? Ripristina IsMyBid per tutte le puntate in RecentBids
if (auction.RecentBids != null && auction.RecentBids.Count > 0 && !string.IsNullOrEmpty(currentUsername)) if (auction.RecentBids != null && auction.RecentBids.Count > 0 && !string.IsNullOrEmpty(currentUsername))
{ {
foreach (var bid in auction.RecentBids) foreach (var bid in auction.RecentBids)
@@ -422,11 +422,11 @@ namespace AutoBidder
} }
// NUOVO: Gestione stato in base a RememberAuctionStates // ? NUOVO: Gestione stato in base a RememberAuctionStates
if (settings.RememberAuctionStates) if (settings.RememberAuctionStates)
{ {
// MODO 1: Ripristina lo stato salvato di ogni asta (IsActive e IsPaused vengono dal file salvato) // MODO 1: Ripristina lo stato salvato di ogni asta (IsActive e IsPaused vengono dal file salvato)
// Non serve fare nulla, lo stato è già quello salvato nel file // Non serve fare nulla, lo stato è già quello salvato nel file
} }
else else
{ {
@@ -455,7 +455,7 @@ namespace AutoBidder
_auctionViewModels.Add(vm); _auctionViewModels.Add(vm);
} }
// Avvia monitoraggio se ci sono aste in stato Active O Paused // ? Avvia monitoraggio se ci sono aste in stato Active O Paused
bool hasActiveOrPausedAuctions = auctions.Any(a => a.IsActive); bool hasActiveOrPausedAuctions = auctions.Any(a => a.IsActive);
if (hasActiveOrPausedAuctions && auctions.Count > 0) if (hasActiveOrPausedAuctions && auctions.Count > 0)
@@ -538,7 +538,7 @@ namespace AutoBidder
// Aggiorna Valore (Compra Subito) // Aggiorna Valore (Compra Subito)
if (auction.BuyNowPrice.HasValue) if (auction.BuyNowPrice.HasValue)
{ {
AuctionMonitor.ProductBuyNowPriceText.Text = $"{auction.BuyNowPrice.Value:F2}€"; AuctionMonitor.ProductBuyNowPriceText.Text = $"{auction.BuyNowPrice.Value:F2}€";
} }
else else
{ {
@@ -548,7 +548,7 @@ namespace AutoBidder
// Aggiorna Spese di Spedizione // Aggiorna Spese di Spedizione
if (auction.ShippingCost.HasValue) if (auction.ShippingCost.HasValue)
{ {
AuctionMonitor.ProductShippingCostText.Text = $"{auction.ShippingCost.Value:F2}€"; AuctionMonitor.ProductShippingCostText.Text = $"{auction.ShippingCost.Value:F2}€";
} }
else else
{ {
@@ -579,28 +579,28 @@ namespace AutoBidder
{ {
bool hasGenericName = auction.Name.StartsWith("Asta ") && bool hasGenericName = auction.Name.StartsWith("Asta ") &&
!auction.Name.Contains("Shop") && !auction.Name.Contains("Shop") &&
!auction.Name.Contains("€") && !auction.Name.Contains("€") &&
!auction.Name.Contains("Buono") && !auction.Name.Contains("Buono") &&
!auction.Name.Contains("Carburante"); !auction.Name.Contains("Carburante");
Log($"[PRODUCT INFO] Caricamento automatico per: {auction.Name}{(hasGenericName ? " (+ nome generico)" : "")}", Utilities.LogLevel.Info); Log($"[PRODUCT INFO] Caricamento automatico per: {auction.Name}{(hasGenericName ? " (+ nome generico)" : "")}", Utilities.LogLevel.Info);
// USA IL SERVIZIO CENTRALIZZATO // ? USA IL SERVIZIO CENTRALIZZATO
var response = await _htmlCacheService.GetHtmlAsync( var response = await _htmlCacheService.GetHtmlAsync(
auction.OriginalUrl, auction.OriginalUrl,
RequestPriority.High, // Priorità alta per info prodotto RequestPriority.High, // Priorità alta per info prodotto
bypassCache: false bypassCache: false
); );
if (!response.Success) if (!response.Success)
{ {
Log($"[PRODUCT INFO] Errore caricamento: {response.Error}", Utilities.LogLevel.Warn); Log($"[PRODUCT INFO] Errore caricamento: {response.Error}", Utilities.LogLevel.Warning);
return; return;
} }
bool updated = false; bool updated = false;
// 1. Se nome generico, estrai nome reale dal <title> // 1. ? Se nome generico, estrai nome reale dal <title>
if (hasGenericName) if (hasGenericName)
{ {
var matchTitle = System.Text.RegularExpressions.Regex.Match(response.Html, @"<title>([^<]+)</title>"); var matchTitle = System.Text.RegularExpressions.Regex.Match(response.Html, @"<title>([^<]+)</title>");
@@ -608,7 +608,7 @@ namespace AutoBidder
{ {
var productName = matchTitle.Groups[1].Value.Trim().Replace(" - Bidoo", ""); var productName = matchTitle.Groups[1].Value.Trim().Replace(" - Bidoo", "");
productName = DecodeAllHtmlEntities(productName); productName = DecodeAllHtmlEntities(productName);
// MODIFICATO: Nome senza ID // ? MODIFICATO: Nome senza ID
var newName = productName; var newName = productName;
auction.Name = newName; auction.Name = newName;
@@ -617,15 +617,15 @@ namespace AutoBidder
} }
} }
// 2. Estrai informazioni prodotto (prezzo, spedizione, limiti) // 2. ? Estrai informazioni prodotto (prezzo, spedizione, limiti)
var extracted = Utilities.ProductValueCalculator.ExtractProductInfo(response.Html, auction); var extracted = Utilities.ProductValueCalculator.ExtractProductInfo(response.Html, auction);
if (extracted) if (extracted)
{ {
updated = true; updated = true;
Log($"[PRODUCT INFO] Valore={auction.BuyNowPrice:F2}€, Spedizione={auction.ShippingCost:F2}€{(response.FromCache ? " (cached)" : "")}", Utilities.LogLevel.Success); Log($"[PRODUCT INFO] Valore={auction.BuyNowPrice:F2}€, Spedizione={auction.ShippingCost:F2}€{(response.FromCache ? " (cached)" : "")}", Utilities.LogLevel.Success);
} }
// 3. Salva e aggiorna UI solo se qualcosa è cambiato // 3. ? Salva e aggiorna UI solo se qualcosa è cambiato
if (updated) if (updated)
{ {
SaveAuctions(); SaveAuctions();
@@ -650,7 +650,7 @@ namespace AutoBidder
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[PRODUCT INFO] Errore caricamento: {ex.Message}", Utilities.LogLevel.Warn); Log($"[PRODUCT INFO] Errore caricamento: {ex.Message}", Utilities.LogLevel.Warning);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows; using System.Windows;
@@ -41,7 +41,7 @@ namespace AutoBidder
Log("[START ALL] Tutte le aste avviate/riprese", LogLevel.Info); Log("[START ALL] Tutte le aste avviate/riprese", LogLevel.Info);
} }
// Salva gli stati aggiornati su disco // ? Salva gli stati aggiornati su disco
SaveAuctions(); SaveAuctions();
UpdateGlobalControlButtons(); UpdateGlobalControlButtons();
} }
@@ -69,13 +69,13 @@ namespace AutoBidder
_isAutomationActive = false; _isAutomationActive = false;
} }
// Salva gli stati aggiornati su disco // ? Salva gli stati aggiornati su disco
SaveAuctions(); SaveAuctions();
UpdateGlobalControlButtons(); UpdateGlobalControlButtons();
if (sender != null) // Solo se chiamato dall'utente if (sender != null) // Solo se chiamato dall'utente
{ {
Log("[STOP ALL] Monitoraggio fermato e tutte le aste arrestate", LogLevel.Warn); Log("[STOP ALL] Monitoraggio fermato e tutte le aste arrestate", LogLevel.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -93,13 +93,13 @@ namespace AutoBidder
vm.IsPaused = true; vm.IsPaused = true;
} }
// Salva gli stati aggiornati su disco // ? Salva gli stati aggiornati su disco
SaveAuctions(); SaveAuctions();
UpdateGlobalControlButtons(); UpdateGlobalControlButtons();
if (sender != null) // Solo se chiamato dall'utente if (sender != null) // Solo se chiamato dall'utente
{ {
Log("[PAUSE ALL] Tutte le aste in pausa", LogLevel.Warn); Log("[PAUSE ALL] Tutte le aste in pausa", LogLevel.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -166,7 +166,7 @@ namespace AutoBidder
MessageBox.Show(summary, "Aggiunta aste", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show(summary, "Aggiunta aste", MessageBoxButton.OK, MessageBoxImage.Information);
// RIMOSSO: Retry automatico ora avviene alla selezione on-demand // ? RIMOSSO: Retry automatico ora avviene alla selezione on-demand
// Le aste con nome generico vengono aggiornate automaticamente quando l'utente le seleziona // Le aste con nome generico vengono aggiornate automaticamente quando l'utente le seleziona
} }
} }
@@ -187,7 +187,7 @@ namespace AutoBidder
// Conferma rimozione // Conferma rimozione
var result = MessageBox.Show( var result = MessageBox.Show(
$"Rimuovere l'asta dal monitoraggio?\n\n{auctionName}\n(ID: {auctionId})\n\nL'asta verrà eliminata dalla lista e non sarà più monitorata.", $"Rimuovere l'asta dal monitoraggio?\n\n{auctionName}\n(ID: {auctionId})\n\nL'asta verrà eliminata dalla lista e non sarà più monitorata.",
"Conferma Rimozione", "Conferma Rimozione",
MessageBoxButton.YesNo, MessageBoxButton.YesNo,
MessageBoxImage.Question); MessageBoxImage.Question);
@@ -213,10 +213,10 @@ namespace AutoBidder
Log($"[REMOVE] Asta rimossa: {auctionName} (ID: {auctionId})", LogLevel.Success); Log($"[REMOVE] Asta rimossa: {auctionName} (ID: {auctionId})", LogLevel.Success);
// NUOVO: Sposta il focus sulla riga successiva // ? NUOVO: Sposta il focus sulla riga successiva
if (_auctionViewModels.Count > 0) if (_auctionViewModels.Count > 0)
{ {
// Se c'è ancora almeno un'asta nella lista // Se c'è ancora almeno un'asta nella lista
int newIndex; int newIndex;
if (currentIndex >= _auctionViewModels.Count) if (currentIndex >= _auctionViewModels.Count)
@@ -234,7 +234,7 @@ namespace AutoBidder
MultiAuctionsGrid.SelectedIndex = newIndex; MultiAuctionsGrid.SelectedIndex = newIndex;
_selectedAuction = _auctionViewModels[newIndex]; _selectedAuction = _auctionViewModels[newIndex];
// FIX: Salva il nome della NUOVA asta selezionata per il log // ? FIX: Salva il nome della NUOVA asta selezionata per il log
var newAuctionName = _selectedAuction?.Name ?? "Sconosciuta"; var newAuctionName = _selectedAuction?.Name ?? "Sconosciuta";
// Forza il focus sulla griglia dopo un breve delay per permettere alla UI di aggiornarsi // Forza il focus sulla griglia dopo un breve delay per permettere alla UI di aggiornarsi
@@ -248,7 +248,7 @@ namespace AutoBidder
MultiAuctionsGrid.ScrollIntoView(MultiAuctionsGrid.SelectedItem); MultiAuctionsGrid.ScrollIntoView(MultiAuctionsGrid.SelectedItem);
} }
// FIX: Usa la variabile locale invece di _selectedAuction.Name // ? FIX: Usa la variabile locale invece di _selectedAuction.Name
Log($"[FOCUS] Focus spostato su: {newAuctionName}", LogLevel.Info); Log($"[FOCUS] Focus spostato su: {newAuctionName}", LogLevel.Info);
}), System.Windows.Threading.DispatcherPriority.Background); }), System.Windows.Threading.DispatcherPriority.Background);
} }
@@ -278,7 +278,7 @@ namespace AutoBidder
// Conferma rimozione // Conferma rimozione
var result = MessageBox.Show( var result = MessageBox.Show(
$"Rimuovere TUTTE le aste dal monitoraggio?\n\nSono presenti {count} aste monitorate.\n\nTutte le aste verranno eliminate dalla lista e non saranno più monitorate.", $"Rimuovere TUTTE le aste dal monitoraggio?\n\nSono presenti {count} aste monitorate.\n\nTutte le aste verranno eliminate dalla lista e non saranno più monitorate.",
"Conferma Rimozione Totale", "Conferma Rimozione Totale",
MessageBoxButton.YesNo, MessageBoxButton.YesNo,
MessageBoxImage.Warning); MessageBoxImage.Warning);
@@ -368,7 +368,7 @@ namespace AutoBidder
} }
// Ultimo tentativo fallito // Ultimo tentativo fallito
Log($"[WARN] Clipboard temporaneamente occupato. Il testo potrebbe essere stato copiato.", LogLevel.Warn); Log($"[WARN] Clipboard temporaneamente occupato. Il testo potrebbe essere stato copiato.", LogLevel.Warning);
return; return;
} }
catch (Exception ex) catch (Exception ex)
@@ -405,8 +405,8 @@ namespace AutoBidder
} }
else else
{ {
Log($"[WARN] Browser interno non ancora inizializzato", LogLevel.Warn); Log($"[WARN] Browser interno non ancora inizializzato", LogLevel.Warning);
MessageBox.Show("Il browser interno non è ancora pronto.\nRiprova tra qualche secondo.", "Browser", MessageBoxButton.OK, MessageBoxImage.Warning); MessageBox.Show("Il browser interno non è ancora pronto.\nRiprova tra qualche secondo.", "Browser", MessageBoxButton.OK, MessageBoxImage.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -456,12 +456,12 @@ namespace AutoBidder
try try
{ {
MessageBox.Show( MessageBox.Show(
$"Esportazione singola asta:\n\n{_selectedAuction.Name}\n(ID: {_selectedAuction.AuctionId})\n\nFunzionalità in sviluppo.\nUsa 'Esporta' dalla toolbar per esportare tutte le aste.", $"Esportazione singola asta:\n\n{_selectedAuction.Name}\n(ID: {_selectedAuction.AuctionId})\n\nFunzionalità in sviluppo.\nUsa 'Esporta' dalla toolbar per esportare tutte le aste.",
"Export Asta", "Export Asta",
MessageBoxButton.OK, MessageBoxButton.OK,
MessageBoxImage.Information); MessageBoxImage.Information);
Log($"[INFO] Richiesto export singolo per asta: {_selectedAuction.Name} (funzionalità in sviluppo)", LogLevel.Info); Log($"[INFO] Richiesto export singolo per asta: {_selectedAuction.Name} (funzionalità in sviluppo)", LogLevel.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -503,12 +503,12 @@ namespace AutoBidder
double shippingCost = auction.ShippingCost ?? 0; double shippingCost = auction.ShippingCost ?? 0;
double totalValue = buyNowPrice + shippingCost; double totalValue = buyNowPrice + shippingCost;
// Max EUR = 40% del valore TOTALE (più conservativo del 50%) // Max EUR = 40% del valore TOTALE (più conservativo del 50%)
double suggestedMaxPrice = totalValue * 0.40; double suggestedMaxPrice = totalValue * 0.40;
suggestedMaxPrice = Math.Round(suggestedMaxPrice, 2); suggestedMaxPrice = Math.Round(suggestedMaxPrice, 2);
// CALCOLA MAX CLICKS (numero massimo puntate conservativo) // CALCOLA MAX CLICKS (numero massimo puntate conservativo)
// Formula: (Valore Totale - Max EUR) / 0.20€ per puntata // Formula: (Valore Totale - Max EUR) / 0.20€ per puntata
// Poi riduciamo del 20% per maggiore margine di sicurezza // Poi riduciamo del 20% per maggiore margine di sicurezza
int maxClicksTheoretical = (int)Math.Floor((totalValue - suggestedMaxPrice) / 0.20); int maxClicksTheoretical = (int)Math.Floor((totalValue - suggestedMaxPrice) / 0.20);
int suggestedMaxClicks = (int)Math.Floor(maxClicksTheoretical * 0.80); // 80% del teorico int suggestedMaxClicks = (int)Math.Floor(maxClicksTheoretical * 0.80); // 80% del teorico
@@ -516,12 +516,12 @@ namespace AutoBidder
// Minimo 10 puntate per dare comunque una chance // Minimo 10 puntate per dare comunque una chance
if (suggestedMaxClicks < 10) suggestedMaxClicks = 10; if (suggestedMaxClicks < 10) suggestedMaxClicks = 10;
Log($"[LIMITI] Valore={buyNowPrice:F2}€ + Extra={shippingCost:F2}€ = Tot={totalValue:F2}€ MaxEUR={suggestedMaxPrice:F2}€ (40%), MaxClicks={suggestedMaxClicks}", LogLevel.Info); Log($"[LIMITI] Valore={buyNowPrice:F2}€ + Extra={shippingCost:F2}€ = Tot={totalValue:F2}€ ? MaxEUR={suggestedMaxPrice:F2}€ (40%), MaxClicks={suggestedMaxClicks}", LogLevel.Info);
// CHIEDI CONFERMA // CHIEDI CONFERMA
var result = MessageBox.Show( var result = MessageBox.Show(
$"Limiti suggeriti (conservativi):\n\n" + $"Limiti suggeriti (conservativi):\n\n" +
$"Max EUR: {suggestedMaxPrice:F2}€\n" + $"Max EUR: {suggestedMaxPrice:F2}€\n" +
$"Max Clicks: {suggestedMaxClicks}\n\n" + $"Max Clicks: {suggestedMaxClicks}\n\n" +
$"Applicare questi valori?", $"Applicare questi valori?",
"Conferma Limiti", "Conferma Limiti",
@@ -544,7 +544,7 @@ namespace AutoBidder
// SALVA // SALVA
SaveAuctions(); SaveAuctions();
Log($"[LIMITI] Applicati: MaxEUR={suggestedMaxPrice:F2}€, MaxClicks={suggestedMaxClicks}", LogLevel.Success); Log($"[LIMITI] Applicati: MaxEUR={suggestedMaxPrice:F2}€, MaxClicks={suggestedMaxClicks}", LogLevel.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -574,8 +574,8 @@ namespace AutoBidder
if (currentIndex <= 0) if (currentIndex <= 0)
{ {
// Già in cima o non trovata // Già in cima o non trovata
Log($"[MOVE] L'asta è già in cima alla lista", LogLevel.Info); Log($"[MOVE] L'asta è già in cima alla lista", LogLevel.Info);
return; return;
} }
@@ -615,8 +615,8 @@ namespace AutoBidder
if (currentIndex < 0 || currentIndex >= _auctionViewModels.Count - 1) if (currentIndex < 0 || currentIndex >= _auctionViewModels.Count - 1)
{ {
// Già in fondo o non trovata // Già in fondo o non trovata
Log($"[MOVE] L'asta è già in fondo alla lista", LogLevel.Info); Log($"[MOVE] L'asta è già in fondo alla lista", LogLevel.Info);
return; return;
} }

View File

@@ -324,23 +324,6 @@ namespace AutoBidder
// ===== SETTINGS CONTROL EVENTS ===== // ===== SETTINGS CONTROL EVENTS =====
// NOTA: Handler cookie RIMOSSI - gestione automatica tramite browser
private void Settings_ExportBrowseClicked(object sender, RoutedEventArgs e)
{
ExportBrowseButton_Click(sender, e);
}
private void Settings_SaveSettingsClicked(object sender, RoutedEventArgs e)
{
SaveSettingsButton_Click(sender, e);
}
private void Settings_CancelSettingsClicked(object sender, RoutedEventArgs e)
{
CancelSettingsButton_Click(sender, e);
}
private void Settings_SaveDefaultsClicked(object sender, RoutedEventArgs e) private void Settings_SaveDefaultsClicked(object sender, RoutedEventArgs e)
{ {
SaveDefaultsButton_Click(sender, e); SaveDefaultsButton_Click(sender, e);

View File

@@ -6,27 +6,63 @@ using AutoBidder.Utilities;
namespace AutoBidder namespace AutoBidder
{ {
/// <summary> /// <summary>
/// Logging functionality with color-coded severity levels /// Logging functionality with color-coded severity levels and configurable minimum level filtering
/// </summary> /// </summary>
public partial class MainWindow public partial class MainWindow
{ {
/// <summary>
/// Scrive un messaggio nel log globale con filtraggio basato sul livello minimo configurato
/// </summary>
/// <param name="message">Messaggio da loggare</param>
/// <param name="level">Livello di severità del messaggio</param>
private void Log(string message, LogLevel level = LogLevel.Info) private void Log(string message, LogLevel level = LogLevel.Info)
{ {
Dispatcher.BeginInvoke(() => Dispatcher.BeginInvoke(() =>
{ {
try try
{ {
// Carica impostazioni per ottenere livello minimo e limite righe
var settings = SettingsManager.Load();
// Filtra messaggi in base al livello minimo configurato
MinimumLogLevel minLevel = MinimumLogLevel.Normal; // Default
if (Enum.TryParse<MinimumLogLevel>(settings.MinLogLevel, out var parsedLevel))
{
minLevel = parsedLevel;
}
// Se il livello del messaggio è maggiore del minimo configurato, ignora
if ((int)level > (int)minLevel)
{
return;
}
var timestamp = DateTime.Now.ToString("HH:mm:ss"); var timestamp = DateTime.Now.ToString("HH:mm:ss");
var logEntry = $"[{timestamp}] {message}";
// Prefisso in base al livello per chiarezza
string prefix = level switch
{
LogLevel.Error => "[ERROR]",
LogLevel.Warning => "[WARN]",
LogLevel.Info => "[INFO]",
LogLevel.Success => "[OK]",
LogLevel.Debug => "[DEBUG]",
LogLevel.Trace => "[TRACE]",
_ => "[LOG]"
};
var logEntry = $"[{timestamp}] {prefix} {message}";
// Color coding based on severity for dark theme // Color coding based on severity for dark theme
var color = level switch var color = level switch
{ {
LogLevel.Error => new SolidColorBrush(Color.FromRgb(232, 17, 35)), // #E81123 (Red) LogLevel.Error => new SolidColorBrush(Color.FromRgb(232, 17, 35)), // #E81123 (Red)
LogLevel.Warn => new SolidColorBrush(Color.FromRgb(255, 183, 0)), // #FFB700 (Yellow/Orange) LogLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 191, 0)), // #FFBF00 (Yellow)
LogLevel.Success => new SolidColorBrush(Color.FromRgb(0, 216, 0)), // #00D800 (Green) LogLevel.Success => new SolidColorBrush(Color.FromRgb(0, 216, 0)), // #00D800 (Green)
LogLevel.Info => new SolidColorBrush(Color.FromRgb(100, 180, 255)), // #64B4FF (Light Blue - più chiaro e leggibile) LogLevel.Info => new SolidColorBrush(Color.FromRgb(100, 180, 255)), // #64B4FF (Light Blue)
_ => new SolidColorBrush(Color.FromRgb(204, 204, 204)) // #CCCCCC (Light Gray) LogLevel.Debug => new SolidColorBrush(Color.FromRgb(255, 140, 255)), // #FF8CFF (Magenta)
LogLevel.Trace => new SolidColorBrush(Color.FromRgb(160, 160, 160)), // #A0A0A0 (Gray)
_ => new SolidColorBrush(Color.FromRgb(204, 204, 204)) // #CCCCCC (Light Gray)
}; };
var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0, 2, 0, 2) }; var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0, 2, 0, 2) };
@@ -34,8 +70,7 @@ namespace AutoBidder
p.Inlines.Add(r); p.Inlines.Add(r);
LogBox.Document.Blocks.Add(p); LogBox.Document.Blocks.Add(p);
// ? Mantieni solo gli ultimi N paragrafi (configurabile dalle impostazioni) // Mantieni solo gli ultimi N paragrafi (configurabile dalle impostazioni)
var settings = SettingsManager.Load();
int maxLogLines = settings.MaxGlobalLogLines; int maxLogLines = settings.MaxGlobalLogLines;
if (LogBox.Document.Blocks.Count > maxLogLines) if (LogBox.Document.Blocks.Count > maxLogLines)

View File

@@ -184,7 +184,7 @@ namespace AutoBidder
else else
{ {
SetUserBanner(string.Empty, 0); SetUserBanner(string.Empty, 0);
Log("[SESSION] Sessione scaduta", LogLevel.Warn); Log("[SESSION] Sessione scaduta", LogLevel.Warning);
CheckBrowserCookieAfterWebViewReady(); CheckBrowserCookieAfterWebViewReady();
} }
}); });
@@ -194,7 +194,7 @@ namespace AutoBidder
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
SetUserBanner(string.Empty, 0); SetUserBanner(string.Empty, 0);
Log($"[SESSION] Errore verifica sessione: {ex.Message}", LogLevel.Warn); Log($"[SESSION] Errore verifica sessione: {ex.Message}", LogLevel.Warning);
CheckBrowserCookieAfterWebViewReady(); CheckBrowserCookieAfterWebViewReady();
}); });
} }
@@ -231,7 +231,7 @@ namespace AutoBidder
{ {
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() =>
{ {
Log("[WARN] WebView non inizializzata dopo 60 secondi", LogLevel.Warn); Log("[WARN] WebView non inizializzata dopo 60 secondi", LogLevel.Warning);
Log("[INFO] Per accedere:", LogLevel.Info); Log("[INFO] Per accedere:", LogLevel.Info);
Log("[INFO] 1. Click su 'Non connesso' nella sidebar", LogLevel.Info); Log("[INFO] 1. Click su 'Non connesso' nella sidebar", LogLevel.Info);
Log("[INFO] 2. Si aprirà la scheda Browser", LogLevel.Info); Log("[INFO] 2. Si aprirà la scheda Browser", LogLevel.Info);
@@ -263,7 +263,7 @@ namespace AutoBidder
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[WARN] Errore verifica cookie: {ex.Message}", LogLevel.Warn); Log($"[WARN] Errore verifica cookie: {ex.Message}", LogLevel.Warning);
} }
}); });
} }

View File

@@ -24,7 +24,7 @@ namespace AutoBidder
{ {
if (EmbeddedWebView == null) if (EmbeddedWebView == null)
{ {
Log("[WARN] WebView2 non disponibile", LogLevel.Warn); Log("[WARN] WebView2 non disponibile", LogLevel.Warning);
_webViewInitCompletionSource?.TrySetResult(false); _webViewInitCompletionSource?.TrySetResult(false);
return; return;
} }
@@ -160,7 +160,7 @@ namespace AutoBidder
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[WARN] Verifica cookie fallita: {ex.Message}", LogLevel.Warn); Log($"[WARN] Verifica cookie fallita: {ex.Message}", LogLevel.Warning);
} }
} }
@@ -180,7 +180,7 @@ namespace AutoBidder
if (completedTask == timeoutTask) if (completedTask == timeoutTask)
{ {
Log("[WARN] Timeout attesa inizializzazione WebView2", LogLevel.Warn); Log("[WARN] Timeout attesa inizializzazione WebView2", LogLevel.Warning);
return false; return false;
} }
@@ -275,7 +275,7 @@ namespace AutoBidder
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[WARN] Impossibile estrarre cookie da WebView: {ex.Message}", LogLevel.Warn); Log($"[WARN] Impossibile estrarre cookie da WebView: {ex.Message}", LogLevel.Warning);
return null; return null;
} }
} }
@@ -289,7 +289,7 @@ namespace AutoBidder
{ {
if (!_isWebViewInitialized || EmbeddedWebView?.CoreWebView2 == null) if (!_isWebViewInitialized || EmbeddedWebView?.CoreWebView2 == null)
{ {
Log("[WARN] Browser non inizializzato - attendi qualche secondo e riprova", LogLevel.Warn); Log("[WARN] Browser non inizializzato - attendi qualche secondo e riprova", LogLevel.Warning);
return false; return false;
} }
@@ -299,7 +299,7 @@ namespace AutoBidder
if (string.IsNullOrEmpty(cookieString)) if (string.IsNullOrEmpty(cookieString))
{ {
Log("[WARN] Nessun cookie trovato nel browser - assicurati di aver effettuato il login su bidoo.com", LogLevel.Warn); Log("[WARN] Nessun cookie trovato nel browser - assicurati di aver effettuato il login su bidoo.com", LogLevel.Warning);
return false; return false;
} }

View File

@@ -334,9 +334,6 @@
<!-- Settings Panel --> <!-- Settings Panel -->
<controls:SettingsControl x:Name="Settings" <controls:SettingsControl x:Name="Settings"
Visibility="Collapsed" Visibility="Collapsed"
ExportBrowseClicked="Settings_ExportBrowseClicked"
SaveSettingsClicked="Settings_SaveSettingsClicked"
CancelSettingsClicked="Settings_CancelSettingsClicked"
SaveDefaultsClicked="Settings_SaveDefaultsClicked" SaveDefaultsClicked="Settings_SaveDefaultsClicked"
CancelDefaultsClicked="Settings_CancelDefaultsClicked"/> CancelDefaultsClicked="Settings_CancelDefaultsClicked"/>
</Grid> </Grid>

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -19,7 +19,7 @@ namespace AutoBidder
private readonly AuctionMonitor _auctionMonitor; private readonly AuctionMonitor _auctionMonitor;
private readonly System.Collections.ObjectModel.ObservableCollection<AuctionViewModel> _auctionViewModels = new System.Collections.ObjectModel.ObservableCollection<AuctionViewModel>(); private readonly System.Collections.ObjectModel.ObservableCollection<AuctionViewModel> _auctionViewModels = new System.Collections.ObjectModel.ObservableCollection<AuctionViewModel>();
// NUOVO: Servizio centralizzato per HTTP requests con cache e rate limiting // ? NUOVO: Servizio centralizzato per HTTP requests con cache e rate limiting
private readonly HtmlCacheService _htmlCacheService; private readonly HtmlCacheService _htmlCacheService;
// UI State // UI State
@@ -69,19 +69,6 @@ namespace AutoBidder
public TextBox BrowserAddress => Browser.BrowserAddress; public TextBox BrowserAddress => Browser.BrowserAddress;
// Properties to access UserControl elements - Settings // Properties to access UserControl elements - Settings
public TextBox ExportPathTextBox => Settings.ExportPathTextBox;
public Button ExportBrowseButton => Settings.ExportBrowseButton;
public RadioButton ExtCsv => Settings.ExtCsv;
public RadioButton ExtJson => Settings.ExtJson;
public RadioButton ExtXml => Settings.ExtXml;
public CheckBox IncludeUsedBids => Settings.IncludeUsedBids;
public CheckBox IncludeLogs => Settings.IncludeLogs;
public CheckBox IncludeUserBids => Settings.IncludeUserBids;
public CheckBox IncludeMetadata => Settings.IncludeMetadata;
public CheckBox RemoveAfterExport => Settings.RemoveAfterExport;
public CheckBox OverwriteExisting => Settings.OverwriteExisting;
// Impostazioni predefinite aste - AGGIORNATO
public TextBox DefaultBidBeforeDeadlineMs => Settings.DefaultBidBeforeDeadlineMsTextBox; public TextBox DefaultBidBeforeDeadlineMs => Settings.DefaultBidBeforeDeadlineMsTextBox;
public CheckBox DefaultCheckAuctionOpen => Settings.DefaultCheckAuctionOpenCheckBox; public CheckBox DefaultCheckAuctionOpen => Settings.DefaultCheckAuctionOpenCheckBox;
public TextBox DefaultMinPrice => Settings.DefaultMinPriceTextBox; public TextBox DefaultMinPrice => Settings.DefaultMinPriceTextBox;
@@ -94,7 +81,7 @@ namespace AutoBidder
{ {
InitializeComponent(); InitializeComponent();
// Inizializza HtmlCacheService con: // ? Inizializza HtmlCacheService con:
// - Max 3 richieste concorrenti // - Max 3 richieste concorrenti
// - Max 5 richieste al secondo // - Max 5 richieste al secondo
// - Cache di 5 minuti // - Cache di 5 minuti
@@ -110,13 +97,13 @@ namespace AutoBidder
// Inizializza servizi // Inizializza servizi
_auctionMonitor = new AuctionMonitor(); _auctionMonitor = new AuctionMonitor();
// NUOVO: Inizializza SessionService // ? NUOVO: Inizializza SessionService
InitializeSessionService(); InitializeSessionService();
// Initialize commands (from MainWindow.Commands.cs) // Initialize commands (from MainWindow.Commands.cs)
InitializeCommands(); InitializeCommands();
// NUOVO: Inizializza validazione campi numerici // ? NUOVO: Inizializza validazione campi numerici
InitializeNumericInputValidation(); InitializeNumericInputValidation();
this.DataContext = this; this.DataContext = this;
@@ -127,7 +114,7 @@ namespace AutoBidder
_auctionMonitor.OnLog += AuctionMonitor_OnLog; _auctionMonitor.OnLog += AuctionMonitor_OnLog;
_auctionMonitor.OnResetCountChanged += AuctionMonitor_OnResetCountChanged; _auctionMonitor.OnResetCountChanged += AuctionMonitor_OnResetCountChanged;
// NUOVO: Registra evento stato connessione // ? NUOVO: Registra evento stato connessione
AuctionMonitor.ConnectionStatusClicked += AuctionMonitor_ConnectionStatusClicked; AuctionMonitor.ConnectionStatusClicked += AuctionMonitor_ConnectionStatusClicked;
// Bind griglia // Bind griglia
@@ -136,24 +123,51 @@ namespace AutoBidder
// Carica aste salvate (from MainWindow.AuctionManagement.cs) // Carica aste salvate (from MainWindow.AuctionManagement.cs)
LoadSavedAuctions(); LoadSavedAuctions();
// Load export settings (from MainWindow.EventHandlers.Export.cs)
LoadExportSettings();
// CARICA IMPOSTAZIONI PREDEFINITE ASTE // CARICA IMPOSTAZIONI PREDEFINITE ASTE
LoadDefaultSettings(); LoadDefaultSettings();
// Update initial button states // Update initial button states
UpdateGlobalControlButtons(); UpdateGlobalControlButtons();
Log("[OK] AutoBidder v4.0 avviato", LogLevel.Success); // === LOG AVVIO APPLICAZIONE ===
Log("???????????????????????????????????????????????", LogLevel.Info);
Log(" AutoBidder v4.0 - Multi-Auction Monitor", LogLevel.Info);
Log("???????????????????????????????????????????????", LogLevel.Info);
var settings = SettingsManager.Load();
Log($"Configurazione caricata:", LogLevel.Info);
Log($" • Livello log: {settings.MinLogLevel}", LogLevel.Info);
Log($" • Limite log globale: {settings.MaxGlobalLogLines} righe", LogLevel.Info);
Log($" • Limite log per asta: {settings.MaxLogLinesPerAuction} righe", LogLevel.Info);
Log($" • Puntate minime da mantenere: {(settings.MinimumRemainingBids > 0 ? settings.MinimumRemainingBids.ToString() : "nessun limite")}", LogLevel.Info);
Log($" • Anticipo puntata default: {settings.DefaultBidBeforeDeadlineMs}ms", LogLevel.Info);
var auctionCount = _auctionViewModels.Count;
if (auctionCount > 0)
{
Log($"Aste caricate: {auctionCount}", LogLevel.Success);
var activeCount = _auctionViewModels.Count(a => a.IsActive && !a.IsPaused);
var pausedCount = _auctionViewModels.Count(a => a.IsPaused);
var stoppedCount = _auctionViewModels.Count(a => !a.IsActive && !a.IsPaused);
if (activeCount > 0) Log($" • Attive: {activeCount}", LogLevel.Info);
if (pausedCount > 0) Log($" • In pausa: {pausedCount}", LogLevel.Info);
if (stoppedCount > 0) Log($" • Fermate: {stoppedCount}", LogLevel.Info);
}
else
{
Log("Nessuna asta caricata", LogLevel.Info);
}
Log("[OK] Applicazione pronta", LogLevel.Success);
Log("???????????????????????????????????????????????", LogLevel.Info);
// Initialize user info timers (from MainWindow.UserInfo.cs) // Initialize user info timers (from MainWindow.UserInfo.cs)
InitializeUserInfoTimers(); InitializeUserInfoTimers();
// NUOVO: Carica sessione salvata // ? NUOVO: Carica sessione salvata
LoadSavedSession(); LoadSavedSession();
// NUOVO: Pre-carica WebView2 in background per renderla subito disponibile // ? NUOVO: Pre-carica WebView2 in background per renderla subito disponibile
InitializeWebView2(); InitializeWebView2();
// Attach WebView2 context menu handler // Attach WebView2 context menu handler
@@ -176,7 +190,7 @@ namespace AutoBidder
} }
catch { } catch { }
// Timer per pulizia cache periodica (ogni 10 minuti) // ? Timer per pulizia cache periodica (ogni 10 minuti)
var cacheCleanupTimer = new System.Windows.Threading.DispatcherTimer var cacheCleanupTimer = new System.Windows.Threading.DispatcherTimer
{ {
Interval = TimeSpan.FromMinutes(10) Interval = TimeSpan.FromMinutes(10)
@@ -222,7 +236,7 @@ namespace AutoBidder
{ {
vm.UpdateState(state); vm.UpdateState(state);
// NUOVO: Aggiorna storia puntate // ? NUOVO: Aggiorna storia puntate
vm.RefreshBidHistory(); vm.RefreshBidHistory();
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -12,7 +12,7 @@ namespace AutoBidder.Services
{ {
/// <summary> /// <summary>
/// Servizio completo API Bidoo (polling, puntate, info utente) /// Servizio completo API Bidoo (polling, puntate, info utente)
/// Solo HTTP, nessuna modalità, browser o multi-click /// Solo HTTP, nessuna modalità, browser o multi-click
/// </summary> /// </summary>
public class BidooApiClient public class BidooApiClient
{ {
@@ -90,7 +90,7 @@ namespace AutoBidder.Services
if (!string.IsNullOrWhiteSpace(_session.CookieString)) if (!string.IsNullOrWhiteSpace(_session.CookieString))
{ {
request.Headers.Add("Cookie", _session.CookieString); request.Headers.Add("Cookie", _session.CookieString);
// Log rimosso per ridurre verbosità // Log rimosso per ridurre verbosità
} }
else else
{ {
@@ -130,13 +130,13 @@ namespace AutoBidder.Services
request.Headers.Add("Referer", "https://it.bidoo.com/"); request.Headers.Add("Referer", "https://it.bidoo.com/");
} }
// Log rimosso per ridurre verbosità - headers sempre aggiunti // Log rimosso per ridurre verbosità - headers sempre aggiunti
} }
/// <summary> /// <summary>
/// Estrae CSRF/Bid token dalla pagina asta /// Estrae CSRF/Bid token dalla pagina asta
/// PASSO 1: Ottenere la pagina HTML dell'asta per estrarre il token di sicurezza /// PASSO 1: Ottenere la pagina HTML dell'asta per estrarre il token di sicurezza
/// Il token può essere chiamato: bid_token, csrf_token, _token, etc. /// Il token può essere chiamato: bid_token, csrf_token, _token, etc.
/// </summary> /// </summary>
private async Task<(string? tokenName, string? tokenValue)> ExtractBidTokenAsync(string auctionId, string? auctionUrl = null) private async Task<(string? tokenName, string? tokenValue)> ExtractBidTokenAsync(string auctionId, string? auctionUrl = null)
{ {
@@ -187,12 +187,12 @@ namespace AutoBidder.Services
if (match.Success) if (match.Success)
{ {
var tokenValue = match.Groups[1].Value; var tokenValue = match.Groups[1].Value;
Log($"[TOKEN] Token found: {pattern.name} = {tokenValue.Substring(0, Math.Min(20, tokenValue.Length))}...", auctionId); Log($"[TOKEN] ? Token found: {pattern.name} = {tokenValue.Substring(0, Math.Min(20, tokenValue.Length))}...", auctionId);
return (pattern.name, tokenValue); return (pattern.name, tokenValue);
} }
} }
Log("[TOKEN] No bid token found in HTML", auctionId); Log("[TOKEN] ? No bid token found in HTML", auctionId);
return (null, null); return (null, null);
} }
catch (Exception ex) catch (Exception ex)
@@ -309,7 +309,7 @@ namespace AutoBidder.Services
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) && state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase); state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
// NUOVO: Parse storia puntate // ? NUOVO: Parse storia puntate
// Formato: 42;fedekikka2323;3,42;fedekikka2323;1764068204;3|41;chamorro1984;1764068194;3|... // Formato: 42;fedekikka2323;3,42;fedekikka2323;1764068204;3|41;chamorro1984;1764068194;3|...
if (!string.IsNullOrEmpty(historyData)) if (!string.IsNullOrEmpty(historyData))
{ {
@@ -317,7 +317,8 @@ namespace AutoBidder.Services
} }
state.ParsingSuccess = true; state.ParsingSuccess = true;
Log($"[PARSE SUCCESS] Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}, History: {state.RecentBidsHistory?.Count ?? 0} bids", auctionId); // Log di successo rimosso - troppo verboso. Solo errori vengono loggati nel log asta.
// Per debug dettagliato, usare livello log "Debug" o "Trace" nelle impostazioni.
return state; return state;
} }
catch (Exception ex) catch (Exception ex)
@@ -337,14 +338,14 @@ namespace AutoBidder.Services
{ {
var entries = new List<BidHistoryEntry>(); var entries = new List<BidHistoryEntry>();
// Il primo record è spesso il prezzo corrente con dati duplicati, lo saltiamo // Il primo record è spesso il prezzo corrente con dati duplicati, lo saltiamo
var records = historyData.Split('|'); var records = historyData.Split('|');
// Parsing prezzo corrente per calcolare i prezzi precedenti // Parsing prezzo corrente per calcolare i prezzi precedenti
if (!int.TryParse(currentPriceStr, out var currentPriceIndex)) if (!int.TryParse(currentPriceStr, out var currentPriceIndex))
return null; return null;
// 📊 NUOVO: Carica impostazione limite visualizzazione puntate // ?? NUOVO: Carica impostazione limite visualizzazione puntate
var settings = Utilities.SettingsManager.Load(); var settings = Utilities.SettingsManager.Load();
var maxEntries = settings?.MaxBidHistoryEntries ?? 20; // Default 20 se non impostato var maxEntries = settings?.MaxBidHistoryEntries ?? 20; // Default 20 se non impostato
@@ -353,7 +354,7 @@ namespace AutoBidder.Services
if (string.IsNullOrWhiteSpace(record)) if (string.IsNullOrWhiteSpace(record))
continue; continue;
// 📊 Limita il numero di puntate basandosi sulle impostazioni // ?? Limita il numero di puntate basandosi sulle impostazioni
if (maxEntries > 0 && entries.Count >= maxEntries) if (maxEntries > 0 && entries.Count >= maxEntries)
break; break;
@@ -523,7 +524,7 @@ namespace AutoBidder.Services
if (creditMatch.Success && double.TryParse(creditMatch.Groups[1].Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double credit)) if (creditMatch.Success && double.TryParse(creditMatch.Groups[1].Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double credit))
{ {
_session.ShopCredit = credit; _session.ShopCredit = credit;
Log($"[USER INFO PARSED] Shop credit: €{credit:F2}"); Log($"[USER INFO PARSED] Shop credit: €{credit:F2}");
foundData = true; foundData = true;
} }
@@ -656,15 +657,15 @@ namespace AutoBidder.Services
/// STATI API BIDOO: /// STATI API BIDOO:
/// - ON: Asta attiva e in corso /// - ON: Asta attiva e in corso
/// - OFF: Asta terminata definitivamente /// - OFF: Asta terminata definitivamente
/// - STOP: Asta in pausa (tipicamente 00:00-10:00) - riprenderà più tardi /// - STOP: Asta in pausa (tipicamente 00:00-10:00) - riprenderà più tardi
/// </summary> /// </summary>
private AuctionStatus DetermineAuctionStatus(string apiStatus, bool hasWinner, bool iAmWinner, ref AuctionState state) private AuctionStatus DetermineAuctionStatus(string apiStatus, bool hasWinner, bool iAmWinner, ref AuctionState state)
{ {
// Gestione stato STOP (pausa notturna) // Gestione stato STOP (pausa notturna)
if (apiStatus == "STOP") if (apiStatus == "STOP")
{ {
// L'asta è iniziata ma è in pausa // L'asta è iniziata ma è in pausa
// Controlla se c'è già un vincitore temporaneo // Controlla se c'è già un vincitore temporaneo
if (hasWinner) if (hasWinner)
{ {
state.LastBidder = state.LastBidder; // Mantieni il last bidder state.LastBidder = state.LastBidder; // Mantieni il last bidder
@@ -689,12 +690,12 @@ namespace AutoBidder.Services
// Asta attiva // Asta attiva
if (hasWinner) if (hasWinner)
{ {
// Ci sono già puntate Running // Ci sono già puntate ? Running
return AuctionStatus.Running; return AuctionStatus.Running;
} }
// Nessuna puntata ancora Pending o Scheduled // Nessuna puntata ancora ? Pending o Scheduled
// Se timer molto alto (> 30 minuti), è programmata per più tardi // Se timer molto alto (> 30 minuti), è programmata per più tardi
if (state.Timer > 1800) // 30 minuti if (state.Timer > 1800) // 30 minuti
{ {
return AuctionStatus.Scheduled; return AuctionStatus.Scheduled;
@@ -899,7 +900,7 @@ namespace AutoBidder.Services
Log($"[USER HTML ERROR] Puntate residue NON trovate nell'HTML"); Log($"[USER HTML ERROR] Puntate residue NON trovate nell'HTML");
} }
// Ritorna dati solo se almeno username è stato trovato // Ritorna dati solo se almeno username è stato trovato
if (foundUsername) if (foundUsername)
{ {
Log($"[USER HTML SUCCESS] Dati estratti: {userData.Username}, {userData.RemainingBids} puntate"); Log($"[USER HTML SUCCESS] Dati estratti: {userData.Username}, {userData.RemainingBids} puntate");

View File

@@ -1,130 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using AutoBidder.Models;
namespace AutoBidder.Utilities
{
/// <summary>
/// Esporta statistiche aste in formato CSV
/// </summary>
public static class CsvExporter
{
/// <summary>
/// Esporta cronologia completa di un'asta in CSV
/// </summary>
public static void ExportAuctionHistory(AuctionInfo auction, string filePath)
{
var csv = new StringBuilder();
// Header
csv.AppendLine("Timestamp,Event Type,Bidder,Price,Timer,Latency (ms),Success,Notes");
// Data
foreach (var entry in auction.BidHistory.OrderBy(h => h.Timestamp))
{
csv.AppendLine($"{entry.Timestamp:yyyy-MM-dd HH:mm:ss.fff}," +
$"{entry.EventType}," +
$"\"{entry.Bidder}\"," +
$"{entry.Price:F2}," +
$"{entry.Timer:F2}," +
$"{entry.LatencyMs}," +
$"{entry.Success}," +
$"\"{entry.Notes}\"");
}
File.WriteAllText(filePath, csv.ToString(), Encoding.UTF8);
}
/// <summary>
/// Esporta statistiche aggregate di un'asta in CSV
/// </summary>
public static void ExportAuctionStatistics(AuctionInfo auction, string filePath)
{
var stats = AuctionStatistics.Calculate(auction);
var csv = new StringBuilder();
// Informazioni asta
csv.AppendLine("=== AUCTION INFO ===");
csv.AppendLine($"Auction ID,{stats.AuctionId}");
csv.AppendLine($"Name,\"{stats.Name}\"");
csv.AppendLine($"Monitoring Started,{stats.MonitoringStarted:yyyy-MM-dd HH:mm:ss}");
csv.AppendLine($"Monitoring Duration,{stats.MonitoringDuration}");
csv.AppendLine();
// Contatori
csv.AppendLine("=== COUNTERS ===");
csv.AppendLine($"Total Bids,{stats.TotalBids}");
csv.AppendLine($"My Bids,{stats.MyBids}");
csv.AppendLine($"Opponent Bids,{stats.OpponentBids}");
csv.AppendLine($"Resets,{stats.Resets}");
csv.AppendLine($"Unique Bidders,{stats.UniqueBidders}");
csv.AppendLine();
// Prezzi
csv.AppendLine("=== PRICES ===");
csv.AppendLine($"Start Price,{stats.StartPrice:F2}");
csv.AppendLine($"Current Price,{stats.CurrentPrice:F2}");
csv.AppendLine($"Min Price,{stats.MinPrice:F2}");
csv.AppendLine($"Max Price,{stats.MaxPrice:F2}");
csv.AppendLine($"Avg Price,{stats.AvgPrice:F2}");
csv.AppendLine();
// Performance
csv.AppendLine("=== PERFORMANCE ===");
csv.AppendLine($"Avg Click Latency (ms),{stats.AvgClickLatencyMs}");
csv.AppendLine($"Min Click Latency (ms),{stats.MinClickLatencyMs}");
csv.AppendLine($"Max Click Latency (ms),{stats.MaxClickLatencyMs}");
csv.AppendLine($"Bids Per Minute,{stats.BidsPerMinute:F2}");
csv.AppendLine($"Resets Per Hour,{stats.ResetsPerHour:F2}");
csv.AppendLine($"My Bid Success Rate,{stats.MyBidSuccessRate:F1}%");
csv.AppendLine();
// Competitor ranking
csv.AppendLine("=== BIDDER RANKING ===");
csv.AppendLine("Bidder,Bids Count");
foreach (var bidder in stats.BidderRanking.OrderByDescending(b => b.Value))
{
csv.AppendLine($"\"{bidder.Key}\",{bidder.Value}");
}
File.WriteAllText(filePath, csv.ToString(), Encoding.UTF8);
}
/// <summary>
/// Esporta tutte le aste in un unico CSV
/// </summary>
public static void ExportAllAuctions(IEnumerable<AuctionInfo> auctions, string filePath)
{
var csv = new StringBuilder();
// Header AGGIORNATO
csv.AppendLine("Auction ID,Name,Bid Before Deadline (ms),Check Before Bid,Min Price,Max Price,My Bids,Resets,Total Bidders,Active,Paused,Added At,Last Click At");
// Data
foreach (var auction in auctions)
{
var totalBidders = auction.BidderStats?.Count ?? 0;
var myBids = auction.BidHistory.Count(h => h.EventType == BidEventType.MyBid);
csv.AppendLine($"{auction.AuctionId}," +
$"\"{auction.Name}\"," +
$"{auction.BidBeforeDeadlineMs}," +
$"{auction.CheckAuctionOpenBeforeBid}," +
$"{auction.MinPrice:F2}," +
$"{auction.MaxPrice:F2}," +
$"{myBids}," +
$"{auction.ResetCount}," +
$"{totalBidders}," +
$"{auction.IsActive}," +
$"{auction.IsPaused}," +
$"{auction.AddedAt:yyyy-MM-dd HH:mm:ss}," +
$"{(auction.LastClickAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "")}");
}
File.WriteAllText(filePath, csv.ToString(), Encoding.UTF8);
}
}
}

View File

@@ -1,41 +0,0 @@
using System;
using System.IO;
using System.Text.Json;
namespace AutoBidder.Utilities
{
internal static class ExportPreferences
{
private class Prefs { public string? LastExportExt { get; set; } }
private static readonly string _folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoBidder");
private static readonly string _file = Path.Combine(_folder, "exportprefs.json");
public static string? LoadLastExportExtension()
{
try
{
if (!File.Exists(_file)) return null;
var txt = File.ReadAllText(_file);
var p = JsonSerializer.Deserialize<Prefs>(txt);
if (p == null || string.IsNullOrEmpty(p.LastExportExt)) return null;
return p.LastExportExt;
}
catch
{
return null;
}
}
public static void SaveLastExportExtension(string? ext)
{
try
{
if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder);
var p = new Prefs { LastExportExt = ext };
var txt = JsonSerializer.Serialize(p);
File.WriteAllText(_file, txt);
}
catch { }
}
}
}

View File

@@ -1,10 +1,82 @@
namespace AutoBidder.Utilities namespace AutoBidder.Utilities
{ {
/// <summary>
/// Livelli di log per differenziare la verbosità dei messaggi.
/// Permette di configurare il dettaglio desiderato tra uso normale e debug.
/// </summary>
internal enum LogLevel internal enum LogLevel
{ {
Info, /// <summary>
Success, /// Errori critici che impediscono il funzionamento dell'applicazione o di una funzionalità.
Warn, /// Sempre visibile. Esempi: errori di connessione, parsing fallito, eccezioni non gestite.
Error /// </summary>
Error = 0,
/// <summary>
/// Avvisi per situazioni anomale ma non critiche.
/// Sempre visibile. Esempi: parametri mancanti, valori inaspettati, retry in corso.
/// </summary>
Warning = 1,
/// <summary>
/// Informazioni importanti sull'operatività dell'applicazione.
/// Visibile in modalità normale e debug. Esempi: asta aggiunta, stato cambiato, operazioni completate.
/// </summary>
Info = 2,
/// <summary>
/// Conferme di operazioni riuscite con successo.
/// Visibile in modalità normale e debug. Esempi: puntata effettuata, login riuscito, salvataggio completato.
/// </summary>
Success = 3,
/// <summary>
/// Informazioni dettagliate per il debug dell'applicazione.
/// Visibile solo in modalità debug. Esempi: parametri chiamate API, valori variabili, flusso logico.
/// </summary>
Debug = 4,
/// <summary>
/// Tracciamento estremamente dettagliato di ogni operazione.
/// Visibile solo in modalità trace. Esempi: ogni singola chiamata, timestamp precisi, dati raw HTTP.
/// </summary>
Trace = 5
}
/// <summary>
/// Livello minimo di log da mostrare all'utente.
/// Controlla quali messaggi vengono effettivamente visualizzati nel log.
/// </summary>
internal enum MinimumLogLevel
{
/// <summary>
/// Solo errori critici (uso giornaliero minimo).
/// Mostra: Error
/// </summary>
ErrorOnly = LogLevel.Error,
/// <summary>
/// Errori e avvisi (uso giornaliero normale).
/// Mostra: Error, Warning
/// </summary>
Normal = LogLevel.Warning,
/// <summary>
/// Informazioni operative standard (uso giornaliero dettagliato).
/// Mostra: Error, Warning, Info, Success
/// </summary>
Informational = LogLevel.Success,
/// <summary>
/// Informazioni dettagliate per debug (sviluppo applicazione).
/// Mostra: Error, Warning, Info, Success, Debug
/// </summary>
Debug = LogLevel.Debug,
/// <summary>
/// Tracciamento completo di ogni operazione (debug avanzato).
/// Mostra: Error, Warning, Info, Success, Debug, Trace
/// </summary>
Trace = LogLevel.Trace
} }
} }

View File

@@ -6,21 +6,6 @@ namespace AutoBidder.Utilities
{ {
internal class AppSettings internal class AppSettings
{ {
public string? ExportPath { get; set; }
public string? LastExportExt { get; set; }
public string ExportScope { get; set; } = "All"; // All, Closed, Unknown
public bool IncludeOnlyUsedBids { get; set; } = true;
public bool IncludeLogs { get; set; } = false;
public bool IncludeUserBids { get; set; } = false;
// Added properties to match MainWindow expectations
public bool ExportOpen { get; set; } = true;
public bool ExportClosed { get; set; } = true;
public bool ExportUnknown { get; set; } = true;
public bool IncludeMetadata { get; set; } = true;
public bool RemoveAfterExport { get; set; } = false;
public bool OverwriteExisting { get; set; } = false;
// NUOVE IMPOSTAZIONI PREDEFINITE PER LE ASTE // NUOVE IMPOSTAZIONI PREDEFINITE PER LE ASTE
public int DefaultBidBeforeDeadlineMs { get; set; } = 200; public int DefaultBidBeforeDeadlineMs { get; set; } = 200;
public bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false; public bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false;
@@ -74,6 +59,15 @@ namespace AutoBidder.Utilities
/// Default: 20 (ultime 20 puntate) /// Default: 20 (ultime 20 puntate)
/// </summary> /// </summary>
public int MaxBidHistoryEntries { get; set; } = 20; public int MaxBidHistoryEntries { get; set; } = 20;
// ?? NUOVO: LIVELLO MINIMO LOG
/// <summary>
/// Livello minimo di log da visualizzare.
/// Valori: "ErrorOnly" (solo errori), "Normal" (errori e warning),
/// "Informational" (info standard), "Debug" (dettagli sviluppo), "Trace" (tutto).
/// Default: "Normal" (uso giornaliero - errori e warning)
/// </summary>
public string MinLogLevel { get; set; } = "Normal";
} }
internal static class SettingsManager internal static class SettingsManager