OSDN Git Service

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