From fc7175c2f7ebcdd6b254847882219e83ea0bafd1 Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Wed, 4 Feb 2026 22:10:16 +0100 Subject: [PATCH] Fixes to settings --- imagecatalog/DataModel.cs | 44 +++ imagecatalog/MainForm.Designer.cs | 7 +- imagecatalog/MainForm.cs | 66 ++-- imagecatalog/Models/SettingsDto.cs | 231 +++++++++++ imagecatalog/ParametriSetup.cs | 464 ++++++++++++----------- imagecatalog/Services/SettingsService.cs | 147 +++++-- 6 files changed, 660 insertions(+), 299 deletions(-) create mode 100644 imagecatalog/Models/SettingsDto.cs diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs index 95fefb9..ce02440 100644 --- a/imagecatalog/DataModel.cs +++ b/imagecatalog/DataModel.cs @@ -730,6 +730,50 @@ namespace ImageCatalog_2 } } + private string _bigPhotoSuffix = ""; + public string BigPhotoSuffix + { + get => _bigPhotoSuffix; + set + { + _bigPhotoSuffix = value; + NotifyPropertyChanged(); + } + } + + private bool _addTextToThumbnails; + public bool AddTextToThumbnails + { + get => _addTextToThumbnails; + set + { + _addTextToThumbnails = value; + NotifyPropertyChanged(); + } + } + + private bool _addRaceTimeToThumbnails; + public bool AddRaceTimeToThumbnails + { + get => _addRaceTimeToThumbnails; + set + { + _addRaceTimeToThumbnails = value; + NotifyPropertyChanged(); + } + } + + private bool _addNumberAndTimeToThumbnails; + public bool AddNumberAndTimeToThumbnails + { + get => _addNumberAndTimeToThumbnails; + set + { + _addNumberAndTimeToThumbnails = value; + NotifyPropertyChanged(); + } + } + private void Test(object parameter) { Debug.WriteLine("Yep"); diff --git a/imagecatalog/MainForm.Designer.cs b/imagecatalog/MainForm.Designer.cs index 79fafe8..fdee7ba 100644 --- a/imagecatalog/MainForm.Designer.cs +++ b/imagecatalog/MainForm.Designer.cs @@ -947,6 +947,7 @@ namespace ImageCatalog // // TextBox18 // + TextBox18.DataBindings.Add(new Binding("Text", bindingSource1, "TimeLabel", true, DataSourceUpdateMode.OnPropertyChanged)); TextBox18.Location = new Point(502, 384); TextBox18.Margin = new Padding(6, 8, 6, 8); TextBox18.Name = "TextBox18"; @@ -966,6 +967,7 @@ namespace ImageCatalog // // DateTimePicker1 // + DateTimePicker1.DataBindings.Add(new Binding("Value", bindingSource1, "RaceStartDate", true, DataSourceUpdateMode.OnPropertyChanged)); DateTimePicker1.Format = DateTimePickerFormat.Time; DateTimePicker1.Location = new Point(850, 384); DateTimePicker1.Margin = new Padding(6, 8, 6, 8); @@ -1154,12 +1156,12 @@ namespace ImageCatalog // // TextBox26 // + TextBox26.DataBindings.Add(new Binding("Text", bindingSource1, "BigPhotoSuffix", true, DataSourceUpdateMode.OnPropertyChanged)); TextBox26.Location = new Point(486, 118); TextBox26.Margin = new Padding(6, 8, 6, 8); TextBox26.Name = "TextBox26"; TextBox26.Size = new Size(116, 39); TextBox26.TabIndex = 20; - TextBox26.Text = "TextBox26"; // // Label37 // @@ -1339,6 +1341,7 @@ namespace ImageCatalog // // RadioButton3 // + RadioButton3.DataBindings.Add(new Binding("Checked", bindingSource1, "AddTextToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged)); RadioButton3.AutoSize = true; RadioButton3.Location = new Point(32, 40); RadioButton3.Margin = new Padding(6, 8, 6, 8); @@ -1351,6 +1354,7 @@ namespace ImageCatalog // // RadioButton7 // + RadioButton7.DataBindings.Add(new Binding("Checked", bindingSource1, "AddNumberAndTimeToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged)); RadioButton7.AutoSize = true; RadioButton7.Location = new Point(280, 99); RadioButton7.Margin = new Padding(6, 8, 6, 8); @@ -1387,6 +1391,7 @@ namespace ImageCatalog // // RadioButton5 // + RadioButton5.DataBindings.Add(new Binding("Checked", bindingSource1, "AddRaceTimeToThumbnails", true, DataSourceUpdateMode.OnPropertyChanged)); RadioButton5.AutoSize = true; RadioButton5.Location = new Point(32, 160); RadioButton5.Margin = new Padding(6, 8, 6, 8); diff --git a/imagecatalog/MainForm.cs b/imagecatalog/MainForm.cs index 6bc9c2c..f18ab89 100644 --- a/imagecatalog/MainForm.cs +++ b/imagecatalog/MainForm.cs @@ -115,31 +115,26 @@ public partial class MainForm private void SetDefaults() { - // Model defaults are already set in DataModel constructor, just bind ComboBoxes + // Bind ComboBoxes to Model using proper data binding ComboBox1.DataSource = new List(Model.VerticalPositions); - ComboBox1.SelectedItem = Model.VerticalPosition; + ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition), + false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox2.DataSource = new List(Model.HorizontalAlignments); - ComboBox2.SelectedItem = Model.HorizontalAlignment; + ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment), + false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox3.DataSource = new List(Model.AvailableFonts); - ComboBox3.SelectedItem = Model.FontName; + ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName), + false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox4.DataSource = new List(Model.HorizontalAlignments); - ComboBox4.SelectedItem = Model.LogoHorizontalPosition; + ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition), + false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox5.DataSource = new List { "Alto", "Centro", "Basso" }; - ComboBox5.SelectedItem = Model.LogoVerticalPosition; - } - - private void RefreshComboBoxSelections() - { - // Update ComboBox selections to reflect loaded Model values - ComboBox1.SelectedItem = Model.VerticalPosition; - ComboBox2.SelectedItem = Model.HorizontalAlignment; - ComboBox3.SelectedItem = Model.FontName; - ComboBox4.SelectedItem = Model.LogoHorizontalPosition; - ComboBox5.SelectedItem = Model.LogoVerticalPosition; + ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition), + false, DataSourceUpdateMode.OnPropertyChanged)); } @@ -289,18 +284,28 @@ public partial class MainForm if (openDialog.ShowDialog() != DialogResult.OK) return; - await Model.LoadSettingsFromFileAsync(openDialog.FileName); - - // Refresh ComboBox selections to reflect loaded settings - RefreshComboBoxSelections(); - - // Update logo preview if logo file exists - if (File.Exists(Model.LogoFile)) + try { - UpdateLogoPictureBox(Model.LogoFile); - } + await Model.LoadSettingsFromFileAsync(openDialog.FileName); + + // Explicitly ensure UI is enabled after loading + Model.UiEnabled = true; + + // Update logo preview if logo file exists + if (File.Exists(Model.LogoFile)) + { + UpdateLogoPictureBox(Model.LogoFile); + } - Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName); + Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName); + + _logger.LogInformation($"Settings loaded successfully from {openDialog.FileName}"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading settings"); + MessageBox.Show($"Error loading settings: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } private void OnSelectColorRequested(object sender, EventArgs e) @@ -380,6 +385,7 @@ public partial class MainForm _picSettings.LarghezzaBig = Model.PhotoBigWidth; _picSettings.FotoGrandeDimOrigina = Model.KeepOriginalDimensions; _picSettings.JpegQuality = Model.JpegQuality; + _picSettings.Codice = Model.BigPhotoSuffix; // Logo settings from Model _picSettings.LogoAggiungi = Model.AddLogo; @@ -414,10 +420,10 @@ public partial class MainForm _picSettings.TestoOrario = Model.TimeLabel; _picSettings.TestoMin = Model.ShowFileNameOnThumbnails; - // Controls that still need binding (TODO: move to Model) - _picSettings.AggiungiScritteMiniature = RadioButton3.Checked; - _picSettings.AggTempoGaraMin = RadioButton5.Checked; - _picSettings.AggNumTempMin = RadioButton7.Checked; + // Thumbnail text options from Model + _picSettings.AggiungiScritteMiniature = Model.AddTextToThumbnails; + _picSettings.AggTempoGaraMin = Model.AddRaceTimeToThumbnails; + _picSettings.AggNumTempMin = Model.AddNumberAndTimeToThumbnails; } private void setLabel18Text(string text) diff --git a/imagecatalog/Models/SettingsDto.cs b/imagecatalog/Models/SettingsDto.cs new file mode 100644 index 0000000..d5a01b2 --- /dev/null +++ b/imagecatalog/Models/SettingsDto.cs @@ -0,0 +1,231 @@ +using System; +using System.Text.Json.Serialization; +using System.Xml.Serialization; + +namespace ImageCatalog_2.Models +{ + /// + /// Data Transfer Object for application settings. + /// Uses attributes to control serialization behavior for both XML (legacy) and JSON (future). + /// + public class SettingsDto + { + // Paths + [JsonPropertyName("SourcePath")] + [XmlElement("DirSorgente")] + public string SourcePath { get; set; } + + [JsonPropertyName("DestinationPath")] + [XmlElement("DirDestinazione")] + public string DestinationPath { get; set; } + + // Thumbnails + [JsonPropertyName("CreateThumbnails")] + [XmlElement("MiniatureCrea")] + public bool CreateThumbnails { get; set; } = true; + + [JsonPropertyName("ThumbnailPrefix")] + [XmlElement("MiniatureSuffisso")] + public string ThumbnailPrefix { get; set; } = "tn_"; + + [JsonPropertyName("ThumbnailHeight")] + [XmlElement("MiniatureAltezza")] + public int ThumbnailHeight { get; set; } = 350; + + [JsonPropertyName("ThumbnailWidth")] + [XmlElement("MiniatureLarghezza")] + public int ThumbnailWidth { get; set; } = 350; + + [JsonPropertyName("FontSizeThumbnail")] + [XmlElement("FontDimensioneMiniatura")] + public int FontSizeThumbnail { get; set; } = 50; + + [JsonPropertyName("JpegQualityThumbnail")] + [XmlElement("CompressioneJpegMiniatura")] + public int JpegQualityThumbnail { get; set; } = 30; + + [JsonPropertyName("AddTimeToThumbnails")] + [XmlElement("MiniatureAddOrario")] + public bool AddTimeToThumbnails { get; set; } + + [JsonPropertyName("ShowFileNameOnThumbnails")] + [XmlElement("NomeMiniatura")] + public bool ShowFileNameOnThumbnails { get; set; } + + [JsonPropertyName("AddTextToThumbnails")] + [XmlElement("MiniatureAddScritta")] + public bool AddTextToThumbnails { get; set; } + + [JsonPropertyName("AddRaceTimeToThumbnails")] + [XmlElement("TempoSmall")] + public bool AddRaceTimeToThumbnails { get; set; } + + [JsonPropertyName("AddNumberAndTimeToThumbnails")] + [XmlElement("NumTempoSmall")] + public bool AddNumberAndTimeToThumbnails { get; set; } + + // Big photo + [JsonPropertyName("BigPhotoSuffix")] + [XmlElement("FotoCodice")] + public string BigPhotoSuffix { get; set; } = ""; + + [JsonPropertyName("PhotoBigHeight")] + [XmlElement("FotoAltezza")] + public int PhotoBigHeight { get; set; } = 2240; + + [JsonPropertyName("PhotoBigWidth")] + [XmlElement("FotoLarghezza")] + public int PhotoBigWidth { get; set; } = 2240; + + [JsonPropertyName("KeepOriginalDimensions")] + [XmlElement("FotoDimOriginali")] + public bool KeepOriginalDimensions { get; set; } + + [JsonPropertyName("JpegQuality")] + [XmlElement("CompressioneJpeg")] + public int JpegQuality { get; set; } = 85; + + // Font + [JsonPropertyName("FontSize")] + [XmlElement("FontDimensione")] + public int FontSize { get; set; } = 20; + + [JsonPropertyName("FontName")] + [XmlElement("FontNome")] + public string FontName { get; set; } = "Arial"; + + [JsonPropertyName("FontBold")] + [XmlElement("FontBold")] + public bool FontBold { get; set; } + + [JsonPropertyName("TextColorRGB")] + [XmlElement("ColoreTestoRGB")] + public string TextColorRGB { get; set; } = "Yellow"; + + // Text + [JsonPropertyName("HorizontalText")] + [XmlElement("TestoTesto")] + public string HorizontalText { get; set; } + + [JsonPropertyName("VerticalText")] + [XmlElement("TestoVerticale")] + public string VerticalText { get; set; } + + [JsonPropertyName("TextTransparency")] + [XmlElement("TestoTrasparente")] + public int TextTransparency { get; set; } + + [JsonPropertyName("TextMargin")] + [XmlElement("TestoMargine")] + public int TextMargin { get; set; } = 8; + + [JsonPropertyName("VerticalPosition")] + [XmlElement("TestoPosizione")] + public string VerticalPosition { get; set; } = "Basso"; + + [JsonPropertyName("HorizontalAlignment")] + [XmlElement("TestoAllineamento")] + public string HorizontalAlignment { get; set; } = "Centro"; + + [JsonPropertyName("VerticalTextSize")] + [XmlElement("GrandezzaVerticale")] + public int VerticalTextSize { get; set; } = 20; + + [JsonPropertyName("VerticalTextMargin")] + [XmlElement("MargineVerticale")] + public int VerticalTextMargin { get; set; } = 6; + + // Logo/Watermark + [JsonPropertyName("LogoFile")] + [XmlElement("MarchioFile")] + public string LogoFile { get; set; } = ""; + + [JsonPropertyName("LogoHeight")] + [XmlElement("MarchioAltezza")] + public int LogoHeight { get; set; } = 430; + + [JsonPropertyName("LogoWidth")] + [XmlElement("MarchioLarghezza")] + public int LogoWidth { get; set; } = 430; + + [JsonPropertyName("LogoMargin")] + [XmlElement("MarchioMargine")] + public int LogoMargin { get; set; } = 290; + + [JsonPropertyName("LogoHorizontalPosition")] + [XmlElement("MarchioAllOrizzontale")] + public string LogoHorizontalPosition { get; set; } = "Destra"; + + [JsonPropertyName("LogoVerticalPosition")] + [XmlElement("MarchioAllVerticale")] + public string LogoVerticalPosition { get; set; } = "Basso"; + + [JsonPropertyName("LogoTransparency")] + [XmlElement("MarchioTrasparenza")] + public int LogoTransparency { get; set; } = 100; + + [JsonPropertyName("AddLogo")] + [XmlElement("MarchioAggiungi")] + public bool AddLogo { get; set; } + + // Options + [JsonPropertyName("ForceJpeg")] + [XmlElement("GeneraleForzaJpg")] + public bool ForceJpeg { get; set; } + + [JsonPropertyName("AutomaticRotation")] + [XmlElement("GeneraleRotazioneAutomatica")] + public bool AutomaticRotation { get; set; } + + [JsonPropertyName("AddRaceTime")] + [XmlElement("TempoGara")] + public bool AddRaceTime { get; set; } + + [JsonPropertyName("AddTime")] + [XmlElement("Orario")] + public bool AddTime { get; set; } + + [JsonPropertyName("TimeLabel")] + [XmlElement("EtichettaOrario")] + public string TimeLabel { get; set; } = ""; + + [JsonPropertyName("ShowDate")] + [XmlElement("DataFoto")] + public bool ShowDate { get; set; } + + [JsonPropertyName("ShowPhotoNumber")] + [XmlElement("NumeroFoto")] + public bool ShowPhotoNumber { get; set; } + + [JsonPropertyName("OverwriteImages")] + [XmlElement("GeneraleSovrascriviFile")] + public bool OverwriteImages { get; set; } + + // Folder division + [JsonPropertyName("FilesPerFolder")] + [XmlElement("DirDividiNumFile")] + public int FilesPerFolder { get; set; } = 99; + + [JsonPropertyName("FolderSuffix")] + [XmlElement("DirDividiSuffisso")] + public string FolderSuffix { get; set; } = ""; + + [JsonPropertyName("CounterDigits")] + [XmlElement("DirDividiNumCifre")] + public int CounterDigits { get; set; } = 2; + + // Processing + [JsonPropertyName("ChunkSize")] + [XmlElement("ChunkSize")] + public int ChunkSize { get; set; } + + [JsonPropertyName("ThreadsCount")] + [XmlElement("ThreadsCount")] + public int ThreadsCount { get; set; } + + // Race date + [JsonPropertyName("RaceStartDate")] + [XmlElement("DataPartenza")] + public DateTime RaceStartDate { get; set; } = DateTime.Now; + } +} diff --git a/imagecatalog/ParametriSetup.cs b/imagecatalog/ParametriSetup.cs index fa9df13..4dcf92c 100644 --- a/imagecatalog/ParametriSetup.cs +++ b/imagecatalog/ParametriSetup.cs @@ -1,239 +1,243 @@ -using System; -using System.Data; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; -namespace ImageCatalog +namespace ImageCatalog; + +/// +/// Modern parameter storage service using XML for persistence. +/// Thread-safe key-value store for application settings. +/// +public class ParametriSetup { - public class ParametriSetup + private readonly object _lock = new(); + private readonly ILogger _logger; + private Dictionary _parameters = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets the XML file path for settings storage. + /// + public string? NomeFileSetup { get; set; } + + /// + /// Initializes a new instance with the specified settings file. + /// + /// Path to the XML settings file. + /// Optional logger for diagnostics. + public ParametriSetup(string? fileSetup = null, ILogger? logger = null) { - private DataSet _elencoParametri; - public string NomeFileSetup { get; set; } - - public ParametriSetup(string fileSetup) + _logger = logger ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; + NomeFileSetup = fileSetup; + if (!string.IsNullOrWhiteSpace(fileSetup)) { - _elencoParametri = new DataSet(); - NomeFileSetup = fileSetup; - if (!string.IsNullOrWhiteSpace(fileSetup)) - { - CaricaParametriSetup(); - } - } - - public ParametriSetup() : this(string.Empty) - { - } - - public void CaricaParametriSetup() - { - if (string.IsNullOrEmpty(NomeFileSetup) || !File.Exists(NomeFileSetup)) - { - _elencoParametri = new DataSet(); - return; - } - - _elencoParametri = LeggiXmlDataSet("Setup", NomeFileSetup, "Nome"); - } - - public void SalvaParametriSetup() - { - if (string.IsNullOrWhiteSpace(NomeFileSetup)) - throw new InvalidOperationException("NomeFileSetup is not set."); - - // overwrite without FileSystem.Kill - _elencoParametri.WriteXml(NomeFileSetup, XmlWriteMode.WriteSchema); - } - - // public string LeggiParametroString(string NomeParametro) - // { - // string Risposta = ""; - // try - // { - // var LElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + NomeParametro + "'"); - // foreach (var LaRiga in LElenco) - // Risposta = LaRiga["Valore"].ToString(); - // } - // catch - // { - // Risposta = ""; - // } - // - // return Risposta; - // } - // - // public bool LeggiParametroBoolean(string nomeParametro) - // { - // var risposta = ""; - // try - // { - // var lElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + nomeParametro + "'"); - // foreach (var laRiga in lElenco) - // risposta = laRiga["Valore"].ToString(); - // } - // catch - // { - // risposta = ""; - // } - // - // switch (risposta.ToUpper() ?? "") - // { - // case "TRUE": - // case "OK": - // case "SI": - // case "1": - // case "YES": - // case "VERO": - // { - // return true; - // } - // - // default: - // { - // return false; - // } - // } - // } - - public string LeggiParametroString(string nomeParametro) - { - return GetRow(nomeParametro)?["Valore"]?.ToString() ?? string.Empty; - } - - public bool LeggiParametroBoolean(string nomeParametro) - { - var raw = LeggiParametroString(nomeParametro); - return raw?.ToUpperInvariant() switch - { - "TRUE" or "OK" or "SI" or "1" or "YES" or "VERO" => true, - _ => false - }; - } - - public void AggiornaParametro(string nomeParametro, object valoreParametro) - { - var table = EnsureSetupTable(); - - var rows = table.Select($"Nome='{nomeParametro.Replace("'", "''")}'"); - if (rows.Length == 0) - { - var newRow = table.NewRow(); - newRow["Nome"] = nomeParametro; - newRow["Valore"] = valoreParametro?.ToString() ?? string.Empty; - table.Rows.Add(newRow); - } - else - { - rows[0]["Valore"] = valoreParametro?.ToString() ?? string.Empty; - } - } - - public T LeggiParametro(string nomeParametro, T defaultValue = default!) - { - var raw = LeggiParametroString(nomeParametro); - if (string.IsNullOrEmpty(raw)) return defaultValue; - - try - { - return (T)Convert.ChangeType(raw, typeof(T)); - } - catch - { - return defaultValue; - } - } - - private static DataSet LeggiXmlDataSet(string nomeTabella, string nomeFileXml, string nomeColonnaChiave = "") - { - var ds = new DataSet(); - ds.ReadXml(nomeFileXml); - - if (!string.IsNullOrEmpty(nomeColonnaChiave) && ds.Tables.Contains(nomeTabella)) - { - var table = ds.Tables[nomeTabella]; - if (table.Constraints[nomeColonnaChiave] == null) - { - table.Constraints.Add(nomeColonnaChiave, table.Columns[nomeColonnaChiave], true); - } - } - - return ds; - } - - private DataTable EnsureSetupTable() - { - if (!_elencoParametri.Tables.Contains("Setup")) - { - var table = new DataTable("Setup"); - table.Columns.Add("Nome", typeof(string)); - table.Columns.Add("Valore", typeof(string)); - _elencoParametri.Tables.Add(table); - } - - return _elencoParametri.Tables["Setup"]; - } - - private DataRow? GetRow(string nomeParametro) - { - if (!_elencoParametri.Tables.Contains("Setup")) return null; - var rows = _elencoParametri.Tables["Setup"] - .Select($"Nome='{nomeParametro.Replace("'", "''")}'"); - return rows.FirstOrDefault(); + CaricaParametriSetup(); } } - // public void AggiornaParametro(string NomeParametro, object ValoreParametro) - // { - // try - // { - // if (_elencoParametri.Tables["Setup"] is null) - // { - // var TabellaTmp = new DataTable("Setup"); - // DataRow RigaTmp; - // DataColumn LaColonna; - // LaColonna = TabellaTmp.Columns.Add("Nome", Type.GetType("System.String")); - // LaColonna = TabellaTmp.Columns.Add("Valore", Type.GetType("System.String")); - // - // // * Aggiunge alla tabella tutte le righe - // RigaTmp = TabellaTmp.NewRow(); - // RigaTmp["Nome"] = NomeParametro; - // RigaTmp["Valore"] = ValoreParametro; - // TabellaTmp.Rows.Add(RigaTmp); - // _elencoParametri.Tables.Add(TabellaTmp); - // } - // else - // { - // var LElenco = _elencoParametri.Tables["Setup"].Select("Nome='" + NomeParametro + "'"); - // if (LElenco.Length == 0) - // { - // DataRow LaRiga; - // LaRiga = _elencoParametri.Tables["Setup"].NewRow(); - // LaRiga["Nome"] = NomeParametro; - // LaRiga["Valore"] = ValoreParametro; - // _elencoParametri.Tables["Setup"].Rows.Add(LaRiga); - // } - // else - // { - // LElenco[0]["Valore"] = ValoreParametro; - // } - // } - // } - // catch - // { - // } - // } + /// + /// Loads settings from the XML file. + /// + public void CaricaParametriSetup() + { + lock (_lock) + { + if (string.IsNullOrEmpty(NomeFileSetup) || !File.Exists(NomeFileSetup)) + { + _parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + return; + } - // private static DataSet LeggiXmlDataSet(string NomeTabella, string NomeFileXml, string NomeColonnaChiave = "") - // { - // // * Crea e Legge il dataset dal file xml - // var DataSetXml = new DataSet(); - // DataSetXml.ReadXml(NomeFileXml); - // - // // * Aggiunge il campo chiave - // if (!string.IsNullOrEmpty(NomeColonnaChiave)) - // { - // DataSetXml.Tables[NomeTabella].Constraints.Add(NomeColonnaChiave, DataSetXml.Tables[NomeTabella].Columns[NomeColonnaChiave], true); - // } - // - // // * Restituisce la risposta - // return DataSetXml; - // } -//} + try + { + var doc = XDocument.Load(NomeFileSetup); + var setupElements = doc.Descendants("Setup"); + + _parameters = setupElements + .Where(e => e.Element("Nome") != null) + .ToDictionary( + e => e.Element("Nome")!.Value, + e => e.Element("Valore")?.Value ?? string.Empty, + StringComparer.OrdinalIgnoreCase + ); + + _logger.LogInformation("Loaded {Count} parameters from {FilePath}", _parameters.Count, NomeFileSetup); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to load settings from {FilePath}", NomeFileSetup); + _parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } + } + + /// + /// Saves settings to the XML file. + /// + /// Thrown when NomeFileSetup is not set. + public void SalvaParametriSetup() + { + if (string.IsNullOrWhiteSpace(NomeFileSetup)) + throw new InvalidOperationException("NomeFileSetup is not set."); + + lock (_lock) + { + try + { + // Ensure directory exists + var directory = Path.GetDirectoryName(NomeFileSetup); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // Create XML document with DataSet-compatible structure + var doc = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("NewDataSet", + _parameters.Select(kvp => + new XElement("Setup", + new XElement("Nome", kvp.Key), + new XElement("Valore", kvp.Value) + ) + ) + ) + ); + + doc.Save(NomeFileSetup); + _logger.LogInformation("Saved {Count} parameters to {FilePath}", _parameters.Count, NomeFileSetup); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to save settings to {FilePath}", NomeFileSetup); + throw; + } + } + } + + /// + /// Reads a parameter as a string. + /// + /// Parameter name. + /// Parameter value or empty string if not found. + public string LeggiParametroString(string nomeParametro) + { + lock (_lock) + { + return _parameters.TryGetValue(nomeParametro, out var value) ? value : string.Empty; + } + } + + /// + /// Reads a parameter as a boolean. + /// + /// Parameter name. + /// True if value is truthy (TRUE, OK, SI, 1, YES, VERO), false otherwise. + public bool LeggiParametroBoolean(string nomeParametro) + { + var raw = LeggiParametroString(nomeParametro); + return raw.ToUpperInvariant() switch + { + "TRUE" or "OK" or "SI" or "1" or "YES" or "VERO" => true, + _ => false + }; + } + + /// + /// Updates or creates a parameter. + /// + /// Parameter name. + /// Parameter value. + public void AggiornaParametro(string nomeParametro, object? valoreParametro) + { + lock (_lock) + { + _parameters[nomeParametro] = valoreParametro?.ToString() ?? string.Empty; + } + } + + /// + /// Reads a parameter and converts it to the specified type. + /// + /// Target type. + /// Parameter name. + /// Default value if conversion fails. + /// Converted value or default value. + public T LeggiParametro(string nomeParametro, T defaultValue = default!) + { + var raw = LeggiParametroString(nomeParametro); + if (string.IsNullOrEmpty(raw)) + return defaultValue; + + try + { + // Handle nullable types + var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + + // Special handling for common types + if (targetType == typeof(bool)) + { + return (T)(object)LeggiParametroBoolean(nomeParametro); + } + + return (T)Convert.ChangeType(raw, targetType, CultureInfo.InvariantCulture); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to convert parameter {ParameterName} to {TypeName}, using default value", + nomeParametro, typeof(T).Name); + return defaultValue; + } + } + + /// + /// Checks if a parameter exists. + /// + /// Parameter name. + /// True if parameter exists, false otherwise. + public bool ParametroExists(string nomeParametro) + { + lock (_lock) + { + return _parameters.ContainsKey(nomeParametro); + } + } + + /// + /// Removes a parameter. + /// + /// Parameter name. + /// True if parameter was removed, false if it didn't exist. + public bool RimuoviParametro(string nomeParametro) + { + lock (_lock) + { + return _parameters.Remove(nomeParametro); + } + } + + /// + /// Gets all parameter names. + /// + /// Collection of parameter names. + public IReadOnlyCollection GetParameterNames() + { + lock (_lock) + { + return _parameters.Keys.ToList(); + } + } + + /// + /// Clears all parameters. + /// + public void ClearAll() + { + lock (_lock) + { + _parameters.Clear(); + } + } } \ No newline at end of file diff --git a/imagecatalog/Services/SettingsService.cs b/imagecatalog/Services/SettingsService.cs index fbf747f..3b0a630 100644 --- a/imagecatalog/Services/SettingsService.cs +++ b/imagecatalog/Services/SettingsService.cs @@ -1,22 +1,25 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Threading.Tasks; +using System.Xml.Serialization; using ImageCatalog; +using ImageCatalog_2.Models; +using Microsoft.Extensions.Logging; namespace ImageCatalog_2.Services { /// - /// Modern settings service that uses reflection to automatically save/load all properties + /// Modern settings service that uses DTO with attributes for serialization /// public class SettingsService : ISettingsService { private readonly ParametriSetup _parametriSetup; + private readonly ILogger _logger; - public SettingsService(ParametriSetup parametriSetup) + public SettingsService(ParametriSetup parametriSetup, ILogger logger) { _parametriSetup = parametriSetup; + _logger = logger; } public Task SaveSettingsAsync(string filePath, object settings) @@ -25,85 +28,153 @@ namespace ImageCatalog_2.Services { _parametriSetup.NomeFileSetup = filePath; - // Use reflection to get all properties and save them - var properties = settings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.CanRead && IsSerializableType(p.PropertyType)); + // Convert ViewModel to DTO + var dto = ViewModelToDto(settings); + + // Use reflection on DTO properties with XmlElement attribute + var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { - var value = prop.GetValue(settings); - _parametriSetup.AggiornaParametro(prop.Name, value); + // Get the XmlElement attribute to determine the XML property name + var xmlAttr = prop.GetCustomAttribute(); + if (xmlAttr == null) + continue; // Skip properties without XmlElement attribute + + var xmlName = xmlAttr.ElementName; + var value = prop.GetValue(dto); + + _parametriSetup.AggiornaParametro(xmlName, value); } _parametriSetup.SalvaParametriSetup(); }); } - public Task LoadSettingsAsync(string filePath, object settings) + public async Task LoadSettingsAsync(string filePath, object settings) { - return Task.Run(() => + // Step 1: Load XML and read into DTO on background thread + var dto = await Task.Run(() => { _parametriSetup.NomeFileSetup = filePath; _parametriSetup.CaricaParametriSetup(); - // Use reflection to get all properties and load them - var properties = settings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.CanWrite && IsSerializableType(p.PropertyType)); + var loadedDto = new SettingsDto(); + var properties = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { + // Get the XmlElement attribute to determine the XML property name + var xmlAttr = prop.GetCustomAttribute(); + if (xmlAttr == null) + continue; // Skip properties without XmlElement attribute + + var xmlName = xmlAttr.ElementName; + try { object value; if (prop.PropertyType == typeof(string)) { - value = _parametriSetup.LeggiParametroString(prop.Name); - // Don't update if empty string and current value is not empty - if (string.IsNullOrEmpty((string)value)) - { - var currentValue = prop.GetValue(settings) as string; - if (!string.IsNullOrEmpty(currentValue)) - { - continue; // Skip if no value in settings but there's a default - } - } + value = _parametriSetup.LeggiParametroString(xmlName); } else if (prop.PropertyType == typeof(bool)) { - value = _parametriSetup.LeggiParametroBoolean(prop.Name); + value = _parametriSetup.LeggiParametroBoolean(xmlName); } else if (prop.PropertyType == typeof(int)) { - value = _parametriSetup.LeggiParametro(prop.Name, (int)(prop.GetValue(settings) ?? 0)); + value = _parametriSetup.LeggiParametro(xmlName, (int)(prop.GetValue(loadedDto) ?? 0)); } else if (prop.PropertyType == typeof(double)) { - value = _parametriSetup.LeggiParametro(prop.Name, (double)(prop.GetValue(settings) ?? 0.0)); + value = _parametriSetup.LeggiParametro(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0)); + } + else if (prop.PropertyType == typeof(DateTime)) + { + var strValue = _parametriSetup.LeggiParametroString(xmlName); + if (DateTime.TryParse(strValue, out var dateValue)) + { + value = dateValue; + } + else + { + value = (DateTime)(prop.GetValue(loadedDto) ?? DateTime.Now); + } } else { - continue; // Skip unsupported types + continue; } - prop.SetValue(settings, value); + prop.SetValue(loadedDto, value); } catch (Exception ex) { - // Skip properties that can't be loaded - System.Diagnostics.Debug.WriteLine($"Failed to load property {prop.Name}: {ex.Message}"); + _logger.LogError(ex, "Failed to read property {XmlName} -> {PropertyName}", xmlName, prop.Name); } } + + return loadedDto; }); + + // Step 2: Apply DTO values to ViewModel on UI thread + DtoToViewModel(dto, settings); } - private bool IsSerializableType(Type type) + /// + /// Converts DataModel (ViewModel) to SettingsDto + /// + private SettingsDto ViewModelToDto(object viewModel) { - return type == typeof(string) || - type == typeof(int) || - type == typeof(bool) || - type == typeof(double) || - type == typeof(float) || - type == typeof(decimal); + var vmType = viewModel.GetType(); + var dto = new SettingsDto(); + var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var dtoProp in dtoProps) + { + // Find matching property in ViewModel by name + var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance); + if (vmProp != null && vmProp.CanRead) + { + var value = vmProp.GetValue(viewModel); + dtoProp.SetValue(dto, value); + } + } + + return dto; + } + + /// + /// Copies values from SettingsDto to DataModel (ViewModel) + /// + private void DtoToViewModel(SettingsDto dto, object viewModel) + { + var vmType = viewModel.GetType(); + var dtoProps = typeof(SettingsDto).GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var dtoProp in dtoProps) + { + // Find matching property in ViewModel by name + var vmProp = vmType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance); + if (vmProp != null && vmProp.CanWrite) + { + var value = dtoProp.GetValue(dto); + + // Don't update if empty string and current value is not empty + if (dtoProp.PropertyType == typeof(string) && string.IsNullOrEmpty((string)value)) + { + var currentValue = vmProp.GetValue(viewModel) as string; + if (!string.IsNullOrEmpty(currentValue)) + { + continue; + } + } + + vmProp.SetValue(viewModel, value); + _logger.LogDebug("Set {PropertyName} = {Value}", vmProp.Name, value); + } + } } } }