OSDN Git Service

Silence couple of warnings about unused variables
[radegast/radegast.git] / Radegast / Core / PluginInterface / PluginManager.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above copyright
12 //       notice, this list of conditions and the following disclaimer in the
13 //       documentation and/or other materials provided with the distribution.
14 //     * Neither the name of the application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.Reflection;
35 using System.CodeDom.Compiler;
36 using System.Windows.Forms;
37 using OpenMetaverse;
38 using Microsoft.CSharp;
39
40 namespace Radegast
41 {
42     /// <summary>
43     /// Information about loaded plugin
44     /// </summary>
45     public class PluginInfo
46     {
47         /// <summary>File name from which the plugin was loaded, cannot load plugin twice from the same file</summary>
48         public string FileName { get; set; }
49         /// <summary>Plugin class</summary>
50         public IRadegastPlugin Plugin { get; set; }
51         /// <summary>Is plugin started</summary>
52         public bool Started { get; set; }
53         /// <summary>Plugin class</summary>
54         public PluginAttribute Attribures
55         {
56             get
57             {
58                 if (Plugin == null) return null;
59                 return PluginManager.GetAttributes(Plugin);
60             }
61         }
62
63         public AppDomain Domain;
64     }
65
66     /// <summary>
67     /// Handles loading Radegast plugins
68     /// </summary>
69     public class PluginManager : IDisposable
70     {
71         /// <summary>List of files that should not be scanned for plugins</summary>
72         public static readonly List<string> PluginBlackList = new List<string>(new string[]
73             {
74                 "AIMLbot.dll",
75                 "CommandLine.dll",
76                 "Meebey.SmartIrc4net.dll",
77                 "Monobjc.Cocoa.dll",
78                 "Monobjc.dll",
79                 "OpenMetaverse.Rendering.Meshmerizer.dll",
80                 "OpenMetaverse.StructuredData.dll",
81                 "OpenMetaverse.dll",
82                 "OpenMetaverseTypes.dll",
83                 "PrimMesher.dll",
84                 "RadSpeechLin.dll",
85                 "RadSpeechMac.dll",
86                 "RadSpeechWin.dll",
87                 "Tao.OpenGl.dll",
88                 "Tao.Platform.Windows.dll",
89                 "Tools.dll",
90                 "XMLRPC.dll",
91                 "fmodex-dotnet.dll",
92                 "fmodex.dll",
93                 "log4net.dll",
94                 "openjpeg-dotnet-x86_64.dll",
95                 "openjpeg-dotnet.dll",
96                 "OpenCyc.dll",
97                 "IKVM.",
98                 "OpenTK",
99                 "zlib.net.dll",
100                 "SmartThreadPool",
101             });
102
103         /// <summary>List of file extensions that could potentially hold plugins</summary>
104         public static readonly List<string> AllowedPluginExtensions = new List<string>(new string[]
105             {
106                 ".cs",
107                 ".dll",
108                 ".exe"
109             });
110
111         List<PluginInfo> PluginsLoaded = new List<PluginInfo>();
112         RadegastInstance instance;
113
114         /// <summary>
115         /// Gets the list of currently loaded plugins
116         /// </summary>
117         public List<PluginInfo> Plugins
118         {
119             get
120             {
121                 return PluginsLoaded;
122             }
123         }
124
125         /// <summary>
126         /// Creates new PluginManager
127         /// </summary>
128         /// <param name="instance">Radegast instance PluginManager is associated with</param>
129         public PluginManager(RadegastInstance instance)
130         {
131             this.instance = instance;
132         }
133
134         /// <summary>
135         /// Unloads all plugins
136         /// </summary>
137         public void Dispose()
138         {
139             lock (PluginsLoaded)
140             {
141                 List<PluginInfo> unload = new List<PluginInfo>(PluginsLoaded);
142                 unload.ForEach(plug =>
143                 {
144                     UnloadPlugin(plug);
145                 });
146             }
147         }
148
149         /// <summary>
150         /// Unloads a plugin
151         /// </summary>
152         /// <param name="plug">Plugin to unload</param>
153         public void UnloadPlugin(PluginInfo plug)
154         {
155             lock (PluginsLoaded)
156             {
157                 var pluginInfos = PluginsLoaded.FindAll(info => { return info.Plugin == plug.Plugin; });
158
159                 foreach (var info in pluginInfos)
160                 {
161                     AppDomain domain = info.Domain;
162                     try { info.Plugin.StopPlugin(instance); }
163                     catch (Exception ex) { Logger.Log("ERROR in unloading plugin: " + info.Plugin.GetType().Name + " because " + ex, Helpers.LogLevel.Debug, ex); }
164                     PluginsLoaded.Remove(info);
165
166                     if (domain != null && PluginsLoaded.Find(dinfo => { return dinfo.Domain == domain; }) == null)
167                     {
168                         try { AppDomain.Unload(domain); }
169                         catch (Exception ex) { Logger.Log("ERROR unloading application domain for : " + plug.FileName + "\n" + ex.Message, Helpers.LogLevel.Debug); }
170                     }
171                 }
172             }
173         }
174
175         /// <summary>
176         /// Gets extended atributes for plugin
177         /// </summary>
178         /// <param name="plug">Plugin to lookup extra attributes</param>
179         /// <returns>Extended atributes for plugin</returns>
180         public static PluginAttribute GetAttributes(IRadegastPlugin plug)
181         {
182             PluginAttribute a = null;
183
184             foreach (Attribute attr in Attribute.GetCustomAttributes(plug.GetType()))
185             {
186                 if (attr is PluginAttribute)
187                     a = (PluginAttribute)attr;
188             }
189
190             if (a == null)
191             {
192                 a = new PluginAttribute();
193                 a.Name = plug.GetType().FullName;
194             }
195
196             return a;
197         }
198
199         /// <summary>
200         /// Starts all loaded plugins
201         /// </summary>
202         public void StartPlugins()
203         {
204             lock (PluginsLoaded)
205             {
206                 foreach (PluginInfo plug in PluginsLoaded)
207                 {
208                     Logger.DebugLog("Starting " + plug.Plugin.GetType().FullName);
209                     try
210                     {
211                         plug.Plugin.StartPlugin(instance);
212                         plug.Started = true;
213                     }
214                     catch (Exception ex)
215                     {
216                         Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
217                     }
218                 }
219             }
220         }
221
222         /// <summary>
223         /// Loads a plugin for a precompiled assembly or source file
224         /// </summary>
225         /// <param name="loadFileName">File to load</param>
226         /// <param name="stratPlugins">Start plugins that are found in the assembly</param>
227         public void LoadPluginFile(string loadFileName, bool stratPlugins)
228         {
229             string ext = Path.GetExtension(loadFileName).ToLower();
230             if (ext == ".cs")
231             {
232                 LoadCSharpScriptFile(loadFileName, stratPlugins);
233             }
234             else if (ext == ".dll" || ext == ".exe")
235             {
236                 try
237                 {
238                     LoadAssembly(loadFileName, stratPlugins);
239                 }
240                 catch (BadImageFormatException)
241                 {
242                     // non .NET .dlls
243                 }
244                 catch (ReflectionTypeLoadException)
245                 {
246                     // Out of date or dlls missing sub dependencies
247                 }
248                 catch (TypeLoadException)
249                 {
250                     // Another version of: Out of date or dlls missing sub dependencies
251                 }
252                 catch (Exception ex)
253                 {
254                     Logger.Log("ERROR in Radegast Plugin: " + loadFileName + " because " + ex, Helpers.LogLevel.Debug);
255                 }
256             }
257         }
258
259         /// <summary>
260         /// Scans and load plugins from Radegast application folder without starting them
261         /// </summary>
262         public void ScanAndLoadPlugins()
263         {
264             string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
265
266             if (!Directory.Exists(dirName)) return;
267
268             foreach (string loadFileName in Directory.GetFiles(dirName))
269             {
270                 if (IsUnusedFile(loadFileName))
271                 {
272                     continue;
273                 }
274
275                 LoadPluginFile(loadFileName, false);
276             }
277         }
278
279         private static bool IsUnusedFile(string loadFileName)
280         {
281             if (!AllowedPluginExtensions.Contains(Path.GetExtension(loadFileName).ToLower())) return true;
282             loadFileName = Path.GetFileName(loadFileName).ToLower();
283
284             foreach (string blackList in PluginBlackList)
285             {
286                 if (loadFileName.StartsWith(blackList.ToLower()))
287                 {
288                     return true;
289                 }
290             }
291             return false;
292         }
293
294         /// <summary>
295         /// Loads and compiles a plugin from a C# source file
296         /// </summary>
297         /// <param name="fileName">Load plugin from this filename</param>
298         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
299         public void LoadCSharpScriptFile(string fileName, bool startPlugins)
300         {
301             try { LoadCSharpScript(fileName, File.ReadAllText(fileName), startPlugins); }
302             catch (Exception ex)
303             {
304                 Logger.Log("Failed loading C# script " + fileName + ": ", Helpers.LogLevel.Warning, ex);
305             }
306         }
307
308         /// <summary>
309         /// Compiles plugin from string source code
310         /// </summary>
311         /// <param name="fileName">File name from which source was loaded</param>
312         /// <param name="code">Source code</param>
313         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
314         public void LoadCSharpScript(string fileName, string code, bool startPlugins)
315         {
316             try
317             {
318                 // *** Generate dynamic compiler
319                 Dictionary<string, string> loCompilerOptions = new Dictionary<string, string>();
320                 loCompilerOptions.Add("CompilerVersion", "v3.5");
321                 CSharpCodeProvider loCompiler = new CSharpCodeProvider(loCompilerOptions);
322                 CompilerParameters loParameters = new CompilerParameters();
323
324                 // *** Start by adding any referenced assemblies
325                 loParameters.ReferencedAssemblies.Add("OpenMetaverse.StructuredData.dll");
326                 loParameters.ReferencedAssemblies.Add("OpenMetaverseTypes.dll");
327                 loParameters.ReferencedAssemblies.Add("OpenMetaverse.dll");
328                 loParameters.ReferencedAssemblies.Add("Radegast.exe");
329                 loParameters.ReferencedAssemblies.Add("System.dll");
330                 loParameters.ReferencedAssemblies.Add("System.Core.dll");
331                 loParameters.ReferencedAssemblies.Add("System.Xml.dll");
332                 loParameters.ReferencedAssemblies.Add("System.Drawing.dll");
333                 loParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
334
335                 // *** Load the resulting assembly into memory
336                 loParameters.GenerateInMemory = true;
337                 loParameters.GenerateExecutable = false;
338
339                 // *** Now compile the whole thing
340                 CompilerResults loCompiled =
341                         loCompiler.CompileAssemblyFromSource(loParameters, code);
342
343                 // *** Check for compilation erros
344                 if (loCompiled.Errors.HasErrors)
345                 {
346                     string lcErrorMsg = "";
347                     lcErrorMsg = "Compilation failed: " + loCompiled.Errors.Count.ToString() + " errors:";
348
349                     for (int x = 0; x < loCompiled.Errors.Count; x++)
350                         lcErrorMsg += "\r\nLine: " +
351                                      loCompiled.Errors[x].Line.ToString() + " - " +
352                                      loCompiled.Errors[x].ErrorText;
353
354                     instance.TabConsole.DisplayNotificationInChat(lcErrorMsg, ChatBufferTextStyle.Alert);
355                     return;
356                 }
357
358                 instance.TabConsole.DisplayNotificationInChat("Compilation successful.");
359                 Assembly loAssembly = loCompiled.CompiledAssembly;
360                 LoadAssembly(fileName, loAssembly, startPlugins);
361             }
362             catch (Exception ex)
363             {
364                 Logger.Log("Failed loading C# script: ", Helpers.LogLevel.Warning, ex);
365             }
366         }
367
368         /// <summary>
369         /// Scans assembly for supported types
370         /// </summary>
371         /// <param name="loadfilename">File name from which assembly was loaded</param>
372         /// <param name="assembly">Assembly to scan for supported types</param>
373         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
374         public void LoadAssembly(string loadfilename, bool startPlugins)
375         {
376             LoadAssembly(loadfilename, null, startPlugins);
377         }
378
379
380         /// <summary>
381         /// Scans assembly for supported types and loads it into it's own domain
382         /// </summary>
383         /// <param name="loadfilename">File name from which assembly was loaded</param>
384         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
385         public void LoadAssembly(string loadfilename, Assembly assembly, bool startPlugins)
386         {
387             if (null != PluginsLoaded.Find((PluginInfo info) => { return info.FileName == loadfilename; }))
388             {
389                 Logger.Log("Plugin already loaded, skipping: " + loadfilename, Helpers.LogLevel.Info);
390                 if (startPlugins)
391                 {
392                     instance.TabConsole.DisplayNotificationInChat("Plugin already loaded, skipping: " + loadfilename);
393                 }
394                 return;
395             }
396
397             AppDomain domain = null;
398
399             if (assembly == null)
400             {
401                 // Don't load ourselves into a domain
402                 if (Path.GetFileName(Assembly.GetEntryAssembly().Location) == Path.GetFileName(loadfilename))
403                 {
404                     assembly = Assembly.GetEntryAssembly();
405                 }
406                 else
407                 {
408                     assembly = Assembly.Load(File.ReadAllBytes(loadfilename));
409                     /* Disable creation of domains for now
410                     domain = AppDomain.CreateDomain("Domain for: " + loadfilename);
411                     var loader = (RemoteLoader)domain.CreateInstanceAndUnwrap("Radegast", "Radegast.RemoteLoader");
412                     assembly = loader.Load(loadfilename);
413                     */
414                 }
415             }
416
417             bool loadedTypesFromAssembly = false;
418
419             foreach (Type type in assembly.GetTypes())
420             {
421                 if (typeof(IRadegastPlugin).IsAssignableFrom(type))
422                 {
423                     if (type.IsInterface) continue;
424                     try
425                     {
426                         IRadegastPlugin plug = null;
427                         ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(RadegastInstance) });
428                         if (constructorInfo != null)
429                         {
430                             plug = (IRadegastPlugin)constructorInfo.Invoke(new[] { instance });
431                         }
432                         else
433                         {
434                             constructorInfo = type.GetConstructor(new Type[] { });
435                             if (constructorInfo != null)
436                             {
437                                 plug = (IRadegastPlugin)constructorInfo.Invoke(new object[0]);
438                             }
439                             else
440                             {
441                                 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + type + " has no usable constructor.", Helpers.LogLevel.Debug);
442                                 continue;
443                             }
444                         }
445
446                         loadedTypesFromAssembly = true;
447
448                         PluginInfo info = new PluginInfo()
449                         {
450                             FileName = loadfilename,
451                             Plugin = plug,
452                             Started = false,
453                             Domain = domain
454                         };
455
456                         lock (PluginsLoaded) PluginsLoaded.Add(info);
457                         if (startPlugins && plug != null)
458                         {
459                             try { plug.StartPlugin(instance); info.Started = true; }
460                             catch (Exception ex) { Logger.Log(string.Format("Failed starting plugin {0}:", type), Helpers.LogLevel.Error, ex); }
461                         }
462                     }
463                     catch (Exception ex)
464                     {
465                         Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,
466                                    Helpers.LogLevel.Debug);
467                     }
468                 }
469                 else
470                 {
471                     try
472                     {
473                         loadedTypesFromAssembly |= instance.CommandsManager.LoadType(type);
474                         loadedTypesFromAssembly |= instance.ContextActionManager.LoadType(type);
475                     }
476                     catch (Exception ex)
477                     {
478                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +
479                                    " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);
480                     }
481                 }
482             }
483
484             if (domain != null && !loadedTypesFromAssembly)
485             {
486                 AppDomain.Unload(domain);
487             }
488
489
490         }
491     }
492
493     public class RemoteLoader : MarshalByRefObject
494     {
495         public Assembly Load(string loadfilename)
496         {
497             return AppDomain.CurrentDomain.Load(File.ReadAllBytes(loadfilename));
498         }
499     }
500
501 }