using Unosquare.Swan.Formatters; using Unosquare.Swan.Reflection; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; namespace Unosquare.Swan.Abstractions { /// /// Represents a provider to save and load settings using a plain JSON file. /// /// /// The following example shows how to save and load settings. /// /// using Unosquare.Swan.Abstractions; /// /// public class Example /// { /// public static void Main() /// { /// // get user from settings /// var user = SettingsProvider<Settings>.Instance.Global.User; /// /// // modify the port /// SettingsProvider<Settings>.Instance.Global.Port = 20; /// /// // if we want these settings to persist /// SettingsProvider<Settings>.Instance.PersistGlobalSettings(); /// } /// /// public class Settings /// { /// public int Port { get; set; } = 9696; /// /// public string User { get; set; } = "User"; /// } /// } /// /// /// The type of settings model. public sealed class SettingsProvider : SingletonBase> { private readonly Object _syncRoot = new Object(); private T _global; /// /// Gets or sets the configuration file path. By default the entry assembly directory is used /// and the filename is 'appsettings.json'. /// /// /// The configuration file path. /// public String ConfigurationFilePath { get; set; } = #if NETSTANDARD1_3 Path.Combine(Runtime.LocalStoragePath, "appsettings.json"); #else Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json"); #endif /// /// Gets the global settings object. /// /// /// The global settings object. /// public T Global { get { lock(this._syncRoot) { if(Equals(this._global, default(T))) { this.ReloadGlobalSettings(); } return this._global; } } } /// /// Reloads the global settings. /// public void ReloadGlobalSettings() { if(File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0) { this.ResetGlobalSettings(); return; } lock(this._syncRoot) { this._global = Json.Deserialize(File.ReadAllText(this.ConfigurationFilePath)); } } /// /// Persists the global settings. /// public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true)); /// /// Updates settings from list. /// /// The list. /// /// A list of settings of type ref="ExtendedPropertyInfo". /// /// propertyList. public List RefreshFromList(List> propertyList) { if(propertyList == null) { throw new ArgumentNullException(nameof(propertyList)); } List changedSettings = new List(); IEnumerable globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties(); foreach(ExtendedPropertyInfo property in propertyList) { PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property); if(propertyInfo == null) { continue; } Object originalValue = propertyInfo.GetValue(this.Global); Boolean isChanged = propertyInfo.PropertyType.IsArray ? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast(), this.Global) : this.SetValue(property.Value, originalValue, propertyInfo); if(!isChanged) { continue; } changedSettings.Add(property.Property); this.PersistGlobalSettings(); } return changedSettings; } /// /// Gets the list. /// /// A List of ExtendedPropertyInfo of the type T. public List> GetList() { Dictionary jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary; return jsonData?.Keys .Select(p => new ExtendedPropertyInfo(p) { Value = jsonData[p] }) .ToList(); } /// /// Resets the global settings. /// public void ResetGlobalSettings() { lock(this._syncRoot) { this._global = Activator.CreateInstance(); } this.PersistGlobalSettings(); } private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo) { switch(property) { case null when originalValue == null: break; case null: propertyInfo.SetValue(this.Global, null); return true; default: if(propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) && !propertyValue.Equals(originalValue)) { propertyInfo.SetValue(this.Global, propertyValue); return true; } break; } return false; } } }