using System; using System.Linq; using System.Text.Json; using System.Reflection; using System.Collections.Generic; using VNLib.Plugins.Extensions.Loading.Events; using VNLib.Plugins.Extensions.Loading.Configuration; namespace VNLib.Plugins.Extensions.Loading.Routing { /// /// Provides advanced QOL features to plugin loading /// public static class RoutingExtensions { /// /// Constructs and routes the specific endpoint type for the current plugin /// /// The type /// /// The path to the plugin sepcific configuration property /// public static T Route(this PluginBase plugin, string? pluginConfigPathName) where T : IEndpoint { Type endpointType = typeof(T); try { //If the config attribute is not set, then ignore the config variables if (string.IsNullOrWhiteSpace(pluginConfigPathName)) { ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}"); //Create the new endpoint and pass the plugin instance T endpoint = (T)constructor.Invoke(new object[] { plugin }); //Register event handlers for the endpoint ScheduleIntervals(plugin, endpoint, endpointType, null); //Route the endpoint plugin.Route(endpoint); return endpoint; } else { ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(Dictionary) }); //Make sure the constructor exists _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}"); //Get config variables for the endpoint IReadOnlyDictionary conf = plugin.GetConfig(pluginConfigPathName); //Create the new endpoint and pass the plugin instance along with the configuration object T endpoint = (T)constructor.Invoke(new object[] { plugin, conf }); //Register event handlers for the endpoint ScheduleIntervals(plugin, endpoint, endpointType, conf); //Route the endpoint plugin.Route(endpoint); return endpoint; } } catch (TargetInvocationException te) when (te.InnerException != null) { throw te.InnerException; } } /// /// Constructs and routes the specific endpoint type for the current plugin /// /// The type /// /// public static T Route(this PluginBase plugin) where T : IEndpoint { Type endpointType = typeof(T); //Get config name attribute ConfigurationNameAttribute? configAttr = endpointType.GetCustomAttribute(); //Route using attribute return plugin.Route(configAttr?.ConfigVarName); } private static void ScheduleIntervals(PluginBase plugin, T endpointInstance, Type epType, IReadOnlyDictionary? endpointLocalConfig) where T: IEndpoint { List registered = new(); try { //Get all methods that have the configureable async interval attribute specified IEnumerable> confIntervals = epType.GetMethods() .Where(m => m.GetCustomAttribute() != null) .Select(m => new Tuple (m.GetCustomAttribute()!, m.CreateDelegate(endpointInstance))); //If the endpoint has a local config, then use it to find the interval if (endpointLocalConfig != null) { //Schedule event handlers on the current plugin foreach (Tuple interval in confIntervals) { int value = endpointLocalConfig[interval.Item1.IntervalPropertyName].GetInt32(); //Get the timeout from its resolution variable TimeSpan timeout = interval.Item1.Resolution switch { IntervalResultionType.Seconds => TimeSpan.FromSeconds(value), IntervalResultionType.Minutes => TimeSpan.FromMinutes(value), IntervalResultionType.Hours => TimeSpan.FromHours(value), _ => TimeSpan.FromMilliseconds(value), }; //Schedule registered.Add(plugin.ScheduleInterval(interval.Item2, timeout)); } } //Get all methods that have the async interval attribute specified IEnumerable> intervals = epType.GetMethods() .Where(m => m.GetCustomAttribute() != null) .Select(m => new Tuple( m.GetCustomAttribute()!, m.CreateDelegate(endpointInstance)) ); //Schedule event handlers on the current plugin foreach (Tuple interval in intervals) { registered.Add(plugin.ScheduleInterval(interval.Item2, interval.Item1.Interval)); } } catch { //Stop all event handles foreach(EventHandle evh in registered) { evh.Dispose(); } throw; } } } }