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

Revision 2298, 11.1 KB checked in by lowjoel, 3 years ago (diff)

Fix parsing of attributes from reflection-only assemblies.

  • 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<PluginInfo> 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        /// <param name="sender"></param>
117        /// <param name="e"></param>
118        internal void OnPluginLoad(object sender, PluginLoadEventArgs e)
119        {
120            if (PluginLoad != null)
121                PluginLoad(sender, e);
122        }
123
124        /// <summary>
125        /// Event callback executor for the OnPluginLoaded vent
126        /// </summary>
127        internal void OnPluginLoaded(object sender, PluginLoadedEventArgs e)
128        {
129            if (PluginLoaded != null)
130                PluginLoaded(sender, e);
131        }
132
133        /// <summary>
134        /// Loads a plugin.
135        /// </summary>
136        /// <param name="filePath">The absolute or relative file path to the
137        /// DLL.</param>
138        /// <returns>True if the plugin is loaded, false otherwise.</returns>
139        /// <remarks>If a plugin is loaded twice, this function should do nothing
140        /// and return True.</remarks>
141        public abstract bool LoadPlugin(string filePath);
142    }
143
144    /// <summary>
145    /// The default plugins host implementation.
146    /// </summary>
147    internal class DefaultHost : Host
148    {
149        /// <summary>
150        /// Constructor.
151        /// </summary>
152        public DefaultHost() : base()
153        {
154            //Specify additional places to load assemblies from
155            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
156            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
157        }
158
159        public override void Load()
160        {
161            //Load all core plugins first
162            foreach (string name in CorePlugins)
163            {
164                if (!LoadPlugin(new AssemblyName(name)))
165                    throw new FileLoadException(S._("The required Core plugin {0} could not be " +
166                        "loaded. Repair the Eraser installation and try again."));
167            }
168
169            //Then load the rest
170            foreach (string fileName in Directory.GetFiles(PluginsFolder))
171            {
172                FileInfo file = new FileInfo(fileName);
173                if (file.Extension.Equals(".dll"))
174                    try
175                    {
176                        LoadPlugin(file.FullName);
177                    }
178                    catch (BadImageFormatException)
179                    {
180                    }
181                    catch (FileLoadException)
182                    {
183                    }
184            }
185        }
186
187        protected override void Dispose(bool disposing)
188        {
189            if (plugins == null)
190                return;
191
192            if (disposing)
193            {
194                //Unload all the plugins. This will cause all the plugins to execute
195                //the cleanup code.
196                foreach (PluginInfo plugin in plugins)
197                    if (plugin.Plugin != null)
198                        plugin.Plugin.Dispose();
199            }
200
201            plugins = null;
202        }
203
204        public override IList<PluginInfo> Plugins
205        {
206            get { return plugins.AsReadOnly(); }
207        }
208
209        /// <summary>
210        /// Verifies whether the provided assembly is a plugin.
211        /// </summary>
212        /// <param name="assembly">The assembly to verify.</param>
213        /// <returns>True if the assembly provided is a plugin, false otherwise.</returns>
214        private bool IsPlugin(Assembly assembly)
215        {
216            //Iterate over every exported type, checking if it implements IPlugin
217            Type typePlugin = assembly.GetExportedTypes().FirstOrDefault(
218                    type => type.GetInterface("Eraser.Plugins.IPlugin", true) != null);
219
220            //If the typePlugin type is empty, the assembly doesn't implement IPlugin and thus
221            //it is not a plugin.
222            return typePlugin != null;
223        }
224
225        public bool LoadPlugin(AssemblyName name)
226        {
227            //Check the plugins folder
228            foreach (string fileName in Directory.GetFiles(PluginsFolder))
229            {
230                FileInfo file = new FileInfo(fileName);
231                if (file.Extension.Equals(".dll"))
232                    try
233                    {
234                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
235                        if (AssemblyMatchesName(assembly, name))
236                        {
237                            return LoadPlugin(assembly);
238                        }
239                    }
240                    catch (BadImageFormatException)
241                    {
242                    }
243                    catch (FileLoadException)
244                    {
245                    }
246            }
247
248            return false;
249        }
250
251        public override bool LoadPlugin(string filePath)
252        {
253            return LoadPlugin(Assembly.ReflectionOnlyLoadFrom(filePath));
254        }
255
256        /// <summary>
257        /// Checks the provided assembly for its name and attempts to load it using the
258        /// plugin loading rules.
259        /// </summary>
260        /// <param name="assembly">The plugin to load. This assembly can be loaded
261        /// in the reflection-only context for security.</param>
262        /// <returns>True if the assembly was a plugin and loaded without error.</returns>
263        private bool LoadPlugin(Assembly assembly)
264        {
265            PluginInfo instance = new PluginInfo(assembly, null);
266
267            //Check that the plugin hasn't yet been loaded.
268            if (Plugins.Count(
269                    plugin => plugin.Assembly.GetName().FullName ==
270                    assembly.GetName().FullName) > 0)
271            {
272                return true;
273            }
274
275            //Ignore non-plugins
276            if (!IsPlugin(instance.Assembly))
277                return false;
278
279            //OK this assembly is a plugin
280            lock (plugins)
281                plugins.Add(instance);
282
283            //Load the plugin, depending on type
284            bool result = instance.LoadingPolicy == LoadingPolicy.Core ?
285                LoadCorePlugin(instance) : LoadNonCorePlugin(instance);
286            if (result)
287            {
288                //And broadcast the plugin load event
289                OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
290            }
291
292            return result;
293        }
294
295        /// <summary>
296        /// Verifies the assembly name and strong name of a plugin, ensuring that the assembly
297        /// contains a core plugin before loading and initialising it.
298        /// </summary>
299        /// <param name="info">The plugin to load.</param>
300        /// <returns>True if the plugin was loaded.</returns>
301        private bool LoadCorePlugin(PluginInfo info)
302        {
303            //Check that this plugin's name appears in our list of core plugins, otherwise this
304            //is a phony
305            if (CorePlugins.Count(x => x == info.Assembly.GetName().Name) == 0)
306            {
307                info.LoadingPolicy = LoadingPolicy.None;
308                return LoadNonCorePlugin(info);
309            }
310
311            //Check for the presence of a valid signature: Core plugins must have the same
312            //public key as the current assembly
313            if (!info.Assembly.GetName().GetPublicKey().SequenceEqual(
314                    Assembly.GetExecutingAssembly().GetName().GetPublicKey()))
315            {
316                throw new FileLoadException(S._("The provided Core plugin does not have an " +
317                    "identical public key as the Eraser assembly.\n\nCheck that the Eraser " +
318                    "installation is not corrupt, or reinstall the program."));
319            }
320
321            //Load the plugin.
322            info.Load(this);
323            return true;
324        }
325
326        /// <summary>
327        /// Queries the Plugin Host's owner on whether to load the current plugin.
328        /// </summary>
329        /// <param name="info">The plugin to load.</param>
330        /// <returns>True if the plugin was loaded.</returns>
331        private bool LoadNonCorePlugin(PluginInfo info)
332        {
333            PluginLoadEventArgs e = new PluginLoadEventArgs(info);
334            OnPluginLoad(this, e);
335           
336            if (e.Load)
337                info.Load(this);
338
339            return e.Load;
340        }
341
342        private static bool AssemblyMatchesName(Assembly assembly, AssemblyName name)
343        {
344            AssemblyName assemblyName = assembly.GetName();
345            return (name.Name == assemblyName.Name &&
346                (name.Version == null || name.Version == assemblyName.Version) &&
347                (name.ProcessorArchitecture == ProcessorArchitecture.None || name.ProcessorArchitecture == assemblyName.ProcessorArchitecture) &&
348                (name.GetPublicKey() == null || name.GetPublicKey().SequenceEqual(assemblyName.GetPublicKey()))
349            );
350        }
351
352        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
353        {
354            //Parse the assembly name
355            AssemblyName name = new AssemblyName(args.Name);
356
357            //Check the plugins folder
358            foreach (string fileName in Directory.GetFiles(PluginsFolder))
359            {
360                FileInfo file = new FileInfo(fileName);
361                if (file.Extension.Equals(".dll"))
362                    try
363                    {
364                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
365                        if (AssemblyMatchesName(assembly, name))
366                        {
367                            return Assembly.LoadFile(file.FullName);
368                        }
369                    }
370                    catch (BadImageFormatException)
371                    {
372                    }
373                    catch (FileLoadException)
374                    {
375                    }
376            }
377
378            return null;
379        }
380
381        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
382        {
383            return Assembly.ReflectionOnlyLoad(args.Name);
384        }
385
386        /// <summary>
387        /// The path to the folder containing the plugins.
388        /// </summary>
389        public readonly string PluginsFolder = Path.Combine(
390            Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
391            "Plugins" //Plugins folder
392        );
393
394        /// <summary>
395        /// The list of plugins which are core. This list contains the names of every
396        /// assembly which are expected to be core plugins.
397        /// </summary>
398        private readonly string[] CorePlugins = new string[] {
399            "Eraser.DefaultPlugins"
400        };
401
402        /// <summary>
403        /// Stores the list of plugins found within the Plugins folder.
404        /// </summary>
405        private List<PluginInfo> plugins = new List<PluginInfo>();
406    }
407}
Note: See TracBrowser for help on using the repository browser.