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

Revision 2295, 10.8 KB checked in by lowjoel, 4 years ago (diff)
  • Fixed the PluginInfo?.Load function to not load the assembly twice if it was loaded normally and not using the reflection-only context (this rule was relaxed in the newest Host implementation)
  • Core plugins will still be loaded before the rest of the plugins, but they will be loaded only by assembly name and not by path
  • Plugin initialisation is handled by the PluginInfo? class as initialisation modifies properties in the PluginInfo? class
  • Non-core plugins call a callback function provided by the host owner to determine if they will be loaded by the host
  • AssemblyResolve? will now support matching of one or more components found in the full name of an assembly name
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
RevLine 
[1359]1/*
2 * $Id$
[1675]3 * Copyright 2008-2010 The Eraser Project
[1359]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;
[1981]25using System.Linq;
26
[1359]27using System.IO;
28using System.Reflection;
[2209]29
[1359]30using Eraser.Util;
31
[2288]32namespace Eraser.Plugins
[1359]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>
[2291]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>
[2286]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>
[1359]80        /// Getter that retrieves the global plugin host instance.
81        /// </summary>
82        public static Host Instance
83        {
[2286]84            get;
85            private set;
[1359]86        }
87
88        /// <summary>
89        /// Retrieves the list of currently loaded plugins.
90        /// </summary>
91        /// <remarks>The returned list is read-only</remarks>
[2295]92        public abstract IList<PluginInfo> Plugins
[1359]93        {
94            get;
95        }
96
97        /// <summary>
98        /// Loads all plugins into memory.
99        /// </summary>
100        public abstract void Load();
101
102        /// <summary>
[2288]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>
[1359]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>
[2050]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);
[1359]131    }
132
133    /// <summary>
134    /// The default plugins host implementation.
135    /// </summary>
136    internal class DefaultHost : Host
137    {
138        /// <summary>
[2291]139        /// Constructor.
[1359]140        /// </summary>
[2286]141        public DefaultHost() : base()
[1359]142        {
[2291]143            //Specify additional places to load assemblies from
144            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
145            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
[1359]146        }
147
148        public override void Load()
149        {
[2056]150            //Load all core plugins first
[2295]151            foreach (string name in CorePlugins)
[1359]152            {
[2295]153                if (!LoadPlugin(new AssemblyName(name)))
154                    throw new FileLoadException(S._("The required Core plugin {0} could not be " +
155                        "loaded. Repair the Eraser installation and try again."));
[2056]156            }
[2050]157
[2056]158            //Then load the rest
159            foreach (string fileName in Directory.GetFiles(PluginsFolder))
[2050]160            {
[2056]161                FileInfo file = new FileInfo(fileName);
162                if (file.Extension.Equals(".dll"))
163                    try
164                    {
165                        LoadPlugin(file.FullName);
166                    }
167                    catch (BadImageFormatException)
168                    {
169                    }
170                    catch (FileLoadException)
171                    {
172                    }
[2050]173            }
[1359]174        }
175
176        protected override void Dispose(bool disposing)
177        {
[1802]178            if (plugins == null)
179                return;
180
[1359]181            if (disposing)
182            {
183                //Unload all the plugins. This will cause all the plugins to execute
184                //the cleanup code.
[2295]185                foreach (PluginInfo plugin in plugins)
[1359]186                    if (plugin.Plugin != null)
187                        plugin.Plugin.Dispose();
188            }
[1802]189
190            plugins = null;
[1359]191        }
192
[2295]193        public override IList<PluginInfo> Plugins
[1359]194        {
195            get { return plugins.AsReadOnly(); }
196        }
197
[2050]198        /// <summary>
199        /// Verifies whether the provided assembly is a plugin.
200        /// </summary>
201        /// <param name="assembly">The assembly to verify.</param>
202        /// <returns>True if the assembly provided is a plugin, false otherwise.</returns>
203        private bool IsPlugin(Assembly assembly)
[1359]204        {
[2050]205            //Iterate over every exported type, checking if it implements IPlugin
206            Type typePlugin = assembly.GetExportedTypes().FirstOrDefault(
[2291]207                    type => type.GetInterface("Eraser.Plugins.IPlugin", true) != null);
[2050]208
[2295]209            //If the typePlugin type is empty, the assembly doesn't implement IPlugin and thus
210            //it is not a plugin.
[2050]211            return typePlugin != null;
212        }
213
[2295]214        public bool LoadPlugin(AssemblyName name)
[2050]215        {
[2295]216            //Check the plugins folder
217            foreach (string fileName in Directory.GetFiles(PluginsFolder))
[2050]218            {
[2295]219                FileInfo file = new FileInfo(fileName);
220                if (file.Extension.Equals(".dll"))
221                    try
222                    {
223                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
224                        if (AssemblyMatchesName(assembly, name))
225                        {
226                            return LoadPlugin(assembly);
227                        }
228                    }
229                    catch (BadImageFormatException)
230                    {
231                    }
232                    catch (FileLoadException)
233                    {
234                    }
[2050]235            }
236
[2295]237            return false;
[2050]238        }
239
240        public override bool LoadPlugin(string filePath)
241        {
[2295]242            return LoadPlugin(Assembly.ReflectionOnlyLoadFrom(filePath));
243        }
[1359]244
[2295]245        /// <summary>
246        /// Checks the provided assembly for its name and attempts to load it using the
247        /// plugin loading rules.
248        /// </summary>
249        /// <param name="assembly">The plugin to load. This assembly can be loaded
250        /// in the reflection-only context for security.</param>
251        /// <returns>True if the assembly was a plugin and loaded without error.</returns>
252        private bool LoadPlugin(Assembly assembly)
253        {
254            PluginInfo instance = new PluginInfo(assembly, null);
255
[2050]256            //Check that the plugin hasn't yet been loaded.
257            if (Plugins.Count(
258                    plugin => plugin.Assembly.GetName().FullName ==
[2295]259                    assembly.GetName().FullName) > 0)
[1359]260            {
[2050]261                return true;
[1359]262            }
263
[2050]264            //Ignore non-plugins
265            if (!IsPlugin(instance.Assembly))
266                return false;
[1359]267
268            //OK this assembly is a plugin
269            lock (plugins)
270                plugins.Add(instance);
271
[2295]272            //Load the plugin, depending on type
273            bool result = instance.LoadingPolicy == LoadingPolicy.Core ?
274                LoadCorePlugin(instance) : LoadNonCorePlugin(instance);
275            if (result)
[1359]276            {
[2295]277                //And broadcast the plugin load event
278                OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
[2050]279            }
[1981]280
[2295]281            return result;
[2050]282        }
283
284        /// <summary>
[2295]285        /// Verifies the assembly name and strong name of a plugin, ensuring that the assembly
286        /// contains a core plugin before loading and initialising it.
[2050]287        /// </summary>
[2295]288        /// <param name="info">The plugin to load.</param>
289        /// <returns>True if the plugin was loaded.</returns>
290        private bool LoadCorePlugin(PluginInfo info)
[2050]291        {
[2295]292            //Check that this plugin's name appears in our list of core plugins, otherwise this
293            //is a phony
294            if (CorePlugins.Count(x => x == info.Assembly.GetName().Name) == 0)
295                return LoadNonCorePlugin(info);
[1359]296
[2295]297            //Check for the presence of a valid signature: Core plugins must have the same
298            //public key as the current assembly
299            if (!info.Assembly.GetName().GetPublicKey().SequenceEqual(
300                    Assembly.GetExecutingAssembly().GetName().GetPublicKey()))
[2050]301            {
[2295]302                throw new FileLoadException(S._("The provided Core plugin does not have an " +
303                    "identical public key as the Eraser assembly.\n\nCheck that the Eraser " +
304                    "installation is not corrupt, or reinstall the program."));
[2050]305            }
[2295]306
307            //Load the plugin.
308            info.Load(this);
309            return true;
[1359]310        }
311
[2295]312        /// <summary>
313        /// Queries the Plugin Host's owner on whether to load the current plugin.
314        /// </summary>
315        /// <param name="info">The plugin to load.</param>
316        /// <returns>True if the plugin was loaded.</returns>
317        private bool LoadNonCorePlugin(PluginInfo info)
318        {
319            PluginLoadEventArgs e = new PluginLoadEventArgs(info);
320            if (PluginLoad != null)
321                PluginLoad(this, e);
322           
323            if (e.Load)
324                info.Load(this);
325
326            return e.Load;
327        }
328
329        private static bool AssemblyMatchesName(Assembly assembly, AssemblyName name)
330        {
331            AssemblyName assemblyName = assembly.GetName();
332            return (name.Name == assemblyName.Name &&
333                (name.Version == null || name.Version == assemblyName.Version) &&
334                (name.ProcessorArchitecture == ProcessorArchitecture.None || name.ProcessorArchitecture == assemblyName.ProcessorArchitecture) &&
335                (name.GetPublicKey() == null || name.GetPublicKey().SequenceEqual(assemblyName.GetPublicKey()))
336            );
337        }
338
[1359]339        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
340        {
[2295]341            //Parse the assembly name
342            AssemblyName name = new AssemblyName(args.Name);
343
[2050]344            //Check the plugins folder
345            foreach (string fileName in Directory.GetFiles(PluginsFolder))
346            {
347                FileInfo file = new FileInfo(fileName);
348                if (file.Extension.Equals(".dll"))
349                    try
350                    {
351                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
[2295]352                        if (AssemblyMatchesName(assembly, name))
353                        {
[2050]354                            return Assembly.LoadFile(file.FullName);
[2295]355                        }
[2050]356                    }
357                    catch (BadImageFormatException)
358                    {
359                    }
360                    catch (FileLoadException)
361                    {
362                    }
363            }
364
[1359]365            return null;
366        }
367
368        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
369        {
370            return Assembly.ReflectionOnlyLoad(args.Name);
371        }
372
373        /// <summary>
[2291]374        /// The path to the folder containing the plugins.
[1359]375        /// </summary>
[2291]376        public readonly string PluginsFolder = Path.Combine(
377            Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
378            "Plugins" //Plugins folder
379        );
[1359]380
381        /// <summary>
[2295]382        /// The list of plugins which are core. This list contains the names of every
383        /// assembly which are expected to be core plugins.
[1359]384        /// </summary>
[2295]385        private readonly string[] CorePlugins = new string[] {
386            "Eraser.DefaultPlugins"
387        };
[1359]388
389        /// <summary>
[2291]390        /// Stores the list of plugins found within the Plugins folder.
[1359]391        /// </summary>
[2295]392        private List<PluginInfo> plugins = new List<PluginInfo>();
[1359]393    }
394}
Note: See TracBrowser for help on using the repository browser.