Files
HawkeyeVision/framework/Inspectron.Settings/InspectronSettings.cs
EugeneTes 8704d0b5ac 2.0.15
profile influences configuration
2026-05-15 11:54:36 +02:00

867 lines
31 KiB
C#

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, string profile = 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");
string settingsFileName = string.IsNullOrEmpty(profile)
? "AppSettings.xml"
: "AppSettings_" + profile + ".xml";
if (path == null)
{
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_settingsPath = string.Format("{0}\\{1}\\{2}\\{3}", appDataPath, _applicationName, _versionString, settingsFileName);
}
else
{
_settingsPath = Path.Combine(path, settingsFileName);
}
}
internal Path<object> GetSettingsPath(string pathName)
{
string[] pathSegments = pathName.Split('/', 16);
object[] path = new object[pathSegments.Length + 1];
// first node is the settings tree root
Tree<object> node = (Tree<object>)_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<object> 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<object>(path);
}
/// <summary>
/// Gets or sets the current state of all properties (Memento pattern)</summary>
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<PropertyDescriptor> UserPropertyDescriptors
{
get
{
var userSettings = UserSettings as Tree<object>;
if (userSettings == null)
throw new InvalidOperationException("userSettings");
var all = userSettings.LevelOrder.Where(x => x.Value is UserSettingsInfo);
foreach (Tree<object> 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<PropertyDescriptor> GetProperties(Tree<object> tree)
{
var info = tree.Value as UserSettingsInfo;
if (info != null)
return info.Settings;
return null;
}
protected class SettingsInfo
{
/// <summary>
/// Constructor with name</summary>
/// <param name="name">Name associated with this group of settings</param>
public SettingsInfo(string name)
{
Name = name;
}
/// <summary>
/// Add a setting to group of settings as a name and value, replacing the previous value if present</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
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));
}
}
/// <summary>
/// Add a setting to group of settings as a PropertyDescriptor, replacing the previous value if present</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
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));
}
}
/// <summary>
/// Name associated with this group of settings</summary>
public readonly string Name;
/// <summary>
/// Dictionary for names and values of settings in group</summary>
public readonly SortedDictionary<string, Setting> Settings = new SortedDictionary<string, Setting>();
/// <summary>
/// Class for handling individual setting information</summary>
public class Setting
{
/// <summary>
/// Constructor with PropertyDescriptor</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
public Setting(PropertyDescriptor descriptor)
{
PropertyDescriptor = descriptor;
}
/// <summary>
/// Constructor with name and value of setting</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
public Setting(string name, string valueString)
{
Name = name;
ValueString = valueString;
}
/// <summary>
/// Set a setting's value with name and value of setting</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
public void Set(string name, string valueString)
{
Name = name;
ValueString = valueString;
if (PropertyDescriptor != null)
{
SetValue();
}
}
/// <summary>
/// Set a setting's value with PropertyDescriptor</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
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);
}
/// <summary>
/// Setting name</summary>
public string Name;
/// <summary>
/// Setting value</summary>
public string ValueString;
/// <summary>
/// Setting PropertyDescriptor</summary>
public PropertyDescriptor PropertyDescriptor;
}
/// <summary>
/// Set whether property descriptors are allowed to make changes</summary>
/// <remarks>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</remarks>
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 if (TypeConverterRegistry.CanConvert(type))
{
value = TypeConverterRegistry.GetConverter(type).ConvertFrom(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)));
}
/// <summary>
/// Class for group of user settings</summary>
public class UserSettingsInfo
{
/// <summary>
/// Constructor with name and PropertyDescriptors</summary>
/// <param name="name">Name of group of user settings</param>
/// <param name="settings">PropertyDescriptors with settings</param>
public UserSettingsInfo(string name, PropertyDescriptor[] settings)
{
Name = name;
Settings = new List<PropertyDescriptor>(settings);
}
/// <summary>
/// Name of group of user settings</summary>
public readonly string Name;
/// <summary>
/// PropertyDescriptors with settings</summary>
public readonly List<PropertyDescriptor> Settings;
}
private readonly SortedDictionary<string, SettingsInfo> _settings = new SortedDictionary<string, SettingsInfo>();
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<Func<object>> 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<object> folder = UserSettings as Tree<object>;
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<object> 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<object> node = new Tree<object>(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 (TypeConverterRegistry.TryGetConverter(type, out var typeConverter))
{
valueString = typeConverter.ConvertTo(value, typeof(string)) as string;
}
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("<?xmlversion="))
{
xmlDoc = new XmlDocument();
xmlDoc.LoadXml(strXml);
}
}
catch
{
xmlDoc = null;
}
return xmlDoc;
}
public static string RemoveAllWhiteSpace(string s)
{
if (string.IsNullOrEmpty(s))
return s;
StringBuilder result = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if (!char.IsWhiteSpace(c))
result.Append(c);
}
return result.ToString();
}
public void SaveSettings()
{
string tempNew = string.Empty;
string mutexName = GetMutexName(_settingsPath);
using (Mutex saveMutex = new Mutex(false, mutexName))
{
try
{
saveMutex.WaitOne();
// Create zero-size file.
tempNew = Path.GetTempFileName();
using (Stream stream = File.Create(tempNew))
Serialize(stream);
// Make sure the settings directory exists. Do nothing if it already exists.
string settingsDir = Path.GetDirectoryName(_settingsPath);
Directory.CreateDirectory(settingsDir);
// Erase old backup (if any) and move current settings file (if any).
string tempBackup = Path.Combine(settingsDir, "~Settings.xml");
if (File.Exists(tempBackup)) // seems unnecessary, but Dan put this check in the WPF version --Ron
File.Delete(tempBackup);
if (File.Exists(_settingsPath))
File.Move(_settingsPath, tempBackup);
// Move temporary file to be the new settings file, then delete backup.
File.Move(tempNew, _settingsPath);
File.Delete(tempBackup);
}
catch (TargetInvocationException)
{
// Catch and ignore TargetInvocationException happening if Windows
// is shut down with the application still running.
// TO DO: Find a way to successfully save settings and exit on shutdown.
}
catch (Exception ex)
{
}
finally
{
// Attempt clean-up. No exception is thrown if file doesn't exist.
File.Delete(tempNew);
saveMutex.ReleaseMutex();
}
}
}
private Tree<object> GetOrCreateFolder(string name, Tree<object> tree)
{
// search for folder
Tree<object> result = null;
int index = 0;
foreach (Tree<object> 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<object>(name);
tree.Children.Insert(index, result);
}
return result;
}
public event EventHandler Saving;
public event EventHandler Loading;
public event EventHandler Reloaded;
private class TreeView : Tree<object>, ITreeView
{
public TreeView(object root)
: base(root)
{
}
#region ITreeView Members
public object Root
{
get { return this; }
}
public IEnumerable<object> GetChildren(object parent)
{
foreach (object child in ((Tree<object>)parent).Children)
yield return child;
}
#endregion
#region IItemView Members
/// <summary>
/// Gets item's display information</summary>
/// <param name="item">Item being displayed</param>
/// <param name="info">Item info, to fill out</param>
public void GetInfo(object item, ItemInfo info)
{
object value = ((Tree<object>)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;
}
}
}