You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
9.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using IPA.Config.ConfigProviders;
  6. using IPA.Utilities;
  7. namespace IPA.Config
  8. {
  9. /// <summary>
  10. /// A class to handle updating ConfigProviders automatically
  11. /// </summary>
  12. public static class Config
  13. {
  14. static Config()
  15. {
  16. JsonConfigProvider.RegisterConfig();
  17. }
  18. /// <inheritdoc />
  19. /// <summary>
  20. /// Defines the type of the <see cref="T:IPA.Config.IConfigProvider" />
  21. /// </summary>
  22. [AttributeUsage(AttributeTargets.Class)]
  23. public class TypeAttribute : Attribute
  24. {
  25. /// <summary>
  26. /// The extension associated with this type, without the '.'
  27. /// </summary>
  28. // ReSharper disable once UnusedAutoPropertyAccessor.Global
  29. public string Extension { get; private set; }
  30. /// <inheritdoc />
  31. /// <summary>
  32. /// Constructs the attribute with a specified extension.
  33. /// </summary>
  34. /// <param name="ext">the extension associated with this type, without the '.'</param>
  35. public TypeAttribute(string ext)
  36. {
  37. Extension = ext;
  38. }
  39. }
  40. /// <inheritdoc />
  41. /// <summary>
  42. /// Specifies that a particular parameter is preferred to be a specific type of <see cref="T:IPA.Config.IConfigProvider" />. If it is not available, also specifies backups. If none are available, the default is used.
  43. /// </summary>
  44. [AttributeUsage(AttributeTargets.Parameter)]
  45. public class PreferAttribute : Attribute
  46. {
  47. /// <summary>
  48. /// The order of preference for the config type.
  49. /// </summary>
  50. // ReSharper disable once UnusedAutoPropertyAccessor.Global
  51. public string[] PreferenceOrder { get; private set; }
  52. /// <inheritdoc />
  53. /// <summary>
  54. /// Constructs the attribute with a specific preference list. Each entry is the extension without a '.'
  55. /// </summary>
  56. /// <param name="preference">The preferences in order of preference.</param>
  57. public PreferAttribute(params string[] preference)
  58. {
  59. PreferenceOrder = preference;
  60. }
  61. }
  62. private static readonly Dictionary<string, Type> registeredProviders = new Dictionary<string, Type>();
  63. /// <summary>
  64. /// Registers a <see cref="IConfigProvider"/> to use for configs.
  65. /// </summary>
  66. /// <typeparam name="T">the type to register</typeparam>
  67. public static void Register<T>() where T : IConfigProvider => Register(typeof(T));
  68. /// <summary>
  69. /// Registers a <see cref="IConfigProvider"/> to use for configs.
  70. /// </summary>
  71. /// <param name="type">the type to register</param>
  72. public static void Register(Type type)
  73. {
  74. if (!(type.GetCustomAttribute(typeof(TypeAttribute)) is TypeAttribute ext))
  75. throw new InvalidOperationException("Type does not have TypeAttribute");
  76. if (!typeof(IConfigProvider).IsAssignableFrom(type))
  77. throw new InvalidOperationException("Type not IConfigProvider");
  78. if (registeredProviders.ContainsKey(ext.Extension))
  79. throw new InvalidOperationException($"Extension provider for {ext.Extension} already exists");
  80. registeredProviders.Add(ext.Extension, type);
  81. }
  82. private static SortedList<Ref<DateTime>, IConfigProvider> configProviders = new SortedList<Ref<DateTime>, IConfigProvider>();
  83. /// <summary>
  84. /// Gets an <see cref="IConfigProvider"/> using the specified list pf preferred config types.
  85. /// </summary>
  86. /// <param name="filename">the name of the file to associate it with</param>
  87. /// <param name="extensions">the preferred config types to try to get</param>
  88. /// <returns>an <see cref="IConfigProvider"/> of the requested type, or of type JSON.</returns>
  89. public static IConfigProvider GetProviderFor(string filename, params string[] extensions)
  90. {
  91. var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json";
  92. var type = registeredProviders[chosenExt];
  93. var provider = Activator.CreateInstance(type) as IConfigProvider;
  94. if (provider != null)
  95. {
  96. provider.Filename = filename;
  97. configProviders.Add(provider.LastModified, provider);
  98. }
  99. return provider;
  100. }
  101. /// <summary>
  102. /// Gets an <see cref="IConfigProvider"/> using the specified list pf preferred config types.
  103. /// </summary>
  104. /// <param name="filename">the name of the file to associate it with</param>
  105. /// <param name="info">the parameter info to try and get info for</param>
  106. /// <returns>an <see cref="IConfigProvider"/> of the requested type, or of type JSON.</returns>
  107. public static IConfigProvider GetProviderFor(string filename, ParameterInfo info)
  108. {
  109. var prefs = new string[0];
  110. if (info.GetCustomAttribute(typeof(PreferAttribute)) is PreferAttribute prefer)
  111. prefs = prefer.PreferenceOrder;
  112. return GetProviderFor(filename, prefs);
  113. }
  114. private static SortedDictionary<IConfigProvider, Action> linkedProviders =
  115. new SortedDictionary<IConfigProvider, Action>();
  116. /// <summary>
  117. /// Creates a linked <see cref="Ref{T}"/> for the config provider. This <see cref="Ref{T}"/> will be automatically updated whenever the file on-disk changes.
  118. /// </summary>
  119. /// <typeparam name="T">the type of the parsed value</typeparam>
  120. /// <param name="config">the <see cref="IConfigProvider"/> to create a link to</param>
  121. /// <param name="onChange">an action to perform on value change</param>
  122. /// <returns>a <see cref="Ref{T}"/> to an ever-changing value, mirroring whatever the file contains.</returns>
  123. public static Ref<T> MakeLink<T>(this IConfigProvider config, Action<IConfigProvider, Ref<T>> onChange = null)
  124. {
  125. Ref<T> @ref = config.Parse<T>();
  126. void ChangeDelegate()
  127. {
  128. @ref.Value = config.Parse<T>();
  129. onChange?.Invoke(config, @ref);
  130. }
  131. if (linkedProviders.ContainsKey(config))
  132. linkedProviders[config] = (Action) Delegate.Combine(linkedProviders[config], (Action) ChangeDelegate);
  133. else
  134. linkedProviders.Add(config, ChangeDelegate);
  135. ChangeDelegate();
  136. return @ref;
  137. }
  138. /// <summary>
  139. /// Removes all linked <see cref="Ref{T}"/> such that they are no longer updated.
  140. /// </summary>
  141. /// <param name="config">the <see cref="IConfigProvider"/> to unlink</param>
  142. public static void RemoveLinks(this IConfigProvider config)
  143. {
  144. if (linkedProviders.ContainsKey(config))
  145. linkedProviders.Remove(config);
  146. }
  147. internal static void Update()
  148. {
  149. foreach (var provider in configProviders)
  150. {
  151. if (provider.Value.LastModified > provider.Key.Value)
  152. {
  153. try
  154. {
  155. provider.Value.Load(); // auto reload if it changes
  156. provider.Key.Value = provider.Value.LastModified;
  157. }
  158. catch (Exception e)
  159. {
  160. Logging.Logger.config.Error("Error when trying to load config");
  161. Logging.Logger.config.Error(e);
  162. }
  163. }
  164. if (provider.Value.HasChanged)
  165. {
  166. try
  167. {
  168. provider.Value.Save();
  169. provider.Key.Value = DateTime.Now;
  170. }
  171. catch (Exception e)
  172. {
  173. Logging.Logger.config.Error("Error when trying to save config");
  174. Logging.Logger.config.Error(e);
  175. }
  176. }
  177. if (provider.Value.InMemoryChanged)
  178. {
  179. provider.Value.InMemoryChanged = false;
  180. try
  181. {
  182. if (linkedProviders.ContainsKey(provider.Value))
  183. linkedProviders[provider.Value]();
  184. }
  185. catch (Exception e)
  186. {
  187. Logging.Logger.config.Error("Error running link change events");
  188. Logging.Logger.config.Error(e);
  189. }
  190. }
  191. }
  192. }
  193. internal static void Save()
  194. {
  195. foreach (var provider in configProviders)
  196. if (provider.Value.HasChanged)
  197. try
  198. {
  199. provider.Value.Save();
  200. }
  201. catch (Exception e)
  202. {
  203. Logging.Logger.config.Error("Error when trying to save config");
  204. Logging.Logger.config.Error(e);
  205. }
  206. }
  207. }
  208. }