OSDN Git Service

Updated license year
[radegast/radegast.git] / Radegast / Core / PluginInterface / PluginManager.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2012, 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
64     /// <summary>
65     /// Handles loading Radegast plugins
66     /// </summary>
67     public class PluginManager : IDisposable
68     {
69         /// <summary>List of files that should not be scanned for plugins</summary>
70         public static readonly List<string> PluginBlackList = new List<string>(new string[]
71             {
72                 "AIMLbot.dll",
73                 "CommandLine.dll",
74                 "Meebey.SmartIrc4net.dll",
75                 "Monobjc.Cocoa.dll",
76                 "Monobjc.dll",
77                 "OpenMetaverse.Rendering.Meshmerizer.dll",
78                 "OpenMetaverse.StructuredData.dll",
79                 "OpenMetaverse.dll",
80                 "OpenMetaverseTypes.dll",
81                 "PrimMesher.dll",
82                 "RadSpeechLin.dll",
83                 "RadSpeechMac.dll",
84                 "RadSpeechWin.dll",
85                 "Tao.OpenGl.dll",
86                 "Tao.Platform.Windows.dll",
87                 "Tools.dll",
88                 "XMLRPC.dll",
89                 "fmodex-dotnet.dll",
90                 "fmodex.dll",
91                 "log4net.dll",
92                 "openjpeg-dotnet-x86_64.dll",
93                 "openjpeg-dotnet.dll",
94                 "OpenCyc.dll",
95                 "IKVM.",
96             });
97
98         /// <summary>List of file extensions that could potentially hold plugins</summary>
99         public static readonly List<string> AllowedPluginExtensions = new List<string>(new string[]
100             {
101                 ".cs",
102                 ".dll",
103                 ".exe"
104             });
105
106         List<PluginInfo> PluginsLoaded = new List<PluginInfo>();
107         RadegastInstance instance;
108
109         /// <summary>
110         /// Gets the list of currently loaded plugins
111         /// </summary>
112         public List<PluginInfo> Plugins
113         {
114             get
115             {
116                 return PluginsLoaded;
117             }
118         }
119
120         /// <summary>
121         /// Creates new PluginManager
122         /// </summary>
123         /// <param name="instance">Radegast instance PluginManager is associated with</param>
124         public PluginManager(RadegastInstance instance)
125         {
126             this.instance = instance;
127         }
128
129         /// <summary>
130         /// Unloads all plugins
131         /// </summary>
132         public void Dispose()
133         {
134             lock (PluginsLoaded)
135             {
136                 List<PluginInfo> unload = new List<PluginInfo>(PluginsLoaded);
137                 unload.ForEach(plug =>
138                 {
139                     UnloadPlugin(plug);
140                 });
141             }
142         }
143
144         /// <summary>
145         /// Unloads a plugin
146         /// </summary>
147         /// <param name="plug">Plugin to unload</param>
148         public void UnloadPlugin(PluginInfo plug)
149         {
150             lock (PluginsLoaded)
151             {
152                 PluginsLoaded.ForEach((PluginInfo info) =>
153                     {
154                         if (info.FileName == plug.FileName || info.Plugin == plug.Plugin)
155                         {
156                             try { info.Plugin.StopPlugin(instance); }
157                             catch (Exception ex) { Logger.Log("ERROR in unloading plugin: " + info.Plugin.GetType().Name + " because " + ex, Helpers.LogLevel.Debug, ex); }
158                         }
159                     });
160                 PluginsLoaded.RemoveAll((PluginInfo info) => { return plug.FileName == info.FileName || info.Plugin == plug.Plugin; });
161             }
162         }
163
164         /// <summary>
165         /// Gets extended atributes for plugin
166         /// </summary>
167         /// <param name="plug">Plugin to lookup extra attributes</param>
168         /// <returns>Extended atributes for plugin</returns>
169         public static PluginAttribute GetAttributes(IRadegastPlugin plug)
170         {
171             PluginAttribute a = null;
172
173             foreach (Attribute attr in Attribute.GetCustomAttributes(plug.GetType()))
174             {
175                 if (attr is PluginAttribute)
176                     a = (PluginAttribute)attr;
177             }
178
179             if (a == null)
180             {
181                 a = new PluginAttribute();
182                 a.Name = plug.GetType().FullName;
183             }
184
185             return a;
186         }
187
188         /// <summary>
189         /// Starts all loaded plugins
190         /// </summary>
191         public void StartPlugins()
192         {
193             lock (PluginsLoaded)
194             {
195                 foreach (PluginInfo plug in PluginsLoaded)
196                 {
197                     Logger.DebugLog("Starting " + plug.Plugin.GetType().FullName);
198                     try
199                     {
200                         plug.Plugin.StartPlugin(instance);
201                         plug.Started = true;
202                     }
203                     catch (Exception ex)
204                     {
205                         Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
206                     }
207                 }
208             }
209         }
210
211         /// <summary>
212         /// Loads a plugin for a precompiled assembly or source file
213         /// </summary>
214         /// <param name="loadFileName">File to load</param>
215         /// <param name="stratPlugins">Start plugins that are found in the assembly</param>
216         public void LoadPluginFile(string loadFileName, bool stratPlugins)
217         {
218             string ext = Path.GetExtension(loadFileName).ToLower();
219             if (ext == ".cs")
220             {
221                 LoadCSharpScriptFile(loadFileName, stratPlugins);
222             }
223             else if (ext == ".dll" || ext == ".exe")
224             {
225                 try
226                 {
227                     Assembly assembly = Assembly.LoadFile(loadFileName);
228                     LoadAssembly(loadFileName, assembly, stratPlugins);
229                 }
230                 catch (BadImageFormatException)
231                 {
232                     // non .NET .dlls
233                 }
234                 catch (ReflectionTypeLoadException)
235                 {
236                     // Out of date or dlls missing sub dependencies
237                 }
238                 catch (TypeLoadException)
239                 {
240                     // Another version of: Out of date or dlls missing sub dependencies
241                 }
242                 catch (Exception ex)
243                 {
244                     Logger.Log("ERROR in Radegast Plugin: " + loadFileName + " because " + ex, Helpers.LogLevel.Debug);
245                 }
246             }
247         }
248
249         /// <summary>
250         /// Scans and load plugins from Radegast application folder without starting them
251         /// </summary>
252         public void ScanAndLoadPlugins()
253         {
254             string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
255
256             if (!Directory.Exists(dirName)) return;
257
258             foreach (string loadFileName in Directory.GetFiles(dirName))
259             {
260                 if (IsUnusedFile(loadFileName))
261                 {
262                     continue;
263                 }
264
265                 LoadPluginFile(loadFileName, false);
266             }
267         }
268
269         private static bool IsUnusedFile(string loadFileName)
270         {
271             if (!AllowedPluginExtensions.Contains(Path.GetExtension(loadFileName).ToLower())) return true;
272             loadFileName = Path.GetFileName(loadFileName).ToLower();
273
274             foreach (string blackList in PluginBlackList)
275             {
276                 if (loadFileName.StartsWith(blackList.ToLower()))
277                 {
278                     return true;
279                 }
280             }
281             return false;
282         }
283
284         /// <summary>
285         /// Loads and compiles a plugin from a C# source file
286         /// </summary>
287         /// <param name="fileName">Load plugin from this filename</param>
288         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
289         public void LoadCSharpScriptFile(string fileName, bool startPlugins)
290         {
291             try { LoadCSharpScript(fileName, File.ReadAllText(fileName), startPlugins); }
292             catch (Exception ex)
293             {
294                 Logger.Log("Failed loading C# script " + fileName + ": ", Helpers.LogLevel.Warning, ex);
295             }
296         }
297
298         /// <summary>
299         /// Compiles plugin from string source code
300         /// </summary>
301         /// <param name="fileName">File name from which source was loaded</param>
302         /// <param name="code">Source code</param>
303         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
304         public void LoadCSharpScript(string fileName, string code, bool startPlugins)
305         {
306             try
307             {
308                 // *** Generate dynamic compiler
309                 Dictionary<string, string> loCompilerOptions = new Dictionary<string, string>();
310                 loCompilerOptions.Add("CompilerVersion", "v3.5");
311                 CSharpCodeProvider loCompiler = new CSharpCodeProvider(loCompilerOptions);
312                 CompilerParameters loParameters = new CompilerParameters();
313
314                 // *** Start by adding any referenced assemblies
315                 loParameters.ReferencedAssemblies.Add("OpenMetaverse.StructuredData.dll");
316                 loParameters.ReferencedAssemblies.Add("OpenMetaverseTypes.dll");
317                 loParameters.ReferencedAssemblies.Add("OpenMetaverse.dll");
318                 loParameters.ReferencedAssemblies.Add("Radegast.exe");
319                 loParameters.ReferencedAssemblies.Add("System.dll");
320                 loParameters.ReferencedAssemblies.Add("System.Core.dll");
321                 loParameters.ReferencedAssemblies.Add("System.Xml.dll");
322                 loParameters.ReferencedAssemblies.Add("System.Drawing.dll");
323                 loParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
324
325                 // *** Load the resulting assembly into memory
326                 loParameters.GenerateInMemory = true;
327                 loParameters.GenerateExecutable = false;
328
329                 // *** Now compile the whole thing
330                 CompilerResults loCompiled =
331                         loCompiler.CompileAssemblyFromSource(loParameters, code);
332
333                 // *** Check for compilation erros
334                 if (loCompiled.Errors.HasErrors)
335                 {
336                     string lcErrorMsg = "";
337                     lcErrorMsg = "Compilation failed: " + loCompiled.Errors.Count.ToString() + " errors:";
338
339                     for (int x = 0; x < loCompiled.Errors.Count; x++)
340                         lcErrorMsg += "\r\nLine: " +
341                                      loCompiled.Errors[x].Line.ToString() + " - " +
342                                      loCompiled.Errors[x].ErrorText;
343
344                     instance.TabConsole.DisplayNotificationInChat(lcErrorMsg, ChatBufferTextStyle.Alert);
345                     return;
346                 }
347
348                 instance.TabConsole.DisplayNotificationInChat("Compilation successful.");
349                 Assembly loAssembly = loCompiled.CompiledAssembly;
350                 LoadAssembly(fileName, loAssembly, startPlugins);
351             }
352             catch (Exception ex)
353             {
354                 Logger.Log("Failed loading C# script: ", Helpers.LogLevel.Warning, ex);
355             }
356         }
357
358         /// <summary>
359         /// Scans assembly for supported types
360         /// </summary>
361         /// <param name="loadfilename">File name from which assembly was loaded</param>
362         /// <param name="assembly">Assembly to scan for supported types</param>
363         /// <param name="startPlugins">Start plugins found in the assembly after complilation</param>
364         public void LoadAssembly(string loadfilename, Assembly assembly, bool startPlugins)
365         {
366             if (null != PluginsLoaded.Find((PluginInfo info) => { return info.FileName == loadfilename; }))
367             {
368                 Logger.Log("Plugin already loaded, skipping: " + loadfilename, Helpers.LogLevel.Info);
369                 if (startPlugins)
370                 {
371                     instance.TabConsole.DisplayNotificationInChat("Plugin already loaded, skipping: " + loadfilename);
372                 }
373                 return;
374             }
375
376             foreach (Type type in assembly.GetTypes())
377             {
378                 if (typeof(IRadegastPlugin).IsAssignableFrom(type))
379                 {
380                     if (type.IsInterface) continue;
381                     try
382                     {
383                         IRadegastPlugin plug = null;
384                         ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(RadegastInstance) });
385                         if (constructorInfo != null)
386                             plug = (IRadegastPlugin)constructorInfo.Invoke(new[] { instance });
387                         else
388                         {
389                             constructorInfo = type.GetConstructor(new Type[] { });
390                             if (constructorInfo != null)
391                                 plug = (IRadegastPlugin)constructorInfo.Invoke(new object[0]);
392                             else
393                             {
394                                 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + type + " has no usable constructor.", Helpers.LogLevel.Debug);
395                                 continue;
396                             }
397                         }
398                         PluginInfo info = new PluginInfo()
399                         {
400                             FileName = loadfilename,
401                             Plugin = plug,
402                             Started = false
403                         };
404
405                         lock (PluginsLoaded) PluginsLoaded.Add(info);
406                         if (startPlugins && plug != null)
407                         {
408                             try { plug.StartPlugin(instance); info.Started = true; }
409                             catch (Exception ex) { Logger.Log(string.Format("Failed starting plugin {0}:", type), Helpers.LogLevel.Error, ex); }
410                         }
411                     }
412                     catch (Exception ex)
413                     {
414                         Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,
415                                    Helpers.LogLevel.Debug);
416                     }
417                 }
418                 else
419                 {
420                     try
421                     {
422                         instance.CommandsManager.LoadType(type);
423                     }
424                     catch (Exception ex)
425                     {
426                         Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +
427                                    " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);
428                     }
429                 }
430             }
431         }
432     }
433 }