Browse Source

Changed IConfigProvider interface

4.0.0-beta
Anairkoen Schno 5 years ago
parent
commit
45d2247061
3 changed files with 45 additions and 164 deletions
  1. +13
    -118
      IPA.Loader/Config/Config.cs
  2. +29
    -43
      IPA.Loader/Config/IConfigProvider.cs
  3. +3
    -3
      IPA.Loader/Config/Providers/JsonConfigProvider.cs

+ 13
- 118
IPA.Loader/Config/Config.cs View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using IPA.Config.Providers;
using IPA.Utilities;
#if NET3
@ -19,7 +20,7 @@ namespace IPA.Config
{
static Config()
{
JsonConfigProvider.RegisterConfig();
//JsonConfigProvider.RegisterConfig();
}
/// <inheritdoc />
@ -110,19 +111,18 @@ namespace IPA.Config
/// <param name="type">the type to register</param>
public static void Register(Type type)
{
if (!(type.GetCustomAttribute(typeof(TypeAttribute)) is TypeAttribute ext))
throw new InvalidOperationException("Type does not have TypeAttribute");
var inst = Activator.CreateInstance(type) as IConfigProvider;
if (inst == null)
throw new ArgumentException($"Type not an {nameof(IConfigProvider)}");
if (!typeof(IConfigProvider).IsAssignableFrom(type))
throw new InvalidOperationException("Type not IConfigProvider");
if (registeredProviders.ContainsKey(inst.Extension))
throw new InvalidOperationException($"Extension provider for {inst.Extension} already exists");
if (registeredProviders.ContainsKey(ext.Extension))
throw new InvalidOperationException($"Extension provider for {ext.Extension} already exists");
registeredProviders.Add(ext.Extension, type);
registeredProviders.Add(inst.Extension, type);
}
private static List<Tuple<Ref<DateTime>, IConfigProvider>> configProviders = new List<Tuple<Ref<DateTime>, IConfigProvider>>();
private static List<IConfigProvider> configProviders = new List<IConfigProvider>();
private static ConditionalWeakTable<IConfigProvider, FileInfo> file = new ConditionalWeakTable<IConfigProvider, FileInfo>();
/// <summary>
/// Gets an <see cref="IConfigProvider"/> using the specified list of preferred config types.
@ -135,11 +135,9 @@ namespace IPA.Config
var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json";
var type = registeredProviders[chosenExt];
var provider = Activator.CreateInstance(type) as IConfigProvider;
if (provider != null)
{
provider.Filename = Path.Combine(BeatSaber.UserDataPath, configName);
configProviders.Add(Tuple.Create(Ref.Create(provider.LastModified), provider));
}
configProviders.Add(provider);
// TODO: rething this one a bit
return provider;
}
@ -154,108 +152,5 @@ namespace IPA.Config
return GetProviderFor(modName, prefs);
}
private static Dictionary<IConfigProvider, Action> linkedProviders =
new Dictionary<IConfigProvider, Action>();
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">the type of the parsed value</typeparam>
/// <param name="config">the <see cref="IConfigProvider"/> to create a link to</param>
/// <param name="onChange">an action to perform on value change</param>
/// <returns>a <see cref="Ref{T}"/> to an ever-changing value, mirroring whatever the file contains.</returns>
public static Ref<T> MakeLink<T>(this IConfigProvider config, Action<IConfigProvider, Ref<T>> onChange = null)
{ // TODO: rework this stuff
Ref<T> @ref = config.Parse<T>();
void ChangeDelegate()
{
@ref.Value = config.Parse<T>();
onChange?.Invoke(config, @ref);
}
if (linkedProviders.ContainsKey(config))
linkedProviders[config] = (Action) Delegate.Combine(linkedProviders[config], (Action) ChangeDelegate);
else
linkedProviders.Add(config, ChangeDelegate);
ChangeDelegate();
return @ref;
}
/// <summary>
/// Removes all linked <see cref="Ref{T}"/> such that they are no longer updated.
/// </summary>
/// <param name="config">the <see cref="IConfigProvider"/> to unlink</param>
public static void RemoveLinks(this IConfigProvider config)
{
if (linkedProviders.ContainsKey(config))
linkedProviders.Remove(config);
}
internal static void Update()
{
foreach (var provider in configProviders)
{
if (provider.Item2.LastModified > provider.Item1.Value)
{
try
{
provider.Item2.Load(); // auto reload if it changes
provider.Item1.Value = provider.Item2.LastModified;
}
catch (Exception e)
{
Logging.Logger.config.Error("Error when trying to load config");
Logging.Logger.config.Error(e);
}
}
if (provider.Item2.HasChanged)
{
try
{
provider.Item2.Save();
provider.Item1.Value = Utils.CurrentTime();
}
catch (Exception e)
{
Logging.Logger.config.Error("Error when trying to save config");
Logging.Logger.config.Error(e);
}
}
if (provider.Item2.InMemoryChanged)
{
provider.Item2.InMemoryChanged = false;
try
{
if (linkedProviders.ContainsKey(provider.Item2))
linkedProviders[provider.Item2]();
}
catch (Exception e)
{
Logging.Logger.config.Error("Error running link change events");
Logging.Logger.config.Error(e);
}
}
}
}
internal static void Save()
{
foreach (var provider in configProviders)
if (provider.Item2.HasChanged)
try
{
provider.Item2.Save();
}
catch (Exception e)
{
Logging.Logger.config.Error("Error when trying to save config");
Logging.Logger.config.Error(e);
}
}
}
}

+ 29
- 43
IPA.Loader/Config/IConfigProvider.cs View File

@ -1,63 +1,49 @@
using System;
// ReSharper disable UnusedMember.Global
using System.IO;
using IPA.Config.Data;
namespace IPA.Config
{
/// <summary>
/// An interface for configuration providers.
/// </summary>
/// <remarks>
/// Implementers must provide a default constructor. Do not assume that <see cref="File"/> will ever be set for a given object.
/// </remarks>
public interface IConfigProvider
{ // TODO: rework this
{
/// <summary>
/// Loads the data provided by this <see cref="IConfigProvider"/> into an object of type <typeparamref name="T"/>.
/// Gets the extension <i>without</i> a dot to use for files handled by this provider.
/// </summary>
/// <typeparam name="T">the type of the object to parse into</typeparam>
/// <returns>the values from the config provider parsed into the object</returns>
T Parse<T>();
/// <summary>
/// Stores the data from <paramref name="obj"/> into the <see cref="IConfigProvider"/>.
/// </summary>
/// <typeparam name="T">the type of <paramref name="obj"/></typeparam>
/// <param name="obj">the object containing the data to save</param>
void Store<T>(T obj);
/// <remarks>
/// This must work immediately, and is used to generate the <see cref="FileInfo"/> used to set
/// <see cref="File"/>.
/// </remarks>
string Extension { get; }
#if NET4
/// <summary>
/// Gets a dynamic object providing access to the configuration.
/// Sets the file that this provider will read and write to.
/// </summary>
/// <value>a dynamically bound object to use to access config values directly</value>
dynamic Dynamic { get; }
#endif
/// <remarks>
/// The provider is expected to gracefully handle this changing at any point,
/// and is expected to close any old file handles when this is reassigned.
/// This may be set to the same file multiple times in this object's lifetime.
/// This will always have been set at least once before any calls to <see cref="Load"/>
/// or <see cref="Store"/> are made.
/// </remarks>
FileInfo File { set; }
#region State getters
/// <summary>
/// Returns <see langword="true"/> if object has changed since the last save
/// </summary>
/// <value><see langword="true"/> if object has changed since the last save, else <see langword="false"/></value>
bool HasChanged { get; }
/// <summary>
/// Returns <see langword="true"/> if the data in memory has been changed - notably including loads.
/// </summary>
/// <value><see langword="true"/> if the data in memory has been changed, else <see langword="false"/></value>
bool InMemoryChanged { get; set; }
/// <summary>
/// Will be set with the filename (no extension) to save to. When saving, the implementation should add the appropriate extension. Should error if set multiple times.
/// Stores the <see cref="Value"/> given to disk in the format specified.
/// </summary>
/// <value>the extensionless filename to save to</value>
string Filename { set; }
/// <summary>
/// Gets the last time the config was modified.
/// </summary>
/// <value>the last time the config file was modified</value>
DateTime LastModified { get; }
/// <summary>
/// Saves configuration to file. Should error if not a root object.
/// </summary>
void Save();
/// <param name="value">the <see cref="Value"/> to store</param>
void Store(Value value);
/// <summary>
/// Loads the state of the file on disk.
/// Loads a <see cref="Value"/> from disk in whatever format this provider provides
/// and returns it.
/// </summary>
void Load();
#endregion
/// <returns>the <see cref="Value"/> loaded</returns>
Value Load();
}
}

+ 3
- 3
IPA.Loader/Config/Providers/JsonConfigProvider.cs View File

@ -7,8 +7,8 @@ using System.ComponentModel;
using System.IO;
namespace IPA.Config.Providers
{
[Config.Type("json")]
{ // TODO: implement this for the new provider system
/*[Config.Type("json")]
internal class JsonConfigProvider : IConfigProvider
{
public static void RegisterConfig()
@ -123,5 +123,5 @@ namespace IPA.Config.Providers
HasChanged = true;
InMemoryChanged = true;
}
}
}*/
}

Loading…
Cancel
Save