using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Threading; using System.Xml; namespace Inspectron.Settings { public class InspectronSettings { public InspectronSettings(string path=null) { Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); AssemblyName assemblyName = assembly.GetName(); _applicationName = assemblyName.Name; Version version = assemblyName.Version; _versionString = version.Major + "." + version.Minor; string startupPath = Path.GetDirectoryName(new Uri(assemblyName.CodeBase).LocalPath); _defaultSettingsPath = Path.Combine(startupPath, "DefaultSettings.xml"); if (path == null) { var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); _settingsPath = string.Format("{0}\\{1}\\{2}\\AppSettings.xml", appDataPath, _applicationName, _versionString); } else { _settingsPath = Path.Combine(path,"AppSettings.xml"); } } internal Path GetSettingsPath(string pathName) { string[] pathSegments = pathName.Split('/', 16); object[] path = new object[pathSegments.Length + 1]; // first node is the settings tree root Tree node = (Tree)_userSettings; path[0] = _userSettings; // middle nodes are folders for (int i = 1; i < path.Length - 1; i++) { node = GetOrCreateFolder(pathSegments[i - 1], node); path[i] = node; } // leaf node is user settings object foreach (Tree leaf in node.Children) { UserSettingsInfo info = leaf.Value as UserSettingsInfo; if (info != null && info.Name == pathSegments[pathSegments.Length - 1]) { path[path.Length - 1] = leaf; break; } } return new Path(path); } /// /// Gets or sets the current state of all properties (Memento pattern) public object State { get { MemoryStream stream = new MemoryStream(); Serialize(stream); return stream; } set { MemoryStream stream = value as MemoryStream; if (stream == null) throw new ArgumentException("Not a valid memento"); stream.Position = 0; Deserialize(stream); } } private IEnumerable UserPropertyDescriptors { get { var userSettings = UserSettings as Tree; if (userSettings == null) throw new InvalidOperationException("userSettings"); var all = userSettings.LevelOrder.Where(x => x.Value is UserSettingsInfo); foreach (Tree node in all) { UserSettingsInfo info = node.Value as UserSettingsInfo; if (info != null) { foreach (PropertyDescriptor property in info.Settings) yield return property; } } } } private static readonly object _unusedComponent = new object(); public void SetDefaults() { foreach (SettingsInfo info in _settings.Values) { foreach (SettingsInfo.Setting setting in info.Settings.Values) { if (setting.PropertyDescriptor != null && setting.PropertyDescriptor.CanResetValue(_unusedComponent)) { setting.PropertyDescriptor.ResetValue(_unusedComponent); } } } // Load default settings if exist. if (File.Exists(_defaultSettingsPath)) { using (Stream stream = File.OpenRead(_defaultSettingsPath)) Deserialize(stream); } } internal List GetProperties(Tree tree) { var info = tree.Value as UserSettingsInfo; if (info != null) return info.Settings; return null; } protected class SettingsInfo { /// /// Constructor with name /// Name associated with this group of settings public SettingsInfo(string name) { Name = name; } /// /// Add a setting to group of settings as a name and value, replacing the previous value if present /// Setting name /// Setting value public void Add(string name, string valueString) { Setting setting; if (Settings.TryGetValue(name, out setting)) { setting.Set(name, valueString); } else { Settings.Add(name, new Setting(name, valueString)); } } /// /// Add a setting to group of settings as a PropertyDescriptor, replacing the previous value if present /// PropertyDescriptor describing setting public void Add(PropertyDescriptor descriptor) { Setting setting; if (Settings.TryGetValue(descriptor.Name, out setting)) { setting.Set(descriptor); } else { Settings.Add(descriptor.Name, new Setting(descriptor)); } } /// /// Name associated with this group of settings public readonly string Name; /// /// Dictionary for names and values of settings in group public readonly SortedDictionary Settings = new SortedDictionary(); /// /// Class for handling individual setting information public class Setting { /// /// Constructor with PropertyDescriptor /// PropertyDescriptor describing setting public Setting(PropertyDescriptor descriptor) { PropertyDescriptor = descriptor; } /// /// Constructor with name and value of setting /// Setting name /// Setting value public Setting(string name, string valueString) { Name = name; ValueString = valueString; } /// /// Set a setting's value with name and value of setting /// Setting name /// Setting value public void Set(string name, string valueString) { Name = name; ValueString = valueString; if (PropertyDescriptor != null) { SetValue(); } } /// /// Set a setting's value with PropertyDescriptor /// PropertyDescriptor describing setting public void Set(PropertyDescriptor descriptor) { PropertyDescriptor = descriptor; if (Name != null && ValueString != null) { SetValue(); } } private void SetValue() { if (!CanMakeChanges) return; object value = GetValue(PropertyDescriptor.PropertyType, ValueString); PropertyDescriptor.SetValue(null, value); } /// /// Setting name public string Name; /// /// Setting value public string ValueString; /// /// Setting PropertyDescriptor public PropertyDescriptor PropertyDescriptor; } /// /// Set whether property descriptors are allowed to make changes /// Used when persisted settings are being loaded from disk /// to prevent property descriptors from being set multiple times as /// in the case where DefaultSettings.xml and AppSettings.xml both exist public static bool CanMakeChanges { private get; set; } } private static object GetValue(Type type, string valueString) { object value = null; try { TypeConverter converter = TypeDescriptor.GetConverter(type); if (CanConvertToAndFromString(converter)) { value = converter.ConvertFromInvariantString(valueString); } else { // deserialize byte[] data = Convert.FromBase64String(valueString); using (MemoryStream stream = new MemoryStream(data)) { BinaryFormatter formatter = new BinaryFormatter(); value = formatter.Deserialize(stream); } } } catch { value = null; } return value; } private static bool CanConvertToAndFromString(TypeConverter converter) { return converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(typeof(string)); } /// /// Class for group of user settings public class UserSettingsInfo { /// /// Constructor with name and PropertyDescriptors /// Name of group of user settings /// PropertyDescriptors with settings public UserSettingsInfo(string name, PropertyDescriptor[] settings) { Name = name; Settings = new List(settings); } /// /// Name of group of user settings public readonly string Name; /// /// PropertyDescriptors with settings public readonly List Settings; } private readonly SortedDictionary _settings = new SortedDictionary(); private ITreeView _userSettings = new TreeView(string.Empty); public void RegisterSettings(string uid, params PropertyDescriptor[] settings) { SettingsInfo settingsInfo; if (!_settings.TryGetValue(uid, out settingsInfo)) { settingsInfo = new SettingsInfo(uid); _settings.Add(uid, settingsInfo); } foreach (PropertyDescriptor descriptor in settings) settingsInfo.Add(descriptor); } public ITreeView UserSettings { get { return _userSettings; } } public void RegisterSimple(object owner,Expression> expression,string pathName,string name,string category="General",string description="") { var descriptor = new BoundPropertyDescriptor(owner,expression, name, category, description); RegisterSettings(pathName,descriptor); RegisterUserSettings(pathName,descriptor); } public void RegisterUserSettings(string pathName, params PropertyDescriptor[] settings) { if (string.IsNullOrEmpty(pathName)) throw new ArgumentException("pathName"); string[] path = pathName.Split('/', 16); // get root folder Tree folder = UserSettings as Tree; if (folder == null) throw new InvalidOperationException("userSettings"); // for each subsequent segment of the path, get folder for (int i = 0; i < path.Length - 1; i++) folder = GetOrCreateFolder(path[i], folder); // get the node that should hold the settings, if it already exists string name = path[path.Length - 1]; UserSettingsInfo existing = null; int index = 0; foreach (Tree node in folder.Children) { UserSettingsInfo info = node.Value as UserSettingsInfo; if (info != null) { if (info.Name == name) { existing = info; break; } if (info.Name.CompareTo(name) < 0) index++; } } // add the settings, either by merging with the existing node or creating a new one if (existing != null) { foreach (PropertyDescriptor pd in settings) existing.Settings.Add(pd); } else { Tree node = new Tree(new UserSettingsInfo(name, settings)); folder.Children.Insert(index, node); } } private static string GetMutexName(string pathName) { string safeName = pathName; //255 characters will break IpcChannel constructor. 250 works. if (safeName.Length > 250) safeName = safeName.Substring(safeName.Length - 250); // The Mutex constructor will crash if given '\', unless it's part of a valid path. // NextInstanceMonitor.ActivateApplication() will crash if there are '/' characters. safeName = safeName.Replace('/', '-'); safeName = safeName.Replace('\\', '-'); return safeName; } protected void Serialize(Stream stream) { Saving.Raise(this, EventArgs.Empty); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes")); XmlElement root = xmlDoc.CreateElement("settings"); xmlDoc.AppendChild(root); // add application name and version to the root element. root.SetAttribute("appName", _applicationName); root.SetAttribute("appVersion", _versionString); foreach (SettingsInfo info in _settings.Values) { XmlElement block = xmlDoc.CreateElement("block"); block.SetAttribute("id", info.Name); foreach (SettingsInfo.Setting setting in info.Settings.Values) { PropertyDescriptor descriptor = setting.PropertyDescriptor; if (descriptor != null) { object value = descriptor.GetValue(null); if (CanWriteValue(value)) WriteValue(descriptor.Name, value, block); } else { WriteValue(setting.Name, setting.ValueString, block); } } // skip empty block if (block.ChildNodes.Count > 0) root.AppendChild(block); } XmlWriterSettings settings = new XmlWriterSettings(); settings.CloseOutput = false; settings.Indent = true; using (XmlWriter writer = XmlWriter.Create(stream, settings)) { xmlDoc.WriteTo(writer); } } private bool CanWriteValue(object value) { if (value == null) return false; TypeConverter converter = TypeDescriptor.GetConverter(value.GetType()); return CanConvertToAndFromString(converter) || value.GetType().IsSerializable; } private void WriteValue(string name, object value, XmlElement block) { if (value == null) return; // skip persisting if any exception occurs string valueString = null; Type type = value.GetType(); TypeConverter converter = TypeDescriptor.GetConverter(type); if (CanConvertToAndFromString(converter)) { valueString = converter.ConvertToInvariantString(value); } else if (type.IsSerializable) { // serialize BinaryFormatter formatter = new BinaryFormatter(); using (MemoryStream stream = new MemoryStream()) { formatter.Serialize(stream, value); valueString = Convert.ToBase64String(stream.GetBuffer()); } } if (string.IsNullOrEmpty(valueString)) return; XmlDocument xmlDoc = block.OwnerDocument; XmlElement elmValue = xmlDoc.CreateElement("value"); elmValue.SetAttribute("name", name); elmValue.SetAttribute("type", type.Name); // if the valueString is xmlDoc then XmlDocument temp = StringToXmlDoc(valueString); if (temp != null) { // remove xml declaration if exists XmlDeclaration decl = temp.FirstChild as XmlDeclaration; if (decl != null) temp.RemoveChild(decl); elmValue.InnerXml = temp.DocumentElement.OuterXml; } else { elmValue.InnerText = valueString; } block.AppendChild(elmValue); } private XmlDocument StringToXmlDoc(string strXml) { XmlDocument xmlDoc = null; try { int len = (strXml.Length > 20) ? 20 : strXml.Length; string test = RemoveAllWhiteSpace(strXml.Substring(0, len)).ToLower(); if (test.Contains(" GetOrCreateFolder(string name, Tree tree) { // search for folder Tree result = null; int index = 0; foreach (Tree child in tree.Children) { string folderName = child.Value as string; if (folderName != null) { if (folderName == name) { result = child; break; } if (folderName.CompareTo(name) < 0) index++; } else // child is UserSettingsInfo { index++; // folders should follow settings nodes } } // if not found, create it if (result == null) { result = new Tree(name); tree.Children.Insert(index, result); } return result; } public event EventHandler Saving; public event EventHandler Loading; public event EventHandler Reloaded; private class TreeView : Tree, ITreeView { public TreeView(object root) : base(root) { } #region ITreeView Members public object Root { get { return this; } } public IEnumerable GetChildren(object parent) { foreach (object child in ((Tree)parent).Children) yield return child; } #endregion #region IItemView Members /// /// Gets item's display information /// Item being displayed /// Item info, to fill out public void GetInfo(object item, ItemInfo info) { object value = ((Tree)item).Value; if (value is string) { info.Label = (string)value; //info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.FolderImage); info.AllowSelect = false; } else { UserSettingsInfo settingsInfo = value as UserSettingsInfo; info.Label = settingsInfo.Name; info.AllowLabelEdit = false; //info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.PreferencesImage); info.IsLeaf = true; } } #endregion } private string _settingsPath; private string _defaultSettingsPath; private string _applicationName; private string _versionString; private string _propertyViewState; public void LoadSettings() { Loading.Raise(this, EventArgs.Empty); try { bool defaultSettingsExists = File.Exists(_defaultSettingsPath); bool appSettingsExists = File.Exists(_settingsPath); // only update property descriptors during the DefaultSettings.xml pass // if DefaultSettings.xml exists and AppSettings.xml does not exist SettingsInfo.CanMakeChanges = defaultSettingsExists && !appSettingsExists; // first, load default settings, if they exist: if (defaultSettingsExists) { using (Stream stream = File.OpenRead(_defaultSettingsPath)) Deserialize(stream); } // restore for AppSettings.xml pass SettingsInfo.CanMakeChanges = true; // now load user settings, overriding defaults: if (appSettingsExists) { using (Stream stream = File.OpenRead(_settingsPath)) Deserialize(stream); } } catch (Exception ex) { } finally { // restore value SettingsInfo.CanMakeChanges = true; } } protected void ApplyStoredSettings() { foreach (SettingsInfo info in _settings.Values) { foreach (SettingsInfo.Setting setting in info.Settings.Values) { if (setting.PropertyDescriptor is BoundPropertyDescriptor pd) { if (setting.ValueString == null) { continue; } object v; if (setting.PropertyDescriptor.PropertyType.IsEnum) { v = Enum.Parse(setting.PropertyDescriptor.PropertyType, setting.ValueString); } // if is list else if (setting.PropertyDescriptor.PropertyType.IsGenericType && setting.PropertyDescriptor.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) { Type itemType = setting.PropertyDescriptor.PropertyType.GetGenericArguments()[0]; TypeConverter converter = TypeDescriptor.GetConverter(itemType); string[] items = setting.ValueString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); IList list = (IList)Activator.CreateInstance(setting.PropertyDescriptor.PropertyType); foreach (string item in items) { list.Add(converter.ConvertFromInvariantString(item.Trim())); } v = list; } else { v = Convert.ChangeType(setting.ValueString, setting.PropertyDescriptor.PropertyType); } pd.SetValue(pd.Owner, v); } } } } protected bool Deserialize(Stream stream) { // create XML DOM from stream // if failed, display message box and return try { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(stream); XmlElement root = xmlDoc.DocumentElement; // get all the blocks XmlNodeList blocks = root.SelectNodes("block"); if (blocks == null || blocks.Count == 0) throw new Exception("The setting file is empty"); foreach (XmlElement block in blocks) { try { string id = block.GetAttribute("id"); SettingsInfo info; if (!_settings.TryGetValue(id, out info)) { info = new SettingsInfo(id); _settings.Add(id, info); } // get a list of value element in each block XmlNodeList valueNodes = block.SelectNodes("value"); // skip over empty block if (valueNodes == null || valueNodes.Count == 0) continue; foreach (XmlElement xmlElement in valueNodes) { string name = xmlElement.GetAttribute("name"); string valueString = GetElementValueString(xmlElement); info.Add(name, valueString); } } catch (Exception ex) { } } //ApplyStoredSettings(); Reloaded.Raise(this, EventArgs.Empty); } catch (Exception ex) { return false; } return true; } private static string GetElementValueString(XmlElement element) { string valueString = element.InnerText; if (string.IsNullOrEmpty(valueString)) valueString = element.InnerXml; return valueString; } } }