PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/src/MarkPad/Settings/SettingsProvider.cs

https://github.com/bcott/DownmarkerWPF
C# | 296 lines | 233 code | 51 blank | 12 comment | 27 complexity | 340dbf446ab65632e8df68297e01adb4 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Reflection;
  8. namespace MarkPad.Settings
  9. {
  10. // ReSharper disable InconsistentNaming
  11. public interface ISettingsProvider
  12. {
  13. /// <summary>
  14. ///
  15. /// </summary>
  16. /// <typeparam name="T"></typeparam>
  17. /// <param name="freshCopy">If true, does not fetch from cache (useful for isolated editing)</param>
  18. /// <returns></returns>
  19. T GetSettings<T>(bool freshCopy = false) where T : new();
  20. void SaveSettings<T>(T settings);
  21. IEnumerable<SettingsProvider.SettingDescriptor> ReadSettingMetadata<T>();
  22. IEnumerable<SettingsProvider.SettingDescriptor> ReadSettingMetadata(Type settingsType);
  23. T ResetToDefaults<T>() where T : new();
  24. }
  25. public interface ISettingsStorage
  26. {
  27. string SerializeList(List<string> listOfItems);
  28. List<string> DeserializeList(string serializedList);
  29. void Save(string key, Dictionary<string, string> settings);
  30. Dictionary<string, string> Load(string key);
  31. }
  32. public class SettingsProvider : ISettingsProvider
  33. {
  34. const string NotConvertableMessage = "Settings provider only supports types that Convert.ChangeType supports. See http://msdn.microsoft.com/en-us/library/dtb69x08.aspx";
  35. readonly ISettingsStorage settingsRepository;
  36. readonly Dictionary<Type, object> cache = new Dictionary<Type, object>();
  37. public SettingsProvider(ISettingsStorage settingsRepository = null)
  38. {
  39. this.settingsRepository = settingsRepository ?? new IsolatedStorageSettingsStore();
  40. }
  41. public T GetSettings<T>(bool fresh = false) where T : new()
  42. {
  43. var type = typeof (T);
  44. if (!fresh && cache.ContainsKey(type))
  45. return (T)cache[type];
  46. var settingsLookup = settingsRepository.Load(GetKey<T>());
  47. var settings = new T();
  48. var settingMetadata = ReadSettingMetadata<T>();
  49. foreach (var setting in settingMetadata)
  50. {
  51. // Write over it using the stored value if exists
  52. var key = GetKey<T>(setting);
  53. var value = settingsLookup.ContainsKey(key)
  54. ? ConvertValue(settingsLookup[key], setting)
  55. : GetDefaultValue(setting);
  56. setting.Write(settings, value);
  57. }
  58. cache[typeof(T)] = settings;
  59. return settings;
  60. }
  61. object GetDefaultValue(SettingDescriptor setting)
  62. {
  63. return setting.DefaultValue ?? ConvertValue(null, setting);
  64. }
  65. static string GetKey<T>()
  66. {
  67. return typeof(T).Name;
  68. }
  69. object ConvertValue(string storedValue, SettingDescriptor setting)
  70. {
  71. return ConvertValue(storedValue, setting.UnderlyingType);
  72. }
  73. object ConvertValue(string storedValue, Type underlyingType)
  74. {
  75. if (underlyingType == typeof(string)) return storedValue;
  76. var isList = IsList(underlyingType);
  77. if (isList && string.IsNullOrEmpty(storedValue)) return CreateListInstance(underlyingType);
  78. if (underlyingType != typeof(string) && string.IsNullOrEmpty(storedValue)) return null;
  79. if (underlyingType.IsEnum) return Enum.Parse(underlyingType, storedValue, false);
  80. if (underlyingType == typeof(Guid)) return Guid.Parse(storedValue);
  81. if (isList) return ReadList(storedValue, underlyingType);
  82. object converted;
  83. try
  84. {
  85. converted = Convert.ChangeType(storedValue, underlyingType, CultureInfo.InvariantCulture);
  86. }
  87. catch (InvalidCastException ex)
  88. {
  89. throw new NotSupportedException(NotConvertableMessage, ex);
  90. }
  91. catch (FormatException ex)
  92. {
  93. throw new NotSupportedException(NotConvertableMessage, ex);
  94. }
  95. return converted;
  96. }
  97. private object ReadList(string storedValue, Type propertyType)
  98. {
  99. var listItemType = propertyType.GetGenericArguments()[0];
  100. var list = CreateListInstance(propertyType);
  101. var listInterface = (IList)list;
  102. var valueList = settingsRepository.DeserializeList(storedValue);
  103. foreach (var value in valueList)
  104. {
  105. listInterface.Add(ConvertValue(value, listItemType));
  106. }
  107. return list;
  108. }
  109. private static object CreateListInstance(Type propertyType)
  110. {
  111. return Activator.CreateInstance(propertyType.IsClass ? propertyType : typeof(List<>).MakeGenericType(propertyType.GetGenericArguments()[0]));
  112. }
  113. private static bool IsList(Type propertyType)
  114. {
  115. return
  116. typeof(IList).IsAssignableFrom(propertyType) ||
  117. (propertyType.IsGenericType && typeof(IList<>) == propertyType.GetGenericTypeDefinition());
  118. }
  119. public void SaveSettings<T>(T settingsToSave)
  120. {
  121. cache[typeof (T)] = settingsToSave;
  122. var settings = new Dictionary<string, string>();
  123. var settingsMetadata = ReadSettingMetadata<T>();
  124. foreach (var setting in settingsMetadata)
  125. {
  126. var value = setting.ReadValue(settingsToSave) ?? setting.DefaultValue;
  127. if (value == null && setting.UnderlyingType.IsEnum)
  128. value = EnumHelper.GetValues(setting.UnderlyingType).First();
  129. if (IsList(setting.UnderlyingType) && value != null)
  130. settings[GetKey<T>(setting)] = WriteList(value);
  131. else
  132. settings[GetKey<T>(setting)] = Convert.ToString(value ?? string.Empty, CultureInfo.InvariantCulture);
  133. }
  134. settingsRepository.Save(GetKey<T>(), settings);
  135. }
  136. private string WriteList(object value)
  137. {
  138. var list = (
  139. from object item in (IList)value
  140. select Convert.ToString(item ?? string.Empty, CultureInfo.CurrentCulture))
  141. .ToList();
  142. return settingsRepository.SerializeList(list);
  143. }
  144. internal static string GetKey<T>(SettingDescriptor setting)
  145. {
  146. var settingsType = typeof(T);
  147. return string.Format("{0}.{1}", settingsType.FullName, setting.Property.Name);
  148. }
  149. public IEnumerable<SettingDescriptor> ReadSettingMetadata<T>()
  150. {
  151. return ReadSettingMetadata(typeof(T));
  152. }
  153. public IEnumerable<SettingDescriptor> ReadSettingMetadata(Type settingsType)
  154. {
  155. return settingsType.GetProperties()
  156. .Where(x => x.CanRead && x.CanWrite)
  157. .Select(x => new SettingDescriptor(x))
  158. .ToArray();
  159. }
  160. public T ResetToDefaults<T>() where T : new()
  161. {
  162. settingsRepository.Save(GetKey<T>(), new Dictionary<string, string>());
  163. var type = typeof (T);
  164. if (cache.ContainsKey(type))
  165. {
  166. var cachedCopy = cache[type];
  167. var settingMetadata = ReadSettingMetadata<T>();
  168. foreach (var setting in settingMetadata)
  169. {
  170. setting.Write(cachedCopy, GetDefaultValue(setting));
  171. }
  172. return (T)cachedCopy;
  173. }
  174. return GetSettings<T>();
  175. }
  176. public class SettingDescriptor : INotifyPropertyChanged
  177. {
  178. readonly PropertyInfo property;
  179. public SettingDescriptor(PropertyInfo property)
  180. {
  181. this.property = property;
  182. DisplayName = property.Name;
  183. ReadAttribute<DefaultValueAttribute>(d => DefaultValue = d.Value);
  184. ReadAttribute<DescriptionAttribute>(d => Description = d.Description);
  185. ReadAttribute<DisplayNameAttribute>(d => DisplayName = d.DisplayName);
  186. }
  187. void ReadAttribute<TAttribute>(Action<TAttribute> callback)
  188. {
  189. var instances = property.GetCustomAttributes(typeof(TAttribute), true).OfType<TAttribute>();
  190. foreach (var instance in instances)
  191. {
  192. callback(instance);
  193. }
  194. }
  195. public PropertyInfo Property
  196. {
  197. get { return property; }
  198. }
  199. public object DefaultValue { get; private set; }
  200. public string Description { get; private set; }
  201. public string DisplayName { get; private set; }
  202. public void Write(object settings, object value)
  203. {
  204. property.SetValue(settings, value, null);
  205. }
  206. /// <summary>
  207. /// If the property type is nullable, returns the type. i.e int? returns int
  208. /// </summary>
  209. public Type UnderlyingType
  210. {
  211. get
  212. {
  213. if (Property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  214. return property.PropertyType.GetGenericArguments()[0];
  215. return property.PropertyType;
  216. }
  217. }
  218. public object ReadValue(object settings)
  219. {
  220. return property.GetValue(settings, null);
  221. }
  222. #pragma warning disable 67
  223. public event PropertyChangedEventHandler PropertyChanged;
  224. #pragma warning restore 67
  225. }
  226. }
  227. public static class EnumHelper
  228. {
  229. public static IEnumerable<T> GetValues<T>()
  230. {
  231. return GetValues(typeof(T))
  232. .OfType<T>();
  233. }
  234. public static IEnumerable<object> GetValues(Type enumType)
  235. {
  236. if (!enumType.IsEnum)
  237. throw new ArgumentException("enumType must be an Enum type", "enumType");
  238. return enumType
  239. .GetFields()
  240. .Where(field => field.IsLiteral)
  241. .Select(field => field.GetValue(enumType));
  242. }
  243. }
  244. // ReSharper restore InconsistentNaming
  245. }