2009-07-26 16:31:03 +00:00
|
|
|
|
/*
|
|
|
|
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
|
|
|
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
|
|
|
|
*
|
|
|
|
|
* Version: MPL 1.1
|
|
|
|
|
*
|
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
|
*
|
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
|
* License.
|
|
|
|
|
*
|
|
|
|
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
|
|
|
|
*
|
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
|
|
|
|
* Portions created by the Initial Developer are Copyright (C)
|
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Contributor(s):
|
|
|
|
|
*
|
|
|
|
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
|
|
|
|
*
|
|
|
|
|
* ScriptPluginManager.cs -- Dynamic compilation and script execution
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.CodeDom;
|
|
|
|
|
using System.CodeDom.Compiler;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Reflection.Emit;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace FreeSWITCH {
|
|
|
|
|
|
|
|
|
|
public enum ScriptContextType {
|
2009-07-28 18:45:47 +00:00
|
|
|
|
None,
|
2009-07-26 16:31:03 +00:00
|
|
|
|
App,
|
|
|
|
|
Api,
|
|
|
|
|
ApiBackground,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class Script {
|
|
|
|
|
|
|
|
|
|
[ThreadStatic]
|
|
|
|
|
internal static ScriptContextType contextType;
|
|
|
|
|
[ThreadStatic]
|
|
|
|
|
internal static object context;
|
|
|
|
|
|
|
|
|
|
public static ScriptContextType ContextType { get { return contextType; } }
|
|
|
|
|
|
|
|
|
|
public static ApiContext GetApiContext() {
|
|
|
|
|
return getContext<ApiContext>(ScriptContextType.Api);
|
|
|
|
|
}
|
|
|
|
|
public static ApiBackgroundContext GetApiBackgroundContext() {
|
|
|
|
|
return getContext<ApiBackgroundContext>(ScriptContextType.ApiBackground);
|
|
|
|
|
}
|
|
|
|
|
public static AppContext GetAppContext() {
|
|
|
|
|
return getContext<AppContext>(ScriptContextType.App);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static T getContext<T>(ScriptContextType sct) {
|
|
|
|
|
var ctx = context;
|
|
|
|
|
if (ctx == null) throw new InvalidOperationException("Current context is null.");
|
|
|
|
|
if (contextType != sct) throw new InvalidOperationException("Current ScriptContextType is not " + sct.ToString() + ".");
|
|
|
|
|
return (T)ctx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal class ScriptPluginManager : PluginManager {
|
|
|
|
|
|
|
|
|
|
protected override bool LoadInternal(string fileName) {
|
|
|
|
|
Assembly asm;
|
|
|
|
|
if (Path.GetExtension(fileName).ToLowerInvariant() == ".exe") {
|
|
|
|
|
asm = Assembly.LoadFrom(fileName);
|
|
|
|
|
} else {
|
|
|
|
|
asm = compileAssembly(fileName);
|
|
|
|
|
}
|
|
|
|
|
if (asm == null) return false;
|
|
|
|
|
|
|
|
|
|
return processAssembly(fileName, asm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assembly compileAssembly(string fileName) {
|
|
|
|
|
var comp = new CompilerParameters();
|
|
|
|
|
var mainRefs = new List<string> {
|
|
|
|
|
Path.Combine(Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir, "FreeSWITCH.Managed.dll"),
|
|
|
|
|
"System.dll", "System.Xml.dll", "System.Data.dll"
|
|
|
|
|
};
|
|
|
|
|
var extraRefs = new List<string> {
|
|
|
|
|
"System.Core.dll",
|
|
|
|
|
"System.Xml.Linq.dll",
|
|
|
|
|
};
|
|
|
|
|
comp.ReferencedAssemblies.AddRange(mainRefs.ToArray());
|
|
|
|
|
comp.ReferencedAssemblies.AddRange(extraRefs.ToArray());
|
|
|
|
|
CodeDomProvider cdp;
|
|
|
|
|
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
|
|
|
|
switch (ext) {
|
|
|
|
|
case ".fsx":
|
|
|
|
|
cdp = CodeDomProvider.CreateProvider("f#");
|
|
|
|
|
break;
|
|
|
|
|
case ".csx":
|
|
|
|
|
cdp = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });
|
|
|
|
|
break;
|
|
|
|
|
case ".vbx":
|
|
|
|
|
cdp = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });
|
|
|
|
|
break;
|
|
|
|
|
case ".jsx":
|
|
|
|
|
// Have to figure out better JS support
|
|
|
|
|
cdp = CodeDomProvider.CreateProvider("js");
|
|
|
|
|
extraRefs.ForEach(comp.ReferencedAssemblies.Remove);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (CodeDomProvider.IsDefinedExtension(ext)) {
|
|
|
|
|
cdp = CodeDomProvider.CreateProvider(CodeDomProvider.GetLanguageFromExtension(ext));
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
comp.GenerateInMemory = true;
|
|
|
|
|
comp.GenerateExecutable = true;
|
|
|
|
|
|
|
|
|
|
Log.WriteLine(LogLevel.Info, "Compiling {0}", fileName);
|
|
|
|
|
var res = cdp.CompileAssemblyFromFile(comp, fileName);
|
|
|
|
|
|
|
|
|
|
var errors = res.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).ToList();
|
|
|
|
|
if (errors.Count > 0) {
|
|
|
|
|
Log.WriteLine(LogLevel.Error, "There were {0} errors compiling {1}.", errors.Count, fileName);
|
|
|
|
|
foreach (var err in errors) {
|
|
|
|
|
if (string.IsNullOrEmpty(err.FileName)) {
|
|
|
|
|
Log.WriteLine(LogLevel.Error, "{0}: {1}", err.ErrorNumber, err.ErrorText);
|
|
|
|
|
} else {
|
|
|
|
|
Log.WriteLine(LogLevel.Error, "{0}: {1}:{2}:{3} {4}", err.ErrorNumber, err.FileName, err.Line, err.Column, err.ErrorText);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Log.WriteLine(LogLevel.Info, "File {0} compiled successfully.", fileName);
|
|
|
|
|
return res.CompiledAssembly;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool processAssembly(string fileName, Assembly asm) {
|
2009-07-28 18:45:47 +00:00
|
|
|
|
// Call the entrypoint once, to initialize apps that need their main called
|
|
|
|
|
var entryPoint = getEntryDelegate(asm.EntryPoint);
|
|
|
|
|
try { entryPoint(); } catch { }
|
|
|
|
|
|
|
|
|
|
// Check for loading
|
2009-07-26 16:31:03 +00:00
|
|
|
|
var allTypes = asm.GetExportedTypes();
|
|
|
|
|
if (!RunLoadNotify(allTypes)) return false;
|
|
|
|
|
|
|
|
|
|
// Scripts can specify classes too
|
|
|
|
|
AddApiPlugins(allTypes);
|
|
|
|
|
AddAppPlugins(allTypes);
|
|
|
|
|
|
|
|
|
|
// Add the script executors
|
|
|
|
|
var name = Path.GetFileName(fileName);
|
|
|
|
|
var aliases = new List<string> { name };
|
|
|
|
|
this.ApiExecutors.Add(new ApiPluginExecutor(name, aliases, () => new ScriptApiWrapper(entryPoint)));
|
|
|
|
|
this.AppExecutors.Add(new AppPluginExecutor(name, aliases, () => new ScriptAppWrapper(entryPoint)));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ScriptApiWrapper : IApiPlugin {
|
|
|
|
|
|
|
|
|
|
readonly Action entryPoint;
|
|
|
|
|
public ScriptApiWrapper(Action entryPoint) {
|
|
|
|
|
this.entryPoint = entryPoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Execute(ApiContext context) {
|
|
|
|
|
Script.contextType = ScriptContextType.Api;
|
|
|
|
|
Script.context = context;
|
|
|
|
|
try {
|
|
|
|
|
entryPoint();
|
|
|
|
|
} finally {
|
2009-07-28 18:45:47 +00:00
|
|
|
|
Script.contextType = ScriptContextType.None;
|
2009-07-26 16:31:03 +00:00
|
|
|
|
Script.context = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ExecuteBackground(ApiBackgroundContext context) {
|
|
|
|
|
Script.contextType = ScriptContextType.ApiBackground;
|
|
|
|
|
Script.context = context;
|
|
|
|
|
try {
|
|
|
|
|
entryPoint();
|
|
|
|
|
} finally {
|
2009-07-28 18:45:47 +00:00
|
|
|
|
Script.contextType = ScriptContextType.None;
|
2009-07-26 16:31:03 +00:00
|
|
|
|
Script.context = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ScriptAppWrapper : IAppPlugin {
|
|
|
|
|
|
|
|
|
|
readonly Action entryPoint;
|
|
|
|
|
public ScriptAppWrapper(Action entryPoint) {
|
|
|
|
|
this.entryPoint = entryPoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Run(AppContext context) {
|
|
|
|
|
Script.contextType = ScriptContextType.App;
|
|
|
|
|
Script.context = context;
|
|
|
|
|
try {
|
|
|
|
|
entryPoint();
|
|
|
|
|
} finally {
|
2009-07-28 18:45:47 +00:00
|
|
|
|
Script.contextType = ScriptContextType.None;
|
2009-07-26 16:31:03 +00:00
|
|
|
|
Script.context = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Action getEntryDelegate(MethodInfo entryPoint) {
|
|
|
|
|
if (!entryPoint.IsPublic || !entryPoint.DeclaringType.IsPublic) {
|
|
|
|
|
Log.WriteLine(LogLevel.Error, "Entry point: {0}.{1} is not public. This may cause errors with Mono.",
|
|
|
|
|
entryPoint.DeclaringType.FullName, entryPoint.Name);
|
|
|
|
|
}
|
|
|
|
|
var dm = new DynamicMethod(entryPoint.DeclaringType.Assembly.GetName().Name + "_entrypoint_" + entryPoint.DeclaringType.FullName + entryPoint.Name, null, null, true);
|
|
|
|
|
var il = dm.GetILGenerator();
|
|
|
|
|
var args = entryPoint.GetParameters();
|
|
|
|
|
if (args.Length > 1) throw new ArgumentException("Cannot handle entry points with more than 1 parameter.");
|
|
|
|
|
if (args.Length == 1) {
|
|
|
|
|
if (args[0].ParameterType != typeof(string[])) throw new ArgumentException("Entry point paramter must be a string array.");
|
|
|
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
|
|
|
il.Emit(OpCodes.Newarr, typeof(string));
|
|
|
|
|
}
|
|
|
|
|
il.EmitCall(OpCodes.Call, entryPoint, null);
|
|
|
|
|
if (entryPoint.ReturnType != typeof(void)) {
|
|
|
|
|
il.Emit(OpCodes.Pop);
|
|
|
|
|
}
|
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
|
return (Action)dm.CreateDelegate(typeof(Action));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|