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.Windows;

View File

@@ -89,79 +89,7 @@
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="30,20">
<!-- SEZIONE 1: Impostazioni Export -->
<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 -->
<!-- SEZIONE 1: Impostazioni Predefinite Aste -->
<Border Background="#252526"
BorderBrush="#3E3E42"
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"/>
<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"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Prezzo Minimo (€)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/>
@@ -209,7 +137,7 @@
</StackPanel>
</Border>
<!-- SEZIONE 3: Stato Iniziale Aste -->
<!-- SEZIONE 2: Stato Iniziale Aste -->
<Border Background="#252526"
BorderBrush="#3E3E42"
BorderThickness="1"
@@ -294,7 +222,7 @@
</StackPanel>
</Border>
<!-- SEZIONE 4: Protezione Account -->
<!-- SEZIONE 3: Protezione Account -->
<Border Background="#252526"
BorderBrush="#3E3E42"
BorderThickness="1"
@@ -357,7 +285,7 @@
</StackPanel>
</Border>
<!-- SEZIONE 5: Limiti Log -->
<!-- SEZIONE 4: Limiti Log -->
<Border Background="#252526"
BorderBrush="#3E3E42"
BorderThickness="1"
@@ -439,6 +367,131 @@
</Border>
</StackPanel>
</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>
</ScrollViewer>

View File

@@ -25,26 +25,6 @@ namespace AutoBidder.Controls
// ?? NUOVO: Proprietà per limite storia puntate
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)
{
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
@@ -60,10 +40,7 @@ namespace AutoBidder.Controls
{
try
{
// 1. Salva impostazioni export
RaiseEvent(new RoutedEventArgs(SaveSettingsClickedEvent, this));
// 2. Salva impostazioni predefinite aste
// Salva impostazioni predefinite aste (export rimosso)
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
// UNICO MessageBox di conferma
@@ -88,44 +65,16 @@ namespace AutoBidder.Controls
private void CancelAllSettings_Click(object sender, RoutedEventArgs e)
{
// Annulla tutte le modifiche
RaiseEvent(new RoutedEventArgs(CancelSettingsClickedEvent, this));
RaiseEvent(new RoutedEventArgs(CancelDefaultsClickedEvent, this));
}
// Routed Events (cookie events RIMOSSI)
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));
// Routed Events
public static readonly RoutedEvent SaveDefaultsClickedEvent = EventManager.RegisterRoutedEvent(
"SaveDefaultsClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SettingsControl));
public static readonly RoutedEvent CancelDefaultsClickedEvent = EventManager.RegisterRoutedEvent(
"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
{
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
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
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)
{
try
@@ -239,6 +205,29 @@ namespace AutoBidder
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 ===
var loadAuctionsRemember = Settings.FindName("LoadAuctionsRemember") 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);
}
}
// === 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.Threading.Tasks;
using System.Windows;
using AutoBidder.Models;
using AutoBidder.ViewModels;
using AutoBidder.Utilities;
using AutoBidder.Services; // AGGIUNTO per RequestPriority e HtmlResponse
using AutoBidder.Services; // ? AGGIUNTO per RequestPriority e HtmlResponse
namespace AutoBidder
{
@@ -28,10 +28,10 @@ namespace AutoBidder
string? productName = null;
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"))
{
// È un URL - estrai ID e nome prodotto dall'URL stesso
// È un URL - estrai ID e nome prodotto dall'URL stesso
originalUrl = input.Trim();
auctionId = ExtractAuctionId(originalUrl);
if (string.IsNullOrEmpty(auctionId))
@@ -44,7 +44,7 @@ namespace AutoBidder
}
else
{
// È solo un ID numerico - costruisci URL generico
// È solo un ID numerico - costruisci URL generico
auctionId = input.Trim();
originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
}
@@ -52,11 +52,11 @@ namespace AutoBidder
// Verifica duplicati
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;
}
// MODIFICATO: Nome senza ID (già nella colonna separata)
// ? MODIFICATO: Nome senza ID (già nella colonna separata)
var displayName = string.IsNullOrEmpty(productName)
? $"Asta {auctionId}"
: DecodeAllHtmlEntities(productName);
@@ -64,7 +64,7 @@ namespace AutoBidder
// CARICA IMPOSTAZIONI PREDEFINITE SALVATE
var settings = Utilities.SettingsManager.Load();
// Determina stato iniziale dalla configurazione
// ? Determina stato iniziale dalla configurazione
bool isActive = false;
bool isPaused = false;
@@ -109,7 +109,7 @@ namespace AutoBidder
};
_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)
{
_auctionMonitor.Start();
@@ -124,7 +124,7 @@ namespace AutoBidder
var stateText = isActive ? (isPaused ? "Paused" : "Active") : "Stopped";
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))
{
_ = FetchAuctionNameInBackgroundAsync(auction, vm);
@@ -144,7 +144,7 @@ namespace AutoBidder
{
try
{
// USA IL SERVIZIO CENTRALIZZATO invece di HttpClient diretto
// ? USA IL SERVIZIO CENTRALIZZATO invece di HttpClient diretto
var response = await _htmlCacheService.GetHtmlAsync(
auction.OriginalUrl,
RequestPriority.Normal,
@@ -153,7 +153,7 @@ namespace AutoBidder
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;
}
@@ -163,9 +163,9 @@ namespace AutoBidder
if (match.Success)
{
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);
// MODIFICATO: Nome senza ID
// ? MODIFICATO: Nome senza ID
var newName = productName;
// Aggiorna il nome su thread UI
@@ -182,12 +182,12 @@ namespace AutoBidder
}
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)
{
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
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("&equals;", "=");
decoded = decoded.Replace("&minus;", "-");
decoded = decoded.Replace("&times;", "×");
decoded = decoded.Replace("&divide;", "÷");
decoded = decoded.Replace("&times;", "×");
decoded = decoded.Replace("&divide;", "÷");
decoded = decoded.Replace("&percnt;", "%");
decoded = decoded.Replace("&dollar;", "$");
decoded = decoded.Replace("&euro;", "€");
decoded = decoded.Replace("&pound;", "£");
decoded = decoded.Replace("&euro;", "€");
decoded = decoded.Replace("&pound;", "£");
return decoded;
}
@@ -236,7 +236,7 @@ namespace AutoBidder
// Verifica duplicati
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;
}
@@ -244,7 +244,7 @@ namespace AutoBidder
var name = $"Asta {auctionId}";
try
{
// USA IL SERVIZIO CENTRALIZZATO
// ? USA IL SERVIZIO CENTRALIZZATO
var response = await _htmlCacheService.GetHtmlAsync(url, RequestPriority.Normal);
if (response.Success)
@@ -261,7 +261,7 @@ namespace AutoBidder
// CARICA IMPOSTAZIONI PREDEFINITE SALVATE
var settings = Utilities.SettingsManager.Load();
// Determina stato iniziale dalla configurazione
// ? Determina stato iniziale dalla configurazione
bool isActive = false;
bool isPaused = false;
@@ -306,7 +306,7 @@ namespace AutoBidder
};
_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)
{
_auctionMonitor.Start();
@@ -353,12 +353,12 @@ namespace AutoBidder
{
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));
// Trova aste con nomi generici "Asta XXXX"
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();
if (auctionsWithGenericNames.Count > 0)
@@ -375,7 +375,7 @@ namespace AutoBidder
}
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
{
// Carica impostazioni
// ? Carica impostazioni
var settings = Utilities.SettingsManager.Load();
// Ottieni username corrente dalla sessione per ripristinare IsMyBid
@@ -409,10 +409,10 @@ namespace AutoBidder
// Protezione: rimuovi eventuali BidHistory null
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 { }
// 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))
{
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)
{
// 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
{
@@ -455,7 +455,7 @@ namespace AutoBidder
_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);
if (hasActiveOrPausedAuctions && auctions.Count > 0)
@@ -538,7 +538,7 @@ namespace AutoBidder
// Aggiorna Valore (Compra Subito)
if (auction.BuyNowPrice.HasValue)
{
AuctionMonitor.ProductBuyNowPriceText.Text = $"{auction.BuyNowPrice.Value:F2}€";
AuctionMonitor.ProductBuyNowPriceText.Text = $"{auction.BuyNowPrice.Value:F2}€";
}
else
{
@@ -548,7 +548,7 @@ namespace AutoBidder
// Aggiorna Spese di Spedizione
if (auction.ShippingCost.HasValue)
{
AuctionMonitor.ProductShippingCostText.Text = $"{auction.ShippingCost.Value:F2}€";
AuctionMonitor.ProductShippingCostText.Text = $"{auction.ShippingCost.Value:F2}€";
}
else
{
@@ -579,28 +579,28 @@ namespace AutoBidder
{
bool hasGenericName = auction.Name.StartsWith("Asta ") &&
!auction.Name.Contains("Shop") &&
!auction.Name.Contains("€") &&
!auction.Name.Contains("€") &&
!auction.Name.Contains("Buono") &&
!auction.Name.Contains("Carburante");
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(
auction.OriginalUrl,
RequestPriority.High, // Priorità alta per info prodotto
RequestPriority.High, // Priorità alta per info prodotto
bypassCache: false
);
if (!response.Success)
{
Log($"[PRODUCT INFO] Errore caricamento: {response.Error}", Utilities.LogLevel.Warn);
Log($"[PRODUCT INFO] Errore caricamento: {response.Error}", Utilities.LogLevel.Warning);
return;
}
bool updated = false;
// 1. Se nome generico, estrai nome reale dal <title>
// 1. ? Se nome generico, estrai nome reale dal <title>
if (hasGenericName)
{
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", "");
productName = DecodeAllHtmlEntities(productName);
// MODIFICATO: Nome senza ID
// ? MODIFICATO: Nome senza ID
var newName = productName;
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);
if (extracted)
{
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)
{
SaveAuctions();
@@ -650,7 +650,7 @@ namespace AutoBidder
}
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.Text.RegularExpressions;
using System.Windows;
@@ -41,7 +41,7 @@ namespace AutoBidder
Log("[START ALL] Tutte le aste avviate/riprese", LogLevel.Info);
}
// Salva gli stati aggiornati su disco
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
}
@@ -69,13 +69,13 @@ namespace AutoBidder
_isAutomationActive = false;
}
// Salva gli stati aggiornati su disco
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
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)
@@ -93,13 +93,13 @@ namespace AutoBidder
vm.IsPaused = true;
}
// Salva gli stati aggiornati su disco
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
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)
@@ -166,7 +166,7 @@ namespace AutoBidder
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
}
}
@@ -187,7 +187,7 @@ namespace AutoBidder
// Conferma rimozione
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",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
@@ -213,10 +213,10 @@ namespace AutoBidder
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)
{
// Se c'è ancora almeno un'asta nella lista
// Se c'è ancora almeno un'asta nella lista
int newIndex;
if (currentIndex >= _auctionViewModels.Count)
@@ -234,7 +234,7 @@ namespace AutoBidder
MultiAuctionsGrid.SelectedIndex = 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";
// 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);
}
// 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);
}), System.Windows.Threading.DispatcherPriority.Background);
}
@@ -278,7 +278,7 @@ namespace AutoBidder
// Conferma rimozione
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",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
@@ -368,7 +368,7 @@ namespace AutoBidder
}
// 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;
}
catch (Exception ex)
@@ -405,8 +405,8 @@ namespace AutoBidder
}
else
{
Log($"[WARN] Browser interno non ancora inizializzato", LogLevel.Warn);
MessageBox.Show("Il browser interno non è ancora pronto.\nRiprova tra qualche secondo.", "Browser", MessageBoxButton.OK, MessageBoxImage.Warning);
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);
}
}
catch (Exception ex)
@@ -456,12 +456,12 @@ namespace AutoBidder
try
{
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",
MessageBoxButton.OK,
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)
{
@@ -503,12 +503,12 @@ namespace AutoBidder
double shippingCost = auction.ShippingCost ?? 0;
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;
suggestedMaxPrice = Math.Round(suggestedMaxPrice, 2);
// 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
int maxClicksTheoretical = (int)Math.Floor((totalValue - suggestedMaxPrice) / 0.20);
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
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
var result = MessageBox.Show(
$"Limiti suggeriti (conservativi):\n\n" +
$"Max EUR: {suggestedMaxPrice:F2}€\n" +
$"Max EUR: {suggestedMaxPrice:F2}€\n" +
$"Max Clicks: {suggestedMaxClicks}\n\n" +
$"Applicare questi valori?",
"Conferma Limiti",
@@ -544,7 +544,7 @@ namespace AutoBidder
// SALVA
SaveAuctions();
Log($"[LIMITI] Applicati: MaxEUR={suggestedMaxPrice:F2}€, MaxClicks={suggestedMaxClicks}", LogLevel.Success);
Log($"[LIMITI] Applicati: MaxEUR={suggestedMaxPrice:F2}€, MaxClicks={suggestedMaxClicks}", LogLevel.Success);
}
catch (Exception ex)
{
@@ -574,8 +574,8 @@ namespace AutoBidder
if (currentIndex <= 0)
{
// Già in cima o non trovata
Log($"[MOVE] L'asta è già in cima alla lista", LogLevel.Info);
// Già in cima o non trovata
Log($"[MOVE] L'asta è già in cima alla lista", LogLevel.Info);
return;
}
@@ -615,8 +615,8 @@ namespace AutoBidder
if (currentIndex < 0 || currentIndex >= _auctionViewModels.Count - 1)
{
// Già in fondo o non trovata
Log($"[MOVE] L'asta è già in fondo alla lista", LogLevel.Info);
// Già in fondo o non trovata
Log($"[MOVE] L'asta è già in fondo alla lista", LogLevel.Info);
return;
}

View File

@@ -324,23 +324,6 @@ namespace AutoBidder
// ===== 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)
{
SaveDefaultsButton_Click(sender, e);

View File

@@ -6,27 +6,63 @@ using AutoBidder.Utilities;
namespace AutoBidder
{
/// <summary>
/// Logging functionality with color-coded severity levels
/// Logging functionality with color-coded severity levels and configurable minimum level filtering
/// </summary>
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)
{
Dispatcher.BeginInvoke(() =>
{
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 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
var color = level switch
{
LogLevel.Error => new SolidColorBrush(Color.FromRgb(232, 17, 35)), // #E81123 (Red)
LogLevel.Warn => new SolidColorBrush(Color.FromRgb(255, 183, 0)), // #FFB700 (Yellow/Orange)
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)
_ => new SolidColorBrush(Color.FromRgb(204, 204, 204)) // #CCCCCC (Light Gray)
LogLevel.Error => new SolidColorBrush(Color.FromRgb(232, 17, 35)), // #E81123 (Red)
LogLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 191, 0)), // #FFBF00 (Yellow)
LogLevel.Success => new SolidColorBrush(Color.FromRgb(0, 216, 0)), // #00D800 (Green)
LogLevel.Info => new SolidColorBrush(Color.FromRgb(100, 180, 255)), // #64B4FF (Light Blue)
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) };
@@ -34,8 +70,7 @@ namespace AutoBidder
p.Inlines.Add(r);
LogBox.Document.Blocks.Add(p);
// ? Mantieni solo gli ultimi N paragrafi (configurabile dalle impostazioni)
var settings = SettingsManager.Load();
// Mantieni solo gli ultimi N paragrafi (configurabile dalle impostazioni)
int maxLogLines = settings.MaxGlobalLogLines;
if (LogBox.Document.Blocks.Count > maxLogLines)

View File

@@ -184,7 +184,7 @@ namespace AutoBidder
else
{
SetUserBanner(string.Empty, 0);
Log("[SESSION] Sessione scaduta", LogLevel.Warn);
Log("[SESSION] Sessione scaduta", LogLevel.Warning);
CheckBrowserCookieAfterWebViewReady();
}
});
@@ -194,7 +194,7 @@ namespace AutoBidder
Dispatcher.Invoke(() =>
{
SetUserBanner(string.Empty, 0);
Log($"[SESSION] Errore verifica sessione: {ex.Message}", LogLevel.Warn);
Log($"[SESSION] Errore verifica sessione: {ex.Message}", LogLevel.Warning);
CheckBrowserCookieAfterWebViewReady();
});
}
@@ -231,7 +231,7 @@ namespace AutoBidder
{
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] 1. Click su 'Non connesso' nella sidebar", LogLevel.Info);
Log("[INFO] 2. Si aprirà la scheda Browser", LogLevel.Info);
@@ -263,7 +263,7 @@ namespace AutoBidder
}
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)
{
Log("[WARN] WebView2 non disponibile", LogLevel.Warn);
Log("[WARN] WebView2 non disponibile", LogLevel.Warning);
_webViewInitCompletionSource?.TrySetResult(false);
return;
}
@@ -160,7 +160,7 @@ namespace AutoBidder
}
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)
{
Log("[WARN] Timeout attesa inizializzazione WebView2", LogLevel.Warn);
Log("[WARN] Timeout attesa inizializzazione WebView2", LogLevel.Warning);
return false;
}
@@ -275,7 +275,7 @@ namespace AutoBidder
}
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;
}
}
@@ -289,7 +289,7 @@ namespace AutoBidder
{
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;
}
@@ -299,7 +299,7 @@ namespace AutoBidder
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;
}

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -19,7 +19,7 @@ namespace AutoBidder
private readonly AuctionMonitor _auctionMonitor;
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;
// UI State
@@ -69,19 +69,6 @@ namespace AutoBidder
public TextBox BrowserAddress => Browser.BrowserAddress;
// 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 CheckBox DefaultCheckAuctionOpen => Settings.DefaultCheckAuctionOpenCheckBox;
public TextBox DefaultMinPrice => Settings.DefaultMinPriceTextBox;
@@ -94,7 +81,7 @@ namespace AutoBidder
{
InitializeComponent();
// Inizializza HtmlCacheService con:
// ? Inizializza HtmlCacheService con:
// - Max 3 richieste concorrenti
// - Max 5 richieste al secondo
// - Cache di 5 minuti
@@ -110,13 +97,13 @@ namespace AutoBidder
// Inizializza servizi
_auctionMonitor = new AuctionMonitor();
// NUOVO: Inizializza SessionService
// ? NUOVO: Inizializza SessionService
InitializeSessionService();
// Initialize commands (from MainWindow.Commands.cs)
InitializeCommands();
// NUOVO: Inizializza validazione campi numerici
// ? NUOVO: Inizializza validazione campi numerici
InitializeNumericInputValidation();
this.DataContext = this;
@@ -127,7 +114,7 @@ namespace AutoBidder
_auctionMonitor.OnLog += AuctionMonitor_OnLog;
_auctionMonitor.OnResetCountChanged += AuctionMonitor_OnResetCountChanged;
// NUOVO: Registra evento stato connessione
// ? NUOVO: Registra evento stato connessione
AuctionMonitor.ConnectionStatusClicked += AuctionMonitor_ConnectionStatusClicked;
// Bind griglia
@@ -136,24 +123,51 @@ namespace AutoBidder
// Carica aste salvate (from MainWindow.AuctionManagement.cs)
LoadSavedAuctions();
// Load export settings (from MainWindow.EventHandlers.Export.cs)
LoadExportSettings();
// CARICA IMPOSTAZIONI PREDEFINITE ASTE
LoadDefaultSettings();
// Update initial button states
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)
InitializeUserInfoTimers();
// NUOVO: Carica sessione salvata
// ? NUOVO: Carica sessione salvata
LoadSavedSession();
// NUOVO: Pre-carica WebView2 in background per renderla subito disponibile
// ? NUOVO: Pre-carica WebView2 in background per renderla subito disponibile
InitializeWebView2();
// Attach WebView2 context menu handler
@@ -176,7 +190,7 @@ namespace AutoBidder
}
catch { }
// Timer per pulizia cache periodica (ogni 10 minuti)
// ? Timer per pulizia cache periodica (ogni 10 minuti)
var cacheCleanupTimer = new System.Windows.Threading.DispatcherTimer
{
Interval = TimeSpan.FromMinutes(10)
@@ -222,7 +236,7 @@ namespace AutoBidder
{
vm.UpdateState(state);
// NUOVO: Aggiorna storia puntate
// ? NUOVO: Aggiorna storia puntate
vm.RefreshBidHistory();
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -12,7 +12,7 @@ namespace AutoBidder.Services
{
/// <summary>
/// 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>
public class BidooApiClient
{
@@ -90,7 +90,7 @@ namespace AutoBidder.Services
if (!string.IsNullOrWhiteSpace(_session.CookieString))
{
request.Headers.Add("Cookie", _session.CookieString);
// Log rimosso per ridurre verbosità
// Log rimosso per ridurre verbosità
}
else
{
@@ -130,13 +130,13 @@ namespace AutoBidder.Services
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>
/// Estrae CSRF/Bid token dalla pagina asta
/// 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>
private async Task<(string? tokenName, string? tokenValue)> ExtractBidTokenAsync(string auctionId, string? auctionUrl = null)
{
@@ -187,12 +187,12 @@ namespace AutoBidder.Services
if (match.Success)
{
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);
}
}
Log("[TOKEN] No bid token found in HTML", auctionId);
Log("[TOKEN] ? No bid token found in HTML", auctionId);
return (null, null);
}
catch (Exception ex)
@@ -309,7 +309,7 @@ namespace AutoBidder.Services
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
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|...
if (!string.IsNullOrEmpty(historyData))
{
@@ -317,7 +317,8 @@ namespace AutoBidder.Services
}
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;
}
catch (Exception ex)
@@ -337,14 +338,14 @@ namespace AutoBidder.Services
{
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('|');
// Parsing prezzo corrente per calcolare i prezzi precedenti
if (!int.TryParse(currentPriceStr, out var currentPriceIndex))
return null;
// 📊 NUOVO: Carica impostazione limite visualizzazione puntate
// ?? NUOVO: Carica impostazione limite visualizzazione puntate
var settings = Utilities.SettingsManager.Load();
var maxEntries = settings?.MaxBidHistoryEntries ?? 20; // Default 20 se non impostato
@@ -353,7 +354,7 @@ namespace AutoBidder.Services
if (string.IsNullOrWhiteSpace(record))
continue;
// 📊 Limita il numero di puntate basandosi sulle impostazioni
// ?? Limita il numero di puntate basandosi sulle impostazioni
if (maxEntries > 0 && entries.Count >= maxEntries)
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))
{
_session.ShopCredit = credit;
Log($"[USER INFO PARSED] Shop credit: €{credit:F2}");
Log($"[USER INFO PARSED] Shop credit: €{credit:F2}");
foundData = true;
}
@@ -656,15 +657,15 @@ namespace AutoBidder.Services
/// STATI API BIDOO:
/// - ON: Asta attiva e in corso
/// - 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>
private AuctionStatus DetermineAuctionStatus(string apiStatus, bool hasWinner, bool iAmWinner, ref AuctionState state)
{
// Gestione stato STOP (pausa notturna)
if (apiStatus == "STOP")
{
// L'asta è iniziata ma è in pausa
// Controlla se c'è già un vincitore temporaneo
// L'asta è iniziata ma è in pausa
// Controlla se c'è già un vincitore temporaneo
if (hasWinner)
{
state.LastBidder = state.LastBidder; // Mantieni il last bidder
@@ -689,12 +690,12 @@ namespace AutoBidder.Services
// Asta attiva
if (hasWinner)
{
// Ci sono già puntate Running
// Ci sono già puntate ? Running
return AuctionStatus.Running;
}
// Nessuna puntata ancora Pending o Scheduled
// Se timer molto alto (> 30 minuti), è programmata per più tardi
// Nessuna puntata ancora ? Pending o Scheduled
// Se timer molto alto (> 30 minuti), è programmata per più tardi
if (state.Timer > 1800) // 30 minuti
{
return AuctionStatus.Scheduled;
@@ -899,7 +900,7 @@ namespace AutoBidder.Services
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)
{
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
{
/// <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
{
Info,
Success,
Warn,
Error
/// <summary>
/// Errori critici che impediscono il funzionamento dell'applicazione o di una funzionalità.
/// Sempre visibile. Esempi: errori di connessione, parsing fallito, eccezioni non gestite.
/// </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
{
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
public int DefaultBidBeforeDeadlineMs { get; set; } = 200;
public bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false;
@@ -74,6 +59,15 @@ namespace AutoBidder.Utilities
/// Default: 20 (ultime 20 puntate)
/// </summary>
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