aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Runtime/src/LivePlugin.cs
blob: c0011dd504d537bf3f7031a301635d300f36a09e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
* Copyright (c) 2022 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Plugins.Runtime
* File: LivePlugin.cs 
*
* LivePlugin.cs is part of VNLib.Plugins.Runtime which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Runtime is free software: you can redistribute it and/or modify 
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Plugins.Runtime is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License 
* along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
*/

using System;
using System.Linq;
using System.Reflection;
using System.Text.Json;

using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Attributes;

namespace VNLib.Plugins.Runtime
{
    /// <summary>
    /// <para>
    /// Wrapper for a loaded <see cref="IPlugin"/> instance, used internally 
    /// for a single instance. 
    /// </para>
    /// <para>
    /// Lifetime: for the existance of a single loaded 
    /// plugin instance. Created once per loaded plugin instance. Once the plugin
    /// is unloaded, it is no longer useable.
    /// </para>
    /// </summary>
    public class LivePlugin : IEquatable<IPlugin>, IEquatable<LivePlugin>
    {
        /// <summary>
        /// The plugin's <see cref="IPlugin.PluginName"/> property during load time
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        public string PluginName => Plugin?.PluginName ?? throw new InvalidOperationException("Plugin is not loaded");

        /// <summary>
        /// The underlying <see cref="IPlugin"/> that is warpped
        /// by he current instance
        /// </summary>
        public IPlugin? Plugin { get; private set; }
        
        private readonly Type PluginType;
       
        private ConsoleEventHandlerSignature? PluginConsoleHandler;

        internal LivePlugin(IPlugin plugin)
        {
            Plugin = plugin;
            PluginType = plugin.GetType();
            GetConsoleHandler();
        }

        private void GetConsoleHandler()
        {
            //Get the console handler method from the plugin instance
            MethodInfo? handler = (from m in PluginType.GetMethods()
                                   where m.GetCustomAttribute<ConsoleEventHandlerAttribute>() != null
                                   select m)
                                   .FirstOrDefault();
            //Get a delegate handler for the plugin
            PluginConsoleHandler = handler?.CreateDelegate<ConsoleEventHandlerSignature>(Plugin);
        }

        /// <summary>
        /// Sets the plugin's configuration if it defines a <see cref="ConfigurationInitalizerAttribute"/>
        /// on an instance method
        /// </summary>
        /// <param name="hostConfig">The host configuration DOM</param>
        /// <param name="pluginConf">The plugin local configuration DOM</param>
        internal void InitConfig(JsonDocument hostConfig, JsonDocument pluginConf)
        {
            //Get the console handler method from the plugin instance
            MethodInfo? confHan = PluginType.GetMethods().Where(static m => m.GetCustomAttribute<ConfigurationInitalizerAttribute>() != null)
                                  .FirstOrDefault();
            //Get a delegate handler for the plugin
            ConfigInitializer? configInit = confHan?.CreateDelegate<ConfigInitializer>(Plugin);
            if (configInit == null)
            {
                return;
            }
            //Merge configurations before passing to plugin
            JsonDocument merged = hostConfig.Merge(pluginConf, "host", PluginType.Name);
            try
            {
                //Invoke
                configInit.Invoke(merged);
            }
            catch
            {
                merged.Dispose();
                throw;
            }
        }
        
        /// <summary>
        /// Invokes the plugin's log initalizer method if it defines a <see cref="LogInitializerAttribute"/>
        /// on an instance method
        /// </summary>
        /// <param name="cliArgs">The current process's CLI args</param>
        internal void InitLog(string[] cliArgs)
        {
            //Get the console handler method from the plugin instance
            MethodInfo? logInit = (from m in PluginType.GetMethods()
                                   where m.GetCustomAttribute<LogInitializerAttribute>() != null
                                   select m)
                                   .FirstOrDefault();
            //Get a delegate handler for the plugin
            LogInitializer? logFunc = logInit?.CreateDelegate<LogInitializer>(Plugin);
            //Invoke
            logFunc?.Invoke(cliArgs);
        }

        /// <summary>
        /// Invokes the plugins console event handler if the type has one 
        /// and the plugin is loaded.
        /// </summary>
        /// <param name="message">The message to pass to the plugin handler</param>
        /// <returns>
        /// True if the command was sent to the plugin, false if the plugin is
        /// unloaded or did not export a console event handler
        /// </returns>
        public bool SendConsoleMessage(string message)
        {
            //Make sure plugin is loaded and has a console handler
            if (PluginConsoleHandler == null)
            {
                return false;
            }
            //Invoke plugin console handler
            PluginConsoleHandler(message);
            return true;
        }

        /// <summary>
        /// Calls the <see cref="IPlugin.Load"/> method on the plugin if its loaded
        /// </summary>
        internal void LoadPlugin() => Plugin?.Load();

        /// <summary>
        /// Unloads all loaded endpoints from 
        /// that they were loaded to, then unloads the plugin.
        /// </summary>
        /// <param name="logSink">An optional log provider to write unload exceptions to</param>
        /// <remarks>
        /// If <paramref name="logSink"/> is no null unload exceptions are swallowed and written to the log
        /// </remarks>
        internal void UnloadPlugin(ILogProvider? logSink)
        {
            /*
             * We need to swallow plugin unload errors to avoid 
             * unknown state, making sure endpoints are properly 
             * unloaded!
             */
            try
            {
                //Unload the plugin
                Plugin?.Unload();
            }
            catch (Exception ex)
            {
                //Create an unload wrapper for the exception
                PluginUnloadException wrapper = new("Exception raised during plugin unload", ex);
                if (logSink == null)
                {
                    throw wrapper;
                }
                //Write error to log sink
                logSink.Error(wrapper);
            }
            Plugin = null;
            PluginConsoleHandler = null;
        }
        ///<inheritdoc/>
        public override bool Equals(object? obj)
        {
            Type? pluginType = Plugin?.GetType();
            Type? otherType = obj?.GetType();
            if(pluginType == null || otherType == null)
            {
                return false;
            }
            //If the other plugin is the same type as the current instance return true
            return pluginType.FullName == otherType.FullName;
        }
        ///<inheritdoc/>
        public bool Equals(LivePlugin? other)
        {
            return Equals(other?.Plugin);
        }
        ///<inheritdoc/>
        public bool Equals(IPlugin? other)
        {
            return Equals((object?)other);
        }
        ///<inheritdoc/>
        public override int GetHashCode()
        {
            return Plugin?.GetHashCode() ?? throw new InvalidOperationException("Plugin is null");
        }       
    }
}