* Aggiornamento alla versione 4.0.0 * Refactoring architetturale: introdotte partial classes e UserControls modulari per migliorare manutenibilità e leggibilità. * Aggiunti nuovi UserControls: `AuctionMonitorControl`, `BrowserControl`, `SettingsControl`, `StatisticsControl`. * Introdotto supporto per WebView2 per il browser integrato. * Migliorata gestione delle aste: aggiunta/rimozione tramite URL o ID, configurazione predefinita. * Nuove funzionalità di esportazione: supporto CSV, JSON, XML con opzioni configurabili. * Logging avanzato: codifica colore per severità e auto-scroll. * Tema scuro moderno e miglioramenti UI/UX: sidebar di navigazione, griglie virtualizzate, icone emoji. * Persistenza dati: salvataggio automatico di aste e impostazioni in file JSON. * Documentazione aggiornata: `README.md`, `CHANGELOG.md` e nuovi file di supporto. * Miglioramenti alla sicurezza: cookie di sessione salvati in modo sicuro con DPAPI. * Preparazione per future estensioni: placeholder per funzionalità avanzate e struttura modulare.
350 lines
18 KiB
C#
350 lines
18 KiB
C#
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 { }
|
|
// Note: Progress bar rimosso con refactoring Statistics
|
|
}
|
|
|
|
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))
|
|
{
|
|
var obj = new
|
|
{
|
|
AuctionId = a.AuctionId,
|
|
Name = a.Name,
|
|
OriginalUrl = a.OriginalUrl,
|
|
MinPrice = a.MinPrice,
|
|
MaxPrice = a.MaxPrice,
|
|
TimerClick = a.TimerClick,
|
|
DelayMs = a.DelayMs,
|
|
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))
|
|
{
|
|
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("TimerClick", a.TimerClick),
|
|
new XElement("DelayMs", a.DelayMs),
|
|
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
|
|
{
|
|
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($"TimerClick,{a.TimerClick}");
|
|
sw.WriteLine($"DelayMs,{a.DelayMs}");
|
|
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("\"", "\"\"");
|
|
}
|
|
}
|
|
}
|