OSDN Git Service

Implemented reload button.
[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.Linq;
34 using System.Text;
35 using System.IO;
36 using System.Threading;
37 using System.Reflection;
38 using System.CodeDom.Compiler;
39 using System.Windows.Forms;
40 using OpenMetaverse;
41 using Microsoft.CSharp;
42
43 namespace Radegast
44 {
45     /// <summary>
46     /// Information about loaded plugin
47     /// </summary>
48     public class PluginInfo
49     {
50         /// <summary>File name from which the plugin was loaded, cannot load plugin twice from the same file</summary>
51         public string FileName { get; set; }
52         /// <summary>Plugin class</summary>
53         public IRadegastPlugin Plugin { get; set; }
54         /// <summary>Is plugin started</summary>
55         public bool Started { get; set; }
56         /// <summary>Plugin class</summary>
57         public PluginAttribute Attribures
58         {
59             get
60             {
61                 if (Plugin == null) return null;
62                 return PluginManager.GetAttributes(Plugin);
63             }
64         }
65     }
66
67     /// <summary>
68     /// Handles loading Radegast plugins
69     /// </summary>
70     public class PluginManager : IDisposable
71     {
72         List<PluginInfo> PluginsLoaded = new List<PluginInfo>();
73         RadegastInstance instance;
74
75         /// <summary>
76         /// Gets the list of currently loaded plugins
77         /// </summary>
78         public List<PluginInfo> Plugins
79         {
80             get
81             {
82                 return PluginsLoaded;
83             }
84         }
85
86         /// <summary>
87         /// Creates new PluginManager
88         /// </summary>
89         /// <param name="instance">Radegast instance PluginManager is associated with</param>
90         public PluginManager(RadegastInstance instance)
91         {
92             this.instance = instance;
93         }
94
95         /// <summary>
96         /// Unloads all plugins
97         /// </summary>
98         public void Dispose()
99         {
100             lock (PluginsLoaded)
101             {
102                 List<PluginInfo> unload = new List<PluginInfo>(PluginsLoaded);
103                 unload.ForEach(plug =>
104                 {
105                     UnloadPlugin(plug);
106                 });
107             }
108         }
109
110         /// <summary>
111         /// Unloads a plugin
112         /// </summary>
113         /// <param name="plug">Plugin to unload</param>
114         public void UnloadPlugin(PluginInfo plug)
115         {
116             try
117             {
118                 lock (PluginsLoaded)
119                     PluginsLoaded.RemoveAll((PluginInfo info) => { return plug.FileName == info.FileName; });
120                 plug.Plugin.StopPlugin(instance);
121             }
122             catch (Exception ex)
123             {
124                 Logger.Log("ERROR in Shutdown Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug, ex);
125             }
126         }
127
128         /// <summary>
129         /// Gets extended atributes for plugin
130         /// </summary>
131         /// <param name="plug">Plugin to lookup extra attributes</param>
132         /// <returns>Extended atributes for plugin</returns>
133         public static PluginAttribute GetAttributes(IRadegastPlugin plug)
134         {
135             PluginAttribute a = null;
136
137             foreach (Attribute attr in Attribute.GetCustomAttributes(plug.GetType()))
138             {
139                 if (attr is PluginAttribute)
140                     a = (PluginAttribute)attr;
141             }
142
143             if (a == null)
144             {
145                 a = new PluginAttribute();
146                 a.Name = plug.GetType().FullName;
147             }
148
149             return a;
150         }
151
152         /// <summary>
153         /// Starts all loaded plugins
154         /// </summary>
155         public void StartPlugins()
156         {
157             lock (PluginsLoaded)
158             {
159                 foreach (PluginInfo plug in PluginsLoaded)
160                 {
161                     Logger.DebugLog("Starting " + plug.Plugin.GetType().FullName);
162                     try
163                     {
164                         plug.Plugin.StartPlugin(instance);
165                         plug.Started = true;
166                     }
167                     catch (Exception ex)
168                     {
169                         Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
170                     }
171                 }
172             }
173         }
174
175         /// <summary>
176         /// Loads a plugin for a precompiled assembly or source file
177         /// </summary>
178         /// <param name="loadFileName">File to load</param>
179         /// <param name="stratPlugins">Start plugins that are found in the assembly</param>
180         public void LoadPluginFile(string loadFileName, bool stratPlugins)
181         {
182             if (loadFileName.ToLower().EndsWith(@".cs"))
183             {
184                 LoadCSharpScriptFile(loadFileName, stratPlugins);
185             }
186             else if (loadFileName.ToLower().EndsWith(".dll") || loadFileName.ToLower().EndsWith(".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 }