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

Revision 2353, 11.2 KB checked in by lowjoel, 2 years ago (diff)

Still not compilable, but one major change: when Dispose from Host is called, we should reset the global instance to null.
Also repositioned the declaration for LoadPlugin?(string) to be more logically grouped.

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