source: branches/eraser6/pluginsRewrite/Eraser.Plugins/Host.cs @ 2292

Revision 2292, 10.6 KB checked in by lowjoel, 4 years ago (diff)

Fixed compilation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 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.Linq;
26
27using System.IO;
28using System.Reflection;
29
30using Eraser.Util;
31
32namespace Eraser.Plugins
33{
34    /// <summary>
35    /// The plugins host interface which is used for communicating with the host
36    /// program.
37    /// </summary>
38    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
39    /// they will never be loaded.</remarks>
40    public abstract class Host : IDisposable
41    {
42        #region IDisposable members
43        protected virtual void Dispose(bool disposing)
44        {
45        }
46
47        /// <summary>
48        /// Cleans up resources used by the host. Also unloads all loaded plugins.
49        /// </summary>
50        public void Dispose()
51        {
52            Dispose(true);
53            GC.SuppressFinalize(this);
54        }
55        #endregion
56
57        /// <summary>
58        /// Initialises the Plugins library. Call <see cref="Host.Load"/> when object
59        /// initialisation is complete.
60        /// </summary>
61        /// <remarks>Call <see cref="Host.Instance.Dispose"/> when exiting.</remarks>
62        public static void Initialise()
63        {
64            new DefaultHost();
65        }
66
67        /// <summary>
68        /// Constructor. Sets the global Plugin Host instance.
69        /// </summary>
70        /// <see cref="Host.Instance"/>
71        protected Host()
72        {
73            if (Instance != null)
74                throw new InvalidOperationException("Only one global Plugin Host instance can " +
75                    "exist at any one point of time.");
76            Instance = this;
77        }
78
79        /// <summary>
80        /// Getter that retrieves the global plugin host instance.
81        /// </summary>
82        public static Host Instance
83        {
84            get;
85            private set;
86        }
87
88        /// <summary>
89        /// Retrieves the list of currently loaded plugins.
90        /// </summary>
91        /// <remarks>The returned list is read-only</remarks>
92        public abstract IList<PluginInstance> Plugins
93        {
94            get;
95        }
96
97        /// <summary>
98        /// Loads all plugins into memory.
99        /// </summary>
100        public abstract void Load();
101
102        /// <summary>
103        /// The plugin load event, allowing clients to decide whether to load
104        /// the given plugin.
105        /// </summary>
106        public EventHandler<PluginLoadEventArgs> PluginLoad { get; set; }
107
108        /// <summary>
109        /// The plugin loaded event.
110        /// </summary>
111        public EventHandler<PluginLoadedEventArgs> PluginLoaded { get; set; }
112
113        /// <summary>
114        /// Event callback executor for the OnPluginLoad Event
115        /// </summary>
116        internal void OnPluginLoaded(object sender, PluginLoadedEventArgs e)
117        {
118            if (PluginLoaded != null)
119                PluginLoaded(sender, e);
120        }
121
122        /// <summary>
123        /// Loads a plugin.
124        /// </summary>
125        /// <param name="filePath">The absolute or relative file path to the
126        /// DLL.</param>
127        /// <returns>True if the plugin is loaded, false otherwise.</returns>
128        /// <remarks>If a plugin is loaded twice, this function should do nothing
129        /// and return True.</remarks>
130        public abstract bool LoadPlugin(string filePath);
131    }
132
133    /// <summary>
134    /// The default plugins host implementation.
135    /// </summary>
136    internal class DefaultHost : Host
137    {
138        /// <summary>
139        /// Constructor.
140        /// </summary>
141        public DefaultHost() : base()
142        {
143            //Specify additional places to load assemblies from
144            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
145            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
146        }
147
148        public override void Load()
149        {
150            //Load all core plugins first
151            foreach (KeyValuePair<string, string> plugin in CorePlugins)
152            {
153                LoadCorePlugin(Path.Combine(PluginsFolder, plugin.Key), plugin.Value);
154            }
155
156            //Then load the rest
157            foreach (string fileName in Directory.GetFiles(PluginsFolder))
158            {
159                FileInfo file = new FileInfo(fileName);
160                if (file.Extension.Equals(".dll"))
161                    try
162                    {
163                        LoadPlugin(file.FullName);
164                    }
165                    catch (BadImageFormatException)
166                    {
167                    }
168                    catch (FileLoadException)
169                    {
170                    }
171            }
172        }
173
174        protected override void Dispose(bool disposing)
175        {
176            if (plugins == null)
177                return;
178
179            if (disposing)
180            {
181                //Unload all the plugins. This will cause all the plugins to execute
182                //the cleanup code.
183                foreach (PluginInstance plugin in plugins)
184                    if (plugin.Plugin != null)
185                        plugin.Plugin.Dispose();
186            }
187
188            plugins = null;
189        }
190
191        public override IList<PluginInstance> Plugins
192        {
193            get { return plugins.AsReadOnly(); }
194        }
195
196        /// <summary>
197        /// Verifies whether the provided assembly is a plugin.
198        /// </summary>
199        /// <param name="assembly">The assembly to verify.</param>
200        /// <returns>True if the assembly provided is a plugin, false otherwise.</returns>
201        private bool IsPlugin(Assembly assembly)
202        {
203            //Iterate over every exported type, checking if it implements IPlugin
204            Type typePlugin = assembly.GetExportedTypes().FirstOrDefault(
205                    type => type.GetInterface("Eraser.Plugins.IPlugin", true) != null);
206
207            //If the typePlugin type is empty the assembly doesn't implement IPlugin it's not
208            //a plugin.
209            return typePlugin != null;
210        }
211
212        /// <summary>
213        /// Loads the assembly at the specified path, and verifying its assembly name,
214        /// ensuring that the assembly contains a core plugin.
215        /// </summary>
216        /// <param name="filePath">The path to the assembly.</param>
217        /// <param name="assemblyName">The name of the assembly.</param>
218        private void LoadCorePlugin(string filePath, string assemblyName)
219        {
220            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(filePath);
221            if (assembly.GetName().FullName.Substring(0, assemblyName.Length + 1) !=
222                assemblyName + ",")
223            {
224                throw new FileLoadException(S._("The Core plugin assembly is not one which" +
225                    "Eraser expects.\n\nCheck that the Eraser installation is not corrupt, or " +
226                    "reinstall the program."));
227            }
228
229            //Create the PluginInstance structure
230            PluginInstance instance = new PluginInstance(assembly, null);
231
232            //Ignore non-plugins
233            if (!IsPlugin(instance.Assembly))
234                throw new FileLoadException(S._("The provided Core plugin assembly is not a " +
235                    "plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall " +
236                    "the program."));
237
238            //OK this assembly is a plugin
239            lock (plugins)
240                plugins.Add(instance);
241
242            //Check for the presence of a valid signature: Core plugins must have the same
243            //public key as the current assembly
244            if (!assembly.GetName().GetPublicKey().SequenceEqual(
245                    Assembly.GetExecutingAssembly().GetName().GetPublicKey()))
246            {
247                throw new FileLoadException(S._("The provided Core plugin does not have an " +
248                    "identical public key as the Eraser assembly.\n\nCheck that the Eraser " +
249                    "installation is not corrupt, or reinstall the program."));
250            }
251
252            //Okay, everything's fine, initialise the plugin
253            instance.Assembly = Assembly.Load(instance.Assembly.GetName());
254            instance.LoadingPolicy = LoadingPolicy.Core;
255            InitialisePlugin(instance);
256        }
257
258        public override bool LoadPlugin(string filePath)
259        {
260            //Create the PluginInstance structure
261            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
262            PluginInstance instance = new PluginInstance(reflectAssembly, null);
263
264            //Check that the plugin hasn't yet been loaded.
265            if (Plugins.Count(
266                    plugin => plugin.Assembly.GetName().FullName ==
267                    reflectAssembly.GetName().FullName) > 0)
268            {
269                return true;
270            }
271
272            //Ignore non-plugins
273            if (!IsPlugin(instance.Assembly))
274                return false;
275
276            //OK this assembly is a plugin
277            lock (plugins)
278                plugins.Add(instance);
279
280            PluginLoadEventArgs e = new PluginLoadEventArgs(instance);
281            PluginLoad(this, e);
282            if (PluginLoad == null || e.Load)
283            {
284                InitialisePlugin(instance);
285                return true;
286            }
287
288            return false;
289        }
290
291        /// <summary>
292        /// Initialises the given plugin from the plugin's description.
293        /// </summary>
294        /// <param name="instance">The <see cref="PluginInstance"/> structure to fill.</param>
295        /// <exception cref="System.IO.FileLoadException" />
296        private void InitialisePlugin(PluginInstance instance)
297        {
298            try
299            {
300                //Iterate over every exported type, checking for the IPlugin implementation
301                Type typePlugin = instance.Assembly.GetExportedTypes().First(
302                    type => type.GetInterface("Eraser.Manager.Plugin.IPlugin", true) != null);
303                if (typePlugin == null)
304                    return;
305
306                //Initialize the plugin
307                instance.Plugin = (IPlugin)Activator.CreateInstance(
308                    instance.Assembly.GetType(typePlugin.ToString()));
309                instance.Plugin.Initialize(this);
310
311                //And broadcast the plugin load event
312                OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
313            }
314            catch (System.Security.SecurityException e)
315            {
316                throw new FileLoadException(S._("Could not load the plugin."),
317                    instance.Assembly.Location, e);
318            }
319        }
320
321        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
322        {
323            //Check the plugins folder
324            foreach (string fileName in Directory.GetFiles(PluginsFolder))
325            {
326                FileInfo file = new FileInfo(fileName);
327                if (file.Extension.Equals(".dll"))
328                    try
329                    {
330                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
331                        if (assembly.GetName().FullName == args.Name)
332                            return Assembly.LoadFile(file.FullName);
333                    }
334                    catch (BadImageFormatException)
335                    {
336                    }
337                    catch (FileLoadException)
338                    {
339                    }
340            }
341
342            return null;
343        }
344
345        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
346        {
347            return Assembly.ReflectionOnlyLoad(args.Name);
348        }
349
350        /// <summary>
351        /// The path to the folder containing the plugins.
352        /// </summary>
353        public readonly string PluginsFolder = Path.Combine(
354            Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
355            "Plugins" //Plugins folder
356        );
357
358        /// <summary>
359        /// The list of plugins which are core, the key is the file name, the value
360        /// is the assembly name.
361        /// </summary>
362        private readonly KeyValuePair<string, string>[] CorePlugins =
363            new KeyValuePair<string, string>[]
364            {
365                new KeyValuePair<string, string>(
366                    "Eraser.DefaultPlugins.dll",
367                    "Eraser.DefaultPlugins"
368                )
369            };
370
371        /// <summary>
372        /// Stores the list of plugins found within the Plugins folder.
373        /// </summary>
374        private List<PluginInstance> plugins = new List<PluginInstance>();
375    }
376}
Note: See TracBrowser for help on using the repository browser.