See change log

This commit is contained in:
skrusty_cp 2014-01-03 08:39:46 -08:00
parent 205ad4bef1
commit 173d321c38
28 changed files with 745 additions and 29 deletions

View file

@ -202,7 +202,7 @@ Ctrl-C to exit");
OriginateAction oc = new OriginateAction();
oc.Context = ORIGINATE_CONTEXT;
oc.Priority = 1;
oc.Priority = "1";
oc.Channel = ORIGINATE_CHANNEL;
oc.CallerId = ORIGINATE_CALLERID;
oc.Exten = ORIGINATE_EXTEN;

View file

@ -127,9 +127,20 @@
<Compile Include="Manager\Action\AgentCallbackLoginAction.cs" />
<Compile Include="Manager\Action\AgentLogoffAction.cs" />
<Compile Include="Manager\Action\AgentsAction.cs" />
<Compile Include="Manager\Action\AGIAction.cs" />
<Compile Include="Manager\Action\ChallengeAction.cs" />
<Compile Include="Manager\Action\ChangeMonitorAction.cs" />
<Compile Include="Manager\Action\CommandAction.cs" />
<Compile Include="Manager\Action\ConfbridgeKickAction.cs" />
<Compile Include="Manager\Action\ConfbridgeListAction.cs" />
<Compile Include="Manager\Action\ConfbridgeListRoomsAction.cs" />
<Compile Include="Manager\Action\ConfbridgeLockAction.cs" />
<Compile Include="Manager\Action\ConfbridgeMuteAction.cs" />
<Compile Include="Manager\Action\ConfbridgeSetSingleVideoSrcAction.cs" />
<Compile Include="Manager\Action\ConfbridgeStartRecordAction.cs" />
<Compile Include="Manager\Action\ConfbridgeStopRecordAction.cs" />
<Compile Include="Manager\Action\ConfbridgeUnlockAction.cs" />
<Compile Include="Manager\Action\ConfbridgeUnmuteAction.cs" />
<Compile Include="Manager\Action\DBDelAction.cs" />
<Compile Include="Manager\Action\DBDelTreeAction.cs" />
<Compile Include="Manager\Action\DBGetAction.cs" />
@ -174,6 +185,16 @@
<Compile Include="Manager\Action\ZapTransferAction.cs" />
<Compile Include="Manager\AsteriskVersion.cs" />
<Compile Include="Manager\Event\AbstractAgentVariables.cs" />
<Compile Include="Manager\Event\AbstractConfbridgeEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeEndEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeJoinEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeLeaveEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeListCompleteEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeListEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeListRoomsCompleteEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeListRoomsEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeStartEvent.cs" />
<Compile Include="Manager\Event\ConfbridgeTalkingEvent.cs" />
<Compile Include="Manager\Event\ConnectionStateEvent.cs" />
<Compile Include="Manager\Event\AGIExecEvent.cs" />
<Compile Include="Manager\Event\AsyncAGIEvent.cs" />

View file

@ -252,15 +252,15 @@ namespace Asterisk.NET.FastAGI
/// <summary>
/// Returns the priority in the dial plan from which the AGI script was called.
/// </summary>
public int Priority
public string Priority
{
get
{
if (request["priority"] != null)
{
return Int32.Parse((string) request["priority"]);
return (string) request["priority"];
}
return -1;
return "";
}
}

View file

@ -47,6 +47,8 @@ namespace Asterisk.NET.FastAGI.MappingStrategies
/// </summary>
public string ScriptAssmebly { get; set; }
public Assembly PreLoadedAssembly { get; set; }
public static List<ScriptMapping> LoadMappings(string pathToXml)
{
// Load ScriptMappings XML File
@ -92,8 +94,6 @@ namespace Asterisk.NET.FastAGI.MappingStrategies
private List<ScriptMapping> mappings;
private Dictionary<string, MappingAssembly> mapAssemblies;
public string AGIPath = string.Empty;
/// <summary>
///
/// </summary>
@ -142,37 +142,44 @@ namespace Asterisk.NET.FastAGI.MappingStrategies
lock (mapAssemblies)
{
mapAssemblies.Clear();
try
{
foreach (var de in this.mappings)
{
MappingAssembly ma;
if (mapAssemblies.ContainsKey(de.ScriptName))
throw new AGIException(String.Format("Duplicate mapping name '{0}'", de.ScriptName));
if (!string.IsNullOrEmpty(de.ScriptAssmebly))
if (this.mappings == null || this.mappings.Count == 0)
throw new AGIException("No mappings were added, before Load method called.");
foreach (var de in this.mappings)
{
MappingAssembly ma;
if (mapAssemblies.ContainsKey(de.ScriptName))
throw new AGIException(String.Format("Duplicate mapping name '{0}'", de.ScriptName));
if (!string.IsNullOrEmpty(de.ScriptAssmebly))
{
try
{
ma = new MappingAssembly()
{
ClassName = (string)de.ScriptClass,
LoadedAssembly = Assembly.LoadFile(Path.Combine(this.AGIPath, de.ScriptAssmebly))
LoadedAssembly = Assembly.LoadFile(de.ScriptAssmebly)
};
}
else
catch (FileNotFoundException fnfex)
{
ma = new MappingAssembly()
{
ClassName = (string)de.ScriptClass
};
throw new AGIException(string.Format("Unable to load AGI Script {0}, file not found.", Path.Combine(Environment.CurrentDirectory, de.ScriptAssmebly)), fnfex);
}
mapAssemblies.Add(de.ScriptName, ma);
}
else
{
ma = new MappingAssembly()
{
ClassName = (string)de.ScriptClass
};
if (de.PreLoadedAssembly != null)
ma.LoadedAssembly = de.PreLoadedAssembly;
}
mapAssemblies.Add(de.ScriptName, ma);
}
catch (Exception ex)
{
throw new Exception("No mappings were added before 'Load' method called.");
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
/// <summary>
/// This action lets you execute any AGI command through the Manager interface
/// For example, check the Asterisk.Net.Test project
/// </summary>
public class AgiAction : ManagerAction
{
public string Channel { get; set; }
public string Command { get; set; }
/// <summary>
/// Get the name of this action, i.e. "AGI".
/// </summary>
override public string Action
{
get { return "AGI"; }
}
/// <summary>
/// Creates a new empty AgiAction.
/// </summary>
public AgiAction(string channel, string command)
{
Channel = channel;
Command = command;
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeKickAction : ManagerAction
{
public string Conference { get; set; }
public string Channel { get; set; }
public override string Action
{
get { return "ConfbridgeKick"; }
}
/// <summary>
/// Removes a specified user from a specified conference.
/// </summary>
public ConfbridgeKickAction()
{ }
/// <summary>
/// Removes a specified user from a specified conference.
/// </summary>
/// <param name="conference"></param>
/// <param name="channel"></param>
public ConfbridgeKickAction(string conference, string channel)
{
this.Conference = conference;
this.Channel = channel;
}
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
/*
https://wiki.asterisk.org/wiki/display/AST/ConfBridge+10#ConfBridge10-ConfBridgeAsteriskManagerInterface%28AMI%29Events
Action: ConfbridgeList
Conference: 1111
*/
/// <summary>
/// Lists all users in a particular ConfBridge conference. ConfbridgeList will follow as separate events,
/// followed by a final event called ConfbridgeListComplete
/// </summary>
public class ConfbridgeListAction : ManagerActionEvent
{
public string Conference { get; set; }
public override string Action
{
get { return "ConfbridgeList"; }
}
public override Type ActionCompleteEventClass()
{
return typeof(Event.ConfbridgeListCompleteEvent);
}
/// <summary>
/// Lists all users in a particular ConfBridge conference. ConfbridgeList will follow as separate events,
/// followed by a final event called ConfbridgeListComplete
/// </summary>
/// <param name="conference"></param>
public ConfbridgeListAction(string conference)
{
this.Conference = conference;
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
/// <summary>
/// Lists data about all active conferences. ConfbridgeListRooms will follow as separate events,
/// followed by a final event called ConfbridgeListRoomsComplete.
/// </summary>
public class ConfbridgeListRoomsAction : ManagerActionEvent
{
public override string Action
{
get { return "ConfbridgeListRooms"; }
}
public override Type ActionCompleteEventClass()
{
return typeof(Event.ConfbridgeListRoomsCompleteEvent);
}
/// <summary>
/// Lists data about all active conferences. ConfbridgeListRooms will follow as separate events,
/// followed by a final event called ConfbridgeListRoomsComplete.
/// </summary>
public ConfbridgeListRoomsAction()
{
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeLockAction : ManagerAction
{
public string Conference { get; set; }
public override string Action
{
get { return "ConfbridgeLock"; }
}
/// <summary>
/// Locks a specified conference.
/// </summary>
public ConfbridgeLockAction()
{ }
/// <summary>
/// Locks a specified conference.
/// </summary>
/// <param name="conference"></param>
public ConfbridgeLockAction(string conference)
{
this.Conference = conference;
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeMuteAction : ManagerAction
{
public string Conference { get; set; }
public string Channel { get; set; }
public override string Action
{
get { return "ConfbridgeMute"; }
}
/// <summary>
/// Mutes a specified user in a specified conference.
/// </summary>
public ConfbridgeMuteAction()
{ }
/// <summary>
/// Mutes a specified user in a specified conference.
/// </summary>
/// <param name="conference"></param>
/// <param name="channel"></param>
public ConfbridgeMuteAction(string conference, string channel)
{
this.Conference = conference;
this.Channel = channel;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeSetSingleVideoSrcAction : ManagerAction
{
public string Conference { get; set; }
public string Channel { get; set; }
public override string Action
{
get { return "ConfbridgeSetSingleVideoSrc"; }
}
/// <summary>
/// Stops recording a specified conference.
/// </summary>
public ConfbridgeSetSingleVideoSrcAction()
{ }
/// <summary>
/// Stops recording a specified conference.
/// </summary>
/// <param name="conference"></param>
public ConfbridgeSetSingleVideoSrcAction(string conference, string channel)
{
this.Conference = conference;
this.Channel = channel;
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeStartRecordAction : ManagerAction
{
public string Conference { get; set; }
public override string Action
{
get { return "ConfbridgeStartRecord"; }
}
/// <summary>
/// Starts recording a specified conference, with an optional filename.
/// If recording is already in progress, an error will be returned.
/// If RecordFile is not provided, the default record_file as specified in the conferences Bridge Profile will be used.
/// If record_file is not specified, a file will automatically be generated in Asterisk's monitor directory.
/// </summary>
public ConfbridgeStartRecordAction()
{ }
/// <summary>
/// Starts recording a specified conference, with an optional filename.
/// If recording is already in progress, an error will be returned.
/// If RecordFile is not provided, the default record_file as specified in the conferences Bridge Profile will be used.
/// If record_file is not specified, a file will automatically be generated in Asterisk's monitor directory.
/// </summary>
/// <param name="conference"></param>
public ConfbridgeStartRecordAction(string conference)
{
this.Conference = conference;
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeStopRecordAction : ManagerAction
{
public string Conference { get; set; }
public override string Action
{
get { return "ConfbridgeStopRecord"; }
}
/// <summary>
/// Stops recording a specified conference.
/// </summary>
public ConfbridgeStopRecordAction()
{ }
/// <summary>
/// Stops recording a specified conference.
/// </summary>
/// <param name="conference"></param>
public ConfbridgeStopRecordAction(string conference)
{
this.Conference = conference;
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeUnlockAction : ManagerAction
{
public string Conference { get; set; }
public override string Action
{
get { return "ConfbridgeUnlock"; }
}
/// <summary>
/// Unlocks a specified conference.
/// </summary>
public ConfbridgeUnlockAction()
{ }
/// <summary>
/// Unlocks a specified conference.
/// </summary>
/// <param name="conference"></param>
public ConfbridgeUnlockAction(string conference)
{
this.Conference = conference;
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Action
{
public class ConfbridgeUnmuteAction : ManagerAction
{
public string Conference { get; set; }
public string Channel { get; set; }
public override string Action
{
get { return "ConfbridgeUnmute"; }
}
/// <summary>
/// Unmutes a specified user in a specified conference.
/// </summary>
public ConfbridgeUnmuteAction()
{ }
/// <summary>
/// Unmutes a specified user in a specified conference.
/// </summary>
/// <param name="conference"></param>
/// <param name="channel"></param>
public ConfbridgeUnmuteAction(string conference, string channel)
{
this.Conference = conference;
this.Channel = channel;
}
}
}

View file

@ -27,7 +27,7 @@ namespace Asterisk.NET.Manager.Action
private string channel;
private string exten;
private string context;
private int priority;
private string priority;
private int timeout;
private string callerId;
private Dictionary<string, string> variables;
@ -110,7 +110,7 @@ namespace Asterisk.NET.Manager.Action
/// Get /Set the priority of the extension to connect to.
/// If you set the priority you also have to set the context and exten properties.
/// </summary>
public int Priority
public string Priority
{
get { return priority; }
set { this.priority = value; }

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class AbstractConfbridgeEvent : ManagerEvent
{
/// <summary>
///
/// </summary>
public string Conference { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeEndEvent : AbstractConfbridgeEvent
{
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeJoinEvent : AbstractConfbridgeEvent
{
/// <summary>
///
/// </summary>
public string CallerIDnum { get; set; }
/// <summary>
///
/// </summary>
public string CallerIDname { get; set; }
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeLeaveEvent : AbstractConfbridgeEvent
{
/// <summary>
///
/// </summary>
public string CallerIDnum { get; set; }
/// <summary>
///
/// </summary>
public string CallerIDname { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
class ConfbridgeListCompleteEvent
{
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeListEvent : ResponseEvent
{
/// <summary>
///
/// </summary>
public string Conference { get; set; }
/// <summary>
///
/// </summary>
public string CallerIDNum { get; set; }
/// <summary>
///
/// </summary>
public string CallerIDName { get; set; }
/// <summary>
///
/// </summary>
public string Admin { get; set; }
/// <summary>
///
/// </summary>
public string MarkedUser { get; set; }
public ConfbridgeListEvent(ManagerConnection source)
: base(source)
{
}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeListRoomsCompleteEvent : ResponseEvent
{
public ConfbridgeListRoomsCompleteEvent(ManagerConnection source)
: base(source)
{
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeListRoomsEvent : ResponseEvent
{
/// <summary>
///
/// </summary>
public string Conference { get; set; }
/// <summary>
///
/// </summary>
public int Parties { get; set; }
/// <summary>
///
/// </summary>
public int Marked { get; set; }
/// <summary>
///
/// </summary>
public string Locked { get; set; }
public ConfbridgeListRoomsEvent(ManagerConnection source)
: base(source)
{
}
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeStartEvent : AbstractConfbridgeEvent
{
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Asterisk.NET.Manager.Event
{
public class ConfbridgeTalkingEvent : AbstractConfbridgeEvent
{
/// <summary>
///
/// </summary>
public string TalkingStatus { get; set; }
}
}

View file

@ -82,6 +82,12 @@ namespace Asterisk.NET.Manager
public delegate void ZapShowChannelsEventHandler(object sender, Event.ZapShowChannelsEvent e);
public delegate void ConnectionStateEventHandler(object sender, Event.ConnectionStateEvent e);
public delegate void VarSetEventHandler(object sender, Event.VarSetEvent e);
public delegate void AGIExecHandler(object sender, Event.AGIExecEvent e);
public delegate void ConfbridgeStartEventHandler(object sender, Event.ConfbridgeStartEvent e);
public delegate void ConfbridgeJoinEventHandler(object sender, Event.ConfbridgeJoinEvent e);
public delegate void ConfbridgeLeaveEventHandler(object sender, Event.ConfbridgeLeaveEvent e);
public delegate void ConfbridgeEndEventHandler(object sender, Event.ConfbridgeEndEvent e);
public delegate void ConfbridgeTalkingEventHandler(object sender, Event.ConfbridgeTalkingEvent e);
#endregion
@ -420,6 +426,36 @@ namespace Asterisk.NET.Manager
/// </summary>
public event VarSetEventHandler VarSet;
/// <summary>
/// AgiExec is execute
/// </summary>
public event AGIExecHandler AGIExec;
/// <summary>
/// This event is sent when the first user requests a conference and it is instantiated
/// </summary>
public event ConfbridgeStartEventHandler ConfbridgeStart;
/// <summary>
/// This event is sent when a user joins a conference - either one already in progress or as the first user to join a newly instantiated bridge.
/// </summary>
public event ConfbridgeJoinEventHandler ConfbridgeJoin;
/// <summary>
/// This event is sent when a user leaves a conference.
/// </summary>
public event ConfbridgeLeaveEventHandler ConfbridgeLeave;
/// <summary>
/// This event is sent when the last user leaves a conference and it is torn down.
/// </summary>
public event ConfbridgeEndEventHandler ConfbridgeEnd;
/// <summary>
/// This event is sent when the conference detects that a user has either begin or stopped talking.
/// </summary>
public event ConfbridgeTalkingEventHandler ConfbridgeTalking;
#endregion
#region Constructor - ManagerConnection()
@ -513,6 +549,13 @@ namespace Asterisk.NET.Manager
Helper.RegisterEventHandler(registeredEventHandlers, 65, typeof(DTMFEvent));
Helper.RegisterEventHandler(registeredEventHandlers, 70, typeof(VarSetEvent));
Helper.RegisterEventHandler(registeredEventHandlers, 80, typeof(AGIExecEvent));
Helper.RegisterEventHandler(registeredEventHandlers, 81, typeof(ConfbridgeStartEventHandler));
Helper.RegisterEventHandler(registeredEventHandlers, 82, typeof(ConfbridgeJoinEventHandler));
Helper.RegisterEventHandler(registeredEventHandlers, 83, typeof(ConfbridgeLeaveEventHandler));
Helper.RegisterEventHandler(registeredEventHandlers, 84, typeof(ConfbridgeEndEventHandler));
Helper.RegisterEventHandler(registeredEventHandlers, 85, typeof(ConfbridgeTalkingEventHandler));
#endregion
@ -1065,7 +1108,42 @@ namespace Asterisk.NET.Manager
VarSet(this, (VarSetEvent)e);
}
break;
case 80:
if (AGIExec != null)
{
AGIExec(this, (AGIExecEvent)e);
}
break;
case 81:
if (ConfbridgeStart != null)
{
ConfbridgeStart(this, (ConfbridgeStartEvent)e);
}
break;
case 82:
if (ConfbridgeJoin != null)
{
ConfbridgeJoin(this, (ConfbridgeJoinEvent)e);
}
break;
case 83:
if (ConfbridgeLeave != null)
{
ConfbridgeLeave(this, (ConfbridgeLeaveEvent)e);
}
break;
case 84:
if (ConfbridgeEnd != null)
{
ConfbridgeEnd(this, (ConfbridgeEndEvent)e);
}
break;
case 85:
if (ConfbridgeTalking != null)
{
ConfbridgeTalking(this, (ConfbridgeTalkingEvent)e);
}
break;
default:
if (UnhandledEvent != null)
UnhandledEvent(this, e);

View file

@ -1,3 +1,13 @@
03.01.2014 (skrusty)
Added patch submitted by Saritha Bhandarkar
Adds AGIAction to Asterisk Manager
Adds AGIAction Event
Changes PRIORTY (int to string) - BREAKING CHANGE!! If you use Priority for Originate, then this will have broken between last release and now.
Added Support for v10+ ConfBridge events (not yet fully tested)
Added Support for v10+ COnfBridge actions (not yet fully tested)
Changed GeneralMappingStrategy requires a full path to an assembly that requires loading
Added GeneralMappingStrategy can now take an assembly in the mapping strategy, forcing AsterNET not to reload the assembly during load
21.08.2013 (skrusty)
Added VarSetEventHandler (added as contribution by bacobart)
Added IMappingStrategy to allow different mapping strategies to be created (added as contribution by bacobart) (*note to get documentation added for this)