Tutto ciò che l'applicazione registra sta ora in due file SQLite in %LocalAppData%\AutoBidder\Database: autobidder.sqlite per le osservazioni e esercizio.sqlite per i documenti d'esercizio (aste nel monitor, prodotti, promozioni, modelli appresi con prefisso ml/) e per i registri applicativo e del riscatto puntate. Il motore comune è SqliteDatabase; i file JSON e la cartella Apprendimento delle versioni precedenti vengono importati la prima volta e lasciati dove sono. La cartella si cambia dalle Impostazioni: al salvataggio si chiede se spostare i file, e si riavvia. Le cartelle Dati/Statistiche/Registri, i registri su file, le misure dell'anticipo e le impostazioni morte (LogBids, AutoApplyProductDefaults, NewAuctionLimitsPriority…) non esistono più. Il kill-switch se ne va: al suo posto un solo interruttore in barra, accanto ad Avvia/Osserva/Ferma, che spegne l'apprendimento. Spento, il motore punta solo entro i limiti dell'utente (prezzo, puntate, budget, fascia oraria, rischio) con l'anticipo fisso: niente valore atteso, regime, duello, bandit, anticipo adattivo. Le aste vengono comunque registrate e studiate. Tre difetti visti osservando il motore dal vivo per un'ora, in Osserva: - la copertura «Compralo Ora» con V lasciato al listino azzerava la perdita coperta e il motore diceva BID a ogni scadenza con P dell'1%: ora vale solo per i prodotti con «Valore reale €» scritto, altrimenti la puntata si conta persa e P decide; - la chiusura di un'asta arrivava due volte (fine, poi rimozione dal monitor) e di nuovo al riavvio: modello, profilo, bandit e statistiche per prodotto contavano ogni asta due volte. Il monitor la comunica una volta sola, l'apprendimento salta le aste già apprese, e le schede prodotto vengono ricostruite una volta dallo storico; - la pagina delle ricompense veniva scambiata per la pagina di accesso perché porta un collegamento a /login.php: prima si guarda se è la pagina delle ricompense. Interfaccia: la scheda Esporta e tutto il suo codice sono tolti; le esportazioni chiedono sempre dove salvare. Prodotti, Storico e Apprendimento hanno barre a sole icone con tooltip che dicono esattamente cosa succede, colorate come il monitor, con i nuovi pulsanti di pulizia (spegni tutte le stelline, togli i non seguiti, pulizia completa; seleziona tutte / elimina le selezionate / svuota lo storico). Impostazioni: sezione Database, Manutenzione con «Elimina tutti i dati (tranne la login)» e «Impostazioni di fabbrica», esportazione dei registri in testo. Modifiche.txt, il prompt ormai realizzato, esce dal repository. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
242 lines
9.7 KiB
C#
242 lines
9.7 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Windows;
|
|
using AutoBidder.Data;
|
|
using AutoBidder.Utilities;
|
|
|
|
namespace AutoBidder
|
|
{
|
|
/// <summary>
|
|
/// La cartella dei database (con la migrazione quando cambia) e le esportazioni dello
|
|
/// storico, che chiedono sempre dove salvare.
|
|
/// </summary>
|
|
public partial class MainWindow
|
|
{
|
|
// ── Cartella dei database ────────────────────────────────────────
|
|
|
|
/// <summary>Il campo mostra sempre il percorso in uso: selezionabile, copiabile.</summary>
|
|
private void RefreshDataFolderFields()
|
|
{
|
|
try { Settings.DatabaseFolderTextBox.Text = AppPaths.DatabaseFolder; }
|
|
catch { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Traduce quello che c'è nel campo in ciò che va salvato: vuoto = predefinito,
|
|
/// uguale al predefinito = vuoto, un percorso davvero diverso = quel percorso.
|
|
/// </summary>
|
|
private static string NormalizeFolderChoice(string? typed, string resolvedDefault, string previouslySaved)
|
|
{
|
|
var text = typed?.Trim() ?? "";
|
|
|
|
if (text.Length == 0) return "";
|
|
|
|
if (string.Equals(text, resolvedDefault, StringComparison.OrdinalIgnoreCase) &&
|
|
string.IsNullOrWhiteSpace(previouslySaved))
|
|
return "";
|
|
|
|
return text;
|
|
}
|
|
|
|
private void Settings_BrowseDatabaseFolderClicked(object sender, RoutedEventArgs e)
|
|
{
|
|
var chosen = PickFolder("Scegli la cartella dei database (non una cartella sincronizzata)", AppPaths.DatabaseFolder);
|
|
if (chosen != null) Settings.DatabaseFolderTextBox.Text = chosen;
|
|
}
|
|
|
|
private void Settings_OpenDatabaseFolderClicked(object sender, RoutedEventArgs e)
|
|
=> OpenFolder(AppPaths.DatabaseFolder);
|
|
|
|
/// <summary>
|
|
/// Selettore di cartella. Usa quello di WinForms perché WPF non ne offre uno.
|
|
/// </summary>
|
|
private static string? PickFolder(string description, string startFrom)
|
|
{
|
|
try
|
|
{
|
|
using var dialog = new System.Windows.Forms.FolderBrowserDialog
|
|
{
|
|
Description = description,
|
|
UseDescriptionForTitle = true,
|
|
SelectedPath = Directory.Exists(startFrom) ? startFrom : "",
|
|
ShowNewFolderButton = true
|
|
};
|
|
|
|
return dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK
|
|
? dialog.SelectedPath
|
|
: null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void OpenFolder(string path)
|
|
{
|
|
try
|
|
{
|
|
Directory.CreateDirectory(path);
|
|
Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[DATI] Impossibile aprire {path}: {ex.Message}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dopo un salvataggio con la cartella dei database cambiata: chiede se spostare i
|
|
/// file nella nuova posizione, li sposta a database chiusi, e riavvia. Senza
|
|
/// riavvio gli archivi terrebbero in memoria i dati letti dalla posizione vecchia.
|
|
/// </summary>
|
|
private void ApplyDatabaseFolderSetting(AppSettings settings, string previousFolder)
|
|
{
|
|
var newFolder = settings.DatabaseFolder.Length > 0 ? settings.DatabaseFolder : AppPaths.DefaultDatabaseFolder;
|
|
|
|
try { newFolder = Path.GetFullPath(newFolder); } catch { }
|
|
|
|
if (string.Equals(previousFolder.TrimEnd('\\'), newFolder.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
RefreshDataFolderFields();
|
|
return;
|
|
}
|
|
|
|
var answer = MessageBox.Show(this,
|
|
$"La cartella dei database cambia da\n{previousFolder}\na\n{newFolder}\n\n" +
|
|
"Vuoi spostare i database esistenti nella nuova cartella?\n\n" +
|
|
"Sì: sposto i file e riavvio l'applicazione.\n" +
|
|
"No: la nuova cartella parte vuota (i vecchi file restano dove sono) e riavvio.\n" +
|
|
"Annulla: torno alla cartella di prima.",
|
|
"Cartella dei database", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
|
|
|
|
if (answer == MessageBoxResult.Cancel)
|
|
{
|
|
settings.DatabaseFolder = string.Equals(previousFolder, AppPaths.DefaultDatabaseFolder, StringComparison.OrdinalIgnoreCase) ? "" : previousFolder;
|
|
SettingsManager.Save(settings);
|
|
RefreshDataFolderFields();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (answer == MessageBoxResult.Yes)
|
|
{
|
|
// Prima si chiudono: un file SQLite aperto non si sposta, e il giornale
|
|
// WAL va con lui.
|
|
AuctionDatabase.CloseInstance();
|
|
OperationalDatabase.CloseInstance();
|
|
Directory.CreateDirectory(newFolder);
|
|
|
|
var moved = 0;
|
|
foreach (var db in new[] { AppPaths.DatabaseFile, AppPaths.OperationalDatabaseFile })
|
|
{
|
|
foreach (var f in AppPaths.DatabaseFiles(db))
|
|
{
|
|
var target = Path.Combine(newFolder, Path.GetFileName(f));
|
|
if (File.Exists(target)) File.Delete(target);
|
|
File.Move(f, target);
|
|
moved++;
|
|
}
|
|
}
|
|
|
|
Log($"[DATI] Database spostati in {newFolder} ({moved} file). Riavvio.", LogLevel.Warning);
|
|
}
|
|
else
|
|
{
|
|
Log($"[DATI] Nuova cartella dei database: {newFolder} (vuota). Riavvio.", LogLevel.Warning);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show(this, $"Spostamento non riuscito: {ex.Message}\n\nLa cartella resta quella di prima.",
|
|
"Cartella dei database", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
settings.DatabaseFolder = string.Equals(previousFolder, AppPaths.DefaultDatabaseFolder, StringComparison.OrdinalIgnoreCase) ? "" : previousFolder;
|
|
SettingsManager.Save(settings);
|
|
RefreshDataFolderFields();
|
|
return;
|
|
}
|
|
|
|
RestartApplication();
|
|
}
|
|
|
|
/// <summary>Riavvia l'applicazione: lo stesso eseguibile, e questa istanza si chiude.</summary>
|
|
private void RestartApplication()
|
|
{
|
|
try
|
|
{
|
|
var exe = Environment.ProcessPath;
|
|
if (!string.IsNullOrEmpty(exe))
|
|
Process.Start(new ProcessStartInfo { FileName = exe, UseShellExecute = true });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[APP] Riavvio non riuscito: {ex.Message}. Riapri l'applicazione a mano.", LogLevel.Error);
|
|
}
|
|
|
|
Application.Current.Shutdown();
|
|
}
|
|
|
|
// ── Esportazione dello storico: chiede sempre dove ───────────────
|
|
|
|
private void ExportStatsCsvButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var path = AskWhereToSave("Esporta lo storico in CSV", $"storico-aste-{DateTime.Now:yyyyMMdd-HHmm}.csv", "CSV (foglio di calcolo)|*.csv");
|
|
if (path == null) return;
|
|
RunExport(StatsExporter.ExportCsv(path), "CSV");
|
|
}
|
|
|
|
private void ExportStatsJsonButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var path = AskWhereToSave("Esporta lo storico in JSON", $"storico-aste-{DateTime.Now:yyyyMMdd-HHmm}.json", "JSON|*.json");
|
|
if (path == null) return;
|
|
RunExport(StatsExporter.ExportJson(path), "JSON");
|
|
}
|
|
|
|
/// <summary>La finestra «Salva con nome»: nessun percorso predefinito, decide l'utente.</summary>
|
|
private string? AskWhereToSave(string title, string fileName, string filter)
|
|
{
|
|
try
|
|
{
|
|
var dialog = new Microsoft.Win32.SaveFileDialog
|
|
{
|
|
Title = title,
|
|
FileName = fileName,
|
|
Filter = filter,
|
|
OverwritePrompt = true,
|
|
AddExtension = true
|
|
};
|
|
return dialog.ShowDialog(this) == true ? dialog.FileName : null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ESPORTA] Finestra di salvataggio non disponibile: {ex.Message}", LogLevel.Error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void RunExport(StatsExporter.Result result, string format)
|
|
{
|
|
if (!result.Success)
|
|
{
|
|
MessageBox.Show(this, result.Error ?? "Esportazione non riuscita.",
|
|
$"Esporta {format}", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
return;
|
|
}
|
|
|
|
Log($"[EXPORT] {result.Records} aste esportate in {result.Path}", LogLevel.Success);
|
|
|
|
var answer = MessageBox.Show(this,
|
|
$"Esportate {result.Records} aste in formato {format}.\n\n{result.Path}\n\n" +
|
|
"Vuoi aprire la cartella?",
|
|
$"Esporta {format}", MessageBoxButton.YesNo, MessageBoxImage.Information);
|
|
|
|
if (answer == MessageBoxResult.Yes)
|
|
{
|
|
OpenFolder(Path.GetDirectoryName(result.Path) ?? AppPaths.ConfigRoot);
|
|
}
|
|
}
|
|
}
|
|
}
|