source: branches/eraser6/Manager/Plugins.cs @ 571

Revision 571, 9.5 KB checked in by lowjoel, 6 years ago (diff)

Don't dispose plugins that were never loaded.

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.IO;
26using System.Reflection;
27using System.Windows.Forms;
28using System.Runtime.InteropServices;
29
30namespace Eraser.Manager.Plugin
31{
32    /// <summary>
33    /// The plugins host interface which is used for communicating with the host
34    /// program.
35    /// </summary>
36    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
37    /// they will never be loaded.</remarks>
38    public abstract class Host : IDisposable
39    {
40        /// <summary>
41        /// Getter that retrieves the global plugin host instance.
42        /// </summary>
43        public static Host Instance
44        {
45            get { return ManagerLibrary.Instance.Host; }
46        }
47
48        /// <summary>
49        /// Retrieves the list of currently loaded plugins.
50        /// </summary>
51        public abstract List<PluginInstance> Plugins
52        {
53            get;
54        }
55
56        /// <summary>
57        /// Loads all plugins into memory.
58        /// </summary>
59        public abstract void Load();
60
61        /// <summary>
62        /// Cleans up resources used by the host. Also unloads all loaded plugins.
63        /// </summary>
64        public abstract void Dispose();
65
66        /// <summary>
67        /// The plugin load event delegate.
68        /// </summary>
69        /// <param name="instance">The instance of the plugin loaded.</param>
70        public delegate void PluginLoadedFunction(PluginInstance instance);
71
72        /// <summary>
73        /// The plugin loaded event.
74        /// </summary>
75        public event PluginLoadedFunction PluginLoaded;
76
77        /// <summary>
78        /// Event callback executor for the OnPluginLoad Event
79        /// </summary>
80        /// <param name="instance"></param>
81        protected void OnPluginLoaded(PluginInstance instance)
82        {
83            if (PluginLoaded != null)
84                PluginLoaded(instance);
85        }
86
87        /// <summary>
88        /// Loads a plugin.
89        /// </summary>
90        /// <param name="filePath">The absolute or relative file path to the
91        /// DLL.</param>
92        public abstract void LoadPlugin(string filePath);
93    }
94
95    /// <summary>
96    /// The default plugins host implementation.
97    /// </summary>
98    internal class DefaultHost : Host
99    {
100        /// <summary>
101        /// Constructor. Loads all plugins in the Plugins folder.
102        /// </summary>
103        public DefaultHost()
104        {
105        }
106
107        public override void Load()
108        {
109            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
110            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
111            string pluginsFolder = Path.Combine(
112                Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
113                PLUGINSFOLDER //Plugins folder
114            );
115
116            foreach (string fileName in Directory.GetFiles(pluginsFolder))
117            {
118                FileInfo file = new FileInfo(fileName);
119                if (file.Extension.Equals(".dll"))
120                    try
121                    {
122                        LoadPlugin(file.FullName);
123                    }
124                    catch (BadImageFormatException)
125                    {
126                    }
127                    catch (FileLoadException)
128                    {
129                    }
130            }
131        }
132
133        public override void Dispose()
134        {
135            //Unload all the plugins. This will cause all the plugins to execute
136            //the cleanup code.
137            foreach (PluginInstance plugin in plugins)
138                if (plugin.Plugin != null)
139                    plugin.Plugin.Dispose();
140        }
141
142        /// <summary>
143        /// The path to the folder containing the plugins.
144        /// </summary>
145        public const string PLUGINSFOLDER = "Plugins";
146
147        public override List<PluginInstance> Plugins
148        {
149            get { return plugins; }
150        }
151
152        public override void LoadPlugin(string filePath)
153        {
154            //Create the PluginInstance structure
155            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
156            PluginInstance instance = new PluginInstance(reflectAssembly, null);
157            Type typePlugin = null;
158
159            //Iterate over every exported type, checking if it implements IPlugin
160            foreach (Type type in instance.Assembly.GetExportedTypes())
161            {
162                //Check for an implementation of IPlugin
163                Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true);
164                if (typeInterface != null)
165                {
166                    typePlugin = type;
167                    break;
168                }
169            }
170
171            //If the typePlugin type is empty the assembly doesn't implement IPlugin; we
172            //aren't interested.
173            if (typePlugin == null)
174                return;
175
176            //OK this assembly is a plugin
177            lock (plugins)
178                plugins.Add(instance);
179
180            //First check the plugin for the presence of a signature.
181            if (reflectAssembly.GetName().GetPublicKey().Length == 0)
182            {
183                //If the user did not allow the plug-in to load, don't load it.
184                if (ManagerLibrary.Instance.Settings.ApprovedPlugins.
185                    IndexOf(instance.AssemblyInfo.GUID) == -1)
186                {
187                    return;
188                }
189            }
190
191            //Load the plugin
192            instance.Assembly = Assembly.LoadFrom(filePath);
193
194            //Initialize the plugin
195            IPlugin pluginInterface = (IPlugin)Activator.CreateInstance(
196                instance.Assembly.GetType(typePlugin.ToString()));
197            pluginInterface.Initialize(this);
198            instance.Plugin = pluginInterface;
199
200            //And broadcast the plugin load event
201            OnPluginLoaded(instance);
202        }
203
204        Assembly AssemblyResolve(object sender, ResolveEventArgs args)
205        {
206            lock (plugins)
207                foreach (PluginInstance instance in plugins)
208                    if (instance.Assembly.FullName == args.Name)
209                        return instance.Assembly;
210            return null;
211        }
212
213        Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
214        {
215            return Assembly.ReflectionOnlyLoad(args.Name);
216        }
217
218        private List<PluginInstance> plugins = new List<PluginInstance>();
219    }
220
221    /// <summary>
222    /// Structure holding the instance values of the plugin like handle and path.
223    /// </summary>
224    public class PluginInstance
225    {
226        /// <summary>
227        /// Constructor
228        /// </summary>
229        /// <param name="assembly">The assembly representing this plugin.</param>
230        /// <param name="path">The path to the ass</param>
231        /// <param name="plugin"></param>
232        internal PluginInstance(Assembly assembly, IPlugin plugin)
233        {
234            Assembly = assembly;
235            Plugin = plugin;
236        }
237
238        /// <summary>
239        /// Gets the Assembly this plugin instance came from.
240        /// </summary>
241        public Assembly Assembly
242        {
243            get
244            {
245                return assembly;
246            }
247            internal set
248            {
249                assembly = value;
250
251                assemblyInfo.Version = assembly.GetName().Version;
252                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(assembly);
253                foreach (CustomAttributeData attr in attributes)
254                    if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
255                        assemblyInfo.GUID = new Guid((string)attr.ConstructorArguments[0].Value);
256                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute))
257                        assemblyInfo.Author = (string)attr.ConstructorArguments[0].Value;
258            }
259        }
260
261        /// <summary>
262        /// Gets the attributes of the assembly, loading from reflection-only sources.
263        /// </summary>
264        public AssemblyInfo AssemblyInfo
265        {
266            get
267            {
268                return assemblyInfo;
269            }
270            internal set
271            {
272                assemblyInfo = value;
273            }
274        }
275
276        /// <summary>
277        /// Gets the IPlugin interface which the plugin exposed.
278        /// </summary>
279        public IPlugin Plugin
280        {
281            get
282            {
283                return plugin;
284            }
285            internal set
286            {
287                plugin = value;
288            }
289        }
290
291        private Assembly assembly;
292        private AssemblyInfo assemblyInfo;
293        private IPlugin plugin;
294    }
295
296    /// <summary>
297    /// Reflection-only information retrieved from the assembly.
298    /// </summary>
299    public struct AssemblyInfo
300    {
301        /// <summary>
302        /// The GUID of the assembly.
303        /// </summary>
304        public Guid GUID;
305
306        /// <summary>
307        /// The publisher of the assembly.
308        /// </summary>
309        public string Author;
310
311        /// <summary>
312        /// The version of the assembly.
313        /// </summary>
314        public Version Version;
315    }
316
317    /// <summary>
318    /// Basic plugin interface which allows for the main program to utilize the
319    /// functions in the DLL
320    /// </summary>
321    public interface IPlugin : IDisposable
322    {
323        /// <summary>
324        /// Initializer.
325        /// </summary>
326        /// <param name="host">The host object which can be used for two-way
327        /// communication with the program.</param>
328        void Initialize(Host host);
329
330        /// <summary>
331        /// The name of the plug-in, used for descriptive purposes in the UI
332        /// </summary>
333        string Name
334        {
335            get;
336        }
337
338        /// <summary>
339        /// The author of the plug-in, used for display in the UI and for users
340        /// to contact the author about bugs. Must be in the format:
341        ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\>
342        /// </summary>
343        /// <example>Joel Low <joel@joelsplace.sg></example>
344        string Author
345        {
346            get;
347        }
348
349        /// <summary>
350        /// Determines whether the plug-in is configurable.
351        /// </summary>
352        bool Configurable
353        {
354            get;
355        }
356
357        /// <summary>
358        /// Fulfil a request to display the settings for this plug-in.
359        /// </summary>
360        /// <param name="parent">The parent control which the settings dialog should
361        /// be parented with.</param>
362        void DisplaySettings(Control parent);
363    }
364}
Note: See TracBrowser for help on using the repository browser.