diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index 7f195085d2..d3d5d4c83f 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -13,7 +13,7 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Xml.Serialization; +using System.Text.Json.Serialization; // ReSharper disable InconsistentNaming @@ -42,7 +42,7 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null) #region Variables - [XmlIgnore] public bool SettingsChanged { get; set; } + [JsonIgnore] public bool SettingsChanged { get; set; } /// /// Private field for the property. diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index cbd20d5ff4..1ce2e093d4 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -1,9 +1,12 @@ using log4net; using NETworkManager.Models; using NETworkManager.Models.Network; +using NETworkManager.Utilities; using System; using System.IO; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Xml.Serialization; namespace NETworkManager.Settings; @@ -22,6 +25,11 @@ public static class SettingsManager /// private static string SettingsFolderName => "Settings"; + /// + /// Settings backups directory name. + /// + private static string BackupFolderName => "Backups"; + /// /// Settings file name. /// @@ -30,7 +38,13 @@ public static class SettingsManager /// /// Settings file extension. /// - private static string SettingsFileExtension => ".xml"; + private static string SettingsFileExtension => ".json"; + + /// + /// Legacy XML settings file extension. + /// + [Obsolete("Legacy XML settings are no longer used, but the extension is kept for migration purposes.")] + private static string LegacySettingsFileExtension => ".xml"; /// /// Settings that are currently loaded. @@ -42,6 +56,17 @@ public static class SettingsManager /// public static bool HotKeysChanged { get; set; } + /// + /// JSON serializer options for consistent serialization/deserialization. + /// + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Converters = { new JsonStringEnumConverter() } + }; + #endregion #region Settings location, default paths and file names @@ -58,6 +83,15 @@ public static string GetSettingsFolderLocation() AssemblyManager.Current.Name, SettingsFolderName); } + /// + /// Method to get the path of the settings backup folder. + /// + /// Path to the settings backup folder. + public static string GetSettingsBackupFolderLocation() + { + return Path.Combine(GetSettingsFolderLocation(), BackupFolderName); + } + /// /// Method to get the settings file name. /// @@ -67,6 +101,16 @@ public static string GetSettingsFileName() return $"{SettingsFileName}{SettingsFileExtension}"; } + /// + /// Method to get the legacy settings file name. + /// + /// Legacy settings file name. + [Obsolete("Legacy XML settings are no longer used, but the method is kept for migration purposes.")] + public static string GetLegacySettingsFileName() + { + return $"{SettingsFileName}{LegacySettingsFileExtension}"; + } + /// /// Method to get the settings file path. /// @@ -76,6 +120,16 @@ public static string GetSettingsFilePath() return Path.Combine(GetSettingsFolderLocation(), GetSettingsFileName()); } + /// + /// Method to get the legacy XML settings file path. + /// + /// Legacy XML settings file path. + [Obsolete("Legacy XML settings are no longer used, but the method is kept for migration purposes.")] + private static string GetLegacySettingsFilePath() + { + return Path.Combine(GetSettingsFolderLocation(), GetLegacySettingsFileName()); + } + #endregion #region Initialize, load and save @@ -99,7 +153,9 @@ public static void Initialize() public static void Load() { var filePath = GetSettingsFilePath(); + var legacyFilePath = GetLegacySettingsFilePath(); + // Check if JSON file exists if (File.Exists(filePath)) { Current = DeserializeFromFile(filePath); @@ -109,22 +165,66 @@ public static void Load() return; } + // Check if legacy XML file exists and migrate it + if (File.Exists(legacyFilePath)) + { + Log.Info("Legacy XML settings file found. Migrating to JSON format..."); + + Current = DeserializeFromXmlFile(legacyFilePath); + + Current.SettingsChanged = false; + + // Save in new JSON format + Save(); + + // Create a backup of the legacy XML file and delete the original + Directory.CreateDirectory(GetSettingsBackupFolderLocation()); + + var backupFilePath = Path.Combine(GetSettingsBackupFolderLocation(), + $"{TimestampHelper.GetTimestamp()}_{GetLegacySettingsFileName()}"); + + File.Copy(legacyFilePath, backupFilePath, true); + + File.Delete(legacyFilePath); + + Log.Info($"Legacy XML settings file backed up to: {backupFilePath}"); + + Log.Info("Settings migration from XML to JSON completed successfully."); + + return; + } + // Initialize the default settings if there is no settings file. Initialize(); } /// - /// Method to deserialize the settings from a file. + /// Method to deserialize the settings from a JSON file. /// /// Path to the settings file. /// Settings as . private static SettingsInfo DeserializeFromFile(string filePath) + { + var jsonString = File.ReadAllText(filePath); + + var settingsInfo = JsonSerializer.Deserialize(jsonString, JsonOptions); + + return settingsInfo; + } + + /// + /// Method to deserialize the settings from a legacy XML file. + /// + /// Path to the XML settings file. + /// Settings as . + [Obsolete("Legacy XML settings are no longer used, but the method is kept for migration purposes.")] + private static SettingsInfo DeserializeFromXmlFile(string filePath) { var xmlSerializer = new XmlSerializer(typeof(SettingsInfo)); using var fileStream = new FileStream(filePath, FileMode.Open); - var settingsInfo = (SettingsInfo)xmlSerializer.Deserialize(fileStream); + var settingsInfo = xmlSerializer.Deserialize(fileStream) as SettingsInfo; return settingsInfo; } @@ -145,17 +245,28 @@ public static void Save() } /// - /// Method to serialize the settings to a file. + /// Method to serialize the settings to a JSON file. /// /// Path to the settings file. private static void SerializeToFile(string filePath) { - var xmlSerializer = new XmlSerializer(typeof(SettingsInfo)); + var jsonString = JsonSerializer.Serialize(Current, JsonOptions); + + File.WriteAllText(filePath, jsonString); + } - using var fileStream = new FileStream(filePath, FileMode.Create); + #endregion + + #region Backup + /* + private static void Backup() + { + Log.Info("Creating settings backup..."); - xmlSerializer.Serialize(fileStream, Current); + // Create the backup directory if it does not exist + Directory.CreateDirectory(GetSettingsBackupFolderLocation()); } + */ #endregion diff --git a/Source/NETworkManager/App.xaml.cs b/Source/NETworkManager/App.xaml.cs index 249efe1a39..ace2a1267f 100644 --- a/Source/NETworkManager/App.xaml.cs +++ b/Source/NETworkManager/App.xaml.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading; using System.Windows; using System.Windows.Threading; @@ -92,21 +93,15 @@ by BornToBeRoot } catch (InvalidOperationException ex) { - Log.Error("Could not load application settings!"); - Log.Error(ex.Message + "-" + ex.StackTrace); - - // Create backup of corrupted file - var destinationFile = - $"{TimestampHelper.GetTimestamp()}_corrupted_" + SettingsManager.GetSettingsFileName(); - File.Copy(SettingsManager.GetSettingsFilePath(), - Path.Combine(SettingsManager.GetSettingsFolderLocation(), destinationFile)); - Log.Info($"A backup of the corrupted settings file has been saved under {destinationFile}"); - - // Initialize default application settings - Log.Info("Initialize default application settings..."); + Log.Error("Could not load application settings!", ex); + + HandleCorruptedSettingsFile(); + } + catch (JsonException ex) + { + Log.Error("Could not load application settings! JSON file is corrupted or invalid.", ex); - SettingsManager.Initialize(); - ConfigurationManager.Current.ShowSettingsResetNoteOnStartup = true; + HandleCorruptedSettingsFile(); } // Upgrade settings if necessary @@ -220,6 +215,27 @@ by BornToBeRoot } } + /// + /// Handles a corrupted settings file by creating a backup and initializing default settings. + /// + private void HandleCorruptedSettingsFile() + { + // Create backup of corrupted file + var destinationFile = + $"{TimestampHelper.GetTimestamp()}_corrupted_" + SettingsManager.GetSettingsFileName(); + + File.Copy(SettingsManager.GetSettingsFilePath(), + Path.Combine(SettingsManager.GetSettingsFolderLocation(), destinationFile)); + + Log.Info($"A backup of the corrupted settings file has been saved under {destinationFile}"); + + // Initialize default application settings + Log.Info("Initialize default application settings..."); + + SettingsManager.Initialize(); + ConfigurationManager.Current.ShowSettingsResetNoteOnStartup = true; + } + private void DispatcherTimer_Tick(object sender, EventArgs e) { Log.Info("Run background job..."); @@ -267,4 +283,4 @@ private void Save() ProfileManager.Save(); } } -} \ No newline at end of file +} diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md index a06d9051ae..417fc79f9c 100644 --- a/Website/docs/changelog/next-release.md +++ b/Website/docs/changelog/next-release.md @@ -51,6 +51,10 @@ Release date: **xx.xx.2025** - Profile file dialog migrated to a child window to improve usability. [#3227](https://github.com/BornToBeRoot/NETworkManager/pull/3227) - Credential dialogs migrated to child windows to improve usability. [#3231](https://github.com/BornToBeRoot/NETworkManager/pull/3231) +**Settings** + +- Settings format migrated from `XML` to `JSON`. The settings file will be automatically converted on first start after the update. [#3282](https://github.com/BornToBeRoot/NETworkManager/pull/3282) + **DNS Lookup** - Allow hostname as server address in addition to IP address in the add/edit server dialog. [#3261](https://github.com/BornToBeRoot/NETworkManager/pull/3261)