|
@ -1,174 +1,175 @@ |
|
|
using System; |
|
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
using System.IO; |
|
|
|
|
|
using System.Linq; |
|
|
|
|
|
using System.Reflection; |
|
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
using IPA.Config.Providers; |
|
|
|
|
|
using IPA.Utilities; |
|
|
|
|
|
#if NET3
|
|
|
|
|
|
using Net3_Proxy; |
|
|
|
|
|
using Path = Net3_Proxy.Path; |
|
|
|
|
|
using Array = Net3_Proxy.Array; |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace IPA.Config |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// A class to handle updating ConfigProviders automatically
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class Config |
|
|
|
|
|
{ |
|
|
|
|
|
static Config() |
|
|
|
|
|
{ |
|
|
|
|
|
JsonConfigProvider.RegisterConfig(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Specifies that a particular parameter is preferred to use a particular <see cref="IConfigProvider" />.
|
|
|
|
|
|
/// If it is not available, also specifies backups. If none are available, the default is used.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Parameter)] |
|
|
|
|
|
public sealed class PreferAttribute : Attribute |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The order of preference for the config type.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value>the list of config extensions in order of preference</value>
|
|
|
|
|
|
// ReSharper disable once UnusedAutoPropertyAccessor.Global
|
|
|
|
|
|
public string[] PreferenceOrder { get; private set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Constructs the attribute with a specific preference list. Each entry is the extension without a '.'
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="preference">The preferences in order of preference.</param>
|
|
|
|
|
|
public PreferAttribute(params string[] preference) |
|
|
|
|
|
{ |
|
|
|
|
|
PreferenceOrder = preference; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Specifies a preferred config name, instead of using the plugin's name.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Parameter)] |
|
|
|
|
|
public sealed class NameAttribute : Attribute |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The name to use for the config.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value>the name to use for the config</value>
|
|
|
|
|
|
// ReSharper disable once UnusedAutoPropertyAccessor.Global
|
|
|
|
|
|
public string Name { get; private set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Constructs the attribute with a specific name.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name">the name to use for the config.</param>
|
|
|
|
|
|
public NameAttribute(string name) |
|
|
|
|
|
{ |
|
|
|
|
|
Name = name; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<string, IConfigProvider> registeredProviders = new Dictionary<string, IConfigProvider>(); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a <see cref="IConfigProvider"/> to use for configs.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T">the type to register</typeparam>
|
|
|
|
|
|
public static void Register<T>() where T : IConfigProvider => Register(typeof(T)); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a <see cref="IConfigProvider"/> to use for configs.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="type">the type to register</param>
|
|
|
|
|
|
public static void Register(Type type) |
|
|
|
|
|
{ |
|
|
|
|
|
var inst = Activator.CreateInstance(type) as IConfigProvider; |
|
|
|
|
|
if (inst == null) |
|
|
|
|
|
throw new ArgumentException($"Type not an {nameof(IConfigProvider)}"); |
|
|
|
|
|
|
|
|
|
|
|
if (registeredProviders.ContainsKey(inst.Extension)) |
|
|
|
|
|
throw new InvalidOperationException($"Extension provider for {inst.Extension} already exists"); |
|
|
|
|
|
|
|
|
|
|
|
registeredProviders.Add(inst.Extension, inst); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a <see cref="Config"/> object using the specified list of preferred config types.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="configName">the name of the mod for this config</param>
|
|
|
|
|
|
/// <param name="extensions">the preferred config types to try to get</param>
|
|
|
|
|
|
/// <returns>a <see cref="Config"/> using the requested format, or of type JSON.</returns>
|
|
|
|
|
|
public static Config GetConfigFor(string configName, params string[] extensions) |
|
|
|
|
|
{ |
|
|
|
|
|
var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json"; |
|
|
|
|
|
var provider = registeredProviders[chosenExt]; |
|
|
|
|
|
|
|
|
|
|
|
var filename = Path.Combine(BeatSaber.UserDataPath, configName + "." + provider.Extension); |
|
|
|
|
|
var config = new Config(configName, provider, new FileInfo(filename)); |
|
|
|
|
|
|
|
|
|
|
|
ConfigRuntime.RegisterConfig(config); |
|
|
|
|
|
|
|
|
|
|
|
return config; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
internal static Config GetConfigFor(string modName, ParameterInfo info) |
|
|
|
|
|
{ |
|
|
|
|
|
var prefs = Array.Empty<string>(); |
|
|
|
|
|
if (info.GetCustomAttribute<PreferAttribute>() is PreferAttribute prefer) |
|
|
|
|
|
prefs = prefer.PreferenceOrder; |
|
|
|
|
|
if (info.GetCustomAttribute<NameAttribute>() is NameAttribute name) |
|
|
|
|
|
modName = name.Name; |
|
|
|
|
|
|
|
|
|
|
|
return GetConfigFor(modName, prefs); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the name associated with this <see cref="Config"/> object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public string Name { get; } |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the <see cref="IConfigProvider"/> associated with this <see cref="Config"/> object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IConfigProvider Provider { get; } |
|
|
|
|
|
|
|
|
|
|
|
internal IConfigStore Store = null; |
|
|
|
|
|
internal readonly FileInfo File; |
|
|
|
|
|
internal readonly ConfigProvider configProvider; |
|
|
|
|
|
internal int Writes = 0; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Sets this object's <see cref="IConfigStore"/>. Can only be called once.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="store">the <see cref="IConfigStore"/> to add to this instance</param>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">If this was called before.</exception>
|
|
|
|
|
|
public void SetStore(IConfigStore store) |
|
|
|
|
|
{ |
|
|
|
|
|
if (Store != null) |
|
|
|
|
|
throw new InvalidOperationException($"{nameof(SetStore)} can only be called once"); |
|
|
|
|
|
Store = store; |
|
|
|
|
|
ConfigRuntime.ConfigChanged(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Forces a synchronous load from disk.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadSync() => LoadAsync().Wait(); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Forces an asynchronous load from disk.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Task LoadAsync() => ConfigRuntime.TriggerFileLoad(this); |
|
|
|
|
|
|
|
|
|
|
|
private Config(string name, IConfigProvider provider, FileInfo file) |
|
|
|
|
|
{ |
|
|
|
|
|
Name = name; Provider = provider; File = file; |
|
|
|
|
|
configProvider = new ConfigProvider(file, provider); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
using System; |
|
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
using System.IO; |
|
|
|
|
|
using System.Linq; |
|
|
|
|
|
using System.Reflection; |
|
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
using IPA.Config.Providers; |
|
|
|
|
|
using IPA.Utilities; |
|
|
|
|
|
#if NET3
|
|
|
|
|
|
using Net3_Proxy; |
|
|
|
|
|
using Path = Net3_Proxy.Path; |
|
|
|
|
|
using Array = Net3_Proxy.Array; |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace IPA.Config |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// An abstraction of a config file on disk, which handles synchronizing between a memory representation and the
|
|
|
|
|
|
/// disk representation.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class Config |
|
|
|
|
|
{ |
|
|
|
|
|
static Config() |
|
|
|
|
|
{ |
|
|
|
|
|
JsonConfigProvider.RegisterConfig(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Specifies that a particular parameter is preferred to use a particular <see cref="IConfigProvider" />.
|
|
|
|
|
|
/// If it is not available, also specifies backups. If none are available, the default is used.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Parameter)] |
|
|
|
|
|
public sealed class PreferAttribute : Attribute |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The order of preference for the config type.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value>the list of config extensions in order of preference</value>
|
|
|
|
|
|
// ReSharper disable once UnusedAutoPropertyAccessor.Global
|
|
|
|
|
|
public string[] PreferenceOrder { get; private set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Constructs the attribute with a specific preference list. Each entry is the extension without a '.'
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="preference">The preferences in order of preference.</param>
|
|
|
|
|
|
public PreferAttribute(params string[] preference) |
|
|
|
|
|
{ |
|
|
|
|
|
PreferenceOrder = preference; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Specifies a preferred config name, instead of using the plugin's name.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Parameter)] |
|
|
|
|
|
public sealed class NameAttribute : Attribute |
|
|
|
|
|
{ |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The name to use for the config.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value>the name to use for the config</value>
|
|
|
|
|
|
// ReSharper disable once UnusedAutoPropertyAccessor.Global
|
|
|
|
|
|
public string Name { get; private set; } |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Constructs the attribute with a specific name.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name">the name to use for the config.</param>
|
|
|
|
|
|
public NameAttribute(string name) |
|
|
|
|
|
{ |
|
|
|
|
|
Name = name; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<string, IConfigProvider> registeredProviders = new Dictionary<string, IConfigProvider>(); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a <see cref="IConfigProvider"/> to use for configs.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T">the type to register</typeparam>
|
|
|
|
|
|
public static void Register<T>() where T : IConfigProvider => Register(typeof(T)); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Registers a <see cref="IConfigProvider"/> to use for configs.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="type">the type to register</param>
|
|
|
|
|
|
public static void Register(Type type) |
|
|
|
|
|
{ |
|
|
|
|
|
var inst = Activator.CreateInstance(type) as IConfigProvider; |
|
|
|
|
|
if (inst == null) |
|
|
|
|
|
throw new ArgumentException($"Type not an {nameof(IConfigProvider)}"); |
|
|
|
|
|
|
|
|
|
|
|
if (registeredProviders.ContainsKey(inst.Extension)) |
|
|
|
|
|
throw new InvalidOperationException($"Extension provider for {inst.Extension} already exists"); |
|
|
|
|
|
|
|
|
|
|
|
registeredProviders.Add(inst.Extension, inst); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a <see cref="Config"/> object using the specified list of preferred config types.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="configName">the name of the mod for this config</param>
|
|
|
|
|
|
/// <param name="extensions">the preferred config types to try to get</param>
|
|
|
|
|
|
/// <returns>a <see cref="Config"/> using the requested format, or of type JSON.</returns>
|
|
|
|
|
|
public static Config GetConfigFor(string configName, params string[] extensions) |
|
|
|
|
|
{ |
|
|
|
|
|
var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json"; |
|
|
|
|
|
var provider = registeredProviders[chosenExt]; |
|
|
|
|
|
|
|
|
|
|
|
var filename = Path.Combine(BeatSaber.UserDataPath, configName + "." + provider.Extension); |
|
|
|
|
|
var config = new Config(configName, provider, new FileInfo(filename)); |
|
|
|
|
|
|
|
|
|
|
|
ConfigRuntime.RegisterConfig(config); |
|
|
|
|
|
|
|
|
|
|
|
return config; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
internal static Config GetConfigFor(string modName, ParameterInfo info) |
|
|
|
|
|
{ |
|
|
|
|
|
var prefs = Array.Empty<string>(); |
|
|
|
|
|
if (info.GetCustomAttribute<PreferAttribute>() is PreferAttribute prefer) |
|
|
|
|
|
prefs = prefer.PreferenceOrder; |
|
|
|
|
|
if (info.GetCustomAttribute<NameAttribute>() is NameAttribute name) |
|
|
|
|
|
modName = name.Name; |
|
|
|
|
|
|
|
|
|
|
|
return GetConfigFor(modName, prefs); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the name associated with this <see cref="Config"/> object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public string Name { get; } |
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the <see cref="IConfigProvider"/> associated with this <see cref="Config"/> object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IConfigProvider Provider { get; } |
|
|
|
|
|
|
|
|
|
|
|
internal IConfigStore Store = null; |
|
|
|
|
|
internal readonly FileInfo File; |
|
|
|
|
|
internal readonly ConfigProvider configProvider; |
|
|
|
|
|
internal int Writes = 0; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Sets this object's <see cref="IConfigStore"/>. Can only be called once.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="store">the <see cref="IConfigStore"/> to add to this instance</param>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">If this was called before.</exception>
|
|
|
|
|
|
public void SetStore(IConfigStore store) |
|
|
|
|
|
{ |
|
|
|
|
|
if (Store != null) |
|
|
|
|
|
throw new InvalidOperationException($"{nameof(SetStore)} can only be called once"); |
|
|
|
|
|
Store = store; |
|
|
|
|
|
ConfigRuntime.ConfigChanged(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Forces a synchronous load from disk.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LoadSync() => LoadAsync().Wait(); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Forces an asynchronous load from disk.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Task LoadAsync() => ConfigRuntime.TriggerFileLoad(this); |
|
|
|
|
|
|
|
|
|
|
|
private Config(string name, IConfigProvider provider, FileInfo file) |
|
|
|
|
|
{ |
|
|
|
|
|
Name = name; Provider = provider; File = file; |
|
|
|
|
|
configProvider = new ConfigProvider(file, provider); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |