/// François-Régis Colin
/// http://www.tls.cena.fr/products/ivy/
/// *
/// (C) CENA
/// *
///
[assembly: System.CLSCompliant(true)]
namespace IvyBus
{
using System;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Configuration;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections.ObjectModel;
using IvyBus.Properties;
/// The Main bus Class
///
///
public class Ivy : IDisposable
{
/* Event */
///
/// raise when an new IvyClient is connected
///
public event EventHandler ClientConnected;
///
/// raise when an new IvyClient is disconnected
///
public event EventHandler ClientDisconnected;
///
/// raise when a direct message is received
///
public event EventHandler DirectMessageReceived;
///
/// raise when some IvyClient ask me to die
///
public event EventHandler DieReceived;
///
/// raise when an IvyClient register a binding
///
public event EventHandler BindingAdd;
///
/// raise when an IvyClient unregister a binding
///
public event EventHandler BindingRemove;
///
/// raise when an IvyClient binding is filteredout
///
public event EventHandler BindingFilter;
///
/// raise when an IvyClient changer a binding
///
public event EventHandler BindingChange;
///
/// raise when an error message is received
///
public event EventHandler ErrorMessage;
///
/// Flag for displaying internal debug message of the protocol intrinsics
///
public static bool DebugProtocol
{
get
{
return debugProtocol;
}
set
{
debugProtocol = value;
}
}
///
/// Language for the Ivy SendMsg formating default en-US
/// ie: send point on float value sent
///
public CultureInfo Culture
{
get
{
return culture;
}
set
{
culture = value;
}
}
/// IvyClients accesses the list of the connected clients
public ReadOnlyCollection IvyClients
{
get
{
return new ReadOnlyCollection(clients);
}
}
/// AppName the application name
public string AppName
{
set
{
appName = value;
}
get
{
return appName;
}
}
///
/// AppId the Application Unique ID
///
public string AppId
{
get
{
return applicationUniqueId;
}
}
///
/// the Ivy Protocol version
///
public int ProtocolVersion
{
get
{
return protocolVersion;
}
}
///
/// IsSynchronous is the bus Synchronous of the main thread ( ie callback running in the main thread) ?
///
private bool synchronous = true;
public bool Synchronous
{
set
{
synchronous = value && syncContext != null;
}
get
{
return synchronous;
}
}
///
/// IsRunning is the bus Started and connected ?
///
public bool IsRunning
{
get
{
return !stopped;
}
}
///SentMessageClasses the first word token of sent messages
/// optimise the parsing process when sending messages
///
public Collection SentMessageFilter
{
get
{
return sent_messageFilter;
}
}
/// ReadyMessage message send when Application receive all the regexp at the connection of the client
public string ReadyMessage
{
get { return ready_message; }
set { ready_message = value; }
}
internal class MyTcpListener : TcpListener
{
public MyTcpListener(IPAddress address, int port)
: base(address, port)
{
}
public bool IsActive()
{
return this.Active;
}
}
/// the name of the application on the bus
internal string appName;
/// the port for the UDP rendez vous, if none is supplied
internal const ushort DefaultPort = 2010;
/// the domain for the UDP rendez vous
private static readonly string DefaultDomain = "127.0.0.1:" + DefaultPort;
internal int protocolVersion = 3;
private static bool debugProtocol; // false by default runtime
private static int serial = -1; /* an unique ID for each regexp */ // -1 to start numbering at 0
private MyTcpListener app;
private List watchers;
private Thread serverThread; // to ensure quick communication of the end
private AutoResetEvent tcplistener_running;
private bool ipv6;
///
/// the Ivy IP version to use
///
public bool IpV6
{
get { return ipv6; }
}
///
/// the Ivy Assembly version
///
static public Version Version
{
get { return Assembly.GetExecutingAssembly().GetName().Version; }
}
// Class representing Ivy Binding receive from client
internal class IvyClientBinding
{
internal IvyBindingBase binding;
internal Dictionary> client_bindind_ids;
}
// The Application bindings
internal Dictionary bindings;
// the clients bindings
private Dictionary client_bindings;
private List clients;
private Collection sent_messageFilter;
private bool stopped = true;
internal int applicationPort; /* Application port number */
internal IPAddress applicationHost; /* Application host number */
internal string applicationUniqueId; /* identifier Application unique timestamp-ipaddress-port */
private string ready_message;
private CultureInfo culture = new CultureInfo("en-us");
internal static Encoding ivyEncoding = Encoding.GetEncoding("iso-8859-15"); // ASCII 8 bits iso-8859-2
// for synchronous event
#if (PocketPC)
private System.Windows.Forms.ContainerControl parentControl;
#else
private readonly SynchronizationContext syncContext;
#endif
///
/// Initializes a new instance of the Ivy Bus.
/// Readies the structures for the software bus connexion.
///
public Ivy()
{
this.ipv6 = false;
clients = new List();
bindings = new Dictionary();
client_bindings = new Dictionary();
sent_messageFilter = new Collection();
#if (!PocketPC)
syncContext = SynchronizationContext.Current;
if (syncContext == null)
synchronous = false;
debugProtocol = Properties.Settings.Default.IvyDebug;
protocolVersion = Properties.Settings.Default.IvyProtocolVersion;
#endif
// get binding from Attribute IvyBinding
//TODO Autobinding attribute
#if (PocketPC)
if (parentControl != null)
BindAttibute(parentControl);
#endif
Assembly assembly = Assembly.GetCallingAssembly();
if ( assembly != this.GetType().Assembly )
BindAttribute(assembly);
}
///
/// Readies the structures for the software bus connexion.
///
/// This sample shows how to start working with Ivy.
///
/// Ivy bus = new Ivy("Dummy agent","ready");
/// bus.bindMsg("(.*)",myMessageListener);
/// bus.start(null);
///
/// How to send & receive:
/// the Ivy agent A performs b.bindMsg("^Hello (*)",cb);
/// the Ivy agent B performs b2.sendMsg("Hello world");
/// a thread in A will run the callback cb with its second argument set
/// to a array of string, with one single element, "world"
///
///
/// the real work begin in the start() method
///
///
/// The name of your Ivy agent on the software bus
///
/// The hellow message you will send once ready
///
public Ivy(string name, string readyMessage)
: this()
{
appName = name;
this.ready_message = readyMessage;
// get binding from Attribute IvyBinding
//TODO Autobinding attribute
Assembly assembly = Assembly.GetCallingAssembly();
if (assembly != this.GetType().Assembly)
BindAttribute(assembly);
}
internal void AddBinding(int id, IvyClient clnt, IvyBindingBase bindexp)
{
bool change = false;
IvyClientBinding clientbind;
lock (client_bindings)
{
if (client_bindings.ContainsKey(bindexp.Expression))
{
// check if id exits to remove it
clientbind = client_bindings[bindexp.Expression];
foreach (IvyClientBinding clbind in client_bindings.Values)
{
lock (clbind.client_bindind_ids)
{
if (clbind.client_bindind_ids.ContainsKey(clnt))
{
if (clbind.client_bindind_ids[clnt].Contains(id))
{
// remove from old
clbind.client_bindind_ids[clnt].Remove(id);
// add to new later
change = true; //mark to call right handler
break;
}
}
}
}
}
else
{
clientbind = new IvyClientBinding();
clientbind.binding = bindexp;
clientbind.client_bindind_ids = new Dictionary>();
clientbind.client_bindind_ids[clnt] = new List();
client_bindings[bindexp.Expression] = clientbind;
}
}
lock (clientbind.client_bindind_ids)
{
if (!clientbind.client_bindind_ids.ContainsKey(clnt))
{
clientbind.client_bindind_ids[clnt] = new List();
}
clientbind.client_bindind_ids[clnt].Add(id);
}
if (change)
{
OnClientChangeBinding(new IvyEventArgs(clnt, id, bindexp.Expression));
}
else
{
OnClientAddBinding(new IvyEventArgs(clnt, id, bindexp.Expression));
}
}
internal void DelBinding(int id, IvyClient clnt)
{
IvyClientBinding clientbind = null;
lock (client_bindings)
{
foreach (IvyClientBinding clbind in client_bindings.Values)
{
lock (clbind.client_bindind_ids)
{
if (clbind.client_bindind_ids.ContainsKey(clnt))
{
clbind.client_bindind_ids[clnt].Remove(id);
clientbind = clbind;
break;
}
}
}
if (clientbind != null)
{
lock (clientbind.client_bindind_ids)
{
if (clientbind.client_bindind_ids.Count == 0)
{
client_bindings.Remove(clientbind.binding.Expression);
}
}
}
}
if (clientbind != null )
OnClientRemoveBinding(new IvyEventArgs(clnt, id, clientbind.binding.Expression));
}
///
/// Automatic binding on static method of a Type
///
///
public void BindAttribute(Type type)
{
if (type == null) return;
//Get instance of the IvyBindingAttribute.
foreach (MethodInfo m in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic))
{
foreach (IvyBindingAttribute attr in Attribute.GetCustomAttributes(m, typeof(IvyBindingAttribute)))
{
//TODO check paramater type MessageHandler
Debug.WriteLine("IvyBinding '" + attr.GetFormattedExpression(null) + "' to Method " + m.Name);
EventHandler handler;
#if (PocketPC)
//Createdelegate mydlg = new Createdelegate(m, null, EventArgs.Empty);
//bindMsg(attr.GetExpression(null), mydlg);
handler = null;
#else
handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), m);
#endif
BindMsg(attr.GetFormattedExpression(null), handler);
}
}
}
///
/// Autobinding on instance method
///
///
public void BindAttribute(object target)
{
if (target == null) return;
Type type = target.GetType();
//Get instance of the IvyBindingAttribute.
foreach (MethodInfo m in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
{
foreach (IvyBindingAttribute attr in Attribute.GetCustomAttributes(m, typeof(IvyBindingAttribute)))
{
//TODO check paramater type MessageHandler
Ivy.traceProtocol(Resources.Ivy, "BindAttibute " + attr.GetFormattedExpression(target) + "' to Method " + m.Name);
EventHandler handler;
#if (PocketPC)
handler = null; // TODO
#else
handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, m);
#endif
BindMsg(attr.GetFormattedExpression(target), handler);
}
}
}
// Autobinding on IvyBindingAttribute method
///
/// Automatic binding of IvyBindAttribute in an assembly
///
///
public void BindAttribute(Assembly target)
{
if (target != null)
{
foreach (Type typ in target.GetTypes())
{
BindAttribute(typ);
}
}
}
///
/// connects the Ivy bus to a domain or list of domains.
///
/// a domain of the form 10.0.0:1234, it is similar to the
/// netmask without the trailing .255. This will determine the meeting point
/// of the different applications. Right now, this is done with an UDP
/// broadcast. Beware of routing problems ! You can also use a comma
/// separated list of domains.
///
/// One thread (IvyWatcher) for each traffic rendezvous (either UDP broadcast or TCP Multicast).
/// One thread (serverThread/Ivy) to accept incoming connexions on server socket.
/// a thread for each IvyClient when the connexion has been done.
///
public void Start(string domainBus)
{
if (string.IsNullOrEmpty(AppName))
{
AppName = "LazyDevelopper";
}
// check for ReadyMessage empty
if (string.IsNullOrEmpty(ready_message))
{
if ( string.IsNullOrEmpty ( Properties.Settings.Default.ReadyMessage ) )
ready_message = AppName + " READY";
else
ready_message = Properties.Settings.Default.ReadyMessage;
}
domainBus = GetDomain(domainBus);
// check for IPV6 mode
Domain[] d = parseDomains(domainBus);
IPAddress group = IPAddress.Parse(d[0].Domainaddr);
ipv6 = group.IsIPv6Multicast;
try
{
long seed = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
Random rand = new Random( (int)seed );
app = new MyTcpListener(ipv6 ? IPAddress.IPv6Any :IPAddress.Any, 0);
app.Start(); // should be started before getting port
applicationHost = LocalIP;
applicationPort = ((IPEndPoint)app.LocalEndpoint).Port;
applicationUniqueId = string.Format(culture,"{0}:{1}:{2}",
rand.Next(),
seed,
//applicationHost.ToString().Replace(".", ""),
applicationPort);
}
catch (IOException e)
{
throw new IvyException("can't open TCP service socket " + e);
}
Ivy.traceProtocol("BindAttibute", "Ivy protocol: " + ProtocolVersion + " TCP service open on port " + applicationPort);
watchers = new List();
// readies the rendezvous : an IvyWatcher (thread) per domain bus
for (int index = 0; index < d.Length; index++)
{
IvyWatcher watcher = new IvyWatcher(this, d[index].Domainaddr, d[index].Port, ipv6 );
watchers.Add(watcher);
}
serverThread = new Thread(new ThreadStart(this.TcpListener));
serverThread.Name = "Ivy Tcp Server Thread";
stopped = false;
tcplistener_running = new AutoResetEvent(false);
serverThread.Start();
// Wait for readyness
tcplistener_running.WaitOne();
// sends the broadcasts and listen to incoming connexions
for (int i = 0; i < watchers.Count; i++)
{
watchers[i].start();
}
}
internal void SortClients()
{
lock (clients)
{
//clients.Sort(new IvyClient.IvyClientPriority());
clients.Sort();
}
}
internal static Domain[] parseDomains(string domainbus)
{
string[] st = domainbus.Split(',');
Domain[] d = new Domain[st.Length];
for (int i = 0; i < st.Length; i++)
{
d[i] = new Domain(st[i]);
}
return d;
}
///
/// disconnects from the Ivy bus
///
public void Stop()
{
if (stopped)
return;
lock (app)
{
stopped = true;
Ivy.traceProtocol(Resources.Ivy, "beginning stopping the bus");
try
{
// stopping the serverThread
if (serverThread != null)
{
app.Stop();
// Wait for Thread to end.
bool term = serverThread.Join(10000);
if (!term && (serverThread != null)) serverThread.Abort();
serverThread = null;
}
// The serverThread might be stopped even before having been created
if (app != null)
app.Stop();
// stopping the IvyWatchers
if (watchers != null)
for (int i = 0; i < watchers.Count; i++)
{
((IvyWatcher)watchers[i]).stop();
}
// stopping the remaining IvyClients
// copy the values in temporary variable to eliminate Thread modifying collection
if (clients.Count != 0)
{
IvyClient[] copyClient;
lock (clients)
{
copyClient = new IvyClient[clients.Count];
clients.CopyTo(copyClient);
}
foreach (IvyClient client in copyClient)
{
client.close(true); // will notify the reading thread
//removeClient(client); already donne in the thread
}
}
}
catch (IOException e)
{
Ivy.traceError(Resources.Ivy, "IOexception Stop " + e.Message);
}
Ivy.traceProtocol(Resources.Ivy, "the bus should have stopped so far");
}
}
///
/// join the main thread of Ivy ( ie wait until stopped )
///
///
/// Performs a join on the Principal Thread.
///
/// Number of millisecondes to wait before the Thread Stop.
///
/// true if the thread stopped; false if it did not stop after the expiry of the period specified by the parameter millisecondsTimeout
///
public bool Join(int millisecondsTimeout)
{
return serverThread.Join(millisecondsTimeout);
}
///
/// Block the calling Thread until Ivy Stop
///
///
/// Performs a join on the Principal Thread.
///
public void MainLoop()
{
this.Join(Timeout.Infinite);
}
///
/// Send a formated message to someone on the bus
///
///
/// Performs a pattern matching according to everyone's regexps, and sends
/// the results to the relevant ivy agents.
/// There is one thread for each client connected, we could also
/// create another thread each time we send a message.
///
/// A string message format to build the message
/// args used in message format
///
/// the number of messages actually sent
///
public int SendMsg(string format, params object[] args)
{
// send to everybody
string msg = string.Format(culture, format, args);
int count = 0;
// hash message in V4 protocol only
if (ProtocolVersion == 4)
IvyBindingSimple.Prepare(msg);
// an alternate implementation would one sender thread per client
// instead of one for all the clients. It might be a performance issue
// Tentative of using BeginInvoke EndInvoke is slower
lock (client_bindings)
{
foreach (IvyClientBinding clbind in client_bindings.Values)
{
string[] matches = clbind.binding.Match(msg);
if (matches != null)
{
lock (clbind.client_bindind_ids)
{
foreach (KeyValuePair> assoc in clbind.client_bindind_ids)
{
foreach (ushort id in assoc.Value)
{
count += assoc.Key.sendMsg(id, matches);
}
}
}
}
}
}
return count;
}
internal int SendMsgToClient(IvyClient clnt, string format, params object[] args)
{
string msg = string.Format(culture, format, args);
int count = 0;
// hash message in V4 protocol only
if (ProtocolVersion == 4)
IvyBindingSimple.Prepare(msg);
// an alternate implementation would one sender thread per client
// instead of one for all the clients. It might be a performance issue
lock (client_bindings)
{
foreach (IvyClientBinding clbind in client_bindings.Values)
{
string[] matches = clbind.binding.Match(msg);
if (matches != null)
{
lock (clbind.client_bindind_ids)
{
if (clbind.client_bindind_ids.ContainsKey(clnt))
{
List ids = clbind.client_bindind_ids[clnt];
foreach (ushort id in ids)
{
count += clnt.sendMsg(id, matches);
}
}
}
}
}
}
return count;
}
///
/// Make a binding to a message using regular expresssion
///
///
///
public int BindMsg(IvyApplicationBinding newBinding)
{
newBinding.Key = Interlocked.Increment(ref serial);
lock (bindings) bindings.Add(newBinding.Key, newBinding);
// notifies the other clients this new regexp
lock (clients)
{
foreach (IvyClient c in clients)
{
c.stream.TokenAddBinding(newBinding.Binding, newBinding.Key, newBinding.FormattedExpression);
}
}
return newBinding.Key;
}
///
/// Subscribes to a regular expression.
///
/// a regular expression, groups are done with parenthesis
/// any objects implementing the EventHandler
/// The args.
/// the id of the regular expression
///
/// The callback will be executed with
/// the saved parameters of the regexp as arguments when a message will sent
/// by another agent. A program doesn't receive its own messages.
/// the expression cant containt some dynamics argument replacement of the form %number[:objectFormat]%
/// the string will be replaced by arg[number].ToString(objectFormat)
///
//
public int BindMsg(string expression, EventHandler callback, params object[] args)
{
// creates a new binding (regexp,callback)
IvyApplicationBinding newbind;
newbind = new IvyApplicationBinding(BindingType.RegularExpression, expression, args);
newbind.Callback += callback;
return BindMsg(newbind);
}
///
/// unsubscribes a regular expression
///
/// the id of the regular expression, returned when it was bound
public void UnbindMsg(int id)
{
if (!bindings.ContainsKey(id))
{
throw new IvyException("client wants to remove an unexistant regexp " + id);
}
lock (clients)
{
foreach (IvyClient c in clients)
{
try
{
if ( c.stream != null )
c.stream.TokenDelBinding(id);
}
catch (IOException)
{
// client closed ignore it
}
}
}
lock (bindings) bindings.Remove(id);
}
///
/// Change a binding already registred
///
///
///
///
public int ChangeMsg(int id, string expression)
{
IvyApplicationBinding newbind;
if (!bindings.ContainsKey(id))
{
throw new IvyException("client wants to change an unexistant regexp " + id);
}
// change binding (regexp,callback)
lock (bindings)
{
newbind = bindings[id];
newbind.Expression = expression;
}
// notifies the other clients this new regexp
lock (clients)
{
foreach (IvyClient c in clients)
{
c.stream.TokenAddBinding(newbind.Binding, newbind.Key, newbind.FormattedExpression);
}
}
return id;
}
///
/// unsubscribes a regular expression
///
/// the string for the regular expression
///
/// a boolean, true if the regexp existed, false otherwise or
/// whenever an exception occured during unbinding
///
public bool UnbindMsg(string re)
{
foreach (IvyApplicationBinding bind in bindings.Values)
{
if (bind.Expression == re)
{
try
{
UnbindMsg(bind.Key);
}
catch (IvyException)
{
return false;
}
return true;
}
}
return false;
}
///
/// Subscribes to a simple expression ( msg ar1 arg2 arg3 etc).
///
///
/// The callback will be executed with
/// the saved parameters of the regexp as arguments when a message will sent
/// by another agent. A program doesn't receive its own messages.
///
///
/// a regular expression, groups are done with parenthesis
/// any objects implementing the EventHandler
///
/// the id of the regular expression
///
public int BindSimpleMsg(string expression, EventHandler callback, params object[] args)
{
// creates a new binding (regexp,callback)
IvyApplicationBinding newbind;
newbind = new IvyApplicationBinding(BindingType.Simple, expression, args);
newbind.Callback += callback;
return BindMsg(newbind);
}
///
/// Dies the specified target.
///
/// The target.
/// The reason message.
///
public int Die(string target, string message)
{
ReadOnlyCollection v = GetClientsByName(target);
foreach (IvyClient clnt in v)
clnt.stream.TokenDie(0, message);
return v.Count;
}
///
/// Pings the specified target.
///
/// The target.
/// The message.
///
public int Ping(string target, string message)
{
int id = (int)DateTime.Now.Ticks;
ReadOnlyCollection v = GetClientsByName(target);
foreach (IvyClient clnt in v)
{
clnt.stream.TokenPing(id, message);
}
return v.Count;
}
internal virtual void FireEvent(EventHandler ev, IvyEventArgs e)
{
if (ev != null)
{
#if (PocketPC)
if (parentControl != null)
{
parentControl.Invoke(ev, this, e);
}
#else
if (synchronous)
{
SendOrPostCallback update = delegate(object state)
{
IvyEventArgs args = (IvyEventArgs)state;
ev(this, args);
};
syncContext.Post(update, e);
}
#endif
else
ev(this, e);
}
}
internal virtual void OnDirectMessage(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = DirectMessageReceived;
FireEvent(temp,e);
}
internal virtual void OnClientConnected(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = ClientConnected;
FireEvent(temp,e);
}
internal virtual void OnClientDisconnected(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = ClientDisconnected;
try
{
FireEvent(temp,e);
}
#if (!PocketPC)
catch (SynchronizationLockException)
{
// protect terminaison
}
#endif
catch (ObjectDisposedException)
{
// protect terminaison
}
}
internal virtual void OnClientAddBinding(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = BindingAdd;
FireEvent(temp,e);
}
internal virtual void OnClientRemoveBinding(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = BindingRemove;
FireEvent(temp,e);
}
internal virtual void OnClientChangeBinding(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = BindingChange;
FireEvent(temp, e);
}
internal virtual void OnClientFilterBinding(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = BindingFilter;
FireEvent(temp,e);
}
internal virtual void OnError(IvyEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = ErrorMessage;
FireEvent(temp, e);
}
#if (PocketPC)
internal virtual void OnDie(IvyDieEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = DieReceived;
if (temp != null)
{
if (parentControl != null)
{
parentControl.Invoke(temp, this, e);
}
else
temp(this, e);
}
}
#else
internal virtual void OnDie(IvyDieEventArgs e)
{
// Copy to a temporary variable to be thread-safe.
EventHandler temp = DieReceived;
if (temp != null)
{
if (syncContext != null)
{
SendOrPostCallback update = delegate(object state)
{
IvyDieEventArgs args = (IvyDieEventArgs)state;
temp(this, args);
};
syncContext.Post(update, e);
}
else
temp(this, e);
}
}
#endif
internal void OnMessage(IvyMessageEventArgs e)
{
if (!bindings.ContainsKey( e.Id))
{
throw new IvyException("(callCallback) Not regexp matching id " + e.Id);
}
IvyApplicationBinding bind = bindings[e.Id];
#if(PocketPC)
bind.Firevent(parentControl, e);
#else
bind.Firevent(syncContext,synchronous, e);
#endif
}
/*
* removes a client from the list
*/
internal void removeClient(IvyClient c)
{
lock (clients)
{
clients.Remove(c);
}
List purge_bind = new List();
lock (client_bindings)
{
foreach (IvyClientBinding clbind in client_bindings.Values)
{
lock (clbind.client_bindind_ids)
{
clbind.client_bindind_ids.Remove(c);
if ( clbind.client_bindind_ids.Count == 0 )
purge_bind.Add( clbind.binding.Expression);
}
}
// remove Binding if no client
foreach ( string expr in purge_bind )
{
client_bindings.Remove(expr);
}
}
}
///
/// gives a list of IvyClient(s) with the name given in parameter
///
/// The name of the Ivy agent you're looking for
///
public ReadOnlyCollection GetClientsByName(string name)
{
List v = new List();
foreach (IvyClient ic in clients)
{
if (ic.ApplicationName.CompareTo(name) == 0)
v.Add(ic);
}
return new ReadOnlyCollection(v);
}
/////////////////////////////////////////////////////////////////:
//
// Protected methods
//
/////////////////////////////////////////////////////////////////:
internal void addClient(IvyClient client)
{
if (stopped)
return;
lock (clients)
{
clients.Add(client);
}
}
///
/// return the first non loopback adress
/// on a one network interface machine it will be my IP adresse
///
public static IPAddress LocalIP
{
get
{
IPAddress returnaddr = null;
//TODO remove ALL reverse DNS search !!!!
IPHostEntry ip = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress addr in ip.AddressList)
{
returnaddr = addr;
if (IPAddress.IsLoopback(addr)) continue;
break;
}
return returnaddr;
}
}
///
/// Get the FDQN Domain from a string
///
///
///
public static string GetDomain(string domainBus)
{
#if (PocketPC) // TODO integrate in normal version
if (domainbus == null || domainbus.Length == 0)
{
IPAddress addr = GetLocalIP();
//TODO Find Braodcast addr from IP;
byte[] bytes = addr.GetAddressBytes();
if (IPAddress.IsLoopback(addr))
{
// 127.255.255.255
bytes[1] = 255;
bytes[2] = 255;
bytes[3] = 255;
}
else
{
// else assume class C network
// TODO find exact netmask
bytes[3] = 255;
}
IPAddress bcast = new IPAddress(bytes);
domainbus = bcast + ":" + Domain.getPort(domainbus);
}
#else
if (domainBus == null || domainBus.Length == 0 )
{
domainBus = Environment.GetEnvironmentVariable("IVYBUS");
}
if (domainBus == null || domainBus.Length == 0)
{
domainBus = Properties.Settings.Default.IvyBus;
}
#endif
if (domainBus == null || domainBus.Length == 0)
domainBus = DefaultDomain;
return domainBus;
}
/// checks the "validity" of a regular expression. //TODO put in IvyBinding
internal bool CheckRegexp(string exp)
{
bool regexp_ok = true;
// Attention Bug
// ClockStop ClockStart & ^Clock(Start|Pause)
// should Stop to the first parent
if ((sent_messageFilter.Count != 0) && exp.StartsWith("^"))
{
regexp_ok = false;
// extract first word from regexp...
string token = Regex.Replace(exp, @"^\^(?[a-zA-Z_0-9-]+).*", @"${token}");
foreach (string exp_class in sent_messageFilter)
{
if (exp_class.StartsWith(token))
return true;
}
}
return regexp_ok;
}
/*
* prevents two clients from connecting to each other at the same time
* there might still be a lingering bug here, that we could avoid with the
* SchizoToken.
*/
internal IvyClient checkConnected(IvyClient clnt)
{
IvyClient sameClnt = null;
if (clnt.AppPort == 0)
{
Console.WriteLine(" client {0} port == 0 ", clnt.appName);
return sameClnt;
}
lock (clients)
{
foreach (IvyClient client in clients)
{
if (clnt == client) continue; // SKIP himself
if (client.sameIvyClient(clnt))
{
sameClnt = client;
break;
}
}
}
return sameClnt;
}
/*
* the service socket thread reader main loop
*/
private void TcpListener()
{
Ivy.traceProtocol(Resources.Ivy, "Ivy service Thread started");
bool running = true;
tcplistener_running.Set();
while (running)
{
try
{
Socket socket = app.AcceptSocket();
// early disconnexion
if (stopped)
break;
// the peer called me
IvyClient client = new IvyClient(this, socket, "Unkown(waiting for name reception)",0);
client.SendBindings(); //TODO in a Thread or wait for the peer to sent his before sending mine
}
catch (IOException e)
{
Ivy.traceError(Resources.Ivy,Resources.IvyIOException + e.Message);
}
catch (SocketException e)
{
Ivy.traceError(Resources.Ivy,Resources.IvySocketException + e.Message);
running = false;
}
}
Ivy.traceProtocol(Resources.Ivy, "Ivy service Thread stopped");
}
[Conditional("DEBUG")]
internal static void traceProtocol(string name, string message)
{
if ( debugProtocol )
Console.WriteLine( "-->{0}<-- {1}", name, message);
}
internal static void traceError(string name, string message)
{
Console.WriteLine("-->{0}<-- {1}", name, message);
}
internal class Domain
{
public virtual string Domainaddr
{
get
{
return domainaddr;
}
}
public virtual int Port
{
get
{
return port;
}
}
private string domainaddr;
private int port;
public Domain(string net):this( getDomain(net),getPort(net))
{
}
public Domain(string domainaddr, int port)
{
this.domainaddr = domainaddr;
this.port = port;
}
public override string ToString()
{
return domainaddr + ":" + port;
}
public static string getDomain(string net)
{
int sep_index = net.LastIndexOf(":");
if (sep_index != -1)
{
net = net.Substring(0, (sep_index) - (0));
}
try
{
if (!net.Contains("::")) // if not IPV6 multicast paste with 255
{
net += ".255.255.255";
Regex exp = new Regex("^(\\d+\\.\\d+\\.\\d+\\.\\d+).*");
net = exp.Replace(net, "$1");
}
}
catch (ArgumentException e)
{
Ivy.traceError(Resources.Ivy,Resources.BadBroadcast + net + "error " + e.Message);
return null;
}
return net;
}
public static int getPort(string net)
{
if (net == null) { return Ivy.DefaultPort; }
int sep_index = net.LastIndexOf(":");
int port = (sep_index == -1) ? Ivy.DefaultPort : Int32.Parse(net.Substring(sep_index + 1));
return port;
}
}
///
/// check Validity of Ivy Bus Domain
///
///
///
public static bool ValidatingDomain(string domain)
{
IPAddress result;
bool valid = false;
string addr = Domain.getDomain(domain);
try
{
int port = Domain.getPort(domain);
if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort) return false;
}
catch (OverflowException e)
{
return false;
}
if (String.IsNullOrEmpty(addr))
{
// address wasnt provided so return false
valid = false;
}
else
{
//use TryParse to see if this is a
//valid ip address. TryParse returns a
//boolean based on the validity of the
//provided address, so assign that value
//to our boolean variable
valid = IPAddress.TryParse(addr, out result);
}
return valid;
}
#region IDisposable Membres
// needed by tcplistener_running
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free other state (managed objects).
if (tcplistener_running != null)
{
tcplistener_running.Close();
tcplistener_running = null;
}
}
// Free your own state (unmanaged objects).
// Set large fields to null.
}
#endregion
}
}