using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using IPA.Config.ConfigProviders; using IPA.Utilities; namespace IPA.Config { /// /// A class to handle updating ConfigProviders automatically /// public static class Config { static Config() { JsonConfigProvider.RegisterConfig(); } /// /// /// Defines the type of the /// [AttributeUsage(AttributeTargets.Class)] public class TypeAttribute : Attribute { /// /// The extension associated with this type, without the '.' /// // ReSharper disable once UnusedAutoPropertyAccessor.Global public string Extension { get; private set; } /// /// /// Constructs the attribute with a specified extension. /// /// the extension associated with this type, without the '.' public TypeAttribute(string ext) { Extension = ext; } } /// /// /// Specifies that a particular parameter is preferred to be a specific type of . If it is not available, also specifies backups. If none are available, the default is used. /// [AttributeUsage(AttributeTargets.Parameter)] public class PreferAttribute : Attribute { /// /// The order of preference for the config type. /// // ReSharper disable once UnusedAutoPropertyAccessor.Global public string[] PreferenceOrder { get; private set; } /// /// /// Constructs the attribute with a specific preference list. Each entry is the extension without a '.' /// /// The preferences in order of preference. public PreferAttribute(params string[] preference) { PreferenceOrder = preference; } } private static readonly Dictionary registeredProviders = new Dictionary(); /// /// Registers a to use for configs. /// /// the type to register public static void Register() where T : IConfigProvider => Register(typeof(T)); /// /// Registers a to use for configs. /// /// the type to register public static void Register(Type type) { if (!(type.GetCustomAttribute(typeof(TypeAttribute)) is TypeAttribute ext)) throw new InvalidOperationException("Type does not have TypeAttribute"); if (!typeof(IConfigProvider).IsAssignableFrom(type)) throw new InvalidOperationException("Type not IConfigProvider"); if (registeredProviders.ContainsKey(ext.Extension)) throw new InvalidOperationException($"Extension provider for {ext.Extension} already exists"); registeredProviders.Add(ext.Extension, type); } private static SortedList, IConfigProvider> configProviders = new SortedList, IConfigProvider>(); /// /// Gets an using the specified list pf preferred config types. /// /// the name of the file to associate it with /// the preferred config types to try to get /// an of the requested type, or of type JSON. public static IConfigProvider GetProviderFor(string filename, params string[] extensions) { 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 = filename; configProviders.Add(provider.LastModified, provider); } return provider; } /// /// Gets an using the specified list pf preferred config types. /// /// the name of the file to associate it with /// the parameter info to try and get info for /// an of the requested type, or of type JSON. public static IConfigProvider GetProviderFor(string filename, ParameterInfo info) { var prefs = new string[0]; if (info.GetCustomAttribute(typeof(PreferAttribute)) is PreferAttribute prefer) prefs = prefer.PreferenceOrder; return GetProviderFor(filename, prefs); } private static SortedDictionary linkedProviders = new SortedDictionary(); /// /// Creates a linked for the config provider. This will be automatically updated whenever the file on-disk changes. /// /// the type of the parsed value /// the to create a link to /// an action to perform on value change /// a to an ever-changing value, mirroring whatever the file contains. public static Ref MakeLink(this IConfigProvider config, Action> onChange = null) { Ref @ref = config.Parse(); void ChangeDelegate() { @ref.Value = config.Parse(); 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; } /// /// Removes all linked such that they are no longer updated. /// /// the to unlink 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.Value.LastModified > provider.Key.Value) { try { provider.Value.Load(); // auto reload if it changes provider.Key.Value = provider.Value.LastModified; } catch (Exception e) { Logging.Logger.config.Error("Error when trying to load config"); Logging.Logger.config.Error(e); } } if (provider.Value.HasChanged) { try { provider.Value.Save(); provider.Key.Value = DateTime.Now; } catch (Exception e) { Logging.Logger.config.Error("Error when trying to save config"); Logging.Logger.config.Error(e); } } if (provider.Value.InMemoryChanged) { provider.Value.InMemoryChanged = false; try { if (linkedProviders.ContainsKey(provider.Value)) linkedProviders[provider.Value](); } 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.Value.HasChanged) try { provider.Value.Save(); } catch (Exception e) { Logging.Logger.config.Error("Error when trying to save config"); Logging.Logger.config.Error(e); } } } }