summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoretienne2000-12-27 13:13:42 +0000
committeretienne2000-12-27 13:13:42 +0000
commit08fcc3b3043cdd30ac61c6d2b643a1d9228561dc (patch)
tree0a43adb8cb1e67a04607341d86d7053dab16e01c
parent23abb4b87c7e40ed259dd02f653516f60e55ade4 (diff)
downloadivymon-08fcc3b3043cdd30ac61c6d2b643a1d9228561dc.zip
ivymon-08fcc3b3043cdd30ac61c6d2b643a1d9228561dc.tar.gz
ivymon-08fcc3b3043cdd30ac61c6d2b643a1d9228561dc.tar.bz2
ivymon-08fcc3b3043cdd30ac61c6d2b643a1d9228561dc.tar.xz
Initial revision
-rw-r--r--src/Ivy2.pm1845
-rwxr-xr-xsrc/ivymon755
-rwxr-xr-xsrc/ivymon050
3 files changed, 2650 insertions, 0 deletions
diff --git a/src/Ivy2.pm b/src/Ivy2.pm
new file mode 100644
index 0000000..ebcb4ba
--- /dev/null
+++ b/src/Ivy2.pm
@@ -0,0 +1,1845 @@
+#
+# Ivy, Perl interface
+#
+# Copyright 1997-2000
+# Centre d'Études de la Navigation Aérienne
+#
+# Authors: Alexandre Bustico <bustico@tls.cena.fr>
+# Stéphane Chatty <chatty@tls.cena.fr>
+# Hervé Damiano <damiano@tls.cena.fr>
+#
+# All functions
+#
+# $Id$
+#
+# Please refer to the debian/copyright file for the
+# copyright notice regarding this software
+#
+
+package Ivy2 ;
+
+use Sys::Hostname;
+use IO::Socket;
+use strict ;
+use Time::Gettimeofday ;
+
+
+use vars qw($VERSION);
+$VERSION = '4.3';
+
+#############################################################################
+#### PROTOTYPES #####
+#############################################################################
+sub init ($%); # methode de classe, permet de renseigner
+ # tous les parametres globaux. Ces parametres
+ # seront utilises par new si ils ne sont pas
+ # donnes lors de l'appel de new.
+
+
+sub new ($%); # verifie la validite de tous les parametres,
+ # cree et retourne un objet Ivy. Les parametres
+ # appName, networks, messWhenReady, peuvent
+ # etre donnes, meme si ils ont deja ete
+ # donnes dans init, dans ce cas, ce sont ceux
+ # de new qui prevalent
+
+sub start ($); # debut de l'integration au bus :
+ # - cree la socket d'application, recupere le no
+ # de port
+ # - cree la socket supervision
+ # - envoie le "no de port"
+ # - bind le file descriptor de la socket de
+ # supervision a la fonction getBonjour pour
+ # traiter les bonjours
+ # - bind le fd de connection sur la fonction
+ # getConnections
+ # pour etablir les connections "application"
+
+sub DESTROY ($); # - envoie un BYE et clot les connections
+
+sub bindRegexp ($$$) ; # permet d'associer une regexp avec un callBack
+ # ou d'annuler une precedente association
+
+sub bindDirect ($$$); # permet d'associer un identifiant de msg direct
+ # avec une fonction de callBack, ou de l'annuler
+
+sub sendMsgs ($@) ; # envoie une liste de messages
+sub sendAppNameMsgs ($@) ; # envoie une liste de messages precedes
+ # du nom de l'application
+sub sendDirectMsgs ($$$@); # envoie une liste de messages directs a une appli
+sub sendDieTo ($$); # envoie un <<kill>> a une appli
+sub ping ($$$); # teste qu'une appli soit encore vivante
+sub mainLoop (); # la mainloop locale (sans tk)
+sub stop (); # methode de classe : on delete les bus, mais
+ # on reste dans la mainloop
+sub exit (); # methode de classe : on delete tous les
+ # bus (donc on ferme proprement toutes les
+ # connexions).
+ # Si on est en mainloop locale on sort de la
+ # mainloop, le code qui suit l'appel mainLoop
+ # sera execute.
+ # par contre si on est en mainloop Tk,
+ # il faut en plus detruire la mainwindow
+ # pour sortir de la mainloop;
+sub after ($$;$); # temps en millisecondes, callback
+sub repeat ($$;$); # temps en millisecondes, callback
+sub afterCancel ($;$); # l'id d'un cancel ou d'un repeat
+sub fileEvent ($$;$); # associe un fd a un callback pour la mainloop locale
+
+
+################ PRIVEE ####################################################
+sub _getBonjour ($); # lit le (ou les) bonjour(s) sur le canal de supervision
+ # et se connecte, verifie qu'il ne se reponds pas lui
+ # meme, ni qu'il ne repond pas a un service deja connecte
+
+sub _getConnections ($); # est appele lors d'une demande de connection :
+ # accepte la connection et mets a jour @sendRegList
+ # rajoute le fd du canal aux fd a scruter dans la
+ # boucle d'evenements
+
+sub _getMessages ($$); # est appele lorqu'un message arrive
+
+sub _sendWantedRegexp ($$); # envoie les regexp a l'appli distante
+
+sub _sendLastRegexpToAllreadyConnected ($$) ; # envoie la derniere regexp
+ # pushee dans @recCbList
+ # a toutes les applis deja
+ # connectees
+sub _removeFileDescriptor ($$); # on vire un fd et les structures associees
+sub _sendErrorTo ($$$); #(fd, error) envoie un message d'erreur a un fd
+sub _sendDieTo ($$); #(fd) envoie un message de demande de suicide a un fd
+sub _sendMsgTo ($$$); # (fd, message)
+sub _pong ($$); # (fd)
+sub _tkFileEvent ($$); # associe un fd a un callback pour la mainloop tk
+sub _scanAfter () ; # parse si il faut appeler un callback associe a un after
+sub _myCanRead (); # interface au select
+sub _scanConnStatus ($); # verifie les connections effectuees et
+ # appelle la fonction $statusFunc
+sub _inetAdrByName ($$); # transforme une adresse inet native en chaine
+ # $host:$port
+sub _toBePruned ($$$);
+sub _parseIvyBusParam ($); # prends une adresse de bus de la forme
+ # 143.196.53,DGAC-CENATLS:2010 et
+ # renvoie une liste de deux elements :
+ # un numero de port et une ref sur une
+ # liste d'adresses addr_inet
+
+sub _substituteEscapedChar ($$); #permet de transormer une regexp etendue
+ # 'perl' en regexp de base
+
+#############################################################################
+#### CONSTANTES #####
+#############################################################################
+use constant MSG_FMT => "%d %d\002%s\n";
+
+# par defaut, on diffuse le bonjour en local
+use constant BROADCAST_ADDRS => "127.255.255.255" ;
+use constant BROADCAST_PORT => "2010";
+
+use constant BYE => 0;
+use constant REGEXP => 1;
+use constant MSG => 2;
+use constant ERROR => 3;
+use constant DELREGEXP => 4;
+use constant ENDREGEXP => 5;
+use constant APP_NAME => 6;
+use constant DIRECT_MSG => 7;
+use constant DIE => 8;
+use constant PING => 9;
+use constant PONG => 10;
+use constant IVY_PROTOCOLE_VERSION => 3;
+
+use constant AFTER => 0;
+use constant REPEAT => 1;
+use constant MAX_TIMOUT => 1000;
+
+# pour pouvoir employer les regexps perl. Attention lors de l'utilisation
+# ne pas mettre un \n dans une chaine entre "" car l'\n sera interprete.
+use constant REG_PERLISSISME => ('w' => '[a-zA-Z0-9_]',
+ 'W' => '[^a-zA-Z0-9_]',
+ 's' => '[ ]',
+ 'S' => '[^ ]',
+ 'd' => '[0-9]',
+ 'D' => '[^0-9]',
+ 'n' => '', # Il ne faut pas mettre d'\n :
+ # c'est un delimiteur pour le bus
+ 'e' => '[]') ;
+
+#############################################################################
+#### VARIABLES de CLASSE #####
+#############################################################################
+
+
+# optimisation : si l'on connait les sujets des messages
+# qu'on envoie, cette variable contient une liste de
+# sujets qui doivent matcher les regexps d'abonnement
+# pour que celle ci se soient pas eliminees
+my @topicRegexps;
+
+# les adresses de reseau sur lesquelles ont broadcaste
+# suivies du No de port :
+# exemples : "143.196.1.255,143.196.2.255:2010"
+# "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+# ":2010" <= dans ce cas c'est la valeur
+# de reseau de broadcast par defaut qui est prise : 127.255.255.255
+# c.a.d local a la machine
+my $ivyBus ;
+
+# le nom de l'appli pour le bus
+my $appName ;
+
+# message a envoyer a un canal lorsqu'on
+# a recu le message endregexp.
+my $messWhenReady ;
+
+# fonction de cb appelee lorsque l'appli a recu l'ordre
+# de quitter, on peut dans ce callback fermer
+# proprement les ressources avant de sortir.
+# ps : ne pas fasire d'exit dans le callback,
+# c'est le bus qui s'en charge
+my $onDieFunc;
+
+
+# permet de donner des valeurs successives aux constantes permettant
+# d'acceder aux differents champs de l'objet
+my $constantIndexer =0;
+
+# pointeur sur la fonction permettant d'associer
+# des callbacks a un file desc, (ainsi que de les enlever)
+my $fileEventFunc;
+
+# dans le cas ou l'on soit dans une mainLoop
+# locale, cette var pointe une un objet
+# de type IO::Select;
+my $localLoopSel;
+
+# table d'ass. handle -> callback
+my %localBindByHandle;
+
+# tableau d'ass [AFTER ou REPEAT,
+# timeTotal, deadLine, [callback, arg, arg, ...]]
+my %afterList=();
+
+my $afterId = 0;
+
+# timeout le plus petit pour le select
+my $selectTimout = MAX_TIMOUT;
+
+
+# liste des bus actifs
+my %allBuses = ();
+
+
+#############################################################################
+#### CLEFS DES VARIABLES D'INSTANCE #####
+#### #####
+#### l'objet Ivy sera 'blessed' sur une reference sur un array et non #####
+#### sur une table de hash comme pratique courament de facon a #####
+#### 1/ optimiser au niveau vitesse #####
+#### 2/ avoir des clefs sous forme de symboles (use constant...) #####
+#### et nom des clefs sous forme de chaines de caracteres #####
+#### de facon a eviter des erreurs #####
+#### #####
+#### #####
+#############################################################################
+use constant servPort => $constantIndexer++;
+use constant neededApp => $constantIndexer++;
+use constant statusFunc => $constantIndexer++;
+use constant supSock => $constantIndexer++;
+use constant connSock => $constantIndexer++;
+use constant sockList => $constantIndexer++;
+use constant queueList => $constantIndexer++;
+use constant threadList => $constantIndexer++;
+use constant appliList => $constantIndexer++;
+use constant sendRegList => $constantIndexer++;
+use constant topicRegexps => $constantIndexer++;
+use constant recCbList => $constantIndexer++;
+use constant directCbList => $constantIndexer++;
+use constant cnnxion => $constantIndexer++;
+use constant buffByConn => $constantIndexer++;
+use constant broadcastPort => $constantIndexer++;
+use constant broadcastBuses => $constantIndexer++;
+use constant appName => $constantIndexer++;
+use constant messWhenReady => $constantIndexer++;
+
+#############################################################################
+#### METHODES PUBLIQUES #####
+#############################################################################
+sub init ($%)
+{
+ my ($class, %options) = @_;
+
+ # valeurs par defaut pour le parametre : variable d'environnement
+ # ou valeur cablee, a defaut
+ my $default_ivyBus = defined $ENV{"IVYBUS"} ? $ENV{"IVYBUS"} : "";
+
+ my %defaultOptions = ( #PARAMETRES OBLIGATOIRES
+ -loopMode => undef,
+ # TK ou LOCAL
+
+ -appName => undef,
+ # nom de l'appli
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+
+ # les adresses de reseau sur lesquelles ont broadcaste
+ # suivies du No de port :
+ # exemples : "143.196.1.255,143.196.2.255:2010"
+ # "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+ # ":2010" <= dans ce cas c'est la valeur
+ # de reseau de broadcast par defaut qui est prise :
+ # 127.255.255.255 c.a.d local a la machine
+ -ivyBus => $default_ivyBus,
+
+ -messWhenReady => "_APP NAME READY",
+ # message de synchro a envoyer quand pret
+
+ -onDieFunc => [sub {}],
+ # fonction de cb appelee lorsque l'appli a recu l'ordre
+ # de quitter, on peut dans ce callback fermer
+ # proprement les ressources avant de sortir.
+ # ps : ne pas fasire d'exit dans le callback,
+ # c'est le bus qui s'en charge
+
+ -pruneRegexp => [],
+ # optimisation : si l'on connait les sujets des messages
+ # qu'on envoie, on fournit la liste des sujets
+ # et les regexps qui ne matchent pas
+ # ces sujets sont eliminees.
+ ) ;
+
+
+ foreach my $opt (keys %defaultOptions) {
+ # si un parametre n'a pas ete defini
+ next if defined $options{$opt} ;
+ # est-il facultatif
+ if (defined $defaultOptions{$opt}) {
+ $options{$opt} = $defaultOptions{$opt} ;
+ }
+ else {
+ # parametre obligatoire
+ die "ERREUR Ivy::init vous devez specifier ".
+ "l'option $opt\n";
+ }
+ }
+
+ foreach my $opt (keys %options) {
+ die "ERREUR Ivy::init option $opt inconnue\n" unless
+ exists ($defaultOptions{$opt});
+ }
+
+ my $loopMode = $options{-loopMode};
+ $ivyBus = $options{-ivyBus} ne "" ? $options{-ivyBus} : undef;
+ $appName = $options{-appName} ;
+ $messWhenReady =
+ $options{-messWhenReady} eq "_APP NAME READY" ?
+ "$appName READY" : $options{-messWhenReady};
+
+ $onDieFunc = $options{-onDieFunc} ;
+ @topicRegexps = @{$options{-pruneRegexp}};
+
+ if ($loopMode =~ /local/i) {
+ # mode boucle d'evenement locale
+ use IO::Select;
+ $fileEventFunc = \&fileEvent ;
+ $localLoopSel = IO::Select->new ();
+ } elsif ($loopMode =~ /tk/i) {
+ # mode boucle d'evenement de TK
+ $fileEventFunc = \&_tkFileEvent ;
+ } else {
+ die "l'argument \"mainloop mode\" doit etre TK ou LOCAL\n";
+ }
+
+ $SIG{'PIPE'} = 'IGNORE' ;
+}
+
+############# METHODE DE CLASSE NEW
+sub new ($%)
+{
+ my ($class, %options) = @_;
+ my $self = [];
+ $#{$self} = $constantIndexer; # on predimensionne le tableau
+ bless ($self, $class);
+
+ # on verifie que la methode de classe init ait ete appelee
+ unless ((defined $appName) && ($appName ne '')) {
+ die "ERREUR Ivy::new vous devez initialiser le module via Ivy->init ()";
+ }
+
+ # No de port tcp du serveur
+ $self->[servPort] = '';
+
+ # liste des applis necessaires a l'appli locale
+ $self->[neededApp] = [];
+
+ # callback prenant en param 2 refs sur des listes :
+ # [applis presentes, appli absentes]
+ # cette fonction est appelee :
+ # - tout les pollingTime tant que toutes les applis
+ # ne sont pas presentes
+ # - des que toutes les applis sont presentes
+ # - lorsqu'une appli se deconnecte
+ $self->[statusFunc] = '';
+
+ # callback prenant en param 1 refs sur une liste :
+ # [ref sur fonction, parametres]
+
+ # socket de supervision en lecture/ecriture
+ $self->[supSock] = '';
+
+ # socket de connexion tcp
+ $self->[connSock] = '';
+
+ # tab ass : nom du fd => fd
+ $self->[sockList] = {};
+
+ # tab ass : nom de l'appli => fd
+ $self->[appliList] = {};
+
+ # tableau ass de liste du type
+ # sockId => [fonction, fonction, ...]
+ # pour savoir quoi envoyer a qui
+ # les fonctions anonymes sont compilees
+ # dynamiquement a la reception des messages REGEXP
+ # et filtrent les mess a envoyer et les envoient
+ # au besoin
+ $self->[sendRegList] = {};
+
+ # liste des topics qu'on envoie si on
+ # filtre les regexps
+ $self->[topicRegexps] = [];
+
+ # liste de ref sur des couples
+ # (regexp,callBack) les callbacks
+ # sont appeles lors de
+ # la reception de messages en fonction
+ # du numero de regexp.
+ $self->[recCbList] = [];
+
+ # liste de callBack pour les messages directs
+ $self->[directCbList] = [];
+
+ # tableau ass : clef = nom:numero_de port
+ # permet de verifier qu'on ne se connecte pas
+ # sur nous meme et qu'on ne se reconnecte
+ # pas sur un service en cas de bonjours repetes
+ # valeur : nom de l'application
+ $self->[cnnxion] = {};
+
+ # tableau associatif, clef => file desc,
+ # valeur :buffer au cas ou la lacture ne se termine
+ # pas par \n, de maniere a resegmenter les messages
+ $self->[buffByConn] = {};
+
+
+ my %defaultOptions = (
+ -appName => $appName,
+ # nom de l'appli
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+ -messWhenReady => $messWhenReady,
+ # message de synchro a envoyer quand pret
+
+
+ # PARAMETRES FACULTATIFS (avec valeurs par defaut)
+
+ # les adresses de reseau sur lesquelles ont broadcaste
+ # suivies du No de port :
+ # exemples : "143.196.1.255,143.196.2.255:2010"
+ # "DGAC-CENATLS-PII:DGAC-CENATLS:2010"
+ # ":2010" <= dans ce cas c'est la valeur
+ # de reseau de broadcast par defaut qui est prise :
+ # 127.255.255.255 c.a.d local a la machine
+ -ivyBus => $ivyBus,
+
+ -neededApp => [],
+ # liste des appplis necessaires
+
+ -statusFunc => sub {},
+ # fonction de callBack qui sera appelee tant que
+ # toutes les applis necessaires ne sont pas presentes,
+ # et des que toutes les applis necessaires sont
+ # presentes, et si une appli necessaire se deconnecte
+ # les trois parametres passes sont :
+ # [liste des applis presentes],
+ # [liste des applis absentes],
+ # [table de hash, clefs = applis presentes,
+ # valeurs = nombre d'applis .
+ # normalement ce nombre devrait etre 1, sinon
+ # ca veut dire que plus d'une appli de meme nom
+ # tourne sur le meme bus : danger !!
+
+ -pruneRegexp => [@topicRegexps],
+ # optimisation : si l'on connait les sujets des messages
+ # qu'on envoie, on fournit la liste des sujets
+ # et les regexps qui ne matchent pas
+ # ces sujets sont eliminees.
+ ) ;
+
+
+ foreach my $opt (keys %defaultOptions) {
+ # si un parametre n'a pas ete defini
+ next if defined $options{$opt} ;
+ # est-il facultatif
+ if (defined $defaultOptions{$opt}) {
+ $options{$opt} = $defaultOptions{$opt} ;
+ } else {
+ # parametre obligatoire
+ die "ERREUR Ivy::new vous devez specifier ".
+ "l'option $opt\n";
+ }
+ }
+
+ foreach my $opt (keys %options) {
+ die "ERREUR Ivy::start option $opt inconnue\n" unless
+ exists ($defaultOptions{$opt});
+ }
+
+
+
+ $self->[appName] = $options{-appName} ;
+ $self->[messWhenReady] = $options{-messWhenReady} ;
+ @{$self->[neededApp]} = @{$options{-neededApp}} ;
+ $self->[statusFunc] = $options{-statusFunc} ;
+ $self->[topicRegexps] = $options{-pruneRegexp} ;
+ $allBuses{$self} = $self;
+
+ ($self->[broadcastPort], $self->[broadcastBuses]) =
+ _parseIvyBusParam ($options{-ivyBus});
+
+
+ return ($self);
+}
+
+############### METHODE IVY DESTROY
+sub DESTROY ($)
+{
+ my $self = shift;
+ return unless exists $allBuses{$self};
+
+ # print ("DBG DESTROY appele sur l'objet $self\n");
+
+ # pour toutes les connections
+ foreach my $fd (values %{$self->[sockList]}) {
+ next unless exists ($self->[queueList]->{$fd});
+
+ foreach my $fd (values %{$self->[sockList]}) {
+ send ($fd, sprintf (MSG_FMT, BYE, 0, ""), 0)
+ or $self->_removeFileDescriptor ($fd);
+ }
+ }
+
+ # on clos la socket de signalisation (UDP)
+ # print "DBG> fermeture de supSock\n";
+ $self->[supSock]->close() if $self->[supSock];
+ delete $allBuses{$self};
+
+ # on clos la socket de connection
+ # print "DBG> fermeture de connSock\n";
+ $self->[connSock]->close() if $self->[connSock];
+ undef (@$self);
+}
+
+############### METHODE DE CLASSE STOP
+sub stop ()
+{
+ foreach my $bus (values %allBuses) {
+ $bus->DESTROY();
+ } # pour toutes les connections
+}
+
+
+############## METHODE DE CLASSE EXIT
+sub exit ()
+{
+ Ivy::stop ();
+ if (defined $localLoopSel) {
+ # boucle locale, on sait faire
+ # printf ("DBG> undefining localLoopSel\n");
+ undef $localLoopSel;
+ }
+ else {
+ Tk::exit ();
+ }
+}
+
+############### PROCEDURE BUS START
+sub start ($)
+{
+ my $self = shift;
+
+ # cree la socket de connexion, recupere le no de port
+ my $connSock = $self->[connSock] = IO::Socket::INET->new(Listen => 128,
+ Proto => 'tcp',
+ Reuse => 1) ;
+ # on memorise tout ca, ce qui evitera par la suite de se
+ # repondre a soi-meme. On le fait sous nos deux noms :
+ # le nom de machine et 'localhost'
+ my $hostAddr = (gethostbyname (hostname()))[4] ;
+ my $localhostAddr = (gethostbyname ('localhost'))[4] ;
+ $self->[cnnxion]->{"$hostAddr:". $connSock->sockport} = "\004";
+ $self->[cnnxion]->{"$localhostAddr:". $connSock->sockport} = "\004";
+
+ # cree la socket de broadcast
+ $self->[supSock] = IO::Socket::INET->new
+ (LocalPort => $self->[broadcastPort],
+ Proto => 'udp',
+ Type => SOCK_DGRAM,
+ Reuse => 1);
+
+ $self->[supSock]->sockopt (SO_BROADCAST, 1);
+
+
+ # et on envoie envoie le bonjour : "no de version no de port"
+ my $bonjourMsg = sprintf ("%d %d\n", IVY_PROTOCOLE_VERSION, $connSock->sockport());
+
+ foreach my $netBroadcastAddr (@{$self->[broadcastBuses]}) {
+ send ($self->[supSock], $bonjourMsg, 0, $netBroadcastAddr) or
+ warn "Ivy::start envoi du bonjour a echoue sur : $!\n";
+ }
+ # callback pour traiter la reception des bonjours
+ &$fileEventFunc ($self->[supSock], [\&_getBonjour, $self]) ;
+
+ # callback pour traiter les demandes de cxion
+ &$fileEventFunc ($self->[connSock], [\&_getConnections, $self]) ;
+}
+
+
+############### PROCEDURE BIND REGEXP
+sub bindRegexp ($$$) {
+ my ($self, $regexp, $cb) = @_;
+
+ # on substitue les meta caracteres des regexps perl : \d, \w, \s, \e
+ # par les classes de caracteres corespondantes de maniere a ce
+ # qu'une appli distante non perl comprenne ces regexp.
+ $regexp =~ s|
+ (
+ (?<!\\) \[ # le premier crochet ouvrant non precede d'un \
+ .*? # ce qu'il y a dans le crochet, en mode frugal
+ (?<!\\) \] # le premier crochet fermant non precede d'un \
+ )
+ |
+ _substituteEscapedChar ('inside', $1)
+ |xge;
+
+ $regexp = _substituteEscapedChar ('outside', $regexp);
+ # print ("DBG regexp = $regexp\n");
+
+ if ($cb) {
+ my $id;
+ # on rajoute le couple $regexp, $cb dans la liste des messages
+ # qu'on prend
+
+ # on commence par tester si on a un id libere dans le tableau
+ for ($id=0; $id <= ($#{$self->[recCbList]}+1); $id++) {
+ last unless (defined $self->[recCbList][$id]) && @{$self->[recCbList][$id]->[1]};
+ }
+ $self->[recCbList][$id] = [$regexp, $cb];
+
+ # on envoie les messages regexps aux processus deja connectes
+ _sendLastRegexpToAllreadyConnected ($self, $id) ;
+ }
+ else {
+
+ # on vire le callback, et on se desabonne de cette regexp
+ for (my $id=0; $id <= $#{$self->[recCbList]}; $id++) {
+
+ next unless (defined $self->[recCbList][$id]) &&
+ @{$self->[recCbList][$id]->[1]};
+
+ if ($self->[recCbList][$id]->[0] eq $regexp) {
+
+ $self->[recCbList][$id]->[1] = [];
+ # on envoie le mesage delregexp
+ foreach my $fd (values %{$self->[sockList]}) {
+ send ($fd, sprintf (MSG_FMT, DELREGEXP, $id), 0)
+ or $self->_removeFileDescriptor ($fd);
+ }
+ }
+ }
+ }
+}
+
+############### METHODE BIND REGEXP
+sub bindDirect ($$$)
+{
+ my ($self, $id, $cb) = @_;
+
+ if ($cb) {
+ # on rajoute la $cb dans la liste des messages
+ # qu'on prend
+ $self->[directCbList][$id] = $cb;
+ } else {
+ # on vire le callback
+ undef $self->[directCbList][$id];
+ }
+}
+
+############### PROCEDURE SEND MSGS
+sub sendMsgs ($@)
+{
+ use attrs qw(locked);
+
+ my ($self, @msgs) = @_;
+ my $total = 0;
+
+ # pour tous les messages
+ foreach my $msg (@msgs) {
+ study ($msg);
+
+ # pour routes les connections
+ foreach my $fd (keys %{$self->[sockList]}) {
+
+ # pour toutes les fonctions de filtrage de regexp
+ foreach my $regexpFunc (@{$self->[sendRegList]{$fd}}) {
+ $total += &{$regexpFunc} (\$msg) if defined $regexpFunc;
+ }
+ }
+ }
+ # print "DBG> sended $total times\n";
+ return $total;
+}
+
+############### PROCEDURE SEND MSGS
+sub sendAppNameMsgs ($@)
+{
+ use attrs qw(locked);
+
+ my ($self, @msgs) = @_;
+ my $total = 0;
+
+ # pour tous les messages
+ foreach (@msgs) {
+ my $msg = "$self->[appName] $_";
+ study ($msg);
+
+ # pour routes les connections
+ foreach my $fd (keys %{$self->[sockList]}) {
+
+ # pour toutes les fonctions de filtrage de regexp
+ foreach my $regexpFunc (@{$self->[sendRegList]{$fd}}) {
+ $total += &{$regexpFunc} (\$msg) if defined $regexpFunc;
+ }
+ }
+ }
+ # print "DBG> sended $total times\n";
+ return $total;
+}
+
+
+
+############### PROCEDURE SEND DIRECT MSGS
+sub sendDirectMsgs ($$$@)
+{
+ my ($self, $to, $id, @msgs) = @_;
+
+ if (defined ($self->[appliList]{$to})) {
+ my @fds = @{$self->[appliList]{$to}};
+ # pour tous les messages
+ foreach my $msg (@msgs) {
+ foreach my $fd (@fds) {
+ send ($fd, sprintf (MSG_FMT, DIRECT_MSG, $id, "$msg"), 0)
+ or $self->_removeFileDescriptor ($fd);
+ }
+ }
+ return 1;
+ } else {
+ warn "Ivy::sendDirectMsgs appli $to inconnue\n";
+ return 0;
+ }
+}
+
+
+############### METHOD SEND DIE TO
+sub sendDieTo ($$)
+{
+ use attrs qw(locked);
+ my ($self, $to) = @_;
+
+ if (defined ($self->[appliList]{$to})) {
+ my @fds = @{$self->[appliList]{$to}};
+
+ warn "Attention : Ivy::sendDieTo gros BUG \@fds est vide \n"
+ if (scalar (@fds) == 0);
+
+ # pour tous les messages
+ foreach my $fd (@fds) {
+ $self->_sendDieTo($fd);
+ }
+ return 1;
+ }
+ else {
+ warn "Ivy::sendDieTo appli $to inconnue\n" if $^W;
+ return 0;
+ }
+}
+
+
+############### METHOD PING
+sub ping ($$$)
+{
+ use attrs qw(locked);
+
+ my ($self, $to, $timeout) = @_;
+
+ if (defined ($self->[appliList]{$to})) {
+
+ my @fds = @{$self->[appliList]{$to}};
+
+ # pour tous les messages
+ foreach my $fd (@fds) {
+ send ($fd, sprintf (MSG_FMT, PING, 0, " "), 0)
+ or $self->_removeFileDescriptor ($fd);
+ }
+ }
+}
+
+############### METHODE MAINLOOP
+sub mainLoop ()
+{
+ die "Erreur Ivy->mainLoop, Ivy doit etre initialise en mode".
+ " loopMode local\n" unless defined $localLoopSel;
+
+ my ($fd, @ready, @allDesc);
+
+ while (defined $localLoopSel) {
+ @ready = IO::Select::can_read ($localLoopSel, $selectTimout) ;
+ _scanAfter () ;
+
+ foreach my $fd (@ready) {
+ if (ref $localBindByHandle{$fd} eq 'CODE') {
+ &{$localBindByHandle{$fd}} ;
+ }
+ else {
+ my ($cb, @arg) = @{$localBindByHandle{$fd}} ;
+ &$cb (@arg)
+ }
+ }
+ }
+}
+
+
+############### METHODE AFTER
+sub after ($$;$)
+{
+ # test du premier argument au cas ou la fonction soit
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq 'Ivy') || ($_[0] eq 'Ivy')) ;
+
+ my ($timeAfter, $cbListRef) = @_;
+ $timeAfter /= 1000;
+ $selectTimout = $timeAfter if $timeAfter < $selectTimout;
+
+ # si la valeur de timout est negative : c'est un after sinon
+ # c'est un repeat
+ $afterList{++$afterId} = [AFTER, $timeAfter,
+ timeofday()+$timeAfter, $cbListRef];
+
+ return ($afterId);
+}
+
+############### METHODE REPEAT
+sub repeat ($$;$)
+{
+ # test du premier argument au cas ou la fonction soit
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+
+ shift if ((ref ($_[0]) eq 'Ivy') || ($_[0] eq 'Ivy')) ;
+ # on passe le temps en secondes pour le select
+ my ($timeAfter, $cbListRef) = @_;
+ $timeAfter /= 1000;
+ $selectTimout = $timeAfter if $timeAfter < $selectTimout;
+
+ $afterList{++$afterId}= [REPEAT, $timeAfter, timeofday()+$timeAfter,
+ $cbListRef];
+ return ($afterId);
+}
+
+############### METHODE AFTER CANCEL
+sub afterCancel ($;$)
+{
+ # test du premier argument au cas ou la fonction soit
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq 'Ivy') || ($_[0] eq 'Ivy')) ;
+
+ my $id = shift;
+
+ if (defined ($id) && defined $afterList{$id}) {
+ if ($afterList{$id}->[1] <= $selectTimout) {
+ delete $afterList{$id} ;
+ # le timout de l'after/repeat etait le plus petit des timout
+ # on cherche donc le plus petit parmi ceux qui restent;
+ $selectTimout = MAX_TIMOUT;
+ foreach my $af (values %afterList) {
+ $selectTimout = $af->[1] if $af->[1] < $selectTimout ;
+ }
+ }
+ else {
+ delete $afterList{$id} ;
+ }
+ }
+}
+
+############### METHODE FILE EVENT
+sub fileEvent ($$;$)
+{
+ # test du premier argument au cas ou la fonction soit
+ # appelee de maniere objet : premier argument = class ou une instance
+ # de classe
+ shift if ((ref ($_[0]) eq 'Ivy') || ($_[0] eq 'Ivy')) ;
+
+ my ($fd, $cb) = @_;
+
+
+ unless (defined $localLoopSel) {
+ die ("Erreur Ivy::fileEvent : Ivy::fileEvent n'est utilisable qu'en ".
+ "mode mainLoop LOCALE\n");
+ }
+
+ if ($cb) {
+ # adding the handler
+ $localBindByHandle{$fd} = $cb;
+ $localLoopSel->add ($fd);
+ } else {
+ # deleting the handler
+ delete $localBindByHandle{$fd};
+ # print ("DBG: Ivy::fileEvent : removing fd from the select\n");
+ $localLoopSel->remove ($fd);
+ }
+}
+
+#############################################################################
+#### METHODES PRIVEES #####
+#############################################################################
+
+
+############### METHODE GET BONJOUR
+sub _getBonjour ($)
+{
+ my $self = shift;
+
+ my $bonjourMsg = '';
+
+ # l'hote distant
+ my $inetAddr = $self->[supSock]->recv ($bonjourMsg, 1024, 0);
+
+ unless (length $inetAddr) {
+ warn "Attention : Ivy::_getBonjour recv error, bonjour non traite\n";
+ return;
+ }
+
+ my $addr = (unpack_sockaddr_in ($inetAddr))[1];
+
+ my $peerName = gethostbyaddr ($addr, AF_INET);
+ print "peerName=$peerName\n";
+ # on force $peerPort a etre vu comme une valeur numerique
+ my ($version, $peerPort) = $bonjourMsg =~ /^(\d+)\s+(\d+)/;
+
+ unless (defined ($version) && defined ($peerPort)) {
+ warn "Attention : Ivy::_getBonjour format du message bonjour incorrect\n".
+ "message = $bonjourMsg\n" ;
+ return;
+ }
+
+ if ($version != IVY_PROTOCOLE_VERSION) {
+ warn "Attention : Ivy::_getBonjour VERSION: demande de connexion de ".
+ "$peerName\n version courrante : " . IVY_PROTOCOLE_VERSION . ", recue : $version\n" ;
+ return;
+ }
+
+ # on verifie qu'on ne se repond pas et qu'on ne
+ # se reconnecte pas a un process deja connecte
+ if (exists ($self->[cnnxion]{"$addr:$peerPort"})) {
+ #print "DBG> : bonjour de $peerName:$peerPort : DEJA CONNECTE\n" ;
+ return ;
+ }
+ else {
+ #print "DBG> : reception de $peerName : bonjour $peerPort\n" ;
+ }
+
+# on verifie que l'adresse fasse partie de l'ensemble de reseau
+ # definis par ivybus
+ my $addrInIvyBus = 0;
+ my @ivyBusAddrList = map ( (unpack_sockaddr_in ($_))[1],
+ @{$self->[broadcastBuses]});
+ # Bon dans cette version on reponds aux bonjour emis par
+ # la machine locale, on ne peut donc pas avoir
+ # une appli qui ne causerait qu'a des machines sur une
+ # autre reseau, si ca embete qqun, qu'il me le dise
+ push (@ivyBusAddrList, pack ("CCCC", 127,255,255,255));
+ push (@ivyBusAddrList, (gethostbyname (hostname()))[4]);
+ foreach my $ivyBusAddr (@ivyBusAddrList) {
+ $addrInIvyBus = 1 unless (grep ($_ != 0, unpack ("CCCC",
+ ($addr & $ivyBusAddr) ^ $addr)));
+ }
+
+ if ($addrInIvyBus == 0) {
+ warn "bonjour de $peerName ignore, ne fait pas partie des ivyBus\n" if $^W;
+ return;
+ }
+
+ # ouverture du canal de communication
+ my $appSock = IO::Socket::INET->new (PeerAddr => $peerName,
+ PeerPort => $peerPort,
+ Proto => 'tcp');
+
+ if ($appSock) {
+ # on cree une entree pour $appSock dans la liste des regexp
+ $self->[cnnxion]{"$addr:$peerPort"} = 1;
+ $self->[sendRegList]{$appSock} = [];
+ $self->[buffByConn]{$appSock} = '';
+ $self->[sockList]{$appSock} = $appSock;
+ &$fileEventFunc ($appSock, [\&_getMessages, $self, $appSock]) ;
+
+ # on balance les regexps qui nous interessent a l'appli distante
+ $self->_sendWantedRegexp ($appSock);
+ }
+ else {
+ warn "Attention Ivy::_getBonjour impossible de se connecter au serveur" .
+ "$peerName:$peerPort\n" ;
+ }
+}
+
+
+############### PROCEDURE GET CONNECTIONS
+sub _getConnections ($)
+{
+ my $self = shift;
+ print "_getConnections\n";
+ my $appSock = $self->[connSock]->accept();
+
+ unless (defined $appSock) {
+ warn "Attention Ivy::_getConnections, \$appSock not defined\n";
+ return;
+ }
+ else {
+ printf "accepting connection from %s:%d\n",
+ (gethostbyaddr ($appSock->peeraddr(),AF_INET))[0],
+ $appSock->peerport() if $^W;
+ }
+
+ # callback pour traiter la reception des messages
+ &$fileEventFunc ($appSock, [\&_getMessages, $self, $appSock]) ;
+
+ # on cree une entree pour $appSock dans la liste des regexp
+ $self->[sendRegList]{$appSock} = [];
+ $self->[buffByConn]{$appSock} = '';
+ $self->[sockList]{$appSock} = $appSock;
+ # on balance les regexps qui nous interessent a l'appli distante
+ $self->_sendWantedRegexp ($appSock);
+}
+
+
+############### METHODE GET MESSAGES
+sub _getMessages ($$)
+{
+ my ($self, $appSock) = @_;
+ my $mess;
+ my $buffer = '';
+ my ($addr, $peerPort, $senderName);
+
+ # on recupere le message
+ recv ($appSock, $buffer, 65536, 0) ;
+ unless (length $buffer) {
+ # message null : broken pipe, ça s'est deconnecte a l'autre bout
+ # on vire ce fd de la boucle d'evenements
+ # print ("DBG : _getMessages, recv err, calling removeFileDesc.\n");
+ # Bon la il faudra un jour clarifier ce bordel, lister toutes
+ # les facons dont un couple d'applis connectee peuevent sortir et
+ # eviter les dead lock qui doivent subsister.
+ if (defined ($localLoopSel)) {
+ $self->_removeFileDescriptor ($appSock);
+ }
+ else {
+ $self->_removeFileDescriptor ($appSock);
+ }
+ return;
+ }
+
+ if (length ($self->[buffByConn]{$appSock})) {
+ $buffer = $self->[buffByConn]{$appSock} . $buffer ;
+ $self->[buffByConn]{$appSock} = '';
+ }
+ my @messages = split ('\n', $buffer) ;
+ $self->[buffByConn]{$appSock} = pop (@messages) unless
+ ($buffer =~ /\n$/) ;
+
+ # if (defined $appSock->peername) {
+ $addr = $appSock->peeraddr();
+ $peerPort = $appSock->peerport() ;
+ $senderName = $self->[cnnxion]{"$addr:$peerPort"} ;
+ $senderName = "NONAME" unless $senderName;
+
+ foreach my $mess (@messages) {
+ # print "DBG>mess from $senderName *$mess*\n";
+
+ # on recupere les 3 champs : le type, le numero de regexp, les valeurs
+ my ($type, $id, $valeurs) = $mess =~ /^(\d+)
+ \s+
+ (\d+)
+ \002
+ (.*)/x ;
+
+ # si ca a chie on rale
+ (warn "Attention Ivy::_getMessages malformated message $mess\n" and return) unless defined $type ;
+
+ # sinon on fait en fonction du type de message
+ if ($type == MSG) { # M S G
+ # on recupere le couple call back, regexp correspondant
+ # a l'identifiant et on appelle la fonction avec les parametres
+ # traites par la regexp
+ if (my @cb = @{$self->[recCbList][$id]->[1]}) {
+ my $cb = shift @cb;
+ my $refcb = ref($cb);
+ if ($refcb ne 'CODE') {
+ my $method = shift @cb;
+ # on split sur ETX
+ $cb->$method($senderName, @cb, split ("\003", $valeurs)) ;
+ }
+ else {
+ &$cb ($senderName, @cb, split ("\003", $valeurs)) ;
+ }
+ }
+ else {
+ #_sendErrorTo ($appSock, "REEGXP ID $id inconnue");
+ warn ("Attention Ivy::_getMessages reception d'un message ".
+ "MSG : id $id inconnu de $senderName :\n«$mess»");
+ }
+ }
+ elsif ($type == BYE) {
+ #print "reception d'un bye\n";
+ $self->_removeFileDescriptor ($appSock); # B Y E
+ }
+ elsif ($type == REGEXP) { # R E G E X P
+ # on ajoute une fonction traitant la regexp et envoyant le
+ # message sur le bon fd dans la liste des fonctions de filtrage
+ # ca permet de compiler les regexp avec 'once' donc une
+ # fois pour toute, et ainsi optimiser la vitesse de
+ # filtrage des messages a envoyer
+ next if $self->_toBePruned ($senderName, $valeurs);
+ unless (defined $self->[sendRegList]{$appSock}->[$id]) {
+ # si l'id de regexp n'etait pas utilisee c'est tout bon
+ # on affecte la nouvelle regexp a un id
+ $self->[sendRegList]{$appSock}->[$id] = eval <<'_EOL_';
+ sub {
+ use strict;
+ if (my @args = ${$_[0]} =~ /($valeurs)/o) {
+ shift @args;
+ $args[$#args] .= "\003" if @args;
+ send ($appSock, sprintf (MSG_FMT,
+ MSG, $id, join ("\003",@args)), 0)
+ or $self->_removeFileDescriptor ($appSock) ;
+ #print join (' ', "DBG > J'envoie MSG", $id, @args, "\n");
+ return 1;
+ }
+ };
+_EOL_
+ }
+ else {
+ # l'id de la regexp etait deja utilise,
+ # et n'a pas ete libere par un message delregexp,
+ # on renvoie donc un message d'erreur
+ $self->_sendErrorTo($appSock, "ID $id deja utilisee");
+ }
+ }
+ elsif ($type == ERROR) { # E R R O R
+ warn ("Attention Ivy::_getMessages ERREUR recue de ".
+ "$senderName : «$valeurs»\n");
+ }
+ elsif ($type == DELREGEXP) { # D E L R E G E X P
+ # on vire la regexp des regexps vefifier
+ $self->[sendRegList]{$appSock}->[$id] = undef ;
+ }
+ elsif ($type == ENDREGEXP) { # E N D R E G E X P
+ # on envoie le message ready uniquement a celui qui nous
+ # a envoye le message endregexp
+ $self->_sendMsgTo ($appSock, $self->[messWhenReady]);
+
+ # on passe de l'etat Connecte a l'etat Ready
+ $self->[cnnxion]{"$addr:$peerPort"} =~ s/^\004//;
+ $senderName = $self->[cnnxion]{"$addr:$peerPort"};
+
+ unless (exists $self->[appliList]{$senderName}) {
+ $self->[appliList]{$senderName} = [$appSock];
+ }
+ else {
+ push @{$self->[appliList]{$senderName}}, $appSock;
+ }
+
+ $self->_scanConnStatus ();
+ }
+ elsif ($type == APP_NAME) {
+ # etat Connecte
+ if (($self->[appName] eq $valeurs) && $^W) {
+ warn "\033[1mATTENTION : Ivy::_getMessages une instance de ".
+ "$self->[appName] existe deja\033[m\n" ;
+ }
+
+ $senderName = $valeurs;
+ $self->[cnnxion]{"$addr:$peerPort"} = "\004$valeurs";
+ }
+ elsif ($type == DIRECT_MSG) {
+
+ if (defined $self->[directCbList][$id]) {
+ my @cb = @{$self->[directCbList][$id]};
+ my $cb = shift @cb;
+ my $refcb = ref($cb);
+ if ($refcb ne 'CODE') {
+ my $method = shift @cb;
+ $cb->$method(@cb, $valeurs);
+ }
+ else {
+ &$cb (@cb, $valeurs);
+ }
+ }
+ else {
+ $self->_sendErrorTo ($appSock, "DIRECT ID $id inconnue");
+ warn "Attention Ivy::_getMessages reception d'un message ".
+ "DIRECT d'id $id inconnue de $senderName :\n«$mess»";
+ }
+ } elsif ($type == DIE) {
+ # il faut quitter
+ # on commence par appeler la callback de fin
+ my @cb = @{$onDieFunc};
+ my $cb = shift @cb;
+ my $refcb = ref($cb);
+ if ($refcb ne 'CODE') {
+ my $method = shift @cb;
+ $cb->$method(@cb);
+ }
+ else {
+ &$cb (@cb);
+ }
+ # on avertit les autres qu'on se barre
+ my $adr = $self->_inetAdrByName ($senderName) ;
+ warn "Attention Ivy::_getMessages reception d'un ordre " .
+ "de suicide de $senderName ($adr) ... exiting\n" if $^W;
+ # adios
+ Ivy::exit ();
+
+ }
+ elsif ($type == PING) {
+ # si on recois un ping, on envoie un pong
+ $self->_pong ($appSock);
+ }
+ elsif ($type == PONG) {
+ return PONG;
+ }
+ else {
+ _$self->sendErrorTo ($appSock, "TYPE DE MESS $type inconnu");
+ warn ("reception d'un message de type $type inconnu de " .
+ "$senderName :\n«$mess»");
+ }
+ }
+ return 0;
+ }
+
+############### METHODE SEND WANTED REGEXP
+sub _sendWantedRegexp ($$)
+{
+ my ($self, $appSock) = @_;
+
+ # on envoie le message "Nom appli"
+ send ($appSock, sprintf (MSG_FMT, APP_NAME, 0, $self->[appName]), 0)
+ or $self->_removeFileDescriptor ($appSock) ;
+
+ # on envoie les regexps
+ for (my $id = 0; $id <= $#{$self->[recCbList]}; $id++) {
+ next unless defined $self->[recCbList][$id]->[1]->[0];
+
+ send ($appSock,
+ sprintf (MSG_FMT, REGEXP, $id, $self->[recCbList][$id]->[0]),
+ 0) or $self->_removeFileDescriptor ($appSock) ;
+ # print sprintf ("DBG > %s %d %s\n",
+ # 'REGEXP', $id, $self->[recCbList][$id]->[0]);
+ }
+ # on envoie le message de fin d'envoi de regexps
+ send ($appSock, sprintf (MSG_FMT, ENDREGEXP, 0, ""), 0)
+ or $self->_removeFileDescriptor ($appSock) ;
+}
+
+############### METHODE SEND LAST REGEXP TO ALLREADY CONNECTED
+sub _sendLastRegexpToAllreadyConnected ($$)
+{
+ my ($self, $id) = @_;
+
+ foreach my $fd (values %{$self->[sockList]}) {
+ send ($fd, sprintf (MSG_FMT, REGEXP, $id, $self->[recCbList][$id]->[0]),
+ 0) or $self->_removeFileDescriptor ($fd) ;
+ }
+}
+
+############### METHODE INET ADR BY NAME
+sub _inetAdrByName ($$) {
+
+ my ($self, $appName) = @_;
+
+ my $addrInet = (grep ($self->[cnnxion]{$_} eq $appName,
+ keys %{$self->[cnnxion]}))[0];
+
+ return ("unknow") unless defined $addrInet;
+ my ($addr,$port) = $addrInet =~ /(.{4}):(.*)/;
+
+ my $host = (gethostbyaddr ($addr, AF_INET))[0] ;
+ return "$host:$port";
+}
+
+
+############### PROCEDURE REMOVE FILE DESCRIPTOR
+sub _removeFileDescriptor ($$)
+{
+ my ($self, $fd) = @_;
+
+ my $diedAppName;
+
+ # on s'est deja occupe de lui
+ return unless exists $self->[sockList]->{$fd};
+ # printf ("DBG> _removeFileDescriptor IN thread %s\n", ${Thread->self});
+
+ # on efface les structures de donnees associees au fd
+ # on vire ce fd des fd a scruter dans la bcle d'evenements
+ # uniquement si on est dans le thread principal
+ # sinon le select merde salement sur ce coup
+ &$fileEventFunc ($fd, '') ;
+ delete $self->[sendRegList]{$fd};
+ delete $self->[sockList]{$fd};
+ delete $self->[buffByConn]->{$fd};
+
+ $fd->close();
+
+ EXT_LOOP:
+ foreach my $name (keys %{$self->[appliList]}) {
+ foreach my $fdp (@{$self->[appliList]{$name}}) {
+ if ($fd eq $fdp) {
+ $diedAppName = $name;
+ @{$self->[appliList]{$name}} =
+ grep ($_ ne $fdp, @{$self->[appliList]{$name}});
+ if (scalar (@{$self->[appliList]{$name}}) == 0) {
+ delete $self->[appliList]->{$name}
+ }
+ last EXT_LOOP;
+ }
+ }
+ }
+
+ unless (defined $diedAppName) {
+ warn "Ivy::__removeFileDescriptor : deconnection de NONAME\n" if $^W;
+ return;
+ }
+
+ my $addrInet = (grep ($self->[cnnxion]{$_} eq $diedAppName,
+ keys %{$self->[cnnxion]}))[0];
+
+ unless (defined $addrInet) {
+ die "ERREUR _removeFileDescriptor deconnection de $diedAppName ".
+ "addrInet not defined\n";
+ return;
+ }
+
+ #printf "DBG> _removeFileDescriptor : deconnection de %s ($diedAppName)\n", _inetAdrByName ($diedAppName);
+
+ delete $self->[cnnxion]{$addrInet};
+
+ # on vire l'entree correspondant a ce canal dans la liste des
+ # regexps par canal
+ $self->_scanConnStatus () ;
+}
+
+
+############### METHODE SEND ERROR TO
+sub _sendErrorTo ($$$)
+{
+ my ($self, $fd, $error) = @_;
+
+ send ($fd, join (' ', ERROR, "0\002$error\n"), 0)
+ or $self->_removeFileDescriptor ($fd);
+}
+
+
+############### METHODE PONG
+sub _pong ($$)
+{
+ my ($self, $fd) = @_;
+
+ send ($fd, join (' ', PONG, "0\002 \n"), 0)
+ or $self->_removeFileDescriptor ($fd);
+}
+
+
+############### METHODE SEND ERROR TO
+sub _sendDieTo ($$)
+{
+ my ($self, $fd) = @_;
+
+ send ($fd, join (' ', DIE, "0\002\n"), 0)
+ or $self->_removeFileDescriptor ($fd);
+}
+
+
+############### METHODE SEND MSG TO
+sub _sendMsgTo ($$$)
+{
+ my ($self, $fd, $msg) = @_;
+
+ # pour toutes les fonctions de filtrage de regexp
+ foreach my $regexpFunc (@{$self->[sendRegList]{$fd}}) {
+ &{$regexpFunc} (\$msg) if defined $regexpFunc;
+ }
+}
+
+
+############### PROCEDURE TK FILE EVENT
+sub _tkFileEvent ($$)
+{
+ my ($fd, $cb) = @_;
+
+ Tk::fileevent ('', $fd, 'readable', $cb) ;
+}
+
+
+############### PROCEDURE SCAN AFTER
+sub _scanAfter ()
+{
+ my $stamp = timeofday ();
+ $selectTimout = MAX_TIMOUT;
+ foreach my $afk (keys %afterList) {
+ my $af = $afterList{$afk};
+ # si ce timer est a declencher
+ if ($af->[2] <= $stamp) {
+ # on traite : le temps de declencher le cb est arrive
+ if (ref $af->[3] eq 'CODE') {
+ &{$af->[3]};
+ }
+ else {
+ my ($cb, @args) = @{$af->[3]};
+ &$cb (@args);
+ }
+ # si c'est un repeat on le reconduit
+ if ($af->[0]) {
+ $af->[2] = $stamp + $af->[1] ;
+ $selectTimout = $af->[1] if $af->[1] < $selectTimout;
+ }
+ else {
+ # si c'est un after on le vire
+ afterCancel ($afk);
+ }
+ }
+ else {
+ my $timeTotrigg = $af->[2] - $stamp;
+ $selectTimout = $timeTotrigg if $timeTotrigg < $selectTimout;
+ }
+ }
+}
+
+
+############### METHODE SCAN CONN STATUS
+sub _scanConnStatus ($)
+{
+ my $self = shift;
+
+ my (%readyApp, @nonReadyApp);
+
+ foreach (values %{$self->[cnnxion]}) {
+ next if $_ eq "1";
+ $readyApp{$_}++ unless /^\004/; # connecte mais pas ready
+ }
+
+ foreach (@{$self->[neededApp]}) {
+ push (@nonReadyApp, $_) unless exists $readyApp{$_};
+ }
+
+ # par compatibilite avec l'ancienne version, on envoie comme
+ # deux premiers arguments une ref sur la liste des applis presentes,
+ # la liste des applis absentes, mais on rajoute comme troisieme
+ # argument une ref sur une table de hash : comme clef les
+ # applis presentes, comme valeur le nombre d'applis ayant ce nom,
+ # de facon a detecter plus facilement quand il y a trop d'applis
+ # de meme nom sur le meme bus.
+ &{$self->[statusFunc]} ([keys %readyApp], \@nonReadyApp, \%readyApp);
+}
+
+
+############### METHODE TO BE PRUNED
+sub _toBePruned ($$$)
+{
+ my ($self, $from, $regexp) = @_;
+
+ # si il n'y a pas de liste de sujets, on ne
+ # filtre pas
+ return 0 unless @{$self->[topicRegexps]};
+
+ unless ($regexp =~ /^\^/) {
+ #print "DBG> regexp non ANCREE de $from : $regexp\n";
+ return (0);
+ }
+
+ if ($regexp =~ /^\^(\w+)/) {
+ my $topic = $1;
+ if (grep (/$topic/, @{$self->[topicRegexps]})) {
+ # on a trouve ce topic : on ne filtre pas la regexp
+ #print "DBG> on garde de $from : $regexp\n";
+ return (0);
+ }
+ #print "DBG> on ELIMINE de $from : $regexp\n";
+ return (1);
+ }
+ else {
+ #print "DBG> on garde de $from : $regexp\n";
+ return (0);
+ }
+}
+
+
+############### PROCEDURE PARSE IVY BUS PARAM
+sub _parseIvyBusParam ($)
+{
+ my $ivyBus = shift;
+
+ my ($ivyNetworks, $ivyPort) = $ivyBus =~ /^(.*):(.*)/;
+
+ die ("Erreur Ivy::_parseIvyBusParam format de l'adresse ou ".
+ "no de port incorrect : $ivyBus\n")
+ unless $ivyPort =~ /^\d+$/;
+
+ my @ivyAddrInet = ();
+
+ $ivyNetworks =~ s/ //g;
+ my @broadcastAddrs = split (',', $ivyNetworks);
+
+ foreach my $netAddr (@broadcastAddrs) {
+ $netAddr = BROADCAST_ADDRS if
+ (($netAddr eq '') || ($netAddr =~ /^127/) ||
+ ($netAddr =~ /^loopback/));
+
+ # deux cas de figure : on a un nom de sous reseau, ou
+ # une adresse ip de la forme \d+.\d+....
+ my $netAddrInet;
+
+ if ($netAddr !~ /^(\d+\.)+\d+/) {
+ # on a un nom de reseau, il faut trouver son adresse ip
+ # on contourne un bug : Si les adresses sont incompletes
+ # dans la map network : 143.196.53 au lieu de 143.196.53.255
+ # getbyname renvoie une adresse de type 0.143.196.53.
+ # on doit donc faire un decalage des octets vers la gauche,
+ # chaque 0 qui sort a gauche est remplace par un 255 a droite.
+ my $networkAddr = getnetbyname ($netAddr);
+ unless (defined $networkAddr) {
+ warn ("Ivy::_parseIvyBusParam reseau inconnu : $netAddr\n");
+ next;
+ }
+
+ my @dummyNetAddr = unpack ("CCCC", pack ('N', $networkAddr));
+ while (!$dummyNetAddr[0]) {
+ # tant que le premier octet est 0, on decale vers la gauche et
+ # ont fait rentrer un 255 sur la droite
+ shift @dummyNetAddr;
+ push (@dummyNetAddr, 255);
+ }
+ $netAddrInet = pack ("CCCC", @dummyNetAddr);
+ } else {
+ # on a deja une adresse ip, on rajoute les .255
+ # a la fin s'ils ont etes omis.
+ ($netAddr .= ".255.255.255") =~ s/^((\d+\.){3}\d+).*/$1/;
+ $netAddrInet = inet_aton ($netAddr);
+ }
+
+ push (@ivyAddrInet, pack_sockaddr_in ($ivyPort, $netAddrInet));
+ }
+ return ($ivyPort, \@ivyAddrInet);
+}
+
+############# Procedure _SUBSTITUTE ESCAPED CHAR
+sub _substituteEscapedChar ($$)
+{
+ my ($scope, $reg) = @_;
+
+ my %escapeRegexp = REG_PERLISSISME;
+ # Si on fait la substitution dans une classe de caractere
+ # on elimine les crochets.
+ grep ($escapeRegexp{$_} =~ s/[\[\]]//g, keys %escapeRegexp)
+ if ($scope eq 'inside') ;
+
+ $reg =~ s/\\([wWsSdDne])/$escapeRegexp{$1}/ge;
+ return $reg;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Ivy - Perl extension for implementing a software bus
+
+=head1 SYNOPSIS
+
+use Ivy;
+
+=head1 DESCRIPTION
+
+The Ivy perl module implements a software bus to provide with an easy
+communication between applications. Messages are broadcasted as ASCII strings
+over a network defined by a list of domains and a port.
+Messages are received if they match a regular expressions and if your application
+is on the same network as remote ones.
+Before receive or send message you must call 'init', and 'new' class methods,
+followed by 'start' method.
+When you quit your application don't forget to call 'exit' class methods.
+
+=head1 CLASS METHODS
+
+=head2 Ivy->init(...)
+
+Allows one to define global parameters which may be used as default ones
+at object creation time.
+
+Parameters are :
+
+=over 4
+
+=item B<-loopMode =E<gt> 'TK'|'LOCAL'>
+
+Mode of events loop among TK or LOCAL. According to this mode, you must
+use Ivy->mainLoop or Tk::MainLoop(3)
+
+=item B<-appName =E<gt> 'your app ivy name'>
+
+Name of your application used to identify on ivy bus.
+
+=item B<-ivyBus =E<gt> 'domain 1,...,domain n:port number'>
+
+A list of domains, followed by port number where to broadcast messages.
+Default is 127.255.255.255:2010
+
+=item B<-messWhenReady =E<gt> 'your message when ready'>
+
+Synchronisation message sent when application is ready to receive and send
+messages.
+
+=item B<-onDieFunc =E<gt> [\&yourdiefunc, @parameters]>
+
+=item B<-onDieFunc =E<gt> [$an_object, \&a_method, @parameters]>
+
+A callback or method to call when application receive die message.
+Don't make an exit in callback, ivy we'll do it for you.
+
+Prototype of your callback must be :
+
+sub MyCallback {
+ my @parameters = @_;
+
+ ...
+}
+
+Prototype of your method must be :
+
+sub MyMethod {
+ my ($self, @parameters) = @_;
+
+ ...
+}
+
+=item B<-pruneRegexp =E<gt> ['subject 1', ..., 'subject n']>
+
+Optimize communication using this option. Regexps which don't match these subjects are removed.
+
+Example :
+
+ Ivy->init(-loopMode =E<gt> 'TK',
+ -appName =E<gt> 'MyWonderfulApp',
+ -onDieFunc =E<gt> [\&restorecontext]);
+
+=back
+
+=head2 Ivy->new(...);
+
+Check parameters, and create an ivy bus object. You must call Ivy->init before
+this one.
+
+Parameters are :
+
+=over 4
+
+=item B<-appName =E<gt> 'your app ivy name'>
+
+Name of your application used to identify on ivy bus.
+
+=item B<-ivyBus =E<gt> 'domain 1,...,domain n:port number'>
+
+A list of domains, followed by port number where to broadcast messages.
+Default is 127.255.255.255:2010
+
+=item B<-messWhenReady =E<gt> 'your message when ready'>
+
+Synchronisation message sent when application is ready to receive and send
+messages.
+
+=item B<-onDieFunc =E<gt> [\&yourdiefunc, @parameters]>
+
+=item B<-onDieFunc =E<gt> [$an_object, \&a_method, @parameters]>
+
+A callback or method to call when application receive die message.
+Don't make an exit in callback, ivy we'll do it for you.
+Prototype of your callback must be :
+
+ sub MyCallback {
+ my @parameters = @_;
+ ...
+ }
+
+Prototype of your method must be :
+
+ sub MyMethod {
+ my ($self, @parameters) = @_;
+ ...
+ }
+
+=item B<-pruneRegexp =E<gt> ['subject 1', ..., 'subject n']>
+
+Optimize communication using this option. Regexps which don't match these subjects are removed.
+
+=item B<-neededApp =E<gt> ['app 1', ..., 'app n']>
+
+A list of application your own one needs to correctly run.
+
+=item B<-statusFunc =E<gt> sub {}>
+
+A callback which will be called until every needed app is present on bus.
+
+Your callback could be :
+
+ sub MyCallback {
+ my ($present, $absent, %present) = @_;
+
+ foreach my $remoteapp (keys %present) {
+ if ($present{$remoteapp} > 1) {
+ print "n apps $remoteapp are presents on bus\n";
+ }
+ }
+
+Example :
+
+ Ivy->new(-ivyBus => '156.255.255.255,157.255.255.255:2204',
+ -onDieFunc => [\&restorecontext],
+ -neededApp => ["DataServer", "HMI"],
+ -statusFunc => \&startwhenpresents);
+
+=back
+
+=head2 Ivy->mainLoop;
+
+Local main events loop. Use it if you don't use Tk library.
+
+=head2 $ivyobj->stop;
+
+=head1 OBJECT METHODS
+
+=head2 $ivyobj->start;
+
+You must call it after you are ready to communicate through ivy bus
+and before you really communicate.
+
+=head2 $ivyobj->sendMsgs(@messages);
+
+Send a list of messages
+
+Example :
+
+ $ivyobj->sendMsgs("Hello World", "Don't Bother", "Y2K is behind us");
+
+=head2 $ivyobj->sendAppNameMsgs(@messages);
+
+Send a list of messages precedeed by ivy application name.
+
+Example :
+
+ $ivyobj->sendMsgs("Hello World");
+ # it will send "$appName Hello World" over ivy bus
+
+=head2 $ivyobject->bindRegexp($regexp, [\&callback, @cb_parameters]);
+
+=head2 $ivyobject->bindRegexp($regexp, [$an_obj, \&method, @cb_parameters]);
+
+Allow one to associate a message which matches a regular expression and a
+callback or method. See perlre(1), to find how to write regexps.
+Use the bracketing construct ( ... ), so that ivy perl will call
+callback with matched patterns as parameters.
+
+Example :
+
+ $ivyobject->bindRegexp("\w+ (\d+)", [\&callback, @cb_parameters]);
+ # You callback will be called with one more parameter which will be
+ # a number precedeed by a word, because of bracketed regexp.
+
+Your callback and method protos must be :
+
+ sub cb {
+ my ($sendername, @cb_parameters,
+ @matched_regexps_in_brackets) = @_;
+ ...
+ }
+
+ sub method {
+ my ($self, $sendername, @cb_parameters,
+ @matched_regexps_in_brackets) = @_;
+ ...
+ }
+
+=head2 $ivyobj->sendDirectMsgs($to, $id, @msgs);
+
+Send a message but Ask Alex to find what are $to and $id
+
+=head2 $ivyobj->bindDirect($regexp, $id, [\&callback, @cb_parameters]);
+
+=head2 $ivyobj->bindDirect($regexp, $id, [$an_obj, \&method, @cb_parameters]);
+
+Same as bindRegexp method but Ask Alex to find what is $id.
+
+=head2 $ivyobj->sendDieTo($to)
+
+send a die message to $to application name.
+
+=head2 $ivyobj->ping($to, $timeout);
+
+send a ping message and wait until timeout to receive a pong.
+
+=head2 $after_id = $ivyobj->after($timeAfter, \@callbacks_list);
+
+Call a list of callbacks after $timeAfter mseconds.
+
+=head2 $repeat_id = $ivyobj->repeat($timeAfter, \@callbacks_list);
+
+Repeat calls of a list of callbacks after $timeAfter mseconds.
+
+=head2 $ivyobj->afterCancel($after_or_repeat_id);
+
+Cancel an after callback call.
+
+=head2 $ivyobj->fileEvent($fd, $cb);
+
+Ask Alex
+
+=head2 $ivyobj->DESTROY;
+
+=head1 BUGS
+
+No know bugs at this time. Report them to author.
+
+=head1 SEE ALSO
+
+perl(1), perlre(1)
+
+=head1 AUTHORS
+
+Alexandre Bustico <bustico@tls.cena.fr>, Herve Damiano <damiano@tls.cena.fr>
+
+=head1 COPYRIGHT
+
+CENA (C) 1997-2000
+
+=head1 HISTORY
+
+=cut
diff --git a/src/ivymon b/src/ivymon
new file mode 100755
index 0000000..e6f3ceb
--- /dev/null
+++ b/src/ivymon
@@ -0,0 +1,755 @@
+#!/usr/bin/perl
+
+use Tk;
+use Tk::Font;
+use Ivy;
+use strict 'vars';
+use Getopt::Long;
+use vars qw/$opt_help $opt_b $opt_history/;
+
+# geometrie
+my $appliFrameWidth = 150;
+my $appliFrameHeight = 200;
+my $bindingsFrameWidth = 400;
+
+
+my $ivy_port = "127.255.255.255:2010";
+my $history = 10000;
+
+# divers
+my %connectedClients;
+my %clientsIndex;
+my %bindings;
+my %bindingsIndex;
+my $selectedClient;
+
+my $duration = 0;
+my $time = 0;
+my $fmtduration = '00:00';
+my $bytes = 0 ;
+my $messagesNumber = 0;
+my $bindingsFlag = 0;
+my $stopFlag = 0;
+
+my $mw = MainWindow->new;
+$mw->geometry('850x768');
+$mw->minsize(850, 768);
+
+
+if (not GetOptions('-help', '-history=s', '-b=s') or $opt_help) {
+ print "Usage: ivymon [options]\n";
+ print "Options : \n";
+ print " -s <[addr]:port> bus port(default $ivy_port)\n";
+ print " -history <number> messages history length (default $history)\n";
+ print " -help help message \n";
+ exit ;
+}
+
+$ivy_port = $opt_b if $opt_b and
+ $opt_b =~ /^(\d+\.\d+\.\d+\.\d+)?(,\d+\.\d+\.\d+\.\d+)*:\d+/;
+$history = $opt_history if $opt_history;
+$mw->title("IvyMon ($ivy_port)");
+
+#----------------------------------------------------------------------------------
+# La Frame de gauche prend toute la hauteur de l'appli et est composee d'une frame
+# ou sont affiches les Messages et une frame ou sont saisis les Abonnements et les
+# instructions de Recherche
+#----------------------------------------------------------------------------------
+my $frame1left =
+ $mw->Frame()->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'left');
+
+my $frame11messages =
+ $frame1left->Frame()->pack(-fill => 'both',
+ -expand => 1,
+ -padx => 5, -pady => 5);
+my $frame12 =
+ $frame1left->Frame(-height => $appliFrameHeight)->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+my $frame121bindings =
+ $frame12->Frame(-height => $appliFrameHeight,
+ -relief => 'groove',
+ -borderwidth => 3,
+ -width => $bindingsFrameWidth)->pack(-fill => 'both',
+ -ipady => 20, -ipadx => 20,
+ -padx => 5, -pady => 5,
+ -side => 'left',
+ -expand => 0);
+my $frame122search =
+ $frame12->Frame(-relief => 'groove',
+ -borderwidth => 3,
+ -height => $appliFrameHeight)->pack(-fill => 'both',
+ -padx => 5, -pady => 5,
+ -side => 'left',
+ -expand => 0);
+
+
+#----------------------------------------------------------------------------------
+# La Frame de droite prend toute la hauteur de l'appli et est composee d'une frame
+# ou sont affiches les Applications connectees et une frame contenant les boutons
+# de commande
+#----------------------------------------------------------------------------------
+my $frame2right =
+ $mw->Frame(-width => $appliFrameWidth)->pack(-fill => 'both',
+ -expand => 0,
+ -side => 'right');
+my $frame21appli =
+ $frame2right->Frame(-width => $appliFrameWidth)->pack(-fill => 'both',
+ -expand => 1,
+ -padx => 5, -pady => 5);
+
+my $frame21control =
+ $frame2right->Frame(-width => $appliFrameWidth,
+ -height => $appliFrameHeight)->pack(-fill => 'both',
+ -expand => 0,
+ -padx => 5, -pady => 5);
+#----------------------------------------------------------------------------------
+# Description de la zone Messages
+#----------------------------------------------------------------------------------
+my $messagesLabel =
+ $frame11messages->Label(-text => "Messages :")->pack(-side => 'top',
+ -anchor => 'w');
+
+my $textFont = $mw->fontCreate('H_Normal',
+ -family => 'Helvetica',
+ -size => 10);
+my $textItalicFont = $mw->fontCreate('H_Italic',
+ -family => 'Helvetica',
+ -size => 10,
+ -slant => 'italic');
+my $textBoldFont = $mw->fontCreate('H_Bold',
+ -family => 'Helvetica',
+ -size => 10,
+ -weight => 'bold');
+
+my $messagesText =
+ $frame11messages->Scrolled(Text,
+ -scrollbars => 'e',
+ -font => 'H_Bold',
+ -spacing1 => 2,
+ -spacing2 => 0,
+ -spacing3 => 2,
+ -state => 'disabled')->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'bottom');
+my $messagesBg = $messagesText->cget(-background);
+$messagesText->tagConfigure('sender',
+ -background => 'gray50',
+ -foreground => 'gray90');
+$messagesText->tagConfigure('info', -foreground => 'sienna');
+$messagesText->bind('<KeyPress>', sub {
+ my $index = $messagesText->index('insert');
+ #print "index = $index\n";
+ $messagesText->tagAdd('info', "$index - 1 chars", $index);
+});
+
+#----------------------------------------------------------------------------------
+# Description de la zone Clients
+#----------------------------------------------------------------------------------
+my $clientsLabel =
+ $frame21appli->Label(-text => "Applications :")->pack(-side => 'top',
+ -anchor => 'w');
+my $clientsListbox =
+ $frame21appli->Listbox()->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'bottom');
+$clientsListbox->bind('<1>', [\&selectClient]);
+
+#----------------------------------------------------------------------------------
+# Description de la zone Abonnements
+#----------------------------------------------------------------------------------
+my $bindingsLabel =
+ $frame121bindings->Label(-text => 'Bindings : ')->pack(-side => 'top',
+ -anchor => 'w');
+
+my $bindingsEntry =
+ $frame121bindings->Entry(-width => 50)->pack(-fill => 'x',
+ -ipady => 3,
+ -expand => 0,
+ -padx => 5, -pady => 5);
+$bindingsEntry->bind('<Escape>' => [\&addBindingExpression]);
+$bindingsEntry->bind('<Return>' => [\&addBinding, 1]);
+$bindingsEntry->focus;
+
+my $frame1211 =
+ $frame121bindings->Frame()->pack(-fill => 'x',
+ -side => 'bottom',
+ -expand => 1);
+my $bindingsList =
+ $frame1211->Listbox(-width => 40)->pack(-fill => 'y',
+ -side => 'left',
+ -expand => 1);
+$bindingsList->bind('<1>', [\&selectBinding]);
+
+my $frame12111 =
+ $frame1211->Frame()->pack(-fill => 'y',
+ -side => 'right',
+ -expand => 1);
+my $bindingsClear =
+ $frame12111->Button(-text => 'Clear',
+ -command => [\&clearBinding])->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 1);
+my $bindingsAdd =
+ $frame12111->Button(-text => 'Add',
+ -command => [\&addBinding])->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 1);
+my $bindingsChange =
+ $frame12111->Button(-text => 'Change',
+ -state => 'disabled',
+ -command => [\&changeBinding])->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 1);
+my $bindingsRemove =
+ $frame12111->Button(-text => 'Remove',
+ -state => 'disabled',
+ -command => [\&removeBinding])->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 1);
+
+#----------------------------------------------------------------------------------
+# Description de la zone Recherche
+#----------------------------------------------------------------------------------
+my $searchLabel =
+ $frame122search->Label(-text => 'Search : ')->pack(-side => 'top',
+ -anchor => 'w');
+my $searchEntry =
+ $frame122search->Entry(-width => 20)->pack(-fill => 'x',
+ -ipady => 3,
+ -expand => 0,
+ -padx => 5, -pady => 5);
+$searchEntry->bind('<Return>' => [\&searchNext, 1]);
+
+my $frame1222 =
+ $frame122search->Frame()->pack(-fill => 'x',
+ -padx => 10, -pady => 10,
+ -side => 'bottom',
+ -expand => 0);
+my $frame1221 =
+ $frame122search->Frame()->pack(-fill => 'x',
+ -padx => 10,
+ -side => 'bottom',
+ -expand => 0);
+my $searchPrev =
+ $frame1221->Button(-text => 'Previous',
+ -height => 3,
+ -width => 4,
+ -command => [\&searchPrev])->pack(-fill => 'x',
+ -side => 'left',
+ -expand => 1);
+my $searchNext =
+ $frame1221->Button(-text => 'Next',
+ -width => 4,
+ -height => 3,
+ -command => [\&searchNext])->pack(-fill => 'x',
+ -side => 'right',
+ -expand => 1);
+my $searchClear =
+ $frame1222->Button(-text => 'Clear',
+ -width => 4,
+ -height => 3,
+ -command => [\&clearSearch])->pack(-fill => 'x',
+ -side => 'left',
+ -expand => 1);
+my $searchAll =
+ $frame1222->Button(-text => 'All',
+ -width => 4,
+ -height => 3,
+ -command => [\&searchAll])->pack(-fill => 'x',
+ -side => 'right',
+ -expand => 1);
+
+
+#----------------------------------------------------------------------------------
+# Description de a la zone Commande
+#----------------------------------------------------------------------------------
+# les champs de statistiques
+my $frame211control =
+ $frame21control->Frame()->pack(-side => 'top',
+ -fill => 'both',
+ -expand => 1);
+my $frame2111control =
+ $frame211control->Frame()->pack(-side => 'left',
+ -padx => 5,
+ -fill => 'both',
+ -expand => 1);
+my $frame2112control =
+ $frame211control->Frame()->pack(-side => 'right',
+ -fill => 'both',
+ -expand => 1);
+
+
+$frame2111control->Label(-text => "Duration :")->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w',
+ );
+$frame2111control->Label(-text => "Messages :")->pack(-side => 'top',
+ -pady => 2,
+ -anchor => 'w');
+$frame2111control->Label(-text => "Bytes :")->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$fmtduration)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$messagesNumber)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$bytes)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+
+# les boutons
+my $frame212control =
+ $frame21control->Frame()->pack(-side => 'bottom',
+ -pady => 8,
+ -fill => 'both',
+ -expand => 1);
+my $frame2121control =
+ $frame212control->Frame()->pack(-side => 'left',
+ -fill => 'both',
+ -ipady => 10,
+ -padx => 5, -pady => 5,
+ -expand => 1);
+my $frame2122control =
+ $frame212control->Frame()->pack(-side => 'right',
+ -padx => 5, -pady => 5,
+ -ipady => 10,
+ -fill => 'both',
+ -expand => 1);
+my $startButton =
+ $frame2121control->Button(-height => 3,
+ -width => 6,
+ -command => [\&start, 1],
+ -state => 'disabled',
+ -text => 'Start')->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 0);
+my $clearButton =
+ $frame2121control->Button(-height => 3,
+ -width => 6,
+ -command => [\&clearMessages],
+ -text => "Clear")->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+
+my $stopButton =
+ $frame2122control->Button(-height => 3,
+ -width => 6,
+ -command => [\&stop, 1],
+ -text => 'Stop')->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 0);
+my $exitButton =
+ $frame2122control->Button(-height => 3,
+ -width => 6,
+ -command => [\&bye],
+ -text => 'Exit')->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+
+
+
+
+#----------------------------------------------------------------------------------
+# Demarrage d'Ivy et abonnements
+#----------------------------------------------------------------------------------
+
+Ivy->init(-loopMode => 'TK',
+ -appName => "IVYMON",
+ -ivyBus => $ivy_port,
+ );
+my $ivy = Ivy->new(-statusFunc => \&checkClientsStatus);
+$ivy->start;
+
+$mw->repeat(1000, \&updateDuration);
+
+MainLoop;
+
+#==================================================================================
+# FONCTIONS IVY
+#==================================================================================
+sub addIvyBinding {
+ my $binding = shift;
+ #print "in addIvyBinding $binding\n";
+ return if $stopFlag;
+ $ivy->bindRegexp($binding, [\&updateMessages]);
+}
+
+sub removeIvyBinding {
+ my $binding = shift;
+ $ivy->bindRegexp($binding);
+}
+
+sub stopIvyBindings {
+ for (keys(%bindings)) {
+ $ivy->bindRegexp($_);
+ }
+}
+
+sub startIvyBindings {
+ for (keys(%bindings)) {
+ $ivy->bindRegexp($_, [\&updateMessages]);
+ }
+}
+
+#==================================================================================
+# CALLBACK TK
+#==================================================================================
+
+#----------------------------------------------------------------------------------
+# Fonctions associees a la liste des applications connectees
+#----------------------------------------------------------------------------------
+sub checkClientsStatus {
+ my ($present, $absent) = @_;
+ #print "in checkClientsStatus\n";
+ my %present;
+ for (@$present) {
+ $present{$_} = 1;
+ unless ($connectedClients{$_}) {
+ $connectedClients{$_} = 1;
+ &addClient($_);
+ }
+ }
+ for (keys(%connectedClients)) {
+ unless ($present{$_}) {
+ delete $connectedClients{$_};
+ &removeClient($_);
+ }
+ }
+}
+
+sub addClient {
+ my $client = shift;
+ $clientsListbox->insert('end', $client);
+ &clientsGenList;
+
+}
+
+sub removeClient {
+ my $client = shift;
+ my $index = $clientsIndex{$client};
+ $clientsListbox->delete($index) if $index >= 0;
+ &clientsGenList;
+}
+
+sub clientsGenList {
+ my $i = 0;
+ for ($clientsListbox->get(0, 'end')) {
+ $clientsIndex{$_} = $i++;
+ }
+}
+
+sub selectClient {
+ $messagesText->tagConfigure($selectedClient, -background => $messagesBg)
+ if $selectedClient;
+ my $selindex = $clientsListbox->curselection;
+ my $client = $clientsListbox->get($selindex);
+ if ($selectedClient eq $client) {
+ $selectedClient = undef;
+ $clientsListbox->selectionClear($selindex);
+ return;
+ }
+ $messagesText->tagConfigure($client, -background => 'gray70');
+ $selectedClient = $client;
+}
+#----------------------------------------------------------------------------------
+# Fonctions associees a la gestion de l'affichage des messages
+#----------------------------------------------------------------------------------
+sub updateMessages {
+ my ($sender, $message) = @_;
+ return unless $message;
+ return if ($message =~ /READY$/);
+ chomp($message);
+ my $text = "$sender $message\n";
+ $messagesText->configure(-state => 'normal');
+ # on teste la taille de la fenetre de messages
+ my ($linesNb) = split(/\./, $messagesText->index('end'));
+ #print "linesNb=$linesNb history=$history\n";
+ if ($linesNb > $history) {
+ my $dl = $linesNb - $history;
+ $messagesText->delete('1.0', "1.0 + $dl lines");
+ }
+ # on extrait les index de debut et de fin de l'application emettrice
+ my $index1 = $messagesText->index('end');
+ $index1 = "$index1 - 1 lines";
+ my $senderlen = length($sender);
+ my $index2 = "$index1 + $senderlen chars";
+ $messagesText->insert('end', $text);
+ $messagesText->tagAdd('sender', $index1, $index2);
+ # on extrait les index de debut et de fin du message
+ my $msglen = length($message);
+ $index1 = "$index2 + 1 chars";
+ $index2 = "$index1 + $msglen chars";
+ $messagesText->tagAdd($sender, $index1, $index2);
+ $messagesText->configure(-state => 'disabled');
+ $messagesText->see('end');
+ $messagesNumber++;
+ $bytes += length($message);
+ $mw->update;
+}
+
+sub printInfo {
+ my $info = shift;
+ $messagesText->insert('end', "<< $info >>\n");
+ my $index = $messagesText->index('end');
+ my $len = length($info) + 7;
+ my $index1 = "$index - 1 lines";
+ my $index0 = "$index1 - $len chars";
+ $messagesText->tagAdd('info', $index0, $index1);
+ $messagesText->see('end');
+ $messagesText->markSet('insert', 'end');
+}
+
+sub addBindingExpression {
+ $bindingsEntry->insert('insert', '(.*)');
+ $bindingsEntry->xview('end');
+}
+
+sub clearMessages {
+ &stop;
+ $messagesText->configure(-state => 'normal');
+ $messagesText->delete('0.0', 'end');
+ $messagesText->configure(-state => 'disabled');
+ &start;
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees a la gestion des abonnements
+#----------------------------------------------------------------------------------
+
+sub clearBinding {
+ # on vide la le champ de saisie
+ $bindingsEntry->delete(0, 'end');
+ # on enleve la selection dans la listbox
+ $bindingsList->selectionClear(0, 'end');
+ # on desactive les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+}
+
+sub addBinding {
+ my $entry = $bindingsEntry->get;
+ return unless $entry;
+ return if $bindings{$entry};
+ $bindingsList->insert('end', $entry);
+ # on ajoute l'abonnement ivy
+ &addIvyBinding($entry);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+}
+
+sub selectBinding {
+ my $selindex = $bindingsList->curselection;
+ my $selected = $bindingsList->get($selindex);
+ # on active les boutons Change et Remove
+ $bindingsChange->configure(-state => 'normal');
+ $bindingsRemove->configure(-state => 'normal');
+ # on met a jour le champ de saisie
+ $bindingsEntry->delete(0, 'end');
+ $bindingsEntry->insert(0, $selected);
+}
+
+sub removeBinding {
+ my $selindex = $bindingsList->curselection;
+ return if $selindex eq "";
+ my $selected = $bindingsList->get($selindex);
+ # on supprime l'abonnement ivy
+ &removeIvyBinding($selected);
+ # on enleve l'item de la liste
+ $bindingsList->delete($selindex);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+ # on desactive les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+ # on vide le champ de saisie
+ $bindingsEntry->delete(0, 'end');
+
+}
+
+sub changeBinding {
+ my $selindex = $bindingsList->curselection;
+ return if $selindex eq "";
+ my $selected = $bindingsList->get($selindex);
+ my $newbinding = $bindingsEntry->get;
+ return unless $newbinding;
+ return if $bindings{$newbinding};
+ # on supprime l'abonnement ivy
+ &removeIvyBinding($selected);
+ # on ajoute le nouvel abonnement ivy
+ &addIvyBinding($selected);
+ # on enleve la selection de la liste
+ $bindingsList->selectionClear(0, 'end');
+ # on met a jour l'item de la liste
+ $bindingsList->delete($selindex);
+ $bindingsList->insert($selindex, $newbinding);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+ # on desactive les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+
+}
+
+sub bindingsGenList {
+ my $i = 0;
+ my $found = 0;
+ for (keys %bindingsIndex) {
+ delete $bindingsIndex{$_};
+ delete $bindings{$_};
+ }
+ for ($bindingsList->get(0, 'end')) {
+ $found = 1;
+ $bindingsIndex{$_} = $i++;
+ $bindings{$_} = 1
+ }
+ $bindingsFlag = $found;
+ #print "bindingsFlag = $bindingsFlag\n";
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees au panneau de recherche
+#----------------------------------------------------------------------------------
+my $searchIndex;
+sub clearSearch {
+ # on vide la le champ de saisie
+ $searchEntry->delete(0, 'end');
+ &highlightStringOff;
+}
+
+sub searchNext {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ my $index0 = ($searchIndex) ? "$searchIndex + 1 chars": '0.0';
+ my $index = $messagesText->search(-forwards, $string, $index0);
+ $searchIndex = $index;
+ return unless $index;
+ &highlightStringOff;
+ &highlightString($index, "$index + $strlen chars");
+ $messagesText->see($index);
+
+}
+
+sub searchPrev {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ #my $index0 = ($searchIndex) ? "$searchIndex - 1 chars": '0.0';
+ my $index0 = ($searchIndex) ? $searchIndex : '0.0';
+ my $index = $messagesText->search(-backwards, $string, $index0);
+ $searchIndex = $index;
+ return unless $index;
+ &highlightStringOff;
+ &highlightString($index, "$index + $strlen chars");
+ $messagesText->see($index);
+
+}
+
+sub searchAll {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ $messagesText->tagConfigure('found',
+ -background => 'sienna',
+ -foreground => 'ivory');
+ my $index = '0.0';
+ while ($index) {
+ $index = $messagesText->search(-forwards, $string, $index, 'end');
+ return unless $index;
+ $messagesText->tagAdd('found', $index, "$index + $strlen chars");
+ $index = "$index + 1 chars";
+ }
+
+}
+
+sub highlightString {
+ my ($i1, $i2) = @_;
+ $messagesText->tagConfigure('found',
+ -background => 'sienna',
+ -foreground => 'ivory');
+ $messagesText->tagAdd('found', $i1, $i2);
+}
+
+sub highlightStringOff {
+ $messagesText->tagDelete('found');
+ $messagesText->tagConfigure('found',
+ -background => 'sienna',
+ -foreground => 'ivory');
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees au panneau de commande
+#----------------------------------------------------------------------------------
+sub bye {
+ #$ivy->stop;
+ exit;
+}
+
+sub stop {
+ my $flag = shift;
+ $stopFlag = 1;
+ $startButton->configure(-state => 'normal');
+ $stopButton->configure(-state => 'disabled');
+ &stopIvyBindings;
+ if ($flag) {
+ &setEditMode;
+ my $fmttime = &formattime($time);
+ &printInfo("Stopped at $fmttime");
+ }
+
+}
+
+sub start {
+ my $flag = shift;
+ $stopFlag = 0;
+ $stopButton->configure(-state => 'normal');
+ $startButton->configure(-state => 'disabled');
+ &startIvyBindings;
+ if ($flag) {
+ my $fmttime = &formattime($time);
+ &printInfo("Restarted at $fmttime");
+ &unsetEditMode;
+ }
+}
+
+sub updateDuration {
+ return unless $bindingsFlag;
+ $time++;
+ return if $stopFlag;
+ $duration++;
+ $fmtduration = &formattime($duration);
+}
+
+sub formattime {
+ my $duration = shift;
+ my $hour = sprintf("%02d", int($duration/3600));
+ my $sec = $duration % 3600;
+ my $min = sprintf("%02d", int($sec/60));
+ my $sec = sprintf("%02d", $sec % 60);
+ if ($hour eq '00') {
+ return "$min:$sec";
+ } else {
+ return "$hour:$min:$sec";
+ }
+}
+
+# sub Ivy::exit {
+# print "exit\n";
+# }
+
+sub setEditMode {
+ #print "setEditMode\n";
+ $messagesText->configure(-state => 'normal');
+ $messagesText->focus;
+}
+
+sub unsetEditMode {
+ #print "unsetEditMode\n";
+ $messagesText->configure(-state => 'disabled');
+ $bindingsEntry->focus;
+}
diff --git a/src/ivymon0 b/src/ivymon0
new file mode 100755
index 0000000..08bd7d4
--- /dev/null
+++ b/src/ivymon0
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+use Tk;
+
+# geometrie
+my ($width, $height) = (1024, 768);
+# couleurs
+my $frame1color = 'yellow';
+my $frame1Xcolor = 'red';
+my $frame1XXcolor = 'orange';
+
+my $mw = MainWindow->new;
+$mw->geometry($width.'x'.$height);
+$mw->title('IvyMon');
+
+my $frame1 =
+ $mw->Frame(-background => $frame1color)->pack(-fill => 'both', -expand => 1);
+
+my $frame11 =
+ $frame1->Frame(-background => $frame1Xcolor)->pack(-fill => 'both', -expand => 1,
+ -padx => 5, -pady => 5);
+
+my $frame111clients =
+ $frame11->Frame(-background => $frame1XXcolor,
+ -width => 150)->pack(-fill => 'both',
+ -padx => 10,
+ -pady => 10,
+ -side => 'left',
+ -expand => 0);
+my $frame112messages =
+ $frame11->Frame(-background => $frame1XXcolor,
+ -relief => 'sunken',
+ -bd => 5)->pack(-fill => 'both',
+ -padx => 10,
+ -pady => 10,
+ -side => 'right',
+ -expand => 1);
+
+my $frame12bindings =
+ $frame1->Frame(-background => $frame1Xcolor,
+ -height => 100)->pack(-fill => 'both',
+ -padx => 5,
+ -pady => 5,
+ -side => 'bottom',
+ -expand => 0);
+
+$frame112messages->Text(-state => 'disabled')->pack(-fill => 'both',
+ -expand => 1);
+
+MainLoop;