2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2014, Radegast Development Team
4 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
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.
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.
32 using System.Collections.Generic;
34 using System.Reflection;
35 using System.CodeDom.Compiler;
36 using System.Windows.Forms;
38 using Microsoft.CSharp;
43 /// Information about loaded plugin
45 public class PluginInfo
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
58 if (Plugin == null) return null;
59 return PluginManager.GetAttributes(Plugin);
63 public AppDomain Domain;
67 /// Handles loading Radegast plugins
69 public class PluginManager : IDisposable
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[]
76 "Meebey.SmartIrc4net.dll",
79 "OpenMetaverse.Rendering.Meshmerizer.dll",
80 "OpenMetaverse.StructuredData.dll",
82 "OpenMetaverseTypes.dll",
88 "Tao.Platform.Windows.dll",
94 "openjpeg-dotnet-x86_64.dll",
95 "openjpeg-dotnet.dll",
103 /// <summary>List of file extensions that could potentially hold plugins</summary>
104 public static readonly List<string> AllowedPluginExtensions = new List<string>(new string[]
111 List<PluginInfo> PluginsLoaded = new List<PluginInfo>();
112 RadegastInstance instance;
115 /// Gets the list of currently loaded plugins
117 public List<PluginInfo> Plugins
121 return PluginsLoaded;
126 /// Creates new PluginManager
128 /// <param name="instance">Radegast instance PluginManager is associated with</param>
129 public PluginManager(RadegastInstance instance)
131 this.instance = instance;
135 /// Unloads all plugins
137 public void Dispose()
141 List<PluginInfo> unload = new List<PluginInfo>(PluginsLoaded);
142 unload.ForEach(plug =>
152 /// <param name="plug">Plugin to unload</param>
153 public void UnloadPlugin(PluginInfo plug)
157 var pluginInfos = PluginsLoaded.FindAll(info => { return info.Plugin == plug.Plugin; });
159 foreach (var info in pluginInfos)
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);
166 if (domain != null && PluginsLoaded.Find(dinfo => { return dinfo.Domain == domain; }) == null)
168 try { AppDomain.Unload(domain); }
169 catch (Exception ex) { Logger.Log("ERROR unloading application domain for : " + plug.FileName + "\n" + ex.Message, Helpers.LogLevel.Debug); }
176 /// Gets extended atributes for plugin
178 /// <param name="plug">Plugin to lookup extra attributes</param>
179 /// <returns>Extended atributes for plugin</returns>
180 public static PluginAttribute GetAttributes(IRadegastPlugin plug)
182 PluginAttribute a = null;
184 foreach (Attribute attr in Attribute.GetCustomAttributes(plug.GetType()))
186 if (attr is PluginAttribute)
187 a = (PluginAttribute)attr;
192 a = new PluginAttribute();
193 a.Name = plug.GetType().FullName;
200 /// Starts all loaded plugins
202 public void StartPlugins()
206 foreach (PluginInfo plug in PluginsLoaded)
208 Logger.DebugLog("Starting " + plug.Plugin.GetType().FullName);
211 plug.Plugin.StartPlugin(instance);
216 Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug);
223 /// Loads a plugin for a precompiled assembly or source file
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)
229 string ext = Path.GetExtension(loadFileName).ToLower();
232 LoadCSharpScriptFile(loadFileName, stratPlugins);
234 else if (ext == ".dll" || ext == ".exe")
238 LoadAssembly(loadFileName, stratPlugins);
240 catch (BadImageFormatException)
244 catch (ReflectionTypeLoadException)
246 // Out of date or dlls missing sub dependencies
248 catch (TypeLoadException)
250 // Another version of: Out of date or dlls missing sub dependencies
254 Logger.Log("ERROR in Radegast Plugin: " + loadFileName + " because " + ex, Helpers.LogLevel.Debug);
260 /// Scans and load plugins from Radegast application folder without starting them
262 public void ScanAndLoadPlugins()
264 string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
266 if (!Directory.Exists(dirName)) return;
268 foreach (string loadFileName in Directory.GetFiles(dirName))
270 if (IsUnusedFile(loadFileName))
275 LoadPluginFile(loadFileName, false);
279 private static bool IsUnusedFile(string loadFileName)
281 if (!AllowedPluginExtensions.Contains(Path.GetExtension(loadFileName).ToLower())) return true;
282 loadFileName = Path.GetFileName(loadFileName).ToLower();
284 foreach (string blackList in PluginBlackList)
286 if (loadFileName.StartsWith(blackList.ToLower()))
295 /// Loads and compiles a plugin from a C# source file
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)
301 try { LoadCSharpScript(fileName, File.ReadAllText(fileName), startPlugins); }
304 Logger.Log("Failed loading C# script " + fileName + ": ", Helpers.LogLevel.Warning, ex);
309 /// Compiles plugin from string source code
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)
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();
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");
335 // *** Load the resulting assembly into memory
336 loParameters.GenerateInMemory = true;
337 loParameters.GenerateExecutable = false;
339 // *** Now compile the whole thing
340 CompilerResults loCompiled =
341 loCompiler.CompileAssemblyFromSource(loParameters, code);
343 // *** Check for compilation erros
344 if (loCompiled.Errors.HasErrors)
346 string lcErrorMsg = "";
347 lcErrorMsg = "Compilation failed: " + loCompiled.Errors.Count.ToString() + " errors:";
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;
354 instance.TabConsole.DisplayNotificationInChat(lcErrorMsg, ChatBufferTextStyle.Alert);
358 instance.TabConsole.DisplayNotificationInChat("Compilation successful.");
359 Assembly loAssembly = loCompiled.CompiledAssembly;
360 LoadAssembly(fileName, loAssembly, startPlugins);
364 Logger.Log("Failed loading C# script: ", Helpers.LogLevel.Warning, ex);
369 /// Scans assembly for supported types
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)
376 LoadAssembly(loadfilename, null, startPlugins);
381 /// Scans assembly for supported types and loads it into it's own domain
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)
387 if (null != PluginsLoaded.Find((PluginInfo info) => { return info.FileName == loadfilename; }))
389 Logger.Log("Plugin already loaded, skipping: " + loadfilename, Helpers.LogLevel.Info);
392 instance.TabConsole.DisplayNotificationInChat("Plugin already loaded, skipping: " + loadfilename);
397 AppDomain domain = null;
399 if (assembly == null)
401 // Don't load ourselves into a domain
402 if (Path.GetFileName(Assembly.GetEntryAssembly().Location) == Path.GetFileName(loadfilename))
404 assembly = Assembly.GetEntryAssembly();
408 assembly = Assembly.LoadFile(loadfilename);
410 /* Disable creation of domains for now
411 domain = AppDomain.CreateDomain("Domain for: " + loadfilename);
412 var loader = (RemoteLoader)domain.CreateInstanceAndUnwrap("Radegast", "Radegast.RemoteLoader");
413 assembly = loader.Load(loadfilename);
418 bool loadedTypesFromAssembly = false;
420 foreach (Type type in assembly.GetTypes())
422 if (typeof(IRadegastPlugin).IsAssignableFrom(type))
424 if (type.IsInterface) continue;
427 IRadegastPlugin plug = null;
428 ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(RadegastInstance) });
429 if (constructorInfo != null)
431 plug = (IRadegastPlugin)constructorInfo.Invoke(new[] { instance });
435 constructorInfo = type.GetConstructor(new Type[] { });
436 if (constructorInfo != null)
438 plug = (IRadegastPlugin)constructorInfo.Invoke(new object[0]);
442 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + type + " has no usable constructor.", Helpers.LogLevel.Debug);
447 loadedTypesFromAssembly = true;
449 PluginInfo info = new PluginInfo()
451 FileName = loadfilename,
457 lock (PluginsLoaded) PluginsLoaded.Add(info);
458 if (startPlugins && plug != null)
460 try { plug.StartPlugin(instance); info.Started = true; }
461 catch (Exception ex) { Logger.Log(string.Format("Failed starting plugin {0}:", type), Helpers.LogLevel.Error, ex); }
466 Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex,
467 Helpers.LogLevel.Debug);
474 loadedTypesFromAssembly |= instance.CommandsManager.LoadType(type);
475 loadedTypesFromAssembly |= instance.ContextActionManager.LoadType(type);
479 Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type +
480 " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug);
485 if (domain != null && !loadedTypesFromAssembly)
487 AppDomain.Unload(domain);
494 public class RemoteLoader : MarshalByRefObject
496 public Assembly Load(string loadfilename)
498 return AppDomain.CurrentDomain.Load(File.ReadAllBytes(loadfilename));