diff --git a/Common/AppSettings/IAppSettingsProvider.cs b/Common/AppSettings/IAppSettingsProvider.cs index dda2e20..7ed70a1 100644 --- a/Common/AppSettings/IAppSettingsProvider.cs +++ b/Common/AppSettings/IAppSettingsProvider.cs @@ -12,18 +12,44 @@ public interface IAppSettingsProvider /// /// Retrieves the app settings from the persistant location and loads them into memory. /// Loading settings will override the currently stored settings in the memory. + /// If loading happens and there were already settings in the memory, a is generated. /// void Load(); /// /// Saves the app settings to the persistant location. + /// If the saving does not work, logs the exception as a but does not throw an exception. + /// Rationale: A User is happier if the app is useable and does not remember settings than if the app always throws an exception on startup. /// void Save(); - + /// + /// Creates a new setting or overwrites the value of an existing setting in the memory (not persistent location). Settings are identified by key. + /// Informs about issues using the . + /// + /// key of the setting, must not be empty or null + /// value of the setting, must not be empty or null + /// true if the setting is successfully written or overwritten bool WriteSetting(string key, string value); + /// + /// Tries to read a setting from the memory (not persistent location). Settings are identified by key. + /// Informs about issues using the . + /// If no setting with the given is found, the is set to . + /// + /// key of the setting, must not be empty or null + /// the found value or an empty string if no setting is found + /// true when the setting exists and could be read successfully bool TryReadSetting(string key, out string value); + /// + /// Tries to read a setting from the memory (not persistent location). Settings are identified by key. + /// Informs about issues using the . + /// If no setting with the given is found, the setting is created with the given . is also set to . + /// + /// key of the setting, must not be empty or null + /// the found value or an empty string if no setting is found + /// the default value to initialize the setting with if it does not exist yet. must not be empty or null + /// true when the setting could be read or was created successfully bool TryReadSettingOrAddDefault(string key, out string value, string defaultValue); } diff --git a/Common/AppSettings/XmlAppSettingsProvider.cs b/Common/AppSettings/XmlAppSettingsProvider.cs index a5e6fc0..116e05d 100644 --- a/Common/AppSettings/XmlAppSettingsProvider.cs +++ b/Common/AppSettings/XmlAppSettingsProvider.cs @@ -14,12 +14,17 @@ public class XmlAppSettingsProvider : IAppSettingsProvider public XmlAppSettingsProvider(string settingsFilePath) { // check for file path validity - // TODO create file if it does not exist yet if (String.IsNullOrEmpty(settingsFilePath)) { throw new ArgumentNullException(nameof(settingsFilePath)); } this.settingsFilePath = settingsFilePath; this.settings = new List(); this.serializer = new XmlSerializer(typeof(List)); + + // create file if it does not exist yet + if (File.Exists(settingsFilePath) == false) + { + this.Save(); + } } public void Load() @@ -35,23 +40,32 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if(this.settings.Count > 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(Load)}'() deserialization overwrote previously loaded settings.")); + message: $"'{nameof(Load)}()' deserialization overwrote previously loaded settings.")); } this.settings = (List)deserializedObj; } else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(Load)}'() deserialization resulted in null object. Did not change loaded settings.")); + message: $"'{nameof(Load)}()' deserialization resulted in null object. Did not change loaded settings.")); } } public void Save() { - // TODO write log entries for when something goes wrong - TextWriter writer = new StreamWriter(this.settingsFilePath); - this.serializer.Serialize(writer, this.settings); - writer.Close(); + // if saving fails, just log the exception. user is happier if the app is useable but does not remember settings + try + { + // create file and serialize data + TextWriter writer = new StreamWriter(this.settingsFilePath); + this.serializer.Serialize(writer, this.settings); + writer.Close(); + } + catch (Exception ex) + { + this.LogWorthyEvent?.Invoke(this, new LogEntry(){ LogLevel = LogLevel.Error, Category = nameof(XmlAppSettingsProvider), + Message = $"'{nameof(Save)}()' failed to create file or serialize data.", Exception = ex }); + } } @@ -69,7 +83,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if(foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(WriteSetting)}'() cancelled because {foundSettings.Count} settings with the key '{key}' were found. Expected none or exactly one.")); + message: $"'{nameof(WriteSetting)}()' cancelled because {foundSettings.Count} settings with the key '{key}' were found. Expected none or exactly one.")); return false; // cancel } @@ -82,7 +96,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider else if(foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Info, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(WriteSetting)}'() initialized new setting with key '{key}' and value '{value}'.")); + message: $"'{nameof(WriteSetting)}()' initialized new setting with key '{key}' and value '{value}'.")); } // add new setting @@ -102,7 +116,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if (foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSetting)}'() did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); + message: $"'{nameof(TryReadSetting)}()' did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); value = string.Empty; return false; @@ -112,7 +126,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if (foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSetting)}'() did not find a setting with the key '{key}'.")); + message: $"'{nameof(TryReadSetting)}()' did not find a setting with the key '{key}'.")); value = string.Empty; return false; @@ -128,11 +142,10 @@ public class XmlAppSettingsProvider : IAppSettingsProvider else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSetting)}'() value of retrieved setting with key '{key}' was null. Returned empty string instead.")); + message: $"'{nameof(TryReadSetting)}()' value of retrieved setting with key '{key}' was null. Returned empty string instead.")); value = string.Empty; } - return true; } @@ -149,7 +162,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if (foundSettings.Count > 1) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Error, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSettingOrAddDefault)}'() did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); + message: $"'{nameof(TryReadSettingOrAddDefault)}()' did find {foundSettings.Count} settings with the key '{key}'. Expected only one.")); value = string.Empty; return false; @@ -159,11 +172,10 @@ public class XmlAppSettingsProvider : IAppSettingsProvider if (foundSettings.Count == 0) { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Info, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSettingOrAddDefault)}'() did not find a setting with the key '{key}'. Adding setting with value '{defaultValue}'.")); + message: $"'{nameof(TryReadSettingOrAddDefault)}()' did not find a setting with the key '{key}'. Adding setting with value '{defaultValue}'.")); - this.WriteSetting(key, defaultValue); value = defaultValue; - return true; + return this.WriteSetting(key, defaultValue); } // extract correct setting when found exactly one @@ -176,7 +188,7 @@ public class XmlAppSettingsProvider : IAppSettingsProvider else { this.LogWorthyEvent?.Invoke(this, new LogEntry(level: LogLevel.Warn, category: nameof(XmlAppSettingsProvider), - message: $"'{nameof(TryReadSetting)}'() value of retrieved setting with key '{key}' was null. Returned empty string instead.")); + message: $"'{nameof(TryReadSetting)}()' value of retrieved setting with key '{key}' was null. Returned empty string instead.")); value = string.Empty; } diff --git a/MultiTerm.Core/ViewModel/ShellViewModel.cs b/MultiTerm.Core/ViewModel/ShellViewModel.cs index 2e09c1e..4257f14 100644 --- a/MultiTerm.Core/ViewModel/ShellViewModel.cs +++ b/MultiTerm.Core/ViewModel/ShellViewModel.cs @@ -14,21 +14,18 @@ public partial class ShellViewModel : ObservableObject private const string defaultReceiveNewlineSeparatorAppSettingsKey = "DefaultReceiveNewlineSeparator"; private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator"; - [ObservableProperty] - private string title = "ShellView Test"; - + #region Terminal collection [ObservableProperty] private ObservableCollection terminalViewModels = new(); [ObservableProperty] private ITerminalViewModel? selectedTerminalViewModel; + #endregion #region Settings Menu Bar - // TODO Initialize from File [ObservableProperty] private NewlineSeparatorType defaultReceiveNewlineSeparator = NewlineSeparatorType.None; - // TODO Initialize from File [ObservableProperty] private NewlineSeparatorType defaultSendNewlineSeparator = NewlineSeparatorType.None; #endregion @@ -36,7 +33,6 @@ public partial class ShellViewModel : ObservableObject #region New Terminal Context Menu [ObservableProperty] private TerminalViewType selectedTerminalViewType = TerminalViewType.SendReceive; - #endregion private readonly ITerminalViewModelFactory terminalViewModelFactory; @@ -49,11 +45,8 @@ public partial class ShellViewModel : ObservableObject this.logger = logger; this.appSettings = appSettings; - // initialize newline separators from persistent settings - this.appSettings.TryReadSettingOrAddDefault(defaultReceiveNewlineSeparatorAppSettingsKey, out string settingValueReceiveNLSep, NewlineSeparatorType.None.ToString()); - this.DefaultReceiveNewlineSeparator = EnumHelpers.ParseEnum(settingValueReceiveNLSep); - this.appSettings.TryReadSettingOrAddDefault(defaultSendNewlineSeparatorAppSettingsKey, out string settingValueSendNLSep, NewlineSeparatorType.None.ToString()); - this.DefaultSendNewlineSeparator = EnumHelpers.ParseEnum(settingValueSendNLSep); + // intialize values from app settings + this.LoadFromAppSettings(); // TEMP Init this.AppendTerminalWithSelectedViewType(ProtocolType.Serial); @@ -94,20 +87,26 @@ public partial class ShellViewModel : ObservableObject this.TerminalViewModels.Remove(terminalToRemove); } - partial void OnDefaultReceiveNewlineSeparatorChanged(NewlineSeparatorType value) + #region App Settings handling + private void LoadFromAppSettings() { - Console.WriteLine($"Changed to {value}"); + // initialize newline separators from persistent settings + this.appSettings.TryReadSettingOrAddDefault(defaultReceiveNewlineSeparatorAppSettingsKey, out string settingValueReceiveNLSep, NewlineSeparatorType.None.ToString()); + this.DefaultReceiveNewlineSeparator = EnumHelpers.ParseEnum(settingValueReceiveNLSep); + this.appSettings.TryReadSettingOrAddDefault(defaultSendNewlineSeparatorAppSettingsKey, out string settingValueSendNLSep, NewlineSeparatorType.None.ToString()); + this.DefaultSendNewlineSeparator = EnumHelpers.ParseEnum(settingValueSendNLSep); } - partial void OnDefaultSendNewlineSeparatorChanged(NewlineSeparatorType value) + partial void OnDefaultReceiveNewlineSeparatorChanged(NewlineSeparatorType value) { - Console.WriteLine($"Changed Send to {value}"); + // update setting in app settings + this.appSettings.WriteSetting(defaultReceiveNewlineSeparatorAppSettingsKey, value.ToString()); } - [RelayCommand] - private void TestButtonClicked() + partial void OnDefaultSendNewlineSeparatorChanged(NewlineSeparatorType value) { - this.DefaultReceiveNewlineSeparator = NewlineSeparatorType.CR_LF; + // update setting in app settings + this.appSettings.WriteSetting(defaultSendNewlineSeparatorAppSettingsKey, value.ToString()); } - + #endregion } diff --git a/MultiTerm.Wpf/App.xaml.cs b/MultiTerm.Wpf/App.xaml.cs index 03428b4..16a891c 100644 --- a/MultiTerm.Wpf/App.xaml.cs +++ b/MultiTerm.Wpf/App.xaml.cs @@ -49,9 +49,10 @@ public partial class App : Application Application.Current.DispatcherUnhandledException += this.Application_DispatcherUnhandledException; TaskScheduler.UnobservedTaskException += this.TaskScheduler_UnobservedTaskException; - // register log events from AppSettingsProvider + // register log events from AppSettingsProvider and load saved settings var appSettingsProvider = AppHost.Services.GetRequiredService(); appSettingsProvider.LogWorthyEvent += AppSettingsProvider_LogWorthyEvent; + appSettingsProvider.Load(); // instanciate startup form and show var startupForm = AppHost.Services.GetRequiredService(); @@ -67,6 +68,10 @@ public partial class App : Application protected override async void OnExit(ExitEventArgs e) { + // save settings to persistent location + var appSettingsProvider = AppHost!.Services.GetRequiredService(); + appSettingsProvider.Save(); + // log application exit and stop logger (if still available) logger?.LogInfo("Application exited.", nameof(App)); logger?.StopLogging();