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.

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