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.

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