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.

172 lines
6.5 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Runtime.CompilerServices;
  7. using System.Threading.Tasks;
  8. using IPA.Config.Providers;
  9. using IPA.Utilities;
  10. #if NET3
  11. using Net3_Proxy;
  12. using Path = Net3_Proxy.Path;
  13. using Array = Net3_Proxy.Array;
  14. #endif
  15. namespace IPA.Config
  16. {
  17. /// <summary>
  18. /// A class to handle updating ConfigProviders automatically
  19. /// </summary>
  20. public class Config
  21. {
  22. static Config()
  23. {
  24. JsonConfigProvider.RegisterConfig();
  25. }
  26. /// <summary>
  27. /// Specifies that a particular parameter is preferred to use a particular <see cref="IConfigProvider" />.
  28. /// If it is not available, also specifies backups. If none are available, the default is used.
  29. /// </summary>
  30. [AttributeUsage(AttributeTargets.Parameter)]
  31. public sealed class PreferAttribute : Attribute
  32. {
  33. /// <summary>
  34. /// The order of preference for the config type.
  35. /// </summary>
  36. /// <value>the list of config extensions in order of preference</value>
  37. // ReSharper disable once UnusedAutoPropertyAccessor.Global
  38. public string[] PreferenceOrder { get; private set; }
  39. /// <inheritdoc />
  40. /// <summary>
  41. /// Constructs the attribute with a specific preference list. Each entry is the extension without a '.'
  42. /// </summary>
  43. /// <param name="preference">The preferences in order of preference.</param>
  44. public PreferAttribute(params string[] preference)
  45. {
  46. PreferenceOrder = preference;
  47. }
  48. }
  49. /// <summary>
  50. /// Specifies a preferred config name, instead of using the plugin's name.
  51. /// </summary>
  52. [AttributeUsage(AttributeTargets.Parameter)]
  53. public sealed class NameAttribute : Attribute
  54. {
  55. /// <summary>
  56. /// The name to use for the config.
  57. /// </summary>
  58. /// <value>the name to use for the config</value>
  59. // ReSharper disable once UnusedAutoPropertyAccessor.Global
  60. public string Name { get; private set; }
  61. /// <inheritdoc />
  62. /// <summary>
  63. /// Constructs the attribute with a specific name.
  64. /// </summary>
  65. /// <param name="name">the name to use for the config.</param>
  66. public NameAttribute(string name)
  67. {
  68. Name = name;
  69. }
  70. }
  71. private static readonly Dictionary<string, IConfigProvider> registeredProviders = new Dictionary<string, IConfigProvider>();
  72. /// <summary>
  73. /// Registers a <see cref="IConfigProvider"/> to use for configs.
  74. /// </summary>
  75. /// <typeparam name="T">the type to register</typeparam>
  76. public static void Register<T>() where T : IConfigProvider => Register(typeof(T));
  77. /// <summary>
  78. /// Registers a <see cref="IConfigProvider"/> to use for configs.
  79. /// </summary>
  80. /// <param name="type">the type to register</param>
  81. public static void Register(Type type)
  82. {
  83. var inst = Activator.CreateInstance(type) as IConfigProvider;
  84. if (inst == null)
  85. throw new ArgumentException($"Type not an {nameof(IConfigProvider)}");
  86. if (registeredProviders.ContainsKey(inst.Extension))
  87. throw new InvalidOperationException($"Extension provider for {inst.Extension} already exists");
  88. registeredProviders.Add(inst.Extension, inst);
  89. }
  90. /// <summary>
  91. /// Gets a <see cref="Config"/> object using the specified list of preferred config types.
  92. /// </summary>
  93. /// <param name="configName">the name of the mod for this config</param>
  94. /// <param name="extensions">the preferred config types to try to get</param>
  95. /// <returns>a <see cref="Config"/> using the requested format, or of type JSON.</returns>
  96. public static Config GetConfigFor(string configName, params string[] extensions)
  97. {
  98. var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json";
  99. var provider = registeredProviders[chosenExt];
  100. var filename = Path.Combine(BeatSaber.UserDataPath, configName + "." + provider.Extension);
  101. var config = new Config(configName, provider, new FileInfo(filename));
  102. ConfigRuntime.RegisterConfig(config);
  103. return config;
  104. }
  105. internal static Config GetConfigFor(string modName, ParameterInfo info)
  106. {
  107. var prefs = Array.Empty<string>();
  108. if (info.GetCustomAttribute<PreferAttribute>() is PreferAttribute prefer)
  109. prefs = prefer.PreferenceOrder;
  110. if (info.GetCustomAttribute<NameAttribute>() is NameAttribute name)
  111. modName = name.Name;
  112. return GetConfigFor(modName, prefs);
  113. }
  114. /// <summary>
  115. /// Gets the name associated with this <see cref="Config"/> object.
  116. /// </summary>
  117. public string Name { get; private set; }
  118. /// <summary>
  119. /// Gets the <see cref="IConfigProvider"/> associated with this <see cref="Config"/> object.
  120. /// </summary>
  121. public IConfigProvider Provider { get; private set; }
  122. internal IConfigStore Store = null;
  123. internal readonly FileInfo File;
  124. internal int Writes = 0;
  125. /// <summary>
  126. /// Sets this object's <see cref="IConfigStore"/>. Can only be called once.
  127. /// </summary>
  128. /// <param name="store">the <see cref="IConfigStore"/> to add to this instance</param>
  129. /// <exception cref="InvalidOperationException">If this was called before.</exception>
  130. public void SetStore(IConfigStore store)
  131. {
  132. if (Store != null)
  133. throw new InvalidOperationException($"{nameof(SetStore)} can only be called once");
  134. Store = store;
  135. ConfigRuntime.ConfigChanged();
  136. }
  137. /// <summary>
  138. /// Forces a synchronous load from disk.
  139. /// </summary>
  140. public void LoadSync() => LoadAsync().Wait();
  141. /// <summary>
  142. /// Forces an asynchronous load from disk.
  143. /// </summary>
  144. public Task LoadAsync() => ConfigRuntime.TriggerFileLoad(this);
  145. private Config(string name, IConfigProvider provider, FileInfo file)
  146. {
  147. Name = name; Provider = provider; File = file;
  148. }
  149. }
  150. }