init commit
Uri Guttman [Fri, 5 Dec 2008 01:34:29 +0000 (20:34 -0500)]
174 files changed:
Build.PL [new file with mode: 0644]
BuildStem.pm [new file with mode: 0644]
CHANGELOG [new file with mode: 0644]
COPYING [new file with mode: 0644]
CREDITS [new file with mode: 0644]
Cookbook/World1.pm [new file with mode: 0644]
Cookbook/World2.pm [new file with mode: 0644]
Cookbook/World3.pm [new file with mode: 0644]
Cookbook/World4.pm [new file with mode: 0644]
Cookbook/World5.pm [new file with mode: 0644]
Cookbook/cookbook.txt [new file with mode: 0644]
Cookbook/worlds.stem [new file with mode: 0644]
DEMO [new file with mode: 0644]
DEMO_CHAT [new file with mode: 0644]
DEMO_INETD [new file with mode: 0644]
DEMO_TAIL [new file with mode: 0644]
Design/Stem-Mon [new file with mode: 0644]
Design/arch_notes [new file with mode: 0644]
Design/asyncio_notes [new file with mode: 0644]
Design/cell_notes [new file with mode: 0644]
Design/config_notes [new file with mode: 0644]
Design/console_notes [new file with mode: 0644]
Design/cron_notes [new file with mode: 0644]
Design/debug_notes [new file with mode: 0644]
Design/env_notes [new file with mode: 0644]
Design/event_notes [new file with mode: 0644]
Design/id_notes [new file with mode: 0644]
Design/index [new file with mode: 0644]
Design/log_notes [new file with mode: 0644]
Design/logtail_notes [new file with mode: 0644]
Design/message_notes [new file with mode: 0644]
Design/portal_notes [new file with mode: 0644]
Design/proc_notes [new file with mode: 0644]
Design/registry_notes [new file with mode: 0644]
Design/security_notes [new file with mode: 0644]
Design/sock_msg_notes [new file with mode: 0644]
Design/socket_notes [new file with mode: 0644]
Design/switch_notes [new file with mode: 0644]
Doc/FAQ.txt [new file with mode: 0644]
Doc/HISTORY-2001 [new file with mode: 0644]
FAQ/faq.text [new file with mode: 0644]
FAQ/faq_maker.pl [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
META.yml [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
bin/boot_stem [new file with mode: 0755]
bin/cgi2stem.pl [new file with mode: 0755]
bin/chat2_demo [new file with mode: 0755]
bin/chat_demo [new file with mode: 0755]
bin/cli [new file with mode: 0644]
bin/hello_demo [new file with mode: 0644]
bin/inetd_demo [new file with mode: 0755]
bin/quote_serve [new file with mode: 0755]
bin/run_stem [new file with mode: 0755]
bin/stem2pod [new file with mode: 0755]
bin/stem_msg [new file with mode: 0755]
bin/tail_demo [new file with mode: 0755]
certs/client-cert.pem [new file with mode: 0644]
certs/client-key.enc [new file with mode: 0644]
certs/client-key.pem [new file with mode: 0644]
certs/my-ca.pem [new file with mode: 0644]
certs/server-cert.pem [new file with mode: 0644]
certs/server-key.enc [new file with mode: 0644]
certs/server-key.pem [new file with mode: 0644]
certs/test-ca.pem [new file with mode: 0644]
conf/archive.stem [new file with mode: 0644]
conf/boot.stem [new file with mode: 0644]
conf/chat.stem [new file with mode: 0644]
conf/chat_client.stem [new file with mode: 0644]
conf/chat_label.stem [new file with mode: 0644]
conf/chat_server.stem [new file with mode: 0644]
conf/cli.stem [new file with mode: 0644]
conf/cron.stem [new file with mode: 0644]
conf/hello.stem [new file with mode: 0644]
conf/hello_client.stem [new file with mode: 0644]
conf/hello_server.stem [new file with mode: 0644]
conf/hello_shell.stem [new file with mode: 0644]
conf/hello_yaml.stem [new file with mode: 0644]
conf/inetd.stem [new file with mode: 0644]
conf/load_driver.stem [new file with mode: 0644]
conf/load_echo.stem [new file with mode: 0644]
conf/monitor.stem [new file with mode: 0644]
conf/proc.stem [new file with mode: 0644]
conf/slave.stem [new file with mode: 0644]
conf/tail.stem [new file with mode: 0644]
conf/test_flow.stem [new file with mode: 0644]
conf/test_packet_io.stem [new file with mode: 0644]
conf/test_udp.stem [new file with mode: 0644]
conf/ticker.stem [new file with mode: 0644]
conf/ttysock.stem [new file with mode: 0644]
conf/type.stem [new file with mode: 0644]
extras/sirc-2.211.tar.gz [new file with mode: 0644]
lib/Stem.pm [new file with mode: 0644]
lib/Stem/AsyncIO.pm [new file with mode: 0644]
lib/Stem/Boot.pm [new file with mode: 0644]
lib/Stem/Cell.pm [new file with mode: 0644]
lib/Stem/Cell/Clone.pm [new file with mode: 0644]
lib/Stem/Cell/Flow.pm [new file with mode: 0644]
lib/Stem/Cell/Pipe.pm [new file with mode: 0644]
lib/Stem/Cell/Sequence.pm [new file with mode: 0644]
lib/Stem/Cell/Work.pm [new file with mode: 0644]
lib/Stem/ChatLabel.pm [new file with mode: 0644]
lib/Stem/Class.pm [new file with mode: 0644]
lib/Stem/Codec.pm [new file with mode: 0644]
lib/Stem/Codec/Data/Dumper.pm [new file with mode: 0644]
lib/Stem/Codec/Storable.pm [new file with mode: 0644]
lib/Stem/Codec/YAML.pm [new file with mode: 0644]
lib/Stem/Conf.pm [new file with mode: 0644]
lib/Stem/Console.pm [new file with mode: 0644]
lib/Stem/Cron.pm [new file with mode: 0644]
lib/Stem/DBI.pm [new file with mode: 0644]
lib/Stem/Debug.pm [new file with mode: 0644]
lib/Stem/Demo/CLI.pm [new file with mode: 0644]
lib/Stem/Demo/World.pm [new file with mode: 0644]
lib/Stem/Event.pm [new file with mode: 0644]
lib/Stem/Event/EventPM.pm [new file with mode: 0644]
lib/Stem/Event/Perl.pm [new file with mode: 0644]
lib/Stem/Event/Queue.pm [new file with mode: 0644]
lib/Stem/Event/Signal.pm [new file with mode: 0644]
lib/Stem/Event/Tk.pm [new file with mode: 0644]
lib/Stem/Event/Wx.pm [new file with mode: 0644]
lib/Stem/File.pm [new file with mode: 0644]
lib/Stem/Gather.pm [new file with mode: 0644]
lib/Stem/Hub.pm [new file with mode: 0644]
lib/Stem/Id.pm [new file with mode: 0644]
lib/Stem/Inject.pm [new file with mode: 0644]
lib/Stem/Load/Driver.pm [new file with mode: 0644]
lib/Stem/Load/Ticker.pm [new file with mode: 0644]
lib/Stem/Log.pm [new file with mode: 0644]
lib/Stem/Log/Entry.pm [new file with mode: 0644]
lib/Stem/Log/File.pm [new file with mode: 0644]
lib/Stem/Log/Tail.pm [new file with mode: 0644]
lib/Stem/Msg.pm [new file with mode: 0644]
lib/Stem/Packet.pm [new file with mode: 0644]
lib/Stem/Portal.pm [new file with mode: 0644]
lib/Stem/Proc.pm [new file with mode: 0644]
lib/Stem/Route.pm [new file with mode: 0644]
lib/Stem/SockMsg.pm [new file with mode: 0644]
lib/Stem/Socket.pm [new file with mode: 0644]
lib/Stem/Switch.pm [new file with mode: 0644]
lib/Stem/Test/ConfTypes.pm [new file with mode: 0644]
lib/Stem/Test/Echo.pm [new file with mode: 0644]
lib/Stem/Test/Flow.pm [new file with mode: 0644]
lib/Stem/Test/PacketIO.pm [new file with mode: 0644]
lib/Stem/Test/UDP.pm [new file with mode: 0644]
lib/Stem/Trace.pm [new file with mode: 0644]
lib/Stem/TtySock.pm [new file with mode: 0644]
lib/Stem/UDPMsg.pm [new file with mode: 0644]
lib/Stem/Util.pm [new file with mode: 0644]
lib/Stem/Vars.pm [new file with mode: 0644]
lib/Stem/WorkQueue.pm [new file with mode: 0644]
sessions/backend.pl [new file with mode: 0755]
sessions/client.pl [new file with mode: 0755]
sessions/mid_event.pl [new file with mode: 0755]
sessions/mid_event_async.pl [new file with mode: 0755]
t/cell/flow.t [new file with mode: 0644]
t/event/event.t [new file with mode: 0644]
t/event/event_test.pl [new file with mode: 0644]
t/event/gtk.t [new file with mode: 0644]
t/event/perl.t [new file with mode: 0644]
t/event/poe.t [new file with mode: 0644]
t/event/qt.t [new file with mode: 0644]
t/event/tk.t [new file with mode: 0644]
t/event/wx.t [new file with mode: 0644]
t/io/packet.t [new file with mode: 0644]
t/socket/SockFork.pm [new file with mode: 0644]
t/socket/plain.t [new file with mode: 0644]
t/socket/plain_fork.t [new file with mode: 0644]
t/socket/ssl_fork.t [new file with mode: 0644]
t/socket/udp.t [new file with mode: 0644]

diff --git a/Build.PL b/Build.PL
new file mode 100644 (file)
index 0000000..35743fb
--- /dev/null
+++ b/Build.PL
@@ -0,0 +1,33 @@
+use strict;
+use warnings ;
+
+use Config;
+use File::Spec;
+
+use BuildStem ;
+
+my $is_win32 = ( $^O =~ /Win32/) ? 1 : 0 ;
+
+my %requires ;
+
+my $version_from = File::Spec->catfile( File::Spec->curdir, 'lib', 'Stem.pm' );
+
+my $build = BuildStem->new(
+       module_name             => 'Stem',
+       dist_version_from       => $version_from,
+       requires                => \%requires,
+       license                 => 'gpl',
+       dynamic_config          => 1,
+       recursive_test_files    => 1,
+       create_makefile_pl      => 'passthrough'
+);
+
+# since we are making a fresh Build script, delete any older stem config file
+# so Build will create a new one.
+
+my $conf_pm_file = $build->config_pm_path() ;
+unlink $conf_pm_file ;
+
+$build->create_build_script() ;
+
+exit ;
diff --git a/BuildStem.pm b/BuildStem.pm
new file mode 100644 (file)
index 0000000..3711f98
--- /dev/null
@@ -0,0 +1,852 @@
+package BuildStem ;
+
+use strict;
+use warnings qw( all );
+
+use Carp ;
+use Config;
+use File::Path ;
+use File::Spec ;
+
+use lib 'lib' ;
+use base 'Module::Build' ;
+
+$ENV{HARNESS_DEBUG} = 1 ;
+$ENV{HARNESS_VERBOSE} = 1 ;
+
+# this is the common env values to control running stem stuff in the
+# build directory.
+
+my $env =
+       'PATH=blib/bin:blib/demo:$PATH PERL5LIB=blib/lib STEM_CONF_PATH=conf' ;
+
+my %env = (
+       PATH    => "blib/bin:blib/demo:$ENV{PATH}",
+       PERL5LIB => 'blib/lib',
+       STEM_CONF_PATH  => 'conf',
+) ;
+
+local( @ENV{ keys %env } ) = values %env ;
+
+
+my ( @manifest_lines ) ;
+
+eval {
+       require Stem::InstallConfig
+} ;
+my $conf = \%Stem::InstallConfig::Config ;
+
+my $is_win32 = ( $^O =~ /Win32/) ? 1 : 0 ;
+
+my $default_stem_path = $is_win32 ?
+               '/stem' :
+               File::Spec->catfile(
+                       File::Spec->rootdir, qw( usr local stem ) ) ;
+
+my $default_conf_path = File::Spec->catfile( $default_stem_path, 'conf' ) ;
+#my $default_tail_dir = File::Spec->catfile( File::Spec->tmpdir, 'stem_tail' );
+
+my %defaults = (
+       bin_path        => $Config{bin},
+       run_stem_path   => File::Spec->catfile( $Config{bin}, 'run_stem' ),
+       perl_path       => $Config{perlpath},
+       conf_path       => $default_conf_path,
+       prefix          => $Config{prefix},
+#      tail_dir        => $default_tail_dir,
+       build_demos     => ! $is_win32,
+       install_demos   => ! $is_win32,
+       install_ssfe    => ! $is_win32,
+       %{$conf}
+);
+
+################
+# these are the top level action handlers. ACTION_foo gets called when you do
+# 'Build foo' on the command line
+################
+
+sub ACTION_build {
+
+       my ( $self ) = @_ ;
+
+       $self->query_for_config() ;
+
+       $self->SUPER::ACTION_build() ;
+
+#      $self->build_bin() ;
+}
+
+sub ACTION_test {
+
+       my ( $self ) = @_ ;
+
+       local( @ENV{ keys %env } ) = values %env ;
+
+       $self->depends_on('build');
+
+       $self->SUPER::ACTION_test() ;
+}
+
+sub ACTION_install {
+
+       my ( $self ) = @_ ;
+
+       $self->install_config_files() ;
+#      $self->install_ssfe() ;
+
+       $self->SUPER::ACTION_install() ;
+}
+
+sub ACTION_run { 
+
+       my ( $self ) = @_ ;
+
+       $self->depends_on('build');
+
+       my $run_cmd = $self->{'args'}{'cmd'} || '' ;
+
+       $run_cmd or die "Missing cmd=name argument" ;
+
+       my $cmd = "$env $run_cmd" ;
+#      print "CMD: $cmd\n" ;
+
+       system $cmd ;
+}
+
+sub ACTION_run_stem { 
+
+       my ( $self ) = @_ ;
+
+       $self->depends_on('build');
+
+       my $conf = $self->{'args'}{'conf'} || '' ;
+
+       $conf or die "Missing conf=name argument" ;
+
+       my $cmd = "$env run_stem $conf" ;
+#      print "DEMO: $cmd\n" ;
+
+       system $cmd ;
+}
+
+
+sub run_demo { 
+
+       my ( $self ) = @_ ;
+
+       $self->depends_on('build');
+
+       my $cmd = "$env $self->{action}_demo" ;
+       print "DEMO: $cmd\n" ;
+       system $cmd ;
+}
+
+
+sub ACTION_tail {
+
+       mkdir 'tail' ;
+       
+       unlink <tail/*> ;
+
+       goto &run_demo ;
+}
+
+*ACTION_chat = \&run_demo ;
+*ACTION_chat2 = \&run_demo ;
+*ACTION_inetd = \&run_demo ;
+
+sub ACTION_update_pod {
+
+       my( $self ) = @_ ;
+
+       my @manifest_sublist = $self->grep_manifest( qr/\.pm$/ ) ;
+
+       @manifest_sublist = grep /Codec/, @manifest_sublist ;
+
+print join( "\n", @manifest_sublist ), "\n" ;
+       
+       system( "bin/spec2pod.pl @manifest_sublist" ) ;
+
+       return;
+}
+
+# grep through all matched files
+# command line args:
+#      files=<regex> (default is all .pm files)
+#      re=<regex>
+
+sub ACTION_grep {
+
+       my( $self ) = @_ ;
+
+       my $args = $self->{'args'} ;
+
+       my $file_regex = $args->{ files } || qr/\.pm$/ ;
+       my $grep_regex = $args->{ re } or die "need grep regex" ; 
+
+       my @manifest_sublist = $self->grep_manifest( $file_regex ) ;
+
+       local( @ARGV ) = @manifest_sublist ;
+
+       while( <> ) {
+
+               next unless /$grep_regex/ ;
+
+               print "$ARGV:$. $_"
+       }
+       continue {
+
+               close ARGV if eof ;
+       }
+
+       return;
+}
+
+# ACTION: grep through MANIFEST
+# command line args:
+#      files=<regex>
+#
+# do we need this action?
+# 
+
+sub ACTION_grep_manifest {
+
+       my( $self ) = @_ ;
+
+       my @manifest_sublist = $self->grep_manifest() ;
+
+       print join( "\n", @manifest_sublist ), "\n" ;
+       return;
+}
+
+# ACTION: count source lines
+# command line args:
+#      files=<regex> (defaults to all .pm and bin files
+#
+# do we need this action?
+
+sub ACTION_lines {
+
+       my( $self ) = @_ ;
+
+       my $args = $self->{'args'} ;
+       my $file_regex = $args->{ files } || qr/\.pm$|^bin/ ;
+
+       my @manifest_sublist = $self->grep_manifest( $file_regex ) ;
+
+       system( "./util/lines @manifest_sublist" ) ;
+
+       return;
+}
+
+# build a distro and scp to stemsystems.com
+
+sub ACTION_ftp {
+
+       my ( $self ) = @_ ;
+
+       my $dist_tar = $self->dist_dir() . '.tar.gz' ;
+
+       unlink $dist_tar ;
+
+       $self->ACTION_dist() ;
+
+       system "scp $dist_tar stemsystems.com:www/" ;
+}
+
+
+# this sub overrides the find_test_files method in Module::Build
+
+sub find_test_files {
+
+       my ($self) = @_;
+
+       my $test_args = $self->{ args }{ tests } ;
+
+       my @tests = $test_args ? split( ':', $test_args ) :
+                   $self->grep_manifest( qr/\.t$/ ) ;
+
+       return \@tests ;
+}
+
+sub process_script_files {
+       my( $self ) = @_ ;
+
+       my @scripts = $self->grep_manifest( qr{^bin/} ) ;
+
+#print "SCR @scripts\n" ;
+       foreach my $file ( @scripts ) {
+
+               my $bin_dir = File::Spec->catdir(
+                               $self->blib,
+                               $file =~ /_demo$/ ? 'demo' : 'bin' ) ;
+
+               File::Path::mkpath( $bin_dir );
+  
+               my $result = $self->copy_if_modified(
+                       $file, $bin_dir, 'flatten') or next;
+
+#print "COPY $file\n" ;
+               $self->fix_run_stem($result);
+               $self->fix_demos($result);
+               $self->fix_shebang_line($result);
+               $self->make_executable($result);
+       }
+}
+
+sub fix_run_stem {
+
+       my( $self, $file ) = @_ ;
+
+       return unless $file =~ m{/run_stem$} ;
+
+       my $text = read_file( $file ) ;
+
+       $text =~ s/'conf:.'/'$conf->{'conf_path'}'/ if $conf->{'conf_path'} ;
+
+       write_file( $file, $text ) ;
+}
+
+sub fix_demos {
+
+       my( $self, $file ) = @_ ;
+
+       return unless $file =~ /_demo$/ ;
+
+       my $text = read_file( $file ) ;
+
+       $conf->{xterm_path} ||= 'NOT FOUND' ;
+       $conf->{telnet_path} ||= 'NOT FOUND' ;
+
+       $text =~ s[xterm][$conf->{xterm_path}]g;
+       $text =~ s[telnet][$conf->{telnet_path}]g;
+
+       write_file( $file, $text ) ;
+}
+
+# MANIFEST helper subs
+
+sub grep_manifest {
+
+       my( $self, $file_regex ) = @_ ;
+
+       $file_regex ||= $self->{ args }{ files } || qr/.*/ ;
+
+       manifest_load() ;
+
+       return grep( /$file_regex/, @manifest_lines ) ;
+}
+
+sub manifest_load {
+
+       return if @manifest_lines ;
+
+       @manifest_lines = grep ! /^\s*$|^\s*#/, read_file( 'MANIFEST' ) ;
+
+       chomp @manifest_lines ;
+
+       return ;
+}
+
+#################################
+
+sub query_for_config {
+
+       my( $self ) = @_ ;
+
+       return if $defaults{ 'config_done' } ;
+
+       print <<'EOT';
+
+Building Stem
+
+This script will ask you various questions in order to properly
+configure, build and install Stem on your system.  Whenever a question
+is asked, the default answer will be shown inside [brackets].
+Pressing enter will accept the default answer. If a choice needs to be
+made from a list of values, that list will be inside (parentheses).
+
+If you have already configured Stem in a previous build, you can put
+use_defaults=1 on the Build command line and you won't be prompted for
+any answers and the previous settings will be used.
+
+If you want to force a new build, run Build clean.
+
+EOT
+
+       $self->get_path_config() ;
+       $self->get_demo_config() ;
+
+       $defaults{ 'config_done' } = 1 ;
+
+       $self->write_config_pm() ;
+}
+
+
+my $package = 'Stem::InstallConfig' ;
+
+sub config_pm_path {
+
+       return File::Spec->catfile(
+               File::Spec->curdir, 'lib', split( /::/, $package) ) . '.pm' ;
+
+}
+
+sub write_config_pm {
+
+       my ( $self ) = @_ ;
+
+       my $config = Data::Dumper->Dump(
+               [\%defaults],
+               ["*${package}::Config"]
+       );
+
+       my $conf_pm_file = $self->config_pm_path() ;
+
+       $self->add_to_cleanup( $conf_pm_file ) ;
+
+       write_file( $conf_pm_file, <<EOT ) ;
+
+# DO NOT EDIT
+# this file is generated by running Build build
+
+package $package ;
+
+$config
+1 ;
+EOT
+
+}
+
+
+sub get_path_config {
+
+       my( $self ) = @_ ;
+
+#      $self->query_config_value( <<'EOT', 'perl_path' );
+
+# Stem has several executable Perl programs and demonstration scripts
+# and they need to have the correct path to your perl binary.
+
+# What is the path to perl?
+# EOT
+
+#      $self->query_config_value( <<'EOT', 'bin_path' );
+
+# Those Stem executables need to be installed in a directory that is in your
+# shell $PATH variable.
+
+# What directory will have the Stem executables?
+# EOT
+
+       $self->query_config_value( <<'EOT', 'conf_path' );
+
+Stem configuration files are used to create and initialize Stem Cells
+(objects). Stem needs to know the list of directories to search to
+find its configurations files.
+
+Note that the default has a single absolute path. You can test Stem
+configurations easily setting this path when executing run_stem. You
+can override or modify the path time with either a shell environment
+variable or on the command line of run_stem. See the documentation on
+run_stem for how so do this.
+
+The first directory in the list is where the standard Stem
+configuration files will be installed.
+
+Enter a list of absolute directory paths separated by ':'.
+
+What directories do you want to search for Stem configuration files?
+EOT
+
+       return ;
+}
+
+sub get_demo_config {
+
+       my( $self ) = @_ ;
+
+# don't even bother if win32
+
+       return if $is_win32 ;
+
+#      $self->get_config_boolean( <<'EOT', 'build_demos' );
+
+# Stem comes with several demonstration scripts. After building them,
+# they can be run from the main directory by the Build script: ./Build
+# chat, Build inetd, etc.  Do you want to build the demos?
+# EOT
+
+#      return unless $defaults{build_demos};
+
+# all the demos need xterm
+
+       $self->get_xterm_path();
+       $self->get_telnet_path();
+       return unless -x $defaults{xterm_path} && -x $defaults{telnet_path};
+
+#      $self->query_config_value( <<'EOT', 'tail_dir' );
+
+# The tail demo script needs a temporary working directory.  Enter the
+# path to a directory to use for this purpose.  If it does not exist,
+# this directory will be created.
+# EOT
+
+       $self->get_config_boolean( <<'EOT', 'install_ssfe' );
+
+ssfe (Split Screen Front End) is a compiled program optionally used by
+the Stem demonstration scripts that provides a full screen interface
+with command line editing and history. It is not required to run Stem
+but it makes the demonstrations easier to work with and they look much
+nicer. To use ssfe add the '-s' option when you run any demonstration
+script. You can also use ssfe for your own programs.  Install ssfe in
+some place in your \$PATH ($conf->{'bin_path'} is where Stem executables
+are being installed) so it can be used by the demo scripts. The ssfe
+install script will do this for you or you can do it manually after
+building it.
+
+Do you want to install ssfe?
+EOT
+
+}
+
+sub get_xterm_path {
+
+       my( $self ) = @_ ;
+
+       my $xterm_path;
+
+#      unless ( $xterm_path = which_exec( 'xterm' ) ) {
+
+#              foreach my $path ( qw(
+#                      /usr/openwin/bin/xterm
+#                      /usr/bin/X11/xterm
+#                      /usr/X11R6/bin/xterm ) ) {
+
+#                      next unless -x $path;
+#                      $xterm_path = $path ;
+#                      last;
+#              }
+#      }
+
+#      if ( $xterm_path ) {
+
+#              $defaults{'xterm_path'} = $xterm_path ;
+#              print "xterm was found at '$xterm_path'\n";
+#              return ;
+#      }
+
+       $self->query_config_value( <<"EOT", 'xterm_path' );
+
+xterm was not found on this system. you can't run the demo programs
+without xterm.  Make sure you enter a valid path to xterm or some other
+terminal emulator.
+
+NOTE: If you don't have an xterm, you can still run the demo scripts
+by hand. Run a *_demo script and see what commands it issues. Take the
+part after the -e and run that command in its own terminal window.
+
+Enter the path to xterm (or another compatible terminal emulator)
+EOT
+
+}
+
+sub get_telnet_path {
+
+       my( $self ) = @_ ;
+
+       my $telnet_path;
+
+       unless ( $telnet_path = which_exec( 'telnet' ) ) {
+
+# enter a list of common places to find telnet. or delete this as it
+# will almost always be in the path
+
+               foreach my $path ( qw( ) ) {
+
+                       next unless -x $path;
+                       $telnet_path = $path ;
+                       last;
+               }
+       }
+
+       if ( $telnet_path ) {
+
+               $defaults{'telnet_path'} = $telnet_path ;
+               print "telnet was found at '$telnet_path'\n";
+               return ;
+       }
+
+       $self->query_config_value( <<"EOT", 'telnet_path' );
+
+telnet was not found on this system. you can't run the demo programs
+without telnet.  Make sure you enter a valid path to telnet or some other
+terminal emulator.
+
+NOTE: If you don't have an telnet, you can still run the demo scripts
+by hand. Run a *_demo script and see what telnet commands it
+issues. The run those telnet commands using your telnet or another
+similar program.
+
+Enter the path to telnet (or another compatible terminal emulator)
+EOT
+
+}
+
+sub install_config_files {
+
+       my ( $self ) = @_ ;
+
+       my ( $conf_path ) = split /:/, $conf->{conf_path} ;
+
+       mkpath( $conf_path, 1, 0755 ) unless -d $conf_path ;
+
+       my @config_files = $self->grep_manifest( qr{^conf/.+\.stem$} ) ;
+
+       foreach my $conf_file (@config_files) {
+
+               $conf_file =~ s{conf/}{} ;
+
+               my $out_file = File::Spec->catfile( $conf_path, $conf_file );
+
+               print "Installing config file: $out_file\n";
+
+               my $in_file = File::Spec->catfile(
+                           File::Spec->curdir(), 'conf', $conf_file );
+
+               my $conf_text = read_file($in_file);
+
+               if ( $conf_file eq 'inetd.stem' ) {
+
+                       my $quote_serve = File::Spec->catfile(
+                               $conf->{bin_path}, 'quote_serve' );
+
+                       $conf_text =~ s[path\s+=>\s+'bin/quote_serve',]
+                                      [path\t\t=> '$quote_serve',];
+               }
+#              elsif ( $conf eq 'monitor.stem' || $conf eq 'archive.stem' ) {
+
+#                      $conf_text =~ s[path'\s+=>\s+'tail]
+#                                     [path'\t\t=> '$conf->{tail_dir}]g ;
+#              }
+
+               write_file( $out_file, $conf_text );
+       }
+}
+
+
+sub install_ssfe {
+
+       my ( $self ) = @_ ;
+
+       return unless $conf->{install_stem_demos} &&
+                     $conf->{install_ssfe} ;
+
+       print <<'EOT';
+
+Installing ssfe.
+
+This is not a Stem install script and it will ask its own
+questions. It will execute in its own xterm (whatever was configured
+earlier) to keep this install's output clean. The xterm is kept open
+with a long sleep call and can be exited by typing ^C.
+
+EOT
+
+#########
+# UGLY
+#########
+
+    system <<'EOT';
+xterm -e /bin/sh -c 'chdir extras ;
+tar zxvf sirc-2.211.tar.gz ;
+chdir sirc-2.211 ;
+./install ;
+sleep 1000 ;'
+EOT
+
+    print "\nInstallation of ssfe is done\n\n";
+}
+
+#########################################################
+# this sub builds the exec scripts in bin and puts them into blib/bin
+# for local running or later installation
+
+# sub build_bin {
+
+#      my ( $self ) = @_ ;
+
+#      my @bin_scripts = $self->grep_manifest( qr{^bin/} ) ;
+
+#      foreach my $bin_file ( @bin_scripts ) {
+
+# #print "BIN $bin_file\n" ;
+
+#              my $bin_text = read_file( $bin_file ) ;
+
+#              $bin_file =~ s{bin/}{} ;
+
+# # fix the shebang line
+
+#              $bin_text =~ s{/usr/local/bin/perl}{$conf->{'perl_path'}} ;
+
+#              my $bin_dir ;
+
+#              if ( $bin_file =~ /_demo$/ ) {
+
+#                      next unless $conf->{build_demos} ;
+
+#                      $bin_dir = 'demo' ;
+
+# # fix the location of xterms in the demo scripts
+
+#                      $bin_text =~ s[xterm][$conf->{xterm_path}]g;
+#                      $bin_text =~ s[telnet][$conf->{telnet_path}]g;
+
+# # fix the default config search path in run_stem
+#              }
+#              else {
+
+#                      $bin_dir = 'bin' ;
+
+# # fix the default config search path in run_stem
+
+#                      if ( $bin_file eq 'run_stem' ) {
+#                              $bin_text =~
+#                                      s/'conf:.'/'$conf->{'conf_path'}'/ ;
+#                      }
+#              }
+
+# #            elsif ( $bin_file eq 'tail_demo' ) {
+# #                    $bin_text =~ s['tail']['$conf->{tail_dir}'];
+# #            }
+
+# # write the built script into the blib/ dir
+
+#              my $out_file = File::Spec->catfile( 'blib',
+#                                                  $bin_dir,
+#                                                  $bin_file
+#              );
+
+#              mkdir "blib/$bin_dir" ;
+#              print "Building executable script: $out_file\n";
+#              write_file( $out_file, $bin_text );
+#              chmod 0755, $out_file;
+#      }
+# }
+
+#############################################################
+
+# this sub searches the path for the locations of an executable
+
+sub which_exec {
+
+       my ( $exec ) = @_;
+
+       foreach my $path_dir ( split /[:;]/, $ENV{PATH} ) {
+
+               my $exec_path = File::Spec->catfile( $path_dir, $exec );
+               return $exec_path if -x $exec_path ;
+       }
+
+       return;
+}
+
+# the sub searches a list of dir paths to find the first one that
+# exists with a prefix dir
+
+# UNUSED FOR THE MOMENT
+
+# sub which_dir {
+
+#      my ( $prefix, @dirs ) = @_;
+
+#      foreach my $subdir ( @dirs ) {
+
+#              my $dir = File::Spec->catfile( $prefix, $subdir );
+#              return $dir if -x $dir;
+#      }
+
+#      return;
+# }
+
+#############################################################
+
+# these subs handle querying for a user answer. it uses the key to
+# find a current value in the defaults and prompt for another value
+# if 'use_defaults' is set on the command line, then no prompting will be done
+
+sub query_config_value {
+
+       my( $self, $query, $key ) = @_ ;
+
+       my $default = $self->{args}{$key} ;
+
+       $default = $defaults{ $key } unless defined $default ;
+
+       $defaults{ $key } = ( $self->{args}{use_defaults} ) ?
+               $default :
+               $self->prompt( edit_query( $query, $default ), $default ) ;
+}
+
+sub get_config_boolean {
+
+       my( $self, $query, $key ) = @_ ;
+
+       my $default = $self->{args}{$key} ;
+
+       $default = $defaults{ $key } unless defined $default ;
+       $default =~ tr/01/ny/ ;
+
+       $defaults{ $key } = ( $self->{args}{use_defaults} ) ?
+               $default :
+               $self->y_n( edit_query( $query, $default ), $default ) ;
+}
+
+sub edit_query {
+
+       my ( $query, $default ) = @_ ;
+
+       chomp $query ;
+
+       $default ||= '' ;
+
+       my $last_line = (split /\n/, $query)[-1] ;
+
+       if ( length( $last_line ) + 2 * length( $default ) > 70 ) {
+
+               $query .= "\n\t" ;
+       }
+
+       return $query ;
+}
+
+# low level file i/o subs. should be replaced with File::Slurp. stem
+# should depend on it
+
+
+sub read_file {
+
+       my ( $file_name ) = @_ ;
+
+       local( *FH );
+
+       open( FH, $file_name ) || croak "Can't open $file_name $!";
+
+       return <FH> if wantarray;
+
+       read FH, my $buf, -s FH;
+       return $buf;
+}
+
+sub write_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+
+       open( FH, ">$file_name" ) || croak "can't create $file_name $!" ;
+
+       print FH @_ ;
+}
+
+1 ;
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644 (file)
index 0000000..2fdc692
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,189 @@
+Stem Version 0.11
+=================
+Fri Dec  1 03:50:58 EST 2006
+
+
+* The Stem event loop is layered and can easily wrap other event
+  loops. Supported event loops include Event.pm, perl/tk and wxperl and
+  a pure Perl event Loop. See Stem::Event.pm and Stem::Event::EventPM.pm
+  for more.  Other event loops that need wrapping include POE, Gtk and Qt.
+
+* There is a pure Perl event Loop so Stem now runs on windows or on any
+  system where Event.pm isn't installed. 
+
+* Full support for SSL on all socket related objects and cells.
+  Converting from a plain socket to an SSL socket just requires adding
+  the SSL arguments to the Stem::Socket contruction. All Stem modules
+  that use sockets use Stem::Socket which in turn uses IO::Socket::SSL
+  (and that needs openssl). If that module is not installed, its test
+  will be skipped.
+
+* There is a Stem::UDPMsg cell that provides a UDP socket client and/or
+  server.
+
+* The new Stem::Cell::Flow module provides a way for Stem cells to
+  handle a mix of sync/async (local/remote) method calls. You can create
+  a logic flow with if/else/while command in a simple mini-language.
+  This allows a cell to do complex operations such as accessing a DB
+  (via Stem::DBI) or fetching web pages and mix that with local method
+  calls that crunch the data. Normally this would entail a complex state
+  machine or having each method know the next one to call but
+  Stem::Cell::Flow allows you to convert that to a much simpler logic
+  flow. See the test script t/cell/flow.t and the lib/Stem/Test/Flow.pm
+  module for more on this. It requires Parse::RecDescent to be installed
+  (the test will be skipped if it is not found).
+
+* A bunch of new tests were added:
+
+       Event loops
+       Sockets (both with and without SSL)
+       Stem::Cell::Flow
+
+* There is a brand new install based on Module::Build. A Makefile.PL is
+  provided but it is a wrapper around the Module::Build installer. Stem
+  can now be installed with CPAN.pm
+
+* There is more pod (and more is needed). All contructor attributes are
+  now automatically updated to pod by the stem2pod utility. That script
+  also inserts pod templates for all methods. This will make it easier
+  to add pod and keep it up to date.
+
+* Cleaned up the INSTALL, README and other top level documents.
+
+* Many more changes than I have room to write in this file.
+
+Stem Version 0.10
+=================
+Nov-11-2002
+
+* The version number has been bumped to 0.10 because of the large number
+  of changes and the major improvements in reliability and speed.
+
+* Renamed Stem::TtyMsg to Stem::Console. Now it doesn't need any
+  arguments to be initialized. A Stem envioronment variable can be set
+  which will disable it. All the configuration files and demo scripts
+  have been updated to use it. The old TtyMsg module has been deleted.
+
+* Added Stem::Test::Echo.pm and Stem::Load::Driver.pm. They support
+  benchmarking of basic message passing in multiple modes.
+
+* Stem::Msg now only accepts the string format of message
+  addresses. This speeds up message creation and simplifies message
+  handling and delivery.
+
+* Stem::Class now supports attribute type checking and
+  conversion. Supported types include boolean, list, hash, LoL (and
+  friends), object and handle.
+
+* Stem::Conf has better error reporting. The Cell name and class are now
+  printed with the error.
+
+* Added the reply_type field to messages. This is used to make simple
+  state machines with messages (used in the work sequencing system).
+
+* Stem::Portal has been improved and now use the Stem::Packet module to
+  handle its buffering.
+
+* Added Stem::Packet and Stem::Codec::*. These modules support
+  serializing (marshalling) Stem data for use in pipes and files. The
+  codec to be used can be selected in the configuration file. This is
+  now used in Stem::Portal and all worker mode operations. Current codec
+  include Perl (Data::Dumper/eval), YAML, and Storable.
+
+* Split Log.pm into Log.pm and Log/Entry.pm. Moved Stem::LogTail.pm to 
+  Stem::Log::Tail.pm. Added Stem::Log::File which handles physical log
+  files including rotation and archiving.
+
+* Added Stem::Trace.pm which gives modules a simple way to inject log
+  entries during program execution. It allows for creation of customized
+  Trace functions which can have defaults and different calling styles.
+
+* Split Stem::Cell.pm into multiple files to make it easier to
+  maintain. These include Stem::Cell::Sequence.pm, Stem::Cell::Clone.pm,
+  Stem::Cell::Pipe.pm and Stem::Cell::Work.pm 
+
+* Added the Stem::DBI module which is a Stem message based front end to
+  DBI. This Cell can be configured with the all the SQL needed for your
+  application which is shared by all the Cells that use it. It can be
+  run in its own Stem Hub (process) thereby providing non-blocking
+  access to the DB from other Hubs. A farm of these proxies can be
+  created and managed by a WorkQueue Cell which will support parallel DB
+  access from one or more Hubs.
+
+* Added work sequencing support in Stem::Cell. This allows any Cell to
+  call local or remote methods and to manage their flow control. This is
+  an interim version that will be superseded by a new mini-language that
+  will be easier to code and will support more complex flow
+  (e.g. if/else/while). This is a very powerful feature that simplifies
+  complex state operations to simple flow control statements.
+
+* Added the Stem::WorkQueue module which allows a set of work request
+  messages to be distributed to a set of worker Cells. Modified
+  Stem::Cell to support a cell to request a new work message.
+
+* Created worker mode where an object (or data structure) gets
+  sent to a Cell via a message. The Cell can then write the object to a
+  socket or process or crunch it. The Cell then gets back the object
+  (presumably modified) and sends it back to the originator of the work
+  request. This is done in Stem::Cell and used in Stem::Proc,
+  Stem::Sock::Msg and Stem::DBI. 
+
+* Created worker ready mode where a Cell can send out a message stating
+  it can receive a work message. Typically this will go to a WorkQueue
+  Cell. This message is sent out when the Cell starts up or after it
+  completes a previous work request.
+
+* Added Stem::Boot.pm which will run a set of external programs and
+  monitor them. Their output can be logged and they can be
+  restarted. Other options (for each program) include setting the
+  initial directory and which host to run it on (via ssh if desired).
+  The set of programs and options are loaded via a configuration file
+  specific to this module.
+
+* Added Stem::Inject.pm which has one method which will connect to
+  a Stem Hub and send it a single message. Then it will read back a
+  single message and return its data. This is meant to be used in Perl
+  programs that are not Stem driven and want to communicate with Stem
+  cells.
+
+* Updated the Stem Cell cookbook with more examples. It now shows how to
+  create class and object cells and also how to create cloneable Cells.
+
+Stem Version 0.06
+=================
+Feb-26-2002
+
+* Stem is now released under the GPL. If you want to use Stem in a
+  product that will be sold, contact us about acquiring a commercial
+  license.
+       
+* A simple and easy to use installation script is now included.  The
+  installation script allows you to have a Stem environment up and
+  running in a matter of minutes. Read the INSTALL document to learn how
+  to run this script.
+
+* There is a new set of cookbook documents with examples.  These show
+  you how to develop new Stem Cells.
+
+* Complete documentation is now included for all demonstration
+  scripts included with Stem.
+
+* Stem Message Addresses can now be in a string form as well as the hash
+  form. This simplifies creating addresses in Stem configurations. All
+  the installed configurations now use this format. See the Address
+  design notes for more on this.
+
+* The Stem::Cell module has been added. It supports generic functionality for
+  Stem Cells, including cloning, asynchronous I/O, and pipes. It
+  currently is used by the Stem::Proc and Stem::SockMsg modules. All
+  future Cells that need those services can use this module.
+
+* The modules Stem::Proc and Stem::SockMsg have been rewritten to use
+  Stem::Cell.  They are much shorter and simpler now that the common
+  Cell functions are handled by Stem::Cell.
+
+* A new module, Stem::Gather, has been added. It allows you to
+  synchronize multiple asynchronous events. It triggers a callback when
+  all of its required tokens have been 'gathered'.
+
+* Bug fixes and general system improvements.
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/CREDITS b/CREDITS
new file mode 100644 (file)
index 0000000..a83b226
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,3 @@
+People besides Uri who've contributed to Stem code and docs:
+
+Dave Rolsky - various code, bug fixes, docs, etc.
diff --git a/Cookbook/World1.pm b/Cookbook/World1.pm
new file mode 100644 (file)
index 0000000..fbd8d45
--- /dev/null
@@ -0,0 +1,82 @@
+package World1;
+
+# This is  class level cell with no constructor or alias registration.
+# It has one simple command message handler
+
+sub hello_cmd {
+
+    return "Hello world!\n";
+}
+
+=head1 Stem Cookbook - World1
+
+=head1 NAME
+
+World1 - A minimal class level B<Stem> cell.
+
+=head1 DESCRIPTION
+
+This is the simplest possible B<Stem> class level cell.  It contains a
+single method named C<world_cmd>.  Because this method ends in C<_cmd>
+it is capable of being invoked remotely via a command message and have
+its return value sent back as a response message to the sender, which
+in this example is the Stem::Console cell.
+
+=head2 COMMAND METHOD
+
+The following code snippet in the F<World1> class cell is the method
+that will receive a hello command from a remote sender.
+
+    package World1;
+
+    sub hello_cmd {
+
+       return "Hello world!\n";
+    }
+
+B<Stem> makes the creation of Command message handling methods very
+I<easy>.  Any return with defined data will automatically be sent back
+to the sender of this command in a response type message. In the
+method above we return the "Hello world!\n" string which will get printed on 
+the console.
+
+For more information on how a message is routed to its destination
+cell in B<Stem> please see the F<Stem Messaging Design Notes>.
+
+=head1 THE CONFIGURATION FILE
+
+The following B<Stem> configuration file is used to bring a
+World1 class level cell into existance in the B<Stem> environment.
+
+-
+ class: Stem::Console
+-
+ class: World1
+
+The first entry is C<Stem::Console>, a class level cell allows a user
+to manually send command messages into the B<Stem> system.  It is not
+required for this module, but it is used in this example to send
+messages to the World1 class and to print responses from it.  The
+second entry loads the C<World1> class. We can now refer to this class
+cell as I<World1> when we want to send it a message.
+
+=head1 USAGE
+
+Execute C<run_stem world> from the command line to run this configuration.
+You will be greeted with the B<StemE<gt>> prompt.  It is now
+possible to send a message manually to I<World1>.  Type the following
+command at the B<Stem> prompt:
+
+B<World1 hello>
+
+This is standard B<Stem> Console syntax, the cell address followed by
+the command name.  This will send a message world_cmd method in the
+C<World1> class cell. That method returns a value, which is converted
+into a response message addressed to Stem::Console (the originator of
+the command message), and its data will be printed on the console terminal.
+
+B<"Hello world!">
+
+=cut
+
+1 ;
diff --git a/Cookbook/World2.pm b/Cookbook/World2.pm
new file mode 100644 (file)
index 0000000..0b28496
--- /dev/null
@@ -0,0 +1,106 @@
+package World2;
+
+# This is  class level cell with no constructor or alias registration.
+# It has two command message handlers, one to get the name and one to set it.
+
+my $name = 'UNKNOWN' ;
+
+sub hello_cmd {
+
+    return "Hello world from $name\n";
+}
+
+sub name_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless $data ;
+
+       $name = ${$data} ;
+
+       return ;
+}
+
+
+=head1 Stem Cookbook - World2
+
+=head1 NAME
+
+World2 - A minimal class level B<Stem> cell with read/write data.
+
+=head1 DESCRIPTION
+
+This B<Stem> class level cell is an extension of the World1 class. It
+still has a method named C<world_cmd> that will return the stored
+name. The C<name_cmd> method takes a message and set the $name to its
+data.
+
+=head2 COMMAND METHOD
+
+The following code snippet in the F<World2> class
+cell is the method that will receive a hello command from a
+remote sender.
+
+    package World2;
+
+    sub hello_cmd {
+
+       return "Hello world!\n";
+    }
+
+B<Stem> makes the creation of Command message handling methods very
+I<easy>.  Any return with defined data will automatically be sent back
+to the sender of this command in a response type message. In the
+method above we return the "Hello world!\n" string which will get printed on 
+the console.
+
+For more information on how a message is routed to its destination
+cell in B<Stem> please see the F<Stem Messaging Design Notes>.
+
+=head1 THE CONFIGURATION FILE
+
+The following B<Stem> configuration file is used to bring a
+World2 class level cell into existance in the B<Stem> environment.
+
+[
+        class  =>      'Stem::Console',
+],
+[
+        class  =>      'World2',
+]
+
+The first entry is C<Stem::Console>, class level cell allows a user to
+manually send command messages into the B<Stem> system.  It is not
+required for this module, but it is used in this example to send
+messages to the World2 class and to print responses from it.  The
+second entry loads the C<World2> class. We can now refer to this class
+cell as I<World2> when we want to send it a message.
+
+=head1 USAGE
+
+Execute C<run_stem world> from the command line to run this configuration.
+You will be greeted with the B<StemE<gt>> prompt.  It is now
+possible to send a message manually to I<World2>.  Type the following
+command at the B<Stem> prompt:
+
+B<World2 hello>
+
+This is standard B<Stem> Console syntax, the cell address followed by
+the command name.  This will send a message world_cmd method in the
+C<World2> class cell. That method returns a value, which is converted
+into a response message addressed to Stem::Console (the originator of
+the command message), and its data will be printed on the console terminal.
+
+B<"Hello world!">
+
+=head1 SEE ALSO
+
+F<Stem Cookbook Part 2>
+
+F<Stem Cookbook Part 3>
+
+=cut
+
+1;
diff --git a/Cookbook/World3.pm b/Cookbook/World3.pm
new file mode 100644 (file)
index 0000000..d0c58a3
--- /dev/null
@@ -0,0 +1,235 @@
+package World3 ;
+
+use strict;
+
+# This is the specification table that describes the attributes for
+# this object. The only attribute is the name of the planet and it 
+# defaults to 'X'
+
+my $attr_spec =
+[
+       {
+               'name'          => 'planet',
+               'default'       => 'X',
+       },
+];
+
+# The new method constructs the object which is returned to the
+# configuration system where it will be registered.
+
+sub new {
+
+       my( $class ) = shift ;
+
+# The call to parse_args takes the attribute specification and the
+# configuration arguments and creates a cell object
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+
+       return ( $self );
+}
+
+# This command method is similar to the one in World1 except we
+# we use the object argument and return the name from that object.
+
+sub hello_cmd {
+
+       my( $self ) = @_;
+
+       return "Hello world from $self->{'planet'}!\n";
+}
+
+=head1 Stem Cookbook - World3
+
+=head1 NAME
+
+World2 - A simple object level B<Stem> cell.
+
+=head1 DESCRIPTION
+
+This cell is an extension of the B<World1> cell.  In this example,
+instead of a single class cell with a fixed response value, we now can
+create multiple cells (registered objects) each with their own private
+data. The world_cmd method will return the planet's name stored in the
+cell.
+
+=head1 CREATING THE CELL
+
+This cell illustrates the basic way to construct objects in Stem.
+
+=over 4
+
+=item *
+
+A specification table is required to describe the allowed attributes
+of the object. This is a list of hashes with each hash describing one
+attribute. It is usually defined in a file lexical variable commonly
+named $attr_spec which is assigned an anonymous list of attribute
+descriptions. The fields that describe the attributes are defined in
+the F<Stem::Class> module.
+
+=item *
+
+An object constructor is called and is passed a list of key value
+arguments. This class method can be called via a configuration (which
+uses default name of 'new') or from any Stem code. The constructor
+passes its attribute specification table and the passed arguments to
+the Stem::Class::parse_args routine which returns the new object The
+constructor method checks if an error happened by seeing if that
+returned value is an object (ref is true) or else it must be an error
+string. Any error string is returned to the caller of this
+constructor. This is the standard way Stem handles errors, references
+are good values and scalars (error strings) are bad. This propogation
+of error strings up the call stack is consistantly used in all Stem
+modules. After a successful construction of an object, the constructor
+method can do additional work and then it returns the object. The
+caller of the constructor will also check for an object or error
+string. The common case of a configuration file constructing a Stem
+object cell with register a good cell or print the error string and
+die.
+
+=back
+
+=head2 ATTRIBUTE SPECIFICATION
+
+Object cells require an attribute specification that describes
+the information we want to exist independently in each object
+cell when it is created.  The following is the attribute specification
+used in C<World2>:
+
+$attr_spec =
+[
+       {
+             'name'       => 'planet',
+             'default'    => 'X',
+
+       },
+
+];
+
+This specification indicates that this cell has an attribute
+named I<planet>.  It will default to the value of I<X> if
+this attribute is not specified in the configuration arguments
+for this cell.  Some of the attribute specification tags are I<name>,
+I<type>, I<default>, I<required>, I<class>, and I<help>.  For more
+information on cell configuration please see
+B<Stem Object and Cell Creation and Configuration Design Notes> and
+B<Stem Cell Design Notes>.
+
+=head2 OBJECT CONSTRUCTOR
+
+This is a minimal B<Stem> constructor with the usual name I<new>.  you
+can invoke any other method as a constructor from a configuration by
+using the 'method' field:
+
+sub new {
+
+    my ( $class ) = shift;
+
+    my $self = Stem::Class::parse_args( $attr_spec, @_ );
+    return $self unless ref $self ;
+
+    return ( $self );
+
+}
+
+To create a B<Stem> object cell we call the C<Stem::Class::parse_args>
+routine and pass it the object cell attribute specification and the
+rest of the arguments passed into this constructor.  The rest of the
+arguments come from the I<args> field in the configuration for this cell.
+The parse_args function then returns the newly created object to the
+caller, which is usually the configuration system but it could be any
+other code as well.  An important observation to make here is the
+B<Stem> error handling technique.  Errors, in B<Stem>, are propagated
+up the call stack bu returning an error string rather than a
+reference.  This is the typical Stem way of determining whether of not
+an error condition had occurred.  Constructors or subroutines which
+normally return objects or references will return a string value as
+an error message.  This is always checked by the caller and will usually
+be passed up the call stack until a top level subroutine handles it.
+
+=head1 CREATING THE CONFIGURATION FILE
+
+The following B<Stem> configuration file is used to bring a
+World2 object level cell into existance in the B<Stem> environment.
+
+[
+        class  =>       'Console',
+],
+
+[
+        class   =>      'World2',
+        name    =>      'first_planet',
+        args    =>      [],
+
+],
+
+[
+        class   =>      'World2',
+        name    =>      'second_planet',
+        args    =>      [
+                planet  => 'venus',
+
+        ],
+
+],
+
+
+As explained in F<World1.pm>, we create a
+C<Stem::Console> cell to allow for the creation of a Stem console
+to manually send command messages and display their responses.
+We also create two object level C<World2> cells.
+The first, we name I<first_planet> and it defaults to having its planet
+attribute set to 'first_planet'.  The second, we name I<second_planet> and set its
+planet attribute to 'venus'.
+
+Using the I<args> specifier in the cell configuration indicates
+that we are creating an I<object> cell rather than a class cell.
+It indicates to the B<Stem> cell creation environment that we
+wish to execute the constructor of the specified class to
+create an object of the class rather than using the B<Stem>
+module as a class itself.  Using object cells allow us to instantiate
+multiple objects with unique values, addressed and subsequent
+behavior.
+
+=head1 USAGE
+
+Execute C<run_stem world2> from the command line to run this
+configuration.  You will be greeted with the B<StemE<gt>> prompt.
+It is now possible to send a message manually into the system.
+
+Type the following at the B<Stem> prompt:
+
+B<reg status>
+
+This will show the status of the local B<Stem> hub.  You
+will notice the two entries for the object cells created
+by the configuration file under the object cell section.
+
+Now execute the same command as you did in F<World1>:
+
+B<first_planet hello>
+
+B<Hello, World! (from X)>
+
+B<second_planet hello>
+
+B<Hello, World! (from venus)>
+
+As in F<World1>, the above triggers the C<hello_cmd> method.  However,
+now we are triggering the C<hello_cmd> method on separate object cells
+rather than a single class cell.
+
+
+=head1 SEE ALSO
+
+F<Stem Cookbook Part 1>
+
+F<Stem Cookbook Part 3>
+
+F<World2 Module>
+
+=cut
+
+
+1;
diff --git a/Cookbook/World4.pm b/Cookbook/World4.pm
new file mode 100644 (file)
index 0000000..2ae734e
--- /dev/null
@@ -0,0 +1,179 @@
+package World4 ;
+
+use strict;
+
+my $attr_spec =
+[
+       {
+               'name'          => 'planet',
+               'default'       => 'uranus',
+       },
+];
+
+sub new {
+
+       my( $class ) = shift ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+
+       return ( $self );
+}
+
+# based on who was the receiver of the message
+# we return with the appropriate response
+
+sub hello_cmd {
+
+       my( $self ) = @_;
+
+       return "Hello world from $self->{'planet'}!\n";
+}
+
+sub name_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless $data ;
+
+       $self->{'planet'} = ${$data} ;
+
+       return ;
+}
+
+
+
+
+=head1 Stem Cookbook - World3
+
+=head1 NAME
+
+World3 - Mixing class and object B<Stem> cells.
+
+=head1 DESCRIPTION
+
+This is an extension of the B<Stem Cookbook Part 1 & Stem Cookbook Part 2> where
+we talked about the creation of B<Stem> class and object cells.  In
+this example, we take the idea of a class cell and an object cell
+and combine them into a single B<Stem> module.  We then have
+the ability of creating multiple cells (registered objects)
+with their own private data and at the same time have a
+class cell to manage a global resource.
+
+=head1 CREATING THE CELL
+
+The following lists the requirements for creating a B<Stem>
+object level cell:
+
+=over 4
+
+=item *
+
+An attribute specification
+
+=item *
+
+An object constructor
+
+=item *
+
+A class registration
+
+=back
+
+=head2 CHANGES FROM PART 1 AND PART 2
+
+Most of the code from Part 2 and Part 1 remain the same.  We keep
+the same attribute specification as well as the same object cell
+constructor (except for a slight modification, see below).
+You remember from Part 1 that we created a class level
+B<Stem> cell from the configuration file,
+
+[
+        class  =>      'World1',
+        name   =>      'solar_system',
+
+]
+
+Because we do not have an args field, it means we are creating a
+class cell.  In this example, we want a class cell to be created
+as a global resource only if an object cell is created.  If the
+class cell is supposed to manage global information for object
+cells there is no need to create one if an object cell does not
+exist.  To get this type of behavior, we register the class cell
+from within the B<Stem> module rather than from the configuration
+file,
+
+    Stem::Route::register_class( __PACKAGE__, 'solar_system' );
+
+This line (World3.pm, line 5) effectively registers the class cell
+with the B<Stem> message routing system using the package name
+and a name we wish to register the cell as.
+
+We keep referring to the class cell as a global resource, so in
+this example we create a global resource that the class cell will
+manage,
+
+    my @objects = ();
+
+On line 16 in World3.pm we create an array named objects that will
+be used to hold a reference to each of the World3 cell objects that are
+created from the configuration file (Note that this is not a
+requirement for creating this module and is just used as an example.
+It could have just as easily been a simple scalar, a hash, some
+other kind of object, or even nothing!).
+
+In order to populate this array of the objects that are created from
+the configuration file, we simply add them to the array when they
+are created in the object cell constructor,
+
+    push @objects, $self;
+
+This simply pushes the reference to the newely created World3 object cell
+onto the objects array.  The class cell can now be used to represent the
+World3 object cells as a group.
+
+The next modification exists in the hello_cmd subroutine.  We need a way
+to distiguish whether or not a message is being sent to an object cell
+as opposed to a class cell.  As you might recall, the perl I<ref> function
+is used to determine if a scalar refers to a reference or a normal
+scalar value.  If a subroutine is invoked from an object, the first
+argument of the subroutine will be a reference to the object itself,
+otherwise, it will be the string name of the class from which the subroutine
+belongs.  The following code demonstrates a new hello_cmd subroutine
+that makes this distinction and performs accordingly,
+
+sub hello_cmd {
+
+    my ($class) = @_;
+
+    return "Hello world from $class->{'planet'}\n" if ref $class;
+
+    my $response_string = '';
+    foreach my $obj (@objects) {
+
+      $response_string .= "Hello world from $obj->{'planet'}\n";
+    }
+
+    return $response_string;
+}
+
+As you can see, we return the familiar "Hello world from $class->{'planet'}"
+string, but this time we check to make sure $class is a reference before
+returning the string.  If it is not, we know that the hello_cmd was invoked
+from a message that was intended for the class cell.  If this is the case, we
+concatenate a "Hello, World ..." string for each of the Hello3 object cells
+that were stored in the objects array and send that string as a response
+message to the sender.
+
+=head1 SEE ALSO
+
+F<Stem Cookbook Part 1>
+
+F<Stem Cookbook Part 2>
+
+F<Hello3 Module>
+
+=cut
+
+1;
diff --git a/Cookbook/World5.pm b/Cookbook/World5.pm
new file mode 100644 (file)
index 0000000..62d3db0
--- /dev/null
@@ -0,0 +1,59 @@
+package World5 ;
+
+use strict;
+
+use base 'Stem::Cell' ;
+
+my $attr_spec =
+[
+       {
+               'name'          => 'planet',
+               'default'       => 'world',
+       },
+       {
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+       },
+];
+
+sub new {
+
+       my( $class ) = shift ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+# Track the object in the class level hash %planets
+
+       return ( $self );
+}
+
+sub triggered_cell {
+
+       my( $self ) = @_ ;
+
+       $self->{'planet'} = $self->cell_get_args( 'planet' ) || 'pluto' ;
+
+       return;
+}
+
+# based on who was the receiver of the message
+# we return with the appropriate response
+
+sub hello_cmd {
+
+       my( $self ) = @_;
+
+       return "Hello world from $self->{'planet'}\n" ;
+}
+
+=head1 Stem Cookbook - World3
+
+=head1 NAME
+
+World5
+
+=head1 DESCRIPTION
+
+=cut
+
+1;
diff --git a/Cookbook/cookbook.txt b/Cookbook/cookbook.txt
new file mode 100644 (file)
index 0000000..3e47f9a
--- /dev/null
@@ -0,0 +1,331 @@
+chapter: Hello world!
+name: A Stem Cell Cookbook
+
+title: A few questions about a simple sub
+
+*: What can you do with this sub?
+*: Can it be networked?
+*: Can it receive messages? 
+*: Can it send messages? 
+
+code:
+package World1;
+
+sub world_cmd {
+
+       return "Hello world!\n";
+}
+
+PAGE_END
+
+title: A few questions about a simple sub: Answers
+
+*: It can be networked under Stem
+*: It can receive Stem messages
+*: It can send Stem messages
+*: No coding changes need to be made
+
+code:
+package World1;
+
+sub world_cmd {
+
+       return "Hello world!\n";
+}
+
+PAGE_END
+
+title: Loading World1 into Stem
+
+*: Stem configuration files load modules and register Cells
+*: This configuration loads the console module and the World1 module
+*: This is YAML (yaml.org) format and other formats are supported
+*: Start it with: run_stem worlds.stem
+
+code:
+--- #YAML:1.0
+-
+ class: Stem::Console
+-
+ class: World1
+
+PAGE_END
+
+title: Stem overview
+
+*: Stem is a message passing, event driven system
+*: Stem Cells are Perl objects that are registered
+*: Cells can be Perl classes or instantiated objects
+*: Cells can send and receive messages.
+*: Cell classes are loaded and Cells are created via Stem configuration files
+*: Stem Hubs (processes) can support many active Cells
+*: Stem Hubs can be connected in networks on one or more systems
+*: Any Cell can send a message to any other reachable Cell
+
+PAGE_END
+
+title: Stem message delivery
+
+*: Messages are objects with address, content and related fields
+*: The 'to' address of a message is used to identify the destination
+   Cell (Perl object)
+*: The destination Cell is invoked by a method and the message is
+   its only argument
+*: The method to be called is determined by the message type and command
+*: A 'foo' command message is delivered to method 'foo_cmd'
+*: A 'bar' type message is delivered to method 'bar_in'
+*: Command methods can optionaly return data which is sent back in in
+   reply to the sender.
+
+PAGE_END
+
+title: Stem addresses
+
+*: Stem Addresses are name triplets: Hub, Cell, Target
+*: The Hub is the name of Stem process
+*: The Cell is the registered name of the Stem object or class
+*: The target is the unique address of a cloned Cell
+*: The Cell part of an address is required and the Hub and Target are optional
+*: Addresses are written in string form in Stem configuration or from
+   the console.
+
+code:
+       cell
+       hub:cell
+       :cell:target
+       hub:cell:target
+
+PAGE_END
+
+title: Adding class level data
+
+*: We add class level data and a method to change it
+*: The file lexical variable $name stores the planet's name
+*: The method 'name_cmd' can set that name from the data in a message
+*: A 'name' command message can be sent from the console or anywhere
+*: The configuration file doesn't change other except for the name of the
+   class it loads
+
+code:
+<blue>
+package World2;
+use strict ;
+my $name = 'UNKNOWN' ;
+</font>
+sub world_cmd {
+       <blue>return "Hello world from $name\n";</font>
+}
+<blue>
+sub name_cmd {
+
+       my( $class, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless $data ;
+
+       $name = ${$data} ;
+
+       return ;
+}
+</font>
+PAGE_END
+
+title: A basic object level Cell
+
+*: This module has an attribute specification so we can construct an object
+*: If no planet name is passed to the constructor, it will be named 'X'
+*: It also has a constructor method new() that is called from the configuration
+*: The hello_cmd method now returns the object data in the planet attribute
+
+code:
+package World3;
+
+use strict ;
+
+<blue>my $attr_spec = [
+       {
+               'name'          => 'planet',
+               'default'       => 'X',
+       },
+] ;
+
+sub new {
+       my ( $class ) = shift ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return ( $self );
+}</font>
+
+sub hello_cmd {
+
+       <blue>my( $self ) = @_ ;
+
+       return "Hello world from $self->{'planet'}\n" ;</font>
+}
+
+PAGE_END
+
+title: Configuration for object level cells
+
+*: We add a 'name' field which is the cell part of the address for this
+   object Cell
+*: We add an 'args' field whose values are passed to the new() method
+*: The object is constructed and registered with the selected 'name'
+*: The initial value of the planet name can be set in the arguments
+*: We created two object cells here using the same class but the first
+   uses the default planet name of 'X' and the second is named 'venus'
+
+code:
+--- #YAML:1.0
+-
+ class: Stem::Console
+-
+<blue> class: World3
+ name: planet1
+ args: []
+-
+ class: World3
+<blue> name: planet2
+ args:
+  planet: venus</font>
+
+PAGE_END
+
+title: Changing object data
+
+*: All that is needed is a name_cmd method very similar to the one in World2
+*: It just changes the value in the cell itself
+*: The configuration file needs to only change the class and cell names
+
+code:
+package World4 ;
+
+use strict ;
+
+my $attr_spec = [
+       {
+               'name'          => 'planet',
+               'default'       => 'X',
+       },
+] ;
+
+sub new {
+       my ( $class ) = shift ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return ( $self );
+}
+
+sub hello_cmd {
+
+       my( $self ) = @_ ;
+
+       return "Hello world from $self->{'planet'}\n" ;
+}
+<blue>
+sub name_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless $data ;
+
+       $self->{'planet'} = ${$data} ;
+
+       return ;
+}
+
+</font>
+
+
+PAGE_END
+
+title: Cloning object cells
+
+*: Cloned Cells are similar to sessions or state objects but are much
+   simpler to create and manage
+*: Object Cells that use the cloning services of Stem::Cell are called
+   parent Cells
+*: All cloned Cells are owned by the Parent cell
+*: When a parent Cell is triggered (via a message or internal call), it
+   copies and registers the clone with a unique target address
+*: The Stem::Cell module is inherited and it handles the 'cell_trigger'
+   command message
+*: The specification must include a Stem::Cell class attribute
+
+   Note that it has its own default
+*: A callback to the 'triggered_cell' method is made in a newly cloned cell 
+
+code:
+package World5 ;
+
+<blue>use base 'Stem::Cell' ;</font>
+
+use strict ;
+
+my $attr_spec = [
+       {
+               'name'          => 'planet',
+               'default'       => 'X',
+       },
+       <blue>{
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+       },</font>
+] ;
+
+sub new {
+       my ( $class ) = shift ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return ( $self );
+}
+
+sub hello_cmd {
+
+       my( $self ) = @_ ;
+
+       return "Hello world from $self->{'planet'}\n" ;
+}
+<blue>
+sub triggered_cell {
+
+       my( $self ) = @_ ;
+
+       $self->{'planet'} = $self->cell_get_args( 'planet' ) || 'pluto' ;
+
+       return;
+}
+</font>
+
+sub name_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless $data ;
+
+       $self->{'planet'} = ${$data} ;
+
+       return ;
+}
+
+PAGE_END
+
+title: Cloning cell configuration
+
+*: This is the similar to the World4 configuration but we added the
+   'cell_attr' attribute and set its 'cloneable' flag to true
+
+code:
+
+-
+ class: World5
+ name: planet5
+ args:
+  planet: jupiter
+  <blue>cell_attr:
+   cloneable: 1
+</font>
+
+PAGE_END
diff --git a/Cookbook/worlds.stem b/Cookbook/worlds.stem
new file mode 100644 (file)
index 0000000..7a2e7c5
--- /dev/null
@@ -0,0 +1,56 @@
+--- #YAML:1.0
+# This is YAML format Stem configuration file. It load a colsole cell
+# and all the cookbook classes and registers a number of object cells
+#
+# Load and initialize the console class
+-
+ class: Stem::Console
+# Load the World1 class. We pass no arguments as this is a class level cell.
+-
+ class: World1
+# Load the World2 class. We pass no arguments as this is a class level cell.
+-
+ class: World2
+# Load the World3 class, construct an object with an empty argument list
+# and register is as a Cell with the name 'planet1'.
+# This Cell will use the default value for its name attribute
+-
+ class: World3
+ name: planet1
+ args: []
+# Construct another World3 object with an argument list and register
+# that as the Cell with the name 'planet2.
+# This Cell will use the value 'venus' for its name attribute
+-
+ class: World3
+ name: planet2
+ args:
+  planet: venus
+# Load the World4 class, construct an object with an argument list and
+# register that as the Cell with the name 'planet3.
+# This Cell will use the value 'earth' for its name attribute
+-
+ class: World4
+ name: planet3
+ args:
+  name: earth
+# Construct another World4 object with an argument list and
+# register that as the Cell with the name 'planet4.
+# This Cell will use the value 'mars' for its name attribute
+-
+ class: World4
+ name: planet4
+ args:
+  planet: mars
+# Load the World5 class, construct an object with an argument list and
+# register that as the Cell with the name 'system.
+# This Cell will use the value 'jupiter' for its name attribute and be
+# cloneable.  The value in name will be the default name for all Cells
+# cloned from this parent
+-
+ class: World5
+ name: planet5
+ args:
+  planet: jupiter
+  cell_attr:
+   cloneable: 1
diff --git a/DEMO b/DEMO
new file mode 100644 (file)
index 0000000..a3607c6
--- /dev/null
+++ b/DEMO
@@ -0,0 +1,174 @@
+
+                      Stem Demonstration Scripts
+
+Stem comes with several demonstration scripts and example configuration
+files which are used by them. You can optionally install the executable
+demonstrations and their configuration files.  Note that the actual
+demonstration scripts don't do anything special to Stem, they just
+create windows and execute run_stem inside them with selected
+configuration files. They also create telnet connections inside other
+windows which you can use to interact with Stem. You can manually create
+the windows and do the same commands, these scripts are just
+conveniences. In fact, a good way to learn more about Stem is to copy
+and modify the configuration files used in the demonstrations and run
+them yourself.
+
+When you run any of the demo scripts, the commands used to fork off an
+xterm are printed. You can manually run those commands in your own
+terminal windows if you want to experiment with or explore the Stem
+configurations. If you kill the script (e.g. with ^C), all the created
+xterm windows will be killed off leaving you with a cleaned up desktop.
+
+There are 4 demonstration scripts that come with Stem. They are briefly
+described here and in more detail in other files. They all have some
+common features which are also described in detail below.
+
+       chat_demo and chat2_demo demonstrate a simple 4 user chat
+       server. chat_demo runs with a single Stem Hub and chat2_demo
+       uses 2 Hubs. Both bring up an xterm for each Stem Hub and 4 more
+       for the telnet sessions. Read DEMO_CHAT for the full details on
+       how to use this demo.
+
+       inetd_demo emulates the inetd Unix super-daemon. It runs a
+       single Stem Hub in an xterm and 4 telnet sessions each in their
+       own xterm. The server process it runs is proc_serv in the bin
+       directory. You can run it directly from the command to see how
+       it behaves (it is a simple command/response program).  Read
+       DEMO_INETD for the full details on how to use this demo.
+
+       tail_demo monitors a typical log file and copies any new data it
+       finds there to a Stem Logical Log which writes it to a file and
+       optionally to other destinations. The status of the source file
+       is also sent to a Logical Log.  Read DEMO_TAIL for the full
+       details on how to use this demo.
+
+Using the console Cell Stem::TtyMsg
+
+All of the demo configurations include the Stem::TtyMsg module which
+allows you to enter command messages from the keyboard to a running Stem
+Hub (process). This module is not required to run Stem but it is in the
+demo configurations so you can interact with Stem and learn more about
+it.
+
+It reads lines from STDIN (using the Stem::AsyncIO module so the rest of
+the Stem Hub continues to run), parses them and sends out a command
+messages based on the lines. It also can set key/values in the local
+Hub's environment (%Stem::Vars::Env) which is used to control many Stem
+operations.
+
+Command messages can generate response messages which will be sent back
+to the TtyMsg Cell. These responses will be printed to STDOUT (again,
+using the Stem::AsyncIO module). Any Cell can just send a data message
+to the TtyMsg Cell (which is also registered with the class Cell name
+tty) and its data will get printed.
+
+The rules for parsing lines input to TtyMsg are very simple. There are
+three kinds of command lines:
+
+       Direct commands
+
+               The only direct command is 'help' which has to be the
+               only token on the line. It causes the help text to be
+               printed.
+
+       Setting a Stem environment variable
+
+               Key/values in the local Hub's environment can be set
+               with lines of the form:
+
+                       name=value
+
+               Token has to be only word characters ([a-zA-Z0-9_]) and
+               the string after the = is the value (stripped of
+               surrounding white space). The Hub environment variable
+               with the name token is set to the parsed value. The
+               token and value are printed.
+
+               You can also set any environment variable in any remote
+               Hub with the command message:
+
+                       hub:var set_env name=value 
+
+               Note that 'hub' must be the real name of that Stem Hub,
+               and var is already the registered class name of the
+               Stem::Vars class Cell.
+
+               See below for more on entering command messages and the
+               env_notes document in /Design.
+
+       Sending a Command Message
+
+               A command message line starts with a Cell address and
+               then must have a command token. The rest of the line is
+               optional data for the command message.  A minimal Cell
+               address is just a Cell name. It can have an optional Hub
+               name prefix. Also a target name can be suffixed after a
+               trailing :. So the only legal Cell addresses look like
+               this:
+
+                       cell
+                       hub:cell
+                       :cell:target
+                       hub:cell:target
+
+               If the Hub is missing the message is destined for the
+               local Hub and if the Cell doesn't exist here, it is
+               routed to the DEFAULT Hub. Read the Cell and Message
+               technical note for more on this.
+
+               The next token on a message command line is the command
+               name and it is required. It will be the value of the
+               'cmd' field in the message. The rest of the line is used
+               as the data field of the message.
+
+               Some uses of command line messages are getting the
+               status of various Class Cells since almost all of them
+               have a status command. By listing all the registered
+               Cells you can see which ones you can send messages to.
+
+               This will print all of the Cells in this hub. The
+               listing shows all object Cell and Class Cells with their
+               aliases. Command line messages should use the aliases
+               for the Cell name as the class names have colons in
+               them.
+
+               This will print all of the Cells in the local hub.
+
+                       reg status
+
+               This will print all of the Cells in the hub named remote.
+
+                       remote:reg status
+
+               This will print all of the Portals in this hub. You can
+               use their hub names to send command messages to those
+               hubs.
+
+                       port status
+
+               If you are running either chat demo you can change the
+               Switch maps which control which user get sent chat
+               messages. Here are some examples. The Switch Cell is
+               named sw, and the two Hubs in chat2_demo are named
+               chat_client and chat_server. Note that the 'sw' Cell is
+               only in the server Hub in chat2_demo, but since no 'sw'
+               Cell exists in the client Hub, any message sent to 'sw'
+               will still go to the server hub. So all of these map
+               commands can be issued from either Hub input and they
+               will work. The 'map' or 'status' token is the command
+               and the tokens after 'map' are data to the map
+               command. The first data token is the input map you are
+               setting and the rest of the tokens are the output maps
+               to send chat messages to.
+
+               Print the current maps.
+
+                       sw status
+
+               Change the a map to just b.
+
+                       sw map a b
+
+               Change the d map to all users.
+
+                       sw map d a b c d
diff --git a/DEMO_CHAT b/DEMO_CHAT
new file mode 100644 (file)
index 0000000..d56895e
--- /dev/null
+++ b/DEMO_CHAT
@@ -0,0 +1,252 @@
+
+
+                Demonstration of Chat Server Emulation
+
+These two demonstration scripts emulate a very simple chat server with 4
+connected users.  They showcase the Stem modules Stem::Switch (which
+multiplexes Stem Messages) and Stem::SockMsg (a socket to message
+gateway). chat_demo and chat2_demo behave the same but the former runs
+as a single Stem Hub (process) and the latter as two Hubs (which can be
+on separate systems - see below to experiment with that).  Just like
+with real chat, a user can type into their terminal and their text will
+appear on the windows of other users.  The Stem::Switch Cell (configured
+as 'sw') acts as the chat server and it controls which users will see
+the text from other users. You can change that user to user map by
+issuing command messages to the 'sw' Cell (see DEMO for more on entering
+command messages from the terminal). The two demo scripts are described
+in detail below with sections on running, using, configuring and
+experimenting with them.
+
+Running chat_demo
+
+The single Hub chat demonstration is called chat_demo and it uses the
+chat.stem configuration file (found in conf/ and also where you
+installed the demo Stem configurations). It is run with the simple
+command:
+
+chat_demo
+
+To exit, just enter 'quit' in the chat_demo script window itself. It will
+kill all the other windows and then exit. This will also happen if you
+interrupt the demo script with ^C.
+
+If the -s option is used, then all the windows will use ssfe (split
+screen front end which you can install from the Stem distribution) which
+provides a command line edit and history window section and an output
+section. A single Hub window will be created and then 4 smaller telnet
+windows which will be connected to listen sockets in the Stem Hub. These
+telnet windows are the chat users and they can type data and other users
+will see the output. The telnet windows are named A, B, C and D.
+
+Using chat_demo
+
+Now type a line of text into A's window and hit return. Notice how all 4
+windows see that text. Now do the same for D. Only C will see its
+text. This is controlled by the map in the Stem::Switch Cell named
+'sw'. You can print out that map by sending a status command message to
+that Cell. In the Hub window (named Stem) type this command:
+
+sw status
+
+You will see this printout:
+
+       Status of switch: sw
+
+       In Map:
+
+               a -> a b c d
+               b -> a
+               c -> b d
+               d -> c
+
+       Out Map:
+
+               a -> A
+               b -> B
+               c -> C
+               d -> D
+
+This shows that a data message that came in with the Message target 'a'
+will have its data copied to all 4 users and that 'd' will only send
+text to 'c'. The Message target name is used as a key to index into the
+In Map which gets a list of keys to the Out Map.  The Out Map is then
+indexed and a list of Cell addresses is found. Those addresses are sent
+a copy of the data message. Now you should be able to predict what will
+happend to text entered on B or C.  Note that the internal keys are not
+related to any other namespaces and are private to this Cell. The Switch
+Cell's maps can be changed by command messages sent to this Cell.
+
+Also run this command in the Hub window:
+
+reg status
+
+This sends a status message to the Class Cell Stem::Route which has the
+alias 'reg'. It returns a listing of all registered Cells with their
+Cloned Cell names or Class Cell Aliases. You can run this command in any
+Hub window to find the list of registered Cells.  Most of the Class
+Cells support and some Object Cells support status commands which can be
+sent from the console.
+
+
+Configuring chat_demo
+
+Look at the file conf/chat.stem. That is the configuration file used by
+chat_demo. It is very simple and easy to understand. It is a Perl list
+of lists structure with key/value pairs. Read the config_notes for more
+on this.
+
+The first Cell configured is Stem::TtyMsg which supports typing in and
+sending command messages. This Cell is used in all the demo
+configurations. You can use it for any Stem application where you might
+want to enter command messages by hand.
+
+       [
+               class   =>      'Stem::TtyMsg',
+               args    =>      [],
+       ],
+
+Then come four Stem::SockMsg Cells named A, B, C and D. Each has a
+single server socket listening on its own port. Also they are configured
+(via the 'data_addr' attribute) to send their data to the same 'sw' Cell
+but with the target addresse a, b, c, or d. These Cells allow
+the user telnets to connect to this Hub. 
+
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:a'
+               ],
+       ],
+],
+
+Finally we have the Stem::Switch Cell named 'sw' which controls the
+mapping of users to users. It is just like the output from the first
+status command we did above. It sets the input maps to the list of
+internal target names and the output map is set to Cell addresses that
+redirect the incoming messages.
+
+[
+       class   =>      'Stem::Switch',
+       name    =>      'sw',
+       args    =>      [
+
+               in_map => [
+
+                       a => [ qw( a b c d ) ],
+                       b => 'a',
+                       c => [ qw( b d ) ],
+                       d => 'c',
+               ],
+
+               out_map => [
+
+                       a => 'A',
+                       b => 'B',
+                       c => 'C',
+                       d => 'D',
+               ],
+       ],
+],
+
+Experimenting with chat_demo
+
+Now try to send a map command message to the 'sw' Cell. Enter this in
+the Hub window:
+
+sw map b b c d
+
+and then type something into B. You should see it print on B, C, and D's
+windows. You can change any of the maps. The 'map' token is the command
+(as was 'status') and b is the input map name you are changing. The rest
+of the tokens are the internal keys to output map. You can always print
+out the map with the status command (as shown above) and verify your
+changes.
+
+Running chat2_demo
+
+You run chat2_demo also by just typing the script name and its basic
+behavior is just like chat_demo. The main difference is that it runs two
+Stem Hubs and the four users are split with two connecting to each
+Hub. So there are two configuration files named chat_server.stem and
+chat_client.stem and they are in conf/ directory.  When you run
+chat2_demo, two Hub windows will be created with the names Chat1 and
+Chat2.  The two Stem Hubs are called 'server' and 'client' and those
+names only reflect how they initially connect via sockets. Once they are
+properly connected, they communicate in a peer to peer fashion.
+
+Using chat2_demo
+
+You can interact with chat2_demo just as you did with chat_demo. The
+same user to user mapping is in effect and you can enter user text the
+same way and also change the map. In fact you can enter and send all the
+same command messages you did before in either Hub window and you will
+see similar output. The major difference is that 2 of the output map
+Cell addresses have Hub values.
+
+First enter the 'reg status' command in each Hub window. Notice how the
+'server' Hub (window named Chat1) has the C and D Stem::SockMsg Cells
+and the Stem::Switch Cell named 'sw'. The 'client' Hub (window named
+Chat2) has only the A and B Stem::SockMsg Cells. This means that the
+users connected to the 'client' Hub have to 
+
+Now enter this command in each of the two Hub windows:
+
+port status
+
+That sends a 'status' command to the Class Cell Stem::Portal of the Hub.
+
+The 'server' Hub will print:
+
+Portal Status for Hub 'server'
+        client          => Stem::Portal=HASH(0xd2978)
+
+This shows that this Hub can send messages to another Hub named 'client'
+And the 'client' Hub will print:
+
+Portal Status for Hub 'client'
+        DEFAULT         => Stem::Portal=HASH(0xd0930)
+        server          => Stem::Portal=HASH(0xd0930)
+
+This shows that this Hub can send messages to another Hub named 'server'
+and to one named 'DEFAULT' which is the same portal as 'server'. When a
+message doesn't have a Hub name in its 'to' address and it can't be
+delivered locally, it is sent to a Portal named DEFAULT if it can be
+found. This is similar to the default route in IP networks.
+
+How chat2_demo is Configured
+
+Look at the files conf/chat_server.stem and conf/chat_client.stem. They
+are the configuration files used by chat2_demo. They are basically
+copies of chat.stem with support for two hubs and the Stem::SockMsg
+Cells split between them. The new Cell addition to both is Stem::Portal
+which supports send messages between Hubs. The 'server' Hub has this:
+
+[
+       class   =>      'Stem::Portal',
+       args    =>      ['server' => 1 ],
+],
+
+That makes this Hub a server which listen for connections from other
+Stem Hubs. The default port number is 10,000 (though this will change
+soon). There is no 'host' attribute in that Stem::Portal Cell so it uses
+the localhost interface by default. The 'client' Hub doesn't have a
+server attribute so it is a client and it connects by default to
+localhost and the port 10,000.
+
+[
+       class   =>      'Stem::Portal',
+       args    =>      [],
+],
+
+Then come four Stem::SockMsg Cells with A and B in stem_client.stem and
+C and D in stem_server.stem. And finally the Stem::Switch Cell named
+'sw' which is only in stem_server.stem. Note that the output map for 'a'
+and 'b' have the Hub name 'client' in their Cell addresses. This is
+because the A and B users are connecting to the 'client' Hub and this
+Stem::Switch Cell needs to know that so it can send them data. In a more
+realistic chat system, these switch maps would be controlled by end user
+commands and not by entering command messages.
diff --git a/DEMO_INETD b/DEMO_INETD
new file mode 100644 (file)
index 0000000..073cdc3
--- /dev/null
@@ -0,0 +1,231 @@
+
+               Demonstration of Inetd Server Emulation
+
+This demonstration script emulates the standard inetd super-daemon found
+on all Unix systems. It showcases the Stem modules Stem::Proc (which
+handle processes) and Stem::SockMsg (a socket to message gateway).  This
+demonstration runa a single Hub which listens for socket connections on
+two ports. When a connection comes in, a new process is spawned which
+interacts with the remote client that made the connection. This is
+effectively what inetd does but inetd_demo has several major
+advantages. StemInetd can insert filters and/or taps in the data stream,
+all its connections and status changes can be logged and filtered and it
+can be distributed securely across a network.  The demo script is
+described in detail below with sections on running, using, configuring
+and experimenting with it.
+
+Running inetd_demo
+
+The single Hub inetd demonstration is called inetd_demo and it uses the
+inetd.stem configuration file (found in conf/). It is run with the simple
+command:
+
+inetd_demo
+
+To exit, just enter 'q' in the inetd_demo script window itself. It will
+kill all the other windows and then exit. This will also happen if you
+interrupt the demo script with ^C.
+
+If the -s option is used, then all the windows will use ssfe (split
+screen front end) which provides a command line edit and history window
+section and an output section. A single Hub window named Stem will be
+created and then 4 smaller telnet windows which will be connected to
+listen sockets in the Stem Hub. These telnet windows are the inetd users
+and they can type data and interact with a simple command line server
+program named proc_serv. The telnet windows are named A, B, C and D and
+if you use ssfe, each will display the telnet command it ran.
+
+Using inetd_demo
+
+Now enter the help command into window A and hit return. These are the
+commands you give the proc_serv application. Each of the user windows is
+connected to a different running proc_serv process. You can verify this
+by running the pid command in each window. Then have fun with the yow
+and insult commands. Later in the experimenting section, you can change
+the program to run and its options. The major difference among the user
+windows is that two of them A and C are connected to the 6666 port in
+the Hub and run the proc_serv in normal mode. The B and D windows are
+connected to the 6667 port in the Hub and run the proc_serv with -n so
+numbers the successful output lines.
+
+You can print out the list of all the registered Cells by sending a
+status command message to the Stem:Route Class Cell which is aliased to
+'reg'.  In the Hub window (named Stem) type this command:
+
+reg status
+
+You will see this printout:
+
+Route Status for Hub ''
+
+        Object Cells with Target names of their Cloned Cells
+
+        A                               => Stem::SockMsg=HASH(0x2dd5a0)
+                :aaaaaa                 => Stem::SockMsg=HASH(0x2fda20)
+                :aaaaab                 => Stem::SockMsg=HASH(0x350ab0)
+        B                               => Stem::SockMsg=HASH(0x2ed474)
+                :aaaaaa                 => Stem::SockMsg=HASH(0x2c10ac)
+                :aaaaab                 => Stem::SockMsg=HASH(0x355040)
+        proc_serv                       => Stem::Proc=HASH(0x2fdbc4)
+                :aaaaac                 => Stem::Proc=HASH(0x3515c4)
+                :aaaaad                 => Stem::Proc=HASH(0x35554c)
+                :aaaaaa                 => Stem::Proc=HASH(0x2f881c)
+                :aaaaab                 => Stem::Proc=HASH(0x352660)
+
+        Class Cells with their Aliases
+
+        Stem::Conf              => conf
+        Stem::Demo::Cmd         => cmd
+        Stem::Hub               => hub
+        Stem::Log               =>
+        Stem::Log::Entry        => entry
+        Stem::Portal            => port
+        Stem::Route             => foo reg
+        Stem::TtyMsg            => tty
+        Stem::Vars              => var
+
+This shows the Parent Object Cells A, B and proc_serv each with their
+own set of Cloned Cells. Below that are the loaded Class Cells.
+The two Stem::SockMsg Cells have 2 telnet users connected to them and
+the proc_serv Cell has cloned four Objects, each of which manages a
+single process. Note that parent Cells don't do the work, they manage
+the Cloned Cells which do it.
+
+
+How inetd_demo is Configured
+
+Look at the file conf/inetd.stem. That is the configuration file used by
+inetd_demo. It is very simple and easy to understand. It is a Perl list
+of lists structure with pairs of keys and values. Read the config_notes
+for more on this.
+
+The first Cell configured is Stem::TtyMsg which supports typing in and
+sending command messages. This is done in all the demo configurations.
+
+       [
+               class   =>      'Stem::TtyMsg',
+               args    =>      [],
+       ],
+
+Then come two Stem::SockMsg Cells named A and B. Each has a server
+socket listening on its own port. Also they each will create a piped
+connection to the cell named 'proc_serv'. The Cell B has one extra
+attribute set, it adds the -n option when a process is spawned for it.
+
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               piped_to        => 'proc_serv',
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'B',
+       args    =>      [
+               port            => 6667,
+               server          => 1,
+               piped_to        => 'proc_serv',
+               piped_args      => [ '-n' ],
+       ],
+],
+
+
+
+Finally we have the Stem::Proc Cell named 'proc_serv' which can clone
+itself and spawn off processes.
+
+[
+       class   =>      'Stem::Proc',
+       name    =>      'proc_serv',
+       args    =>      [
+               path            => 'proc_serv',
+               use_stderr      => 1,
+               piped_to        => 1,
+               no_init_spawn   => 1,
+       ],
+],
+
+The 'path' attribute is the absolute path or program name to
+be run. Note that this configuration assumes it will find 'proc_serv in
+$PATH. The three boolean attributed tell the Cell that it should handle
+output from stderr of the process, it is expecting a pipe connection
+request and it should only spawn processes when it has been cloned.
+
+
+Experimenting with inetd_demo
+
+These experiments are similar to the Hub and Portal ones in
+DEMO_CHAT. They show you how to change the processes StemInetd runs, and
+to distribute it over multiple systems over secure connections. Choose a
+second system and make sure Stem is properly installed on it (NFS
+mounting the tarball dir will help).
+
+To support a remote Hub connecting the Hub which owns the Stem::Proc
+Cell, you have to add a Stem::Portal Cell to each of them.
+
+Make two copies of the configuration file conf/inetd.stem and call them
+inetd_server.stem and inetd_client.stem
+
+Edit inetd_server.stem and rename the Stem::Hub configuration to
+inetd_server. Also insert this Stem::Portal Cell configuration into it
+replacing 'foo_host' with the server hostname:
+
+[
+       class   =>      'Stem::Portal',
+       args    =>      [
+               'server'        => 1,
+               'host'          => 'foo_host'
+       ],
+],
+
+
+Edit inetd_client.stem and rename the Stem::Hub configuration to
+inetd_client. Delete the Stem::Proc Cell configuration.  Also insert
+this Stem::Portal Cell configuration into it replacing 'foo_host' with
+the server hostname:
+
+[
+       class   =>      'Stem::Portal',
+       args    =>      [
+               'host'          => 'foo_host'
+       ],
+],
+
+
+You can create and modify as many of the Stem::SockMsg Cells as you want
+on each Hub.  Then in a window on the server box, do:
+
+run_stem inetd_server
+
+and on a window in the other box (called bar_host) where Stem is setup do:
+
+run_stem inetd_client
+
+You can create telnet sessions from the 'server' system that connect
+to the ports of the Stem::SockMsg Cells.
+
+telnet localhost 6666
+telnet localhost 6667
+
+And on the 'inetd_client' system, connect telnets to the ports of its
+Stem::SockMsg Cells.
+
+telnet localhost 6666
+telnet localhost 6667
+
+You can now interact with this Stem application just as you did when it
+was running on one system as it did with inetd_demo.
+
+Instead of editing the configuration files, you could also set the
+Stem::Portal host attribute by setting a command line argument or
+environment variable. This command will make the 'server' Hub accept
+connections from the 'foo' host interface:
+
+run_stem host=foo chat_server
+
+You can do the same for the 'client' Hub and have it connect to host
+'foo'. By setting the STEM_HOST environment variable to the host name
+you can get the same effect.
diff --git a/DEMO_TAIL b/DEMO_TAIL
new file mode 100644 (file)
index 0000000..f32dce3
--- /dev/null
+++ b/DEMO_TAIL
@@ -0,0 +1,251 @@
+
+                      Demonstration of Log Tail
+
+This demonstration script illustrates Stem's ability to monitor log
+files.  It showcases the Stem modules Stem::Log (which logs messages)
+and Stem::LogTail (which checks files for changes/updates).  This
+demonstration runs two hubs named archive and monitor.  The monitor
+hub watches a particular log file for changes.  When a change occurs,
+messages are sent to the archive hub to be logged.  The archive
+hub records the contents of the monitored log file (sent by the
+monitor hub) and also records status messages sent by the monitor
+hub.  Log messages that are recorded by the archive hub can be
+stored as either raw data or with custom formats.  This demonstrates
+a single log file being monitored, in a real world case there could be
+several log files being monitored.  It is easy to see in this example that
+Stem can handle this with a small number of additions to its
+configuration files.  This can be distributed securely across a network.
+This demo script is described in detail below with sections on
+running, using, configuring, and experimenting with it.
+
+Running tail_demo
+
+The log tail demonstration is called tail_demo and it uses monitor.stem
+and archive.stem configuration files (found in conf/).  It is run with
+the simple command:
+
+tail_demo
+
+To exit, just enter 'q' in the tail_demo script window itself. It will
+kill all the other windows and then exit. This will also happen if you
+interrupt the demo script with ^C.
+
+If the -s option is used, then all the windows will use ssfe (split
+screen front end) which provides a command line edit and history window
+section and an output section.  Two hub windows named Archive and
+Monitor will be created and a single shell window will be created with its
+current directory set to tail/.  Stem will create two log files
+in the tail/ directory, bar.log and bar_status.log.  bar.log is used
+by the archive hub to record what ever is sent to that log file and
+bar_status.log is used as a log file for status messages.
+The hub windows can be used to interact with that hubs Stem environment
+and the command line window can be used to put contents into a foo.log
+file.  The two hub windows use the standard module Stem::TtyMsg which
+allows you to interact with them.  In this demo they will be used to
+modify the Stem environment which will affect the behavior of the
+logical logs.
+
+Using tail_demo
+
+Initially, bar.log and bar_status.log will be empty files but in 10
+seconds (the tailing interval set in the monitor.stem configuration)
+the status log will have a message about foo.log not being found.  Run
+ls -l several times in the shell (center) window to see when the status
+has been logged and then read that file will
+
+$ cat bar_status.log
+
+Now type the following at the command line (in the shell window):
+
+$ echo 'foobar' > foo.log
+
+After 10 seconds (configured in the Stem configuration file)
+you can look in bar.log and you will notice that there is a single line
+that reads, "foobar", and in bar_status.log you will notice that there
+is a status message saying that it is the first time that foo.log was
+opened.  And, of course, we have the line "foobar" in the monitored log
+file, foo.log.
+
+Configuring tail_demo
+
+Look at the file conf/monitor.stem. That is one of the configuration files
+used by tail_demo. It is very simple and easy to understand. It is a Perl list
+of lists structure with key/value pairs. Read the config_notes for more
+on this.
+
+The first Cell configured is Stem::Hub which names this hub as
+'monitor'.
+
+       [
+               class   =>      'Stem::Hub',
+               name    =>      'monitor',
+               args    =>      [],
+       ],
+
+Next comes the configuration for the Stem::Portal cell,
+
+       [
+               class   =>      'Stem::Portal',
+               args    =>      [
+               ],
+       ],
+
+It is important to note here that there are no args passed into the
+portal.  This means that the portal is a client portal, its default
+host is set to localhost, and its default port is set to 10,000.  For
+more information on portals, read the portal design notes.
+
+The next Cell configured is Stem::TtyMsg which supports typing in and
+sending command messages. This is used in all the demo configurations.
+
+       [
+               class   =>      'Stem::TtyMsg',
+               args    =>      [],
+       ],
+
+The next cell is the application specific part in the monitor.stem
+configuration, the Cell configuration for Stem::LogTail:
+
+       [
+               'class' =>      'Stem::LogTail',
+               'name'  =>      'foo',
+               'args'  =>      [
+                       'path'          => 'tail/foo.log',
+                       'repeat_interval' => 10,
+                       'data_log'      => 'archive:bar',
+                       'status_log'    => 'archive:bar_status',
+               ],
+       ],
+
+This is the cell responsible for monitoring the indicated log file
+(foo.log).  It has arguments for the path to the monitored file, what
+the time interval is (in seconds) to check the file for changes, the
+data log, and the status log.  Note that the address of the data and status
+log indicates both the name of the hub that it is located at, as well as, the
+name of the log cell.  For more information on these configuration options
+please see the tail log design notes.
+
+Now, lets take a look at the conf/archive.stem configuration file.  This
+file defines logical log files  (bar and bar_status) that are used by the
+monitor.stem configuration file to log the changes to foo.log.  
+
+The first three cells that are configured are the same as the monitor
+configuration, Stem::Hub, Stem::Portal, and Stem::TtyMsg.  They are
+for the most part identical.  It is worth mentioning here that the
+configuration for the Portal has its server boolean flag set to true,
+indicating that this portal will be awaiting connection requests from
+remote Portals anywhere on a network.
+
+The next three are the configurations for Stem::Log logical logs.  The
+first one is a typical logical log file configuration,
+
+       [
+               'class' =>      'Stem::Log',
+               'args'  =>      [
+                       'name'          => 'bar',
+                       'path'          => 'tail/bar.log',
+                       'filters'       => [
+                               file    => 1,
+                               forward => [ 'bar_stdout' ],
+                       ],
+               ],
+       ],
+
+This is defining a logical log named "bar" that is associated with
+a real log file indicated by the path, "tail/bar.log".  It also
+has a filters argument that allows Stem::Log::Entries to be filtered
+before they are placed in the log file.  The first of the filter
+operations in the above configuration, 'file', indicates that the 
+incoming log entry should be placed in the file indicated by the 'path'
+argument.  Another one of these filter rules, 'forward, indicates that
+the log entry is always forwarded to the log 'bar_stdout'.
+
+
+The next Stem::Log configuration defines a logical log that conditionally
+outputs its entries to STDOUT.  It will write log file entries to STDOUT
+if the 'bar_stdout' Stem environment variable is set to be greater than
+the severity level of the log entry.
+
+       [
+               'class' =>      'Stem::Log',
+               'args'  =>      [
+
+                       'name'          => 'bar_stdout',
+                       'format'        => '%f [%L][%l] %T',
+                       'strftime'      => '%D %T',
+                       'filters'       => [
+                               'env_gt_level'  => 'bar_stdout',
+                               stdout  => 1,
+                       ],
+               ],
+       ],
+
+This configuration specifies a format (overriding the default raw format 
+like bar.log) that displays the entry with timestamps, label, and level.
+This allows you to customize your log entries to your liking.
+To demonstrate the severity level detection, do the following at the
+Stem prompt in the archive hub window (upper left),
+
+bar_stdout=8
+
+This will ensure that if the severity level of the incoming log entry
+is less than 8, it will be displayed to standard output.  now, append a
+line to the foo.log file in the command line window,
+
+echo 'hello log' >> foo.log
+
+You will see in the archive hub window a log message appear in stdout
+in 10 seconds (according to this configuration),
+
+02/11/02 14:27:15 [tail][5] hello log
+
+This log entry is in raw format, the other Stem::Log configurations add
+formats (as mentioned above).  For more information on the format of the
+log entries and filters please take a look at the log design notes.
+
+The final cell configuration is for filtering the status messages from the
+LogTail Cell,
+
+       [
+               'class' =>      'Stem::Log',
+               'args'  =>      [
+
+                       'name'          => 'bar_status',
+                       'path'          => 'tail/bar_status.log',
+                       'format'        => '[%f]%h:%H:%P - %T',
+                       'strftime'      => '%T',
+                       'filters'       => [
+                               file    => 1,
+                               'env_gt_level'  => 'bar_status',
+                               tty_msg => 1,
+                       ],
+               ],
+       ],
+
+As you can see, it has the same format as the previous Stem::Log
+configurations.  This configuration has both a logfile (tail/bar_status.log)
+and the ability to display the status message if the severity level is less
+than the Stem environment variable 'bar_status' to the tty message
+console (indicated by the tty_msg filter operation, which is set to true).
+Note that tty_msg is different than stdout, the message is being sent to
+the TtyMsg module for output to the console versus just using stdout
+directly.  There are also other actions including custom ones.
+
+Let's see this in action, close the three widows created from the tail_demo
+script and re-run tail_demo.  do the following at the
+Stem prompt in the archive hub window,
+
+bar_status=8
+
+This will ensure that if the severity level of the incoming log entry
+is less than 8, it will be displayed to standard output.  now, append a
+line to the foo.log file in the command line window,
+
+echo 'hello again log' >> foo.log
+
+You will see in the archive hub window a log message appear in stdout
+in 10 seconds (according to this configuration),
+
+[21:58:04]trichards-linux.alias.net:monitor:/opt/bin/run_stem - LogTail: first open of /opt/bin/tail/foo.log
+
+
diff --git a/Design/Stem-Mon b/Design/Stem-Mon
new file mode 100644 (file)
index 0000000..9c939dc
--- /dev/null
@@ -0,0 +1,49 @@
+This module will be on the server:
+SNMP Trap Support (sending and/or receiving)
+-----------------
+Net::Snmp
+--supports v1 and v2
+--can be made non-blocking via new...-nonblocking
+--default is to block
+
+These modules would be run on each client based on a config file
+of sorts.
+
+User Logins
+-----------
+something to read a variant of /var/log/lastlogin, wtmp,etc
+we can do this because the files are usually a fixed binary format
+
+Drive Space Monitoring
+----------------------
+File::df
+
+--requires: statfs()
+--statfs() can be prevented by blocking (on bsdi at least) 
+--works with, solaris, sunos, hp/ux, osf/1, linux
+
+
+Running process monitor
+-----------------------
+Proc::Processtable
+--requires: File::Find, Storable
+--works with: Linux, Solaris, aix, hp/ux, fbsd, irix, osf, bsdi, nbsd
+---Watchdog::Process says 
+# This class is unreliable on Linux as 
+# Proc::ProcessTable::Process::cmndline() sometimes returns undef. 
+--hrm...does this mean Proc::Processtable is broke?
+
+Load monitoring
+---------------
+Unix:Processors, gives info of whether a processor is online
+ how many processors, speed of each processor
+
+Sys::CpuLoad
+--works with: any os that has /proc/loadavg or system call 
+   equivalent of /usr/bin/uptime
+
+Log File Monitoring
+-------------------
+File::Tail
+--I'm sure this one blocks...duh...but it can be changed
+--but this is very useful for monitoring purposes (see swatch for an example)
\ No newline at end of file
diff --git a/Design/arch_notes b/Design/arch_notes
new file mode 100644 (file)
index 0000000..cdd60c6
--- /dev/null
@@ -0,0 +1,37 @@
+                       Stem Architecture Notes
+
+Stem is a network application development toolkit and a suite of network
+applications. Its goal is to transform common network tasks from
+programming to configuration and to make solving difficult network tasks
+much easier.
+
+A running Stem system on a network consists of one or more
+interconnected Stem daemons which are called Hubs.  Each Hub contains a
+collection of modules and objects which are called Cells. Stem Cells are
+capable of sending and receiving messages to any other Cell in the Stem
+system. Modules and Cells are loaded or created in a Hub by using
+configuration files. Configurations can be loaded and sent to any
+connected Hub where they will be executed.
+
+Stem's core set of modules provide all of the common operations needed
+including message passing, interprocess communication, asynchronous
+buffered I/O, socket connections and timers.  Other modules which can be
+loaded and configured, perform such tasks as process creation and
+management, log file management, gateways, protocol support, message
+switching, etc...
+
+Configuring Stem Cells has been designed for simplicity and
+flexibilty. All Cells use a common style of attribute/value pair
+arguments with well chosen default values which make common situations
+easier to configure. Many example configuration files come with Stem as
+well as demonstration scripts which run working applications such as
+inetd emulation, log file transferring and chat servers.
+
+Stem Messages are how Cells communicate with each other. Their names
+are an ordered triplet:  Hub, Cell and Target. Each Cell registers itself with
+a unique name and optional Target name in its Hub. All Hub names in a
+Stem system must be unique too, which makes the address triplet
+sufficient for any Message to be directed to any Cell.
+
+For more detailed information on the architecture and design of Stem,
+read the other technical notes.
diff --git a/Design/asyncio_notes b/Design/asyncio_notes
new file mode 100644 (file)
index 0000000..54d1714
--- /dev/null
@@ -0,0 +1,24 @@
+                      Stem::AsyncIO Design Notes
+
+The Stem::AsyncIO module provides a buffered I/O interface to sockets
+and process handles. It is used by a variety of modules such as
+Stem::Proc, Stem::SockMsg, Stem::Portal::Stream to do the common
+function of doing their buffered I/O.
+
+The constructor takes an owner object and up to 3 handles: input, output
+and stderr (for processes). The input and stderr handles are monitored
+with read events and when data is available, it is read and a callback
+is made to the owner object with the data as its argument.
+
+The write handle is fully buffered and the module provides non-blocking
+asynchronous output to it. Data to be sent to the handle is passed in
+with the write method. If there is any output data buffered, a write
+event monitors the handle and triggers a callback when data can be
+written to it. The callback writes as much data as possible to the write
+handle.
+
+If the read or stderr handle is being used and it is closed (the socket
+is disconnected or the process exits), this is detected and a callback
+to the owner object is made.
+
+This module is only used internally and should not be configured.
diff --git a/Design/cell_notes b/Design/cell_notes
new file mode 100644 (file)
index 0000000..3335925
--- /dev/null
@@ -0,0 +1,107 @@
+
+                        Stem Cell Design Notes
+
+Stem Cells are the fundamental working unit in a Stem application.
+Cells are Perl objects that have 3 primary characteristics: First, they
+must be registered with a Stem address. Second, they must have public
+methods that can take an incoming message as an argument. And third, a
+cell must be able to generate messages.  There are three major types of
+Stem Cells: class, object and cloned Cells.  These are described in
+further detail below.
+
+Class Cells are created by a Stem class which registers itself (using
+the Stem::Route::register_class routine at module load time) and are
+always registered using its class name.  Class Cells are typically
+created by modules which manage some global resource and don't need to
+have multiple object instances created. A common reason for this is a
+module which has a 'status_cmd' (or similar) method that is used to
+get the status of the whole module. The Class Cell registration makes
+those methods accessible via messages. Some Stem classes such as
+Stem::Conf, Stem::Portal, Stem::Msg are Class Cells. Some modules can be
+a Class Cell and also create Object Cells.  Class Cells can optionally
+be registered with aliases. The aliases make it easier to send a command
+message from the terminal (using Stem::TtyMsg) to a class Cell
+(Stem::Route is aliased to 'reg', Stem::Cron is aliased to 'cron').
+
+Object Cells are objects that are created by a class's constructor and
+are then registered with the Stem::Route::register_cell routine. The
+registration takes the object and a required name (unique to this Hub).
+Most often an Object Cell is created by a configuration but any module
+can construct an object and register it. Since configurations can be
+loaded from files and executed anywhere, Stem Cells can be configured at
+any time during the existance of the current Stem system.
+
+Cloned Cells are only created by existing parent Object Cells. (Parent
+Cells are Object Cells set up to create Cloned Cells). When the parent
+Cell gets some form of trigger (typically a socket connection or a
+special command message), it makes a clone of itself and does whatever
+special work the cloned object needs. The parent Cell owns a Stem::Id
+object which it calls to generate a unique (within this Cell) Target
+name which it uses to register the cloned Cell. So the new Cell has a
+unique Cell/Target name pair which can be used in messages directed at
+it. In a typical case, the new Cloned Cell will send a message elsewhere
+informing some other object about its existance; e.g., The Stem::SockMsg
+class can be configured to clone itself when a socket connection is made and
+then it will send a 'pipe_start' command message out. In an Inetd
+configuration that message would be directed to a parent Stem::Proc
+Object Cell which will clone itself and fork a process. This clone will
+respond to the SockMsg message with its new target address, thereby
+setting up a Stem pipe between the socket and process. When either the
+process exits or the socket is closed, the cloned Cells are notified and
+they clean up and unregister themselves.
+
+You can always find the current set of Cells in a Hub by sending a
+'status' command message to the Class Cell Stem::Route. This is also
+registered with the alias of 'reg'. So from the terminal (if your Hub
+has configured in Stem::TtyMsg) you can type:
+
+reg status
+
+to get the registered Cells in this Hub or
+
+:hubname:reg status
+
+to get the Cells in a remote Hub.
+
+
+When a Stem message is delivered in a Hub, its 'to' address is looked up
+in the Hub's registry and the message is delivered to the destination
+Cell via a method.  A Cell must have some well known methods that handle
+incoming messages. These method names have well defined formats and
+uses. In general there are three groups of incoming message methods. All
+of these delivery methods get passed the message itself as their sole
+argument.
+
+The first group of delivery methods are those that handle command
+messages. These are named for the command (the 'cmd' field of a command
+message) with '_cmd' appended. So a foo command message will be
+delivered to the foo_cmd method if it exists in the destination Cell. If
+a command message method returns a value, it is automatically sent back
+to the originating Cell in a response message.
+
+The second group handles all other message types. They are named for the
+message type with a suffix of '_in'. So a 'foo' type message would be
+delivered to the 'foo_in' method if it exists in the destination Cell. A
+very common message type is 'data' and it gets delivered to the
+'data_in' method.
+
+The final group has the single method 'msg_in' which is used if no other
+method can handle the message. This is the default message delivery
+method. You can have a Cell with just this method and it should be
+designed to handle all expected message types.
+
+The use of specific delivery methods is not critical but it encourages
+cleaner Cell design by having methods focus on doing work and not on
+deciding how to handle different message types. This is in keeping with
+the Stem design philosophy of doing as much common work as possible
+behind the scenes, while leaving only the problem specific work to the
+Cell.
+
+There are no design considerations for sending messages from a Cell. It
+just creates a message object with the class call Stem::Msg->new and
+then dispatches it.  If the message doesn't have the 'from' address set,
+it will default to the address of the current Cell (if it is known).  If
+the code that generates a new message is not a registered Cell, then you
+must specify the 'from' address as one can't be deduced.
+
+For more on Cell addresses see registry_notes and message_notes.
diff --git a/Design/config_notes b/Design/config_notes
new file mode 100644 (file)
index 0000000..df1408d
--- /dev/null
@@ -0,0 +1,227 @@
+
+      Stem Object and Cell Creation and Configuration Design Notes
+
+All Stem cells and objects share the same API style in their constructor
+methods (commonly 'new'). All parameters are passed as key/value
+attributes and processed with Stem::Class::parse_args which is driven
+with a simple table of field descriptions. Each field is described by an
+anonymous hash with attribute/values. Each allowed field must have a
+name and it also can have several optional description attributes. Here
+are the supported field description attributes:
+
+       name            The value of this is the name of this
+                       field. Obviously the field name is required.
+
+       required        This is a boolean value that says this field
+                       is required. If it is not set in the constructor
+                       call, an error string is returned.
+
+       default         The value for this is the default for this
+                       field. It is used if this field is not set in
+                       the constructor.
+
+       class           The value for this is a Stem class (Perl
+                       package) name. It means the value of this
+                       parameter will be parsed as the arguments to the
+                       constructor ('new' for now) of that class. The
+                       object returned is saved as the value of this
+                       field. If the called constructor returns an
+                       error string, it is returned.
+
+       class_args      The value of this is an anonymous list
+                       of attribute/value pairs for this class
+                       field. They are passed after the caller's
+                       arguments and so will override any duplicate
+                       passed in parameters.
+
+       callback        The value of this is a code reference which is
+                       called to do custom construction of this
+                       field. The code ref is called with the new
+                       object, the field name and the anonymous
+                       reference which has the field's passed in
+                       values.
+
+       type            The value for this attribute is the type of the
+                       field. Currently unsupported, it is meant for
+                       stronger type checking of parameters such as
+                       booleans. This will be supported soon.
+
+       env             The value for this attribute is a name of a Stem
+                       environment variable. If this name is found in
+                       %Stem::Vars::Env then the value in that
+                       hash is used as the value for this
+                       attribute. This overrides any value passed in
+                       the the constructor or a default option.
+                       NOTE: Stem environment variables can be set from
+                       many places including the command line, the
+                       shell environment, command messages and the terminal.
+
+Here is a typical constructor from the Stem::Log class. It has 3 fields
+of which the first is required the other two have defaults. The
+beginning of the constructor sub is shown below and that same two lines
+of code is used in almost every class constructor.
+
+
+my $field_info = [
+
+       {
+               'name'          =>      'log',
+               'required'      =>      1,
+       },
+       {
+               'name'          =>      'level',
+               'default'       =>      'info',
+       },
+       {
+               'name'          =>      'text',
+               'default'       =>      '',
+       },
+
+] ;
+
+sub new
+
+       my $self = Stem::Class::parse_args( $field_info, @_ ) ;
+       return $self unless ref $self ;
+
+
+Object Creation Error Handling
+
+Stem cells and objects are being created all the time and in many
+ways. There is a standard way Stem constructor subs return errors. If the
+object construction works, it returns the object. If there is an error,
+it returns the error string. The caller must test constructor
+returns with ref to see if they worked. This makes it easy to pass back
+error messages to higher level objects with this code shown above.
+
+The first line parses the passed arguments (in @_) with a field
+description table. The second line tests if an object was created. If it
+was (ref $self is true), then the constructor continues. Otherwise, the
+error string is just returned to the caller. So the exact low level
+error is propagated up the call tree. This is used consistently in all
+of the constructors so even if you have a class which has a field which
+is a class (see the 'class' field type above), and it has a parse error,
+that error will be passed all the way up to the highest level call
+(typically in the Stem::Config module).
+
+Stem Cell Configuration
+
+Stem cells are typically created by the Stem::Conf class. The primary
+source of configuration data is from a file and that is handled by the
+load method. Currently config files are legal Perl and are just parsed
+with string eval. (Don't yell, I know it sucks but it is simple to
+bootstrap.) The next revision of this module will tighten up the
+specifications of config files and create a proper parser for it. The
+parser will probably use Parse::RecDescent (written by our advisor
+Damian Conway who will probably write the parser for us :). The config
+syntax will probably be similar to what it is now, but barewords
+(actually any token without whitespace) will be allowed anywhere. Only
+value strings with white space will need to be quoted. Config keywords will
+always be barewords. Fat comma will be supported and [] will demark
+lists of anything. There won't be any hashes since this is really just a
+mass of initializations and a list is fine to initialize a hash.
+
+A Stem cell configuration is comprised of a list of attribute/value
+pairs. You can also have a list of cell configurations in one file,
+but each configuration is handled independently. Each configuration
+entry has only a few options in the usual format of key/value pairs. The
+first is 'class', which is required and it names the Stem class which
+will be configured. The next one is 'name' and it is optional but almost
+always used. It is the name that this cell will be registered as and
+that is the address that will be used to send messages to this cell. The
+last major option is 'args' and its value is a list of attribute/value
+pairs used to initialize the cell.  Which set of configuration options
+is what controls how a cell is created and/or registered.
+
+The 'class' option is first processed and if it is not loaded, Stem will
+load it for you. This can be done remotely which allows for a servlet
+style of behavior, i.e. a request can come in and be a configuration or
+converted to one and the required Stem class will be loaded and a cell
+created. That cell can then be passed a message and respond to it. All
+of that can occur at runtime on demand.
+
+If the 'args' option is set, then a constructor of the class is called
+and it is passed the attribute/value pairs from the list value of
+'args'. The constructor method is defaulted to 'new' but that can be
+overridden with the 'method' option. The constructor processes its
+arguments (most likely using the Stem::Class::parse_args routine
+described above) and has 3 possible return values. If it returns undef,
+nothing more is done by the Stem::Conf module for this configuration. If
+a string is returned, that is assumed to be an error message and it is
+either printed or returned to the originator of this configuration. Any
+other configurations in this file (or passed in remote configuration)
+are skipped. If the retun value is a ref, then it is assumed
+to be an object and it can be registered with the address in the 'name'
+option.
+
+If the 'name' option is set, that will be used to register the cell or
+class itself. In most of the configuration cases, an object is created
+by the class constructor with the 'args' option and it is then
+registered as a cell with that name for its address. If no 'args'
+option is set, then the class itself is registered under the 'name'
+value and it is a class level cell. There can only be one class level
+cell for any class although it could be registered under multiple names
+(aliases). In addition, the value of the 'name' option is passed along
+with the 'args' values to the constructor as the attribute 'reg_name'.
+
+Here are some example classes which are configured in several of those
+ways:
+
+The Stem::Hub class needs to be initialized with a registration name
+but has no other attributes. So its configuration has a 'name' and an
+'args' option whose value is an empty list (that forces the constructor
+to be called).
+
+       [
+               class   =>      'Stem::Hub',
+               name    =>      'server',
+               args    =>      [],
+       ]
+
+The Stem::TTY::Msg class configuration doesn't use the 'name' option and
+it used an empty 'args' value. So its constructor is called and it
+returns its single class level object, and that is registered under its
+class name.
+
+       [
+               class   =>      'Stem::TTY::Msg',
+               args    =>      [],
+       ]
+
+
+The Stem::Demo::Cmd class is a class level cell that just has a 'name'
+option in its configuration and that is used to register the class
+itself.
+
+       [
+               class   =>      'Stem::Demo::Cmd',
+               name    =>      'cmd',
+       ]
+
+
+The Stem::Sock::Msg is configured in the most common way, with 'name' and
+'args' options and values for the arguments.
+
+       [
+               class   =>      'Stem::Sock::Msg',
+               name    =>      'C',
+               args    =>      [
+                       port    => 6668,
+                       server  => 1,
+                       data_msg        => [
+                               to_cell         => 'sw',
+                               to_target       => 'c',
+                       ],
+               ],
+       ]
+
+
+Normally a single configuration file is loaded by the run_stem program
+at start up time.  The Stem::Conf module also supports loading a
+configuration file via a command message or another configuration (which
+is similar to include files or Perl modules). A configuration which
+loads a configuration file can evaluate in the current Hub or send it to
+any Hub in the Stem system. This allows for centralized management of
+Stem configurations. As an aid to this, the stem_msg program can be used
+to send a 'load' command message to a Hub.
+
diff --git a/Design/console_notes b/Design/console_notes
new file mode 100644 (file)
index 0000000..f7cfe93
--- /dev/null
@@ -0,0 +1,15 @@
+
+                      Stem::Console Design Notes
+
+The Stem::Console is a class Cell which provides a simple command line
+interface to a Stem Hub. It parses lines typed into stdin and creates
+and dispatches command type Stem Messages from them. It also can accept
+and print response messages sent back by the command method in the
+addressed Cell.
+
+This class Cell is registered under its class name and with the
+nicknames console, cons, and tty.  It takes no configure arguments and
+it just creates an Stem::AsyncIO object to handle the console I/O.
+
+When you see the Stem> prompt, you can enter 'help' to see how to enter
+messages and commands.
diff --git a/Design/cron_notes b/Design/cron_notes
new file mode 100644 (file)
index 0000000..e272f8e
--- /dev/null
@@ -0,0 +1,80 @@
+
+                    Stem Cron System Design Notes
+
+Stem::Cron is designed to both supplant the standard OS cron and
+extending it to support more useful time filters. The key difference
+from the OS cron is that Stem::Cron sends a Stem message when it is
+triggered instead of running a process. This message can be addressed to
+any cell on any Stem hub and so it can cause any action to occur. To
+emulate the OS cron, all the message needs to do is to trigger an
+addressed Stem::Proc cell to spawn a process.
+
+
+
+Stem::Cron entries are similar to the OS cron. You can select a set of
+time parts (minutes, hours, dates, months, days of week) to trigger the
+entry and each part is a list which filters the triggers. Each minute
+(run by infinite repeat timer) the list of cron entries is scanned. If
+the current broken out time matches one corresponding value in each of
+the parts of the entry, then the entry is triggered and its message
+is dispatched.
+
+The cron entry is configured with name/value pairs which are the time
+parts and the set of which values to trigger on in that part. If a time 
+is not specified, it is assumed to match all values for that time part
+(like * in crontab). These two differences already make Stem::Cron
+easier to configure than crontab. In addition, each Stem::Cron entry
+must have a message value. This is sent when the cron entry passes all
+of its time filters and gets triggered. The message can be any type,
+carry any data and be addressed to any cell in the Stem network.
+
+Beyond the simple time filters of crontab, Stem::Cron allows you to
+specify complex date descriptions such as last date of the month, first
+weekday of the month, 2nd Thursday of the month, etc. 
+
+Another feature I am looking at is not well defined but i have a use for
+it. The log filters want to have a time based toggle so for example, you
+can enable/disable sending logs to a pager at night by sending it the
+right messages. By attaching those messages to properly configured cron
+entries, you have automated managing the times when the log filter is
+enabled/disabled. But there are several open design issues. First, what
+is the initial boolean state of the log time filter? Maybe we should make
+another boolean attribute: time_filter_enabled => 1. Then how often do
+you send the boolean toggle messages? A normal cron time range would
+send messages every minute from enable to disable time (huh?). or we can
+just send the disable message on one tightly defined cron entry and the
+enable on another. The initial boolean state is used and that will keep
+the log filter in the right state. This time controlled boolean toggle
+is a useful idea we can apply elsewhere. A module just for managing
+these things could be a good idea. It would, of course, use Stem::Cron,
+but it could be specialized in setting the cron entries up and other
+stuff.
+
+Right now, Stem::Cron entries are only created in configuration
+files. Creating remote entries would just require sending a config via a
+message to the global Cron cell.
+
+Stem::Cron is a high-level layer above Stem::Event timers.  The major 
+difference is that Stem::Event only uses callbacks, whereas 
+Stem::Cron only sends out messages.  Also, Stem::Cron has a much more 
+powerful and flexible API for specifying the timing of these messages.
+
+Besides the emulation of the standard OS cron, Stem::Cron can send 
+out a message at a repeated interval which has a resolution of 
+seconds.
+
+TODO
+
+more testing
+
+fancy time parts. i have developed perl code that calculates most of
+those dates so it should be simple to port the logic over to stem. 
+
+timed boolean triggers needs more design work and then coding.
+
+Sending remote configs has not been developed yet but should be done
+soon. should be generic and go into the Stem::Config module.
+
+
+
+
diff --git a/Design/debug_notes b/Design/debug_notes
new file mode 100644 (file)
index 0000000..e2529b1
--- /dev/null
@@ -0,0 +1,22 @@
+                      Stem::Debug Design Notes
+
+Stem modules have a need to report error conditions and trace critical
+events while being able to control when, where and how this information
+is presented.  The Stem::Debug module provides a way for any Stem module
+to create debug/trace subs customized to that module's needs.
+Stem::Debug delegates all of this backend filtering, formatting and
+distribution of these messages to the Stem::Log subsystem. In effect,
+Stem::Debug creates specialized front ends subs to Stem::Log.
+
+Using Stem::Debug is very simple. A module just does a use Stem::Debug
+statement with optional key/value parameters. More than one use
+statement can be made in a module and each one will create a new debug sub
+inside that module's namespace. When you create this sub, you can select
+its name, which logical log the message goes to, and the label and severity
+levels. Also a command line argument name may be specified for filtering
+even before the log entry is generated.
+
+The final design of the Stem::Debug is still in flux. It is not yet
+ready to be used by external developers.
+
+This module is only used internally and should not be configured.
diff --git a/Design/env_notes b/Design/env_notes
new file mode 100644 (file)
index 0000000..0fc2ed8
--- /dev/null
@@ -0,0 +1,63 @@
+
+                        Stem Environment Notes
+
+The global hash %Stem::Vars::Env is used to store environment values for
+the local Hub. These values can be set in a variety of ways:
+
+       Shell environment variables of the form STEM_* are located and
+       the STEM_ prefix is removed and the rest of the name is lower
+       cased. This name is set to the value of the shell variable.
+
+       The command line of run_stem is parsed for name=value tokens. The
+       name/value pair is set in the Stem environment.
+
+       The Stem::TtyMsg module parses its input lines for name=value
+       lines (only if the line doesn't contain a leading Cell address).
+       White space is allowed around each part and surrounding white
+       space is stripped from the value. The name/value pair is set in
+       the local Stem environment.
+
+       The Stem::Vars Class Cell (aliased to 'var') can take a
+       'set_env' command message. The data field is parse for
+       name=value just like Stem::TtyMsg does. This can be used from
+       the terminal of any Hub running Stem::TtyMsg to set environment
+       variables in any Hub.
+
+The environment values are accessable in several ways:
+
+       A Stem module can import %Stem::Vars::Env via a use Stem::Vars
+       statement. The %Env hash can be directly used.
+
+       The Stem::Vars Class Cell supports a 'get_env' command message
+       which uses the data field and the environment variable name. It
+       returns its value via the normal command/response message
+       mechanism.
+
+       The Stem::Vars Class Cell supports a 'status' command message
+       which returns a printout of the entire Stem environment for this
+       Hub. It returns that via the normal command/response message
+       mechanism.
+
+Here are some of the places and ways Stem Environment variables are
+used:
+
+       An attribute description can specify an environment variable
+       name (via the 'env' option). If this environment variable is
+       found it will be used as a default value for this attribute
+       overriding the default option value if it was supplied
+       too. A passed in value will set the attribute regardless of the
+       environment.
+
+       Logical Logs use environment variables to test whether a Log
+       Entry is filtered. The value of the variable is compared to the
+       Entry severity level with any one of the normal numerical
+       boolean tests and it sets the filter flag accordingly.
+
+       The Debug/Trace subsystem uses environment variables in a way
+       similar to the Log subsystem. When a Debug routine is created,
+       an enviroment name can be set and its value is used as a boolean
+       to determine if this call to the Debug sub will happen.
+
+Stem environment values are (and will be) used in many places. If you
+use them in configurations or in code, be sure that the names used are
+unique.
diff --git a/Design/event_notes b/Design/event_notes
new file mode 100644 (file)
index 0000000..49d2b93
--- /dev/null
@@ -0,0 +1,38 @@
+
+Events
+
+The low-level core of a Stem hub is an event-loop engine.  This 
+engine provides support for the common events needed in a networking 
+system, including reading and writing, socket connection and 
+acceptance, and timers.  The Stem::Event system provides a high-level 
+API to these services to the rest of the hub while it, in turn, 
+relies on a lower-level event system which is currently Event.pm, 
+which is available on CPAN.  This design isolates the actual event 
+engine used from the cells which need its services.  There are plans 
+to support other event engines including Perl/Tk, and creating one 
+that runs on NT/Win2K. 
+
+Stem::Event uses the standard Stem callback style which requires 
+parameters designating the object and method to call when an event 
+has been triggered.  In typical Stem fashion, the method names have 
+useful defaults so a Stem::Event call is made with very few 
+parameters. 
+
+Read and write Stem events take a required object and filehandle as 
+parameters, and an optional timeout value.  If the I/O is not 
+completed before the timeout occurs, the timeout method is called 
+instead of the normal I/O completion method.  The connect and accept 
+events also require an object and the appropriate socket parameters. 
+The connect event can take an optional timeout which behaves 
+similarly to the read/write timeout.  A timer event can be created 
+which will be triggered after a given delay and optionally repeated 
+at a specified interval.  As you would expect in an event system, 
+multiple instances of all these events can be active at the same 
+time. 
+
+Most cells will never directly use the Stem::Event interface as there 
+are higher level cells that perform commonly needed services for 
+them.  These include Stem::AsynchIO, Stem::Socket, and Stem::Cron. 
+However, this does not stop any cell from directly calling these if 
+it needs finer control over its events. 
+
diff --git a/Design/id_notes b/Design/id_notes
new file mode 100644 (file)
index 0000000..7308e4b
--- /dev/null
@@ -0,0 +1,18 @@
+
+                        Stem::Id Design Notes
+
+The Stem::Id module generates unique Id strings for use as names in Stem
+addresses. Its most common use is by parent Cells which clone themselves
+and need a unique Target. The parent Cell uses its Cell name and the new
+Target to register the cloned Cell.
+
+A typical use is by a Stem::SockMsg Cell which creates a Stem::Id object
+during its own construction. When this parent Cell accepts a socket
+connection, it clones itself and needs to register this new Cell with a
+unique address.  The parent Cell calls the next method of its Stem::Id
+object to get a unique Id which is uses for the Target address of the
+cloned Cell. Then the cloned Cell is registered with the parent Cell
+name and the new Target name. This address is then sent to other Cells
+so they can communicate with this new Cell.
+
+This module is only used internally and should not be configured.
diff --git a/Design/index b/Design/index
new file mode 100644 (file)
index 0000000..69395f3
--- /dev/null
@@ -0,0 +1,140 @@
+
+                         Stem Technical Notes
+
+
+Stem Architecture
+
+       This describes the top level architecture of Stem, in particular
+       what its components are (Hubs and Cells) and how they work
+       together.
+
+Stem Cells
+
+       This describes Stem Cells, the primary components of a Stem
+       system. It covers both class and object type Cells, their
+       creation and registration and their method conventions.
+
+Stem Messages
+
+       This describes the content and fields of Stem messages. These are
+       used to communicate between Stem Cells.
+
+Stem Registry and Message Addresses
+
+       This describes how Cells are registered so they can receive
+       messages and the way messages are addressed.
+
+Cell Creation and Configuration
+
+       Stem Cells can be created internally via a constructor or from
+       external configurations. This describes the common systems used
+       to create and register Cells.
+
+Logging Subsystem
+
+       This describes the Stem logging subsystem including logical
+       logs, log files, log filters and how to submit log entries.
+
+Cron Subsystem
+
+       This describes the Stem Cron subsystem and how it can schedule
+       regular Stem message submissions based on time and date.
+
+Stem Events
+
+       This describes the low level Stem Event subsystem including
+       reading, writing and socket events.
+
+Security Notes
+
+       This describes the Stem security model and its features.
+
+
+       Stem Cell and Module Notes
+
+These are some of the Cells and modules in the Stem library. These notes
+will describe what they are, why they are needed and how they are
+used. Also some examples of their use and configuration are given. For
+detailed descriptions of their configuration, attributes, and methods
+see their documentation (available in the source tree or <HERE>)
+
+NOTE to jasmine: when we get the pod generation stuff going soon, we
+will make html versions of the pod docs and web them. they need to be
+linked from each cell tech notes and be in a documentation subdir with a
+nav bar link. this is later but i am just letting you know now.
+
+NOTE: also i am going to list all of the cells here, even if they don't
+have a tech notes page yet. make an empty one for them. we should come
+up with a template for their names, format, etc. i want to drop the
+'notes' part in most cases.
+
+
+Stem::Conf
+
+       This module parses and executes Stem configurations from both
+       local files and sent from remote Stem Hubs.
+
+Stem::Proc
+
+       This Cell forks off and manages external processes. They can be
+       monitored and all their I/O can be easily be integrated with
+       Stem via messages, logs and/or callbacks.
+
+Stem::SockMsg
+
+       This Cell connects to and accepts standard Internet
+       sockets. They can be monitored and all their I/O can be easily
+       be integrated with Stem via messages, logs and/or callbacks.
+
+Stem::Cron
+
+       This module creates and manages scheduled message submissions.
+
+Stem::Log
+
+       This module supports the Stem logging subsystem. It encompasses
+       the Stem::Log::File, Stem::Log::Filter and Stem::Log::Entry
+       classes. 
+
+Stem::LogTail
+
+       This Cell monitors an active external log file. It sends new
+       data into the Stem logging subsystem on periodic basis or on
+       demand.
+
+Stem::Switch
+
+       This Cell is a general purpose Stem Message multiplexor.
+       Messages directed to this Cell can be
+
+Stem::Debug
+
+       This class is used to create standard and customized debug and
+       trace subs in any Stem module. The  used
+
+Stem::Id
+
+       This class is used to create ID's for dynamically created
+       Cells that need unique addresses.
+
+Stem::Portal
+
+       This class creates and manages connections between Stem Hubs. It
+       converts Stem Messages to/from a stream format for transmission
+       over the connection.
+
+Stem::AsyncIO
+
+       This class is used by other Cells to create and manage buffered
+       I/O streams to sockets and process handles.
+
+Stem::Socket
+
+       This class is the low level API to create socket connections and
+       accepted sockets.
+
+Stem::TtyMsg
+
+       This class Cell is used to provide a TTY interface to a running
+       Stem Hub. Stem command messages can be entered and sent to Cells
+       and their responses are printed.
diff --git a/Design/log_notes b/Design/log_notes
new file mode 100644 (file)
index 0000000..0aecef0
--- /dev/null
@@ -0,0 +1,133 @@
+
+                         Stem Logging Design
+
+The Stem logging subsystem is designed to be very flexible, powerful and
+yet simple to use. Log data comes into the system via a Log Entry which
+is submitted to Logical Logs. Entries can be submitted to multiple
+Logical Logs which can be local to the current Hub or on remote
+Hubs. Each Logical Log processes the Entry which can be filtered,
+redirected and written to physical log files.  Logical Log filter rules
+can match the text or label with regular expressions, test the range or
+value of the level, check the time of day or do any boolean combination
+of those. If an Entry passes a set of rules, then it is passed to a set
+of actions which can execute a wide range of operations upon it
+including printing the entry to a file, sending it via email or to a
+pager, printing it to stderr, or the entry can be forwarded to other
+Logical Logs. The full set of filter rules and actions are described
+below.
+
+Log Entries are constructed with the 'new' method of the
+Stem::Log::Entry class. The caller can set the entry's text, label, and
+a severity level and the timestamp is automatically stored in the
+entry. If any Logical Logs are specified the entry is submitted to
+them. In any case, the entry object is returned and it can be submitted
+to Logical Logs with its submit method. The class Stem::Log::Entry is
+registered as a Cell so Log Entries that are forwarded from remote Hubs
+can be handled by this class.  Log Entries can be created by created and
+submitted by code anywhere in Stem. Many Cells can be configured to
+submit Log Entries which contain data or status information. The
+Debug/Trace subsystem also generates Log Entries as do any monitoring
+modules such as Stem::LogTail.
+
+
+A Logical Log is constructed by the 'new' method of the Stem::Log
+class. They are typically created by external configurations but some
+modules create them internally for their own use. Each Logical Log on a
+Hub must have a unique name and that is the name used to submit Log
+Entries. Remote Logical Logs are referred to by a string of the form
+'Hub:LogName'. Any place where you can specify a Logical Log name, you
+can also use a remote Log name.
+
+When a Log Entry is submitted to a Logical Log it gets filtered and
+processed. The Logical Log is configured with optional physical file and
+filter attributes. If there is no filter in a Logical Log, its default
+is to print any submitted Entries to its file (if there is one). Logical
+Logs don't need to have a physical file attribute as they can filter and
+print their Entries to many other possible destinations (see below for a
+list of actions and Entry destinations).
+
+The 'path' attribute of a Logical Log specifies its file. Other
+attributes control the long term management of the file. They include
+when to rotate the log file, the format of the timestamp suffix of the
+rotated files, any compression to be performed, where to move archived
+logs, eternal programs to be called to process the log file, etc. These
+log file handling attributes and their code support are under development.
+
+The filter attribute of a Logical Log consists of a set of key/value
+pairs which are called filter operations. When an Entry is submitted to
+a Logical Log which has a filter, a private hash copy of all of its data
+is made and a special boolean called the filter flag is set in that
+hash. All of the filter operations are processed sequentially and work
+with that flag. The operations can be grouped into 3 types, flag
+operations, rules and actions. Flag operations directly modify the
+filter flag and its behavior which is used to control the rules and
+actions of this filter. Rules are boolean tests that check the submitted
+entry for some condition and can set or clear the filter flag. Actions
+print or forward the submitted Entry only if the filter flag is
+currently true. The filter flag is initialized to true so all actions
+and rules will be executed until some rule or flag operation clears it.
+
+Flag operations are always executed regardless of the current value of
+the filter flag. The current value of the filter flag can be set,
+cleared or inverted. Also the boolean operation that is used with the
+rules can be selected. It defaults to 'and' which causes each rule's
+boolean result to be 'and'ed with the filter flag and stored there. If
+the flag operator is set to 'or', then the rule result is or'ed with the
+flag and stored back into it. The boolean test of the filter flag can be
+inverted with the 'invert_test' flag operation. By combining the flag
+operations and the negated prefix of rules (see below) you can get any
+boolean combination of rules. If you want multiple sets of rules each
+with their own set of actions in a filter, just set the filter flag to
+true before each set of rules and follow them by their associated
+actions. If you want to execute some actions if any of a set of rules is
+true, set the filter flag to false, set the flag operation to 'or' and
+set the test to inverted. The next rules will execute since the test in
+inverted and the flags is false. If any rule returns true, it will will
+set the flag since it is 'or'ed with it. The rest of the rules will be
+skipped. Then the normal_test operation should be executed. The actions
+that follow will only be executed if any rule was true.
+
+Filter rules are only executed if the filter flag is currently true (or
+false when the inverted_flag operation is in effect). Each filter rule
+name can be prefixed with 'not_' which will invert the results of the
+rule. There are many builtin rules which are grouped into three
+categories. The first group matches either an Entry label or text with a
+regular expression. The second group compares the Entry severity level
+with an integer. The third group compares the Entry severity level with
+a global value in the %Stem::Vars::Env hash. Those hash values can
+be set on the command line, from environment variables and by code. This
+allows for fine control of how Entries get filtered by level. Examples
+of using that facility are to enable debug/trace calls to output to
+stderr or be forwarded to a remote Logical Log.
+
+Filter actions, like filter rules are only executed if the filter flag
+is currently true (or false when the inverted_flag operation is in
+effect). But actions cannot affect the value of the filter flag and are
+meant to send Log Entries to different destinations. The builtin actions
+can print Log Entries to stdout, stderr or the controlling TTY. Entries
+can be emailed, sent to a pager, written to the console with the wall or
+write commands, or forwarded to other Logical Logs. Of course they also
+can be written the to physical file associated with this Logical Log.
+
+WARNING: Currently forwarding loops can be created with Log filter
+actions. There are plans to detect them with either storing in the Log
+Entry a hop count or a history of which Logical Logs it has seen.
+
+Custom filter rules and actions can also be created. Any module can have
+them and they are called by their name which is the value of the
+'custom' operation. The difference between a custom rule and action is
+that the rules return a defined boolean value while the actions return
+the undefined value (a plain return does that).
+
+When a Log Entry needs to printed by an action (which all builtin ones
+except forwarding does), it must format the Entry. This is controlled by
+the 'format' attribute of the Logical Log. The format value is similar
+to sprintf and uses % as a field marker. It can print the Entry text
+(%T), label (%L), level (%l), timestamp (%l) and original Logical Log
+name (%N) (so forwarded Log Entries can say where they came from). The
+default Log Entry format is %T which will just print the text. Also the
+timestamp which is normally printed as an integer (Unix Epoch time) can
+be printed with the %f marker in a strftime format. The attribute which
+controls the time format is 'strftime'.  The default strftime format is
+%C which will print the time as the command 'date' will.
+
diff --git a/Design/logtail_notes b/Design/logtail_notes
new file mode 100644 (file)
index 0000000..bfc64be
--- /dev/null
@@ -0,0 +1,44 @@
+
+                         Stem::LogTail Design Notes
+
+The Stem::LogTail module provides a service similar to the standard Unix
+program tail -f. It constructs a Cell that can watch a file for changes
+in it size or inode and then sends the new data to a destination. The
+destination is a Logical Log created elsewhere in the Stem application
+and it can be located on any Hub on the network. Also file status
+changes such as not being found, first time it is opened, it was
+truncated, etc. can be sent to a different Logical Log than the data.
+This module is a critical part of the StemLog application which
+transfers active log files from one system to another.
+
+The action of checking the file is triggered by a call to the tail_cmd
+method. There are two main ways of triggering it, either by an internal
+timer or via a command message directed at this Cell. The timer is
+configured when the Cell is created and its resolution in in seconds. So
+e.g. you can cause a file check to occur every 15 seconds. If a 'tail'
+command message is sent to this Cell, it will also trigger a file
+check. The command message is usually sent from a Stem::Cron entry
+configured elsewhere. In either case new data is sent to the 'data_log'
+Log and status is sent to the 'status_log' Log.
+
+The primary configuration parameter is the path to the file to be
+checked. Also the required 'data_log', optional 'status_log' and
+'repeat_interval' are set in the configuration.
+
+When the file is checked, the current size and inode are compared to the
+previous values. If either has changed, then either data and/or status log
+entries are sent.
+
+NOTE: The modification time of a file is not currently checked for
+changes. The reason is that there is no way to determine if a file
+changed (or what data has changed) if the file just has its modification
+time changed (by touch or utime). This means that there is a weakness in
+just checking for the file size changing. If a file were to be truncated
+and then written to with the same amount of data as it had when it was
+last checked, a tail operation would not report any new data. This bug
+exists in both the GNU and Solaris tail programs. The only way around
+this is to check for modification time changes and trust
+this. Stem::LogTail will support an option for doing this instead of
+checking file size but it is up to the user to assure that the file will
+only change its modification time if its data actually changes. Most
+logs generated by programs have this attribute.
diff --git a/Design/message_notes b/Design/message_notes
new file mode 100644 (file)
index 0000000..675f494
--- /dev/null
@@ -0,0 +1,98 @@
+                      Stem Message Design Notes
+
+Stem Messages are how Cells communicate with each other. Messages are
+simple data structures with two major sections: the address and the
+content. The address contains the Cell name the message is directed to and
+which Cell to send replies to. The content has the message type, command
+and data. These sections are described below in further detail.
+
+The Message address section has multiple addresses called types. The two
+supported types correspond to the common email headers and are called
+'to' and 'from'. The 'to' address designates which Cell will get this
+message and the 'from' address says which Cell sent this message. Other
+address types which may be supported are 'reply' (which overrides the
+'from' address for replies) and 'orig' which is the address of the
+original Cell which created this message (useful when messages get
+forwarded).  Each address type contains the standard Stem Cell address
+triplet of Hub/Cell/Target which are called the address parts. The Cell
+name is required and the Hub and Target are optional.
+
+The Message content has information about this message and any data
+being sent to the destination Cell. The primary attribute is 'type'
+which can be set to any string, but common types are 'data', 'cmd',
+'response' and 'status'. Stem modules and Cells can create any Message
+types they want. If the Message is a 'cmd' type, then the 'cmd'
+attribute must be set to the command. A status type Message requires the
+'status' attribute be set and the 'data' or 'response' types has the
+'data' attribute set (though any message type can send data). There is
+also a special flag attribute called 'ack_req' which forces a 'msg_ack'
+type to be sent back to the 'from' address after this message is
+delivered. One important note about the 'data' attribute, it is always a
+reference and never a simple Perl scalar value. You can send a scalar
+value but only by setting the 'data' attribute to its reference.
+
+Messages are constructed with the 'new' method of the Stem::Msg
+class. Any of the message attributes can be specified in that call or
+accessed/modified with the accessor methods. When a Message is completed
+and ready to be sent, the dispatch method is called and the message is
+queued for delivery. For convenience, the Message address types and part
+can be combined into one attribute name in both the constructor and
+accessors. So the Cell the message is directed at can be set with
+'to_cell' and the Hub it came from can be accessed with 'from_hub'. The
+entire address triplet of an address type can be set or accessed just
+with its type name, so the 'to' address is set or accessed with the 'to'
+attribute or method. It takes or returns a hash of the address parts and
+their values.
+
+Messages are delivered only after the current callback is finished
+executing (remember all code in Stem are called as callbacks). Stem
+Message delivery is the heart of Stem operations and is described in
+detail here. Delivery take place in three phases, the first determining
+which Hub the Message is sent to, the second, which Cell in that Hub
+gets it, and the third, which method in that Cell to call for delivery.
+
+If the Message has a 'to_hub' address, then that Hub name is looked up
+in the Portal (which are Cells which connect Hubs together) registry. If
+a Portal is found, the Message is delivered to it to be sent to the
+destination Hub for local delivery. A Message can be forwarded across
+several Hubs before it gets delivered to its destination Cell. If the
+Hub name is the same as the current Hub or there is no Hub name in the
+address, the Message is delivered to a Cell in the current Hub. In the
+special case where there is no Hub name and the Cell name isn't
+registered in the current Hub, the Message is sent to the Portal with an
+alias of DEFAULT. This is just like the default route in IP routing
+tables. If there is a Hub name and the Cell is not found, then there is
+an addressing error and that is logged and the Message is discarded.
+
+Once a Message's destination Hub is reached, it must be delivered to a
+local Cell. The 'to_cell' and 'to_target' attributes are accessed from
+the message and that pair is searched for in this Hub's Cell registry.
+If there is no Target name in the address, it defaults to the null
+string.  If there is a Target and the Cell is not found, the search is
+repeated with no Target name (the Target name will be used by the
+destination Cell). If the Cell still is not found, an addressing error
+will be logged (with the message address) and the Message is discarded.
+
+When the destination Cell of a Message is determined, the method to call
+for delivery must be chosen. The rules for this are simple. If a Message
+is a 'cmd' type, then the method name is made by taking the 'cmd' name
+from that attribute and appending '_cmd' to it. So a 'foo' command
+message will have its type set to 'cmd', the 'cmd' set to 'foo' and it
+will be delivered to the 'foo_cmd' method. If the Message is any other
+type than 'cmd' the method name is created by taking its type name and
+appending '_in' to it. So a 'foo' type Message is delivered to the
+method 'foo_in'. If the delivery method doesn't exist, the default
+method 'msg_in' is used. If no delivery method is found, then an error
+is logged and the Message is discarded.
+
+Command Messages have a useful feature where they can automatically
+generate 'response' messages. When a 'cmd' delivery method is called and
+it returns a defined value, a 'response' type Message is created using
+the 'reply' Message method. This uses the 'reply' or 'from' address in
+'cmd' Message as the 'to' address in the 'response' Message. Its data
+field is set from the data returned from the command delivery
+method. This reduces the work of common command methods to just having
+to return a data value. Many Cells use this technique, e.g.  the
+status_cmd method in a Cell just returns the status text of it. The
+delivery mechanism takes that text and creates and dispatches a
+'response' method with the status text as its 'data' payload.
diff --git a/Design/portal_notes b/Design/portal_notes
new file mode 100644 (file)
index 0000000..5d23d08
--- /dev/null
@@ -0,0 +1,45 @@
+
+                      Stem::Portal Design Notes
+
+The Stem::Portal class supports the transmission of Stem Messages
+between Stem Hubs. Portals are based on sockets and are configured very
+similarly to a Socket with host and port attributes. Also a Portal can
+be designated a TCP/IP server or a client. Once two Hubs are connected
+via their Portals, they communicate peer to peer in full duplex mode.
+
+Portals are sent a Message to transmit by the Stem::Msg delivery
+subsystem. The Message is converted to a byte stream format and written
+to the Portal socket. When a Portal reads a byte stream Message from its
+socket, it converts it back to an internal Message and sends it to the
+Stem::Msg class for delivery.
+
+When a Stem Portal connects to another, they both exchange a special
+'register' type Stem Message. This message when received by a Portal is
+trapped and never sent for delivery to the Stem::Msg subsystem. The
+'register' message contains the Hub name of the remote Portal. This name
+is then stored in a registry private to the Portal class. It is used by
+the Stem::Msg delivery subsystem to look up which Portal to use to send
+out a remote message. You can see the current state of the Portal
+registry by sending a 'status' command message to the Portal class. This
+is easily done from the terminal (if the Hub has Stem::TtyMsg
+configured) with the command:
+
+       :port status
+
+If you put a Hub name before the :, you will get the Portal registry
+status of that Hub.
+
+A special alias is set up for a client Portal that is the first one
+configured in a Hub. It is given the alias DEFAULT and this Portal
+becomes the default destination for any Messages that aren't delivered
+locally. So if a Stem Message has no Hub in its 'to' address and the
+Cell name is not registered locally, it is sent to the DEFAULT Portal
+and hence to the Hub it is connected to. This is very similar to the
+default route in IP routers like the routed daemon. A new feature (to be
+implemented soon) will be to allow the setting of the DEFAULT Portal at
+configuration or run time.
+
+Security of Hub to Hub communications are handled by the Portal
+Cells. There are two designs in progress, one which pipes over ssh to
+the other Hub, and an internal encryption module which will perform the
+same functions but be more efficient.
diff --git a/Design/proc_notes b/Design/proc_notes
new file mode 100644 (file)
index 0000000..265914c
--- /dev/null
@@ -0,0 +1,34 @@
+Stem::Proc
+
+A major service in network management infrastructure is running and 
+controlling processes.  Stem::Proc is a very simple to use cell which 
+has a wide range of useful options to control the process behavior 
+and its standard I/O.  The required parameters to the cell specify 
+the program  and its arguments.   
+
+The first group of configuration parameters controls how and when the 
+process is actually started.  A process can be started at 
+configuration time or initiated by a command message.  The message 
+interface is very important as it allows process management from any 
+other cell, including Stem::Cron (this will allow emulation of the OS 
+cron running processes), Stem::SockMsg (this will allow emulation 
+of the OS inetd), and other similar needs for remote process 
+invocation.  Process monitoring is achieved by handling SIGCHLD 
+signals and I/O handle shut-down detection.  A message can be sent 
+upon process exiting and the process can optionally be restarted 
+automatically, giving you a watchdog service.
+
+The second group is much larger and controls the I/O behavior of the 
+process.  You can enable/disable any subset of its 
+stdin/stdout/stderr handles.  This is needed for processes that don't 
+use all of their standard I/O handles.  Process I/O can be managed by 
+the Stem::AsyncIO class and callbacks.  In addition, the cell can be 
+configured to transfer its I/O data to/from other cells via Stem 
+messages.  One neat feature is the ability to buffer all output from 
+a process and send it via a message only upon process exit.  This 
+emulates the running of a process in backquotes as supported by 
+shells and Perl.
+
+Processes that need to work with a terminal can optionally be run 
+behind a pseudo-TTY device. 
+
diff --git a/Design/registry_notes b/Design/registry_notes
new file mode 100644 (file)
index 0000000..086e3d5
--- /dev/null
@@ -0,0 +1,131 @@
+
+            Stem Cell Registry and Message Address Design Notes
+
+The heart of Stem is the messaging subsystem and the heart of that is
+the registry. This is where all knowledge of how to address cells is
+located. Each cell gets registered by it name and optionally its target
+and messages are directed to it via its names. The decisions made by the
+registry when delivering a message are described here as well as the API
+and other related issues and modules.
+
+
+Stem Message Addresses
+
+Stem messages are sent to registered cells by using an address triplet:
+the hub name, the cell name and the target name.
+
+A hub is a single process running Stem. Its name must be unique among all
+Stem hubs in a single connected net. A hub consists of a set of objects
+and Stem cells. It contains the message registry, the core Stem system
+and it will load other modules on demand.
+
+A Stem cell is a single object in a Stem hub which has registered itself
+under a name and can receive messages via its methods. Not all objects
+in Stem are cells, but all Stem cells are objects. Cells are commonly
+registered by the Stem::Config system or by a parent cell spawning
+targeted cells. Only one cell can be registered in a hub for a given
+cell name. One unusual trick is that a whole class can register itself
+as a cell by using its class name as the object and some fixed string as
+the name (sometimes that is the class name as well). There can only be
+one cell by that class and name but there can be aliases for any cell
+name. That is used by cells which must be implemented with class level
+data.
+
+The target is the last part of an address and is optional. A given cell
+could be registered with a cell name and target and it can send and
+receive messages with its own globally unique address. The cell name is
+either the parent's cell name or a fixed one for the particular class
+(the Stem::Log::Filter class does this). The target name is commonly
+either a Stem::Id value or a name from a configuration. Another use for
+the target is a cell such as Stem::Switch which uses it to address its
+input/output maps. The use of the target is defined by the design of the
+cell.
+
+Message Delivery
+
+The first step in delivering a message is finding out which cell it goes
+to. This is done by looking up the cell that matches the hub/name/target
+address in the message. This is a multistep procedure with the following
+rules:
+
+If the hub name of the message is set and it is not the name of this
+hub, locate the portal that can send to that hub and deliver the message
+to that portal. Portal names are in a different namespace as regular
+cells but portals can also be registered as targeted cells so they can
+have commands sent to them. See more on Portals below.
+
+If the message has a cell name and an optional target name, the cell is
+looked up in the local registry. Cells with just a cell name don't share
+the namespace with cells that have cell and target names. If the cell is
+found the message is delivered by a method. (See how that is chosen
+below.)
+
+If the cell is not found locally it is sent out via a portal with the
+alias DEFAULT. This portal should be connected to a hub which would know
+how to direct the message to the proper destination cell. Typically a
+Stem hub that is a TCP client to a more central server hub will just
+have its portal to the server aliased to DEFAULT.
+
+If the message has the local hub name and couldn't be delivered, it is
+logged and thrown away. Optionally a delivery failure message could be
+sent back to the originator. But this is not the Internet and bounces
+can be automatically fixed in Stem.
+
+NOTE: This brings up the whole subject of message routing. I have been
+thinking about this issue for a while and it is not as tricky as the
+Internet because of several things. First, we can cheat. Stem is
+completely in charge of its routing so it can be smart about itself and
+not deal with worst case situations like the net. A hub can be
+configured to distribute routing information that supports the network
+topology. The discovery of the network and its topology can also be
+automated by a booting Stem network, even from a virgin boot. Remote
+Stem hubs could be installed with minimal (and not customized)
+configurations which will cause itself to connect to a server hub and
+download the real configuration. This simplifies deployment of Stem to a
+new set of boxes. Much more on this subject will be in another design
+notes file.
+
+
+Choosing the Cell Method
+
+Once the destination cell of a message is determined, you then have to
+find out its best method to call to deliver that message. Stem's
+messages can be delivered via a generic method (e.g. 'msg_in') which is
+expected to take any type of message, or via specific methods
+(e.g. 'data_in') which handle selected messages. Here are the rules for
+determining the cell method to call.
+
+If the message type is 'cmd' with a command 'foo' and there is a cell
+method 'foo_cmd', the message is delivered via that method. If a command
+message is delivered via a command method and a value is returned, that
+value is sent back to the 'from' address in a response message.
+
+For all other message types, if the Cell has a method that is the type
+name with '_in' appended to it, that method is used for delivery,
+e.g.; if the message type is 'data', and if the cell has a method named
+'data_in', that is called with the message as its sole argument.
+
+If the message is not delivered by any of those special methods, it will
+be delivered to the generic method 'msg_in'. This method should exist in
+every cell (except those that have the special methods cover all their
+message types).  The method delivery lookup simplifies writing Cells by
+moving the internal dispatching code from the Cell to the registry.
+
+
+
+Stem::Id is a simple module designed to manage a set of unique IDs for
+its owner object, i.e.; it is used by the Stem::SockMsg modules
+to register all of its accepted/connected sockets with unique targets.
+
+Stem::Portal is the class that send messages between hubs over
+pipes. These pipes can be direct sockets or indirect through a secure
+transport such as ssh or stunnel. It receives messages vis the 'send'
+method which are then converted to a string form and written out the
+pipe. The stringify format is currently Data::Dumper but it can be set
+via the configuration of the portal to use Storable, XML or something
+else. Each stringified message is prefixed with 1 or 2 lines containing
+its size and format. Incoming message strings are converted back into
+internal messages and then delivered locally by calling dispatch on
+them. Portals can use any communications channel as long as it gets read
+and write handles. This means that new security and transport protocols
+can be integrated easily into the portal.
diff --git a/Design/security_notes b/Design/security_notes
new file mode 100644 (file)
index 0000000..7757bf8
--- /dev/null
@@ -0,0 +1,18 @@
+Security
+
+A critical aspect of any network application these days is security. 
+Stem hubs communicate with each other via standard internet sockets. 
+This communication can be made secure by tunneling over existing 
+open source products such as SSH and Stunnel.  Stem runs the security 
+program and has it connect with the remote hub and its stdin/stdout 
+is used for the local connection.  The Stem::Proc cell is used to 
+manage this external security program and it provides all the I/O 
+support needed.  The Stem::Portal cell is used to connect Stem hubs 
+together and it has configuration options to select the desired 
+security application and options.  A Stem hub can listen for portal 
+connections using a Listen socket bound to localhost or an IP 
+address.  By using localhost and a secure transport, a Stem hub 
+cannot be accessed by any unauthorized programs.
+
+
+
diff --git a/Design/sock_msg_notes b/Design/sock_msg_notes
new file mode 100644 (file)
index 0000000..45395d6
--- /dev/null
@@ -0,0 +1,33 @@
+Stem::Sock::Msg
+
+This cell is the primary way of interfacing external programs to the 
+Stem messaging system.  It is effectively a gateway with a standard 
+socket on the outside and a message interface on the inside.  The 
+socket side takes standard Stem socket parameters for configuration. 
+The message side has a variety of message types and options to 
+control buffering. 
+
+The socket parameters are directly passed to a Stem::Socket cell, and 
+supports both client and server modes.  Multiple instances of this 
+Stem::Sock::Msg cell are supported and can be created via socket 
+connection or acceptance.  If a connected socket is externally shut 
+down  it can optionally be reconnected automatically.
+
+When the socket is connected or disconnected, a status message can be 
+sent out.  This is used to trigger the addressed cell to be made 
+aware of the new socket status and act accordingly.  For example, in 
+an inetd emulation, the socket connected message would cause the 
+address Stem::Proc cell to start a process and logically connect it 
+to this Stem::Sock::Msg cell.  Similarly, the socket closed message 
+would cause the process to shut down and the logical connection to be 
+broken. 
+
+When data comes in from the socket, it is buffered and sent out via a 
+data message to an addressed cell.  Optionally, all incoming data can 
+be buffered and only sent out in a single message when the socket is 
+closed.  This reduces the need for some cells to do their own 
+buffering. 
+
+As with all Stem cells, various status information can be logged to 
+logical logs using the Stem logging system.  Which status is logged 
+can be controlled by the configuration of the cell.
diff --git a/Design/socket_notes b/Design/socket_notes
new file mode 100644 (file)
index 0000000..0fe4366
--- /dev/null
@@ -0,0 +1,19 @@
+                      Stem::Socket Design Notes
+
+The Stem::Socket module provides an interface to create connected
+sockets. It can either connect to a remote socket as a client or listen
+for connections as a server. Its constructor takes an owner object, an
+optional host or IP address and a required port number. Also a boolean
+flag designates whether is it a client or server. If no host argument is
+provided, it will default to localhost. To make a server listen on any
+IP address (with the wild card INADDR_ANY), pass in the empty string ''
+as the host.
+
+When a socket connection is made (either a client or a server), the
+owner object is notified by a callback and the handle of the newly
+created socket is passed to it. Since a client socket can timeout when
+connecting to a server, a timeout value can be set. If the connection
+request times out a different method is used as the callback to the
+owner object.
+
+This module is only used internally and should not be configured.
diff --git a/Design/switch_notes b/Design/switch_notes
new file mode 100644 (file)
index 0000000..9df97d3
--- /dev/null
@@ -0,0 +1,23 @@
+Stem::Switch
+
+The Stem::Switch cell is a very simple but powerful object which can 
+be used in a wide range of applications.  Its primary function is to 
+receive a message and to copy and redirect it to a list of 
+destination cells.  It can be used as a multiplexer in many-to-one, 
+one-to-many, and many-to-many configurations.  Currently it is used 
+as the heart of the chat server demo and the Inetd demo. 
+
+Stem::Switch has two maps: the input map is used to translate the 
+incoming target address to entries in the output map.  Each input map 
+entry can have multiple output targets and that list can be set via a 
+configuration file at start-up time or a command message at runtime. 
+The output map just converts the output name to an actual cell 
+address and is one-to-one; it also is set via the configuration file 
+or by runtime command messages.  In addition to the explicit command 
+message technique for changing the maps, a publish/subscribe 
+interface is supported.  A given cell can announce to a Stem::Switch 
+cell that it will be publishing messages to a given target address in 
+the switch.  Other cells can send a subscribe message which will 
+cause all messages sent to the selected publish address to be 
+forwarded to themselves. 
+
diff --git a/Doc/FAQ.txt b/Doc/FAQ.txt
new file mode 100644 (file)
index 0000000..2b644bb
--- /dev/null
@@ -0,0 +1,260 @@
+What Can I Do With Stem?
+
+You should probably ask, "What can't I do with Stem?" :-). Stem is not
+bound by any narrow niche definition since it is a general purpose
+networking toolkit. As such it can be the backbone of almost any
+networked application. On the other hand Stem is designed with standard
+modules that are aimed at specific application niches. Some Stem users
+want a a way to accelerate network application development and others
+want a simple solution to common problems. Stem can satisfy the
+differing needs of both groups.
+
+What Are the Top Stem applications?
+
+The most commonly requested applications of Stem are log management,
+file distribution, monitoring and remote command execution. For more on
+Stem applications see the <A href="/app_niches.html">niches</A> page.
+
+Why Should I Use Stem?
+
+You should use Stem if you are tired of reinventing the wheel for each
+network application you build. Or if you don't want to be forced to rely
+on a commercial monolithic network tool that everyone hates to use. Or
+if you want to dramatically reduce the development and maintenance costs
+of developing your new network applications. Or if you just want to use
+a well designed network system that will make your profession much more
+enjoyable. Please contact us if you want more reasons.
+
+Who Is Using Stem?
+
+Currently we are developing a Stem based log management system for a
+Fortune 500 company. It will watch growing log files on hundreds of
+systems and transfer the new log records to a central system for
+analysis and archiving. This complex system will use standard Stem
+modules and require only a single customized tool that creates the
+configuration files. Another Internet content provider company we are
+talking to is interested in a similar system. We don't know of any
+products that can manage logs over a network as simply and elegantly as
+Stem. 
+
+What Existing Products Does Stem Improve On?
+
+Stem is not a direct improvement of any existing product. Rather it is a
+coherent integration of a range of network services as used by many
+products. Stem did not invent logging, monitoring, process management,
+file transfers, etc, but it has put them together under a simple
+architecture that meets the needs of the technical market.  With Stem,
+solving many common network problems doesn't require any coding at all,
+just simple edits to text-based configuration files. Even when coding
+*is* required, Stem allows you to develop and maintain complex network
+applications much more cheaply and reliably than existing application
+frameworks.
+
+Do I Need To Get Rid Of [Commercial App] To Use Stem?
+
+No, Stem can run in parallel with all of your existing applications. As long
+as Stem can have access to its own TCP ports (which can be configured to
+any available ones), it has no need to conflict with any of your current
+systems.
+
+What is a Stem Hub?
+
+A Stem Hub is a single Perl process running Stem. It can function as a
+complete stand-alone application or be connected to other Stem Hubs in a
+networked application. Stem Hubs contain the Stem core system and Stem
+Cells that have been created by configuration files. For more on this
+read the <A href="/tech_notes/arch_notes.html">arch_notes</A> document
+or find it in the Design directory of the tarball.
+
+What is a Stem Cell?
+
+A Stem Cell is a Perl object which has to have 3 major characteristics:
+
+       1. It has been registered as a Cell in this Hub with a cell name
+          and an optional target name.
+
+       2. It has to have a set of methods designated to handle messages
+          directed at the Cell.
+
+       3. It has to be able to send messages to other cells.
+
+What is a Stem Message?
+
+Stem Messages carry commands and data between Stem Cells. They are
+addressed with a Hub/Cell/Target triplet. Messages can be any one of a
+wide range of types including command, data, log, stderr etc. They can
+carry any form of data to a maximum (to be designated later) size. See
+<A /tech_notes/message_notes.html">message_notes</A> document for more.
+
+How Hard Is Stem To Install/Configure?
+
+Stem is very easy to install. It only requires Perl 5.005_03 and one
+code module Event.pm that you can get from CPAN. The rest of Stem is
+comprised of pure Perl modules which need no special installation
+handling. Read the INSTALL document for more on how to
+install it. The quickstart instruction is simply to cd to the stem
+download directory and say
+./install.pl
+
+Much more on this can be found in the <A
+href="/tech_notes/design_notes.html">config_notes</A> document or in
+the Design directory of the tarball.
+
+Can Stem Do Guaranteed Messaging?
+
+*Stem's core design does not directly support guaranteed message
+delivery. It was designed this way, as the technical market that Stem
+targets does not have that strong a requirement for this feature as the
+commercial/e-business markets have. Future releases of Stem will support
+guaranteed delivery via a separate module that is now under development.
+
+Does Stem Use XML?
+
+Stem's messages are designed to not be specific to any format or
+encoding. They can carry data structure in any format the designer wants
+including XML. In fact, the message itself can also be in any format as
+long as it is properly identified and a module to decode that format is
+installed. Currently only the Stem internal message format is used but
+as demand arises, other message formats, including XML will be
+supported. One longer term goal is that message formats from many other
+systems will be supported by gateway modules which will translate
+between Stem and external messages.
+
+What Kind Of Security Does Stem Use?
+
+Stem doesn't directly do authentication and secure transmissions. Rather
+it relies upon industry standard utilities such as SSL, ssh and stunnel to
+provide secure socket connections between Stem hubs (processes).
+
+Can Stem Do Central Configuration Management?
+
+The log management project under development has this requirement. The
+lists of log files on each system that need to be monitored are
+maintained on the central system. Configuration files are auto-generated
+from those lists and distributed to all the Stem hubs in this network
+application. The same mechanism can be used to distribute configuration
+files for other applications and Stem can also notify them to reload the
+new files.
+
+Can Stem Handle My Content Distribution?
+
+Distributing content is similar to distributing configuration files and
+will use the same mechanisms. What content is distributed to where can
+all be easily controlled by Stem configurations without any additional
+programming.
+
+Can I Extend Stem Myself To Suit Our Needs?
+
+Stem is designed to be modified and extended very easily. You can copy
+existing modules or use design ideas from them to created new Cells for
+your own needs.  The high level Cell API hides the complexities of
+network communication and message transfer, leaving you free to focus on
+the specific design and coding of your custom Cells.
+
+Should I Modify Stem On My Own Or Use Stem's Development Team?
+
+If your requirements are simple, and you have skilled staff to do the
+work, there's no reason not to develop and maintain your own Stem-based
+systems. If your applications are complex applications, your staff new
+to Stem, or your time-frame short, you will probably find it more
+cost-effective to let Stem Systems' expert team develop your system for
+you.
+
+Otherwise Stem Systems can assist you in the design, development and
+configuration of your application. How much assistance can be provided
+depends on the customer subscription deal and any other contracts. If
+you do not have a subscription, our support will be limited to bug fixes
+and email and you will have to maintain your modified code on your own.
+
+Which Operating Systems Support Stem?
+
+Stem runs without any modification under any Unix flavor (Solaris,
+Linux, FreeBSD, etc.)  Support for WinX is in the planning stage and is
+expected to be released in Q3 2002. 
+
+How Can I Contribute to Stem?
+
+What Does Stem's Open Source License Allow Me To do?
+
+Is Stem Open Source?
+
+We are happy to announce that as of version 0.06, Stem is now under
+the GPL.
+
+What Is Stem Systems?
+
+Stem Systems is the company which is developing and maintaining Stem. It
+owns the copyright to the Stem code base and is releasing it to its
+customers and the open source community. Stem Systems also sells support
+subscriptions to the Stem user community.
+
+What Is the Business Model Of Stem Systems?
+
+Stem itself is free to use. Users can buy support subscriptions from
+Stem Systems which include Stem configuration and development
+assistance. Subscribers get earlier access to releases of new Stem
+versions and modules and direct support from Stem's development team.
+
+How Much Does a Stem Subscription Cost?
+
+The base price is an annual fee of $300 for each computer system (box)
+which is running Stem. Volume, site and educational discounts are
+available.
+
+Who Are Your Technical Advisors?
+
+
+
+What Are Your Resources?
+
+How Can I Learn Stem?
+
+The best way right now is to start with the technical notes and get a
+basic understanding of Stem, its architecture and its nomenclature. Then
+run the various demo scripts and read their configuration files.
+Developers will go next to the Cell documentation which describes each
+Cell's function, their attributes and how to configure it. There are no
+training materials now, but we are discussing the creation of classes
+with a training firm for when the demand arises.
+
+What Is the Future Of Stem?
+
+Stem will constantly be growing it Cell library and adding support for
+new features. Whenever any repeated networked operation can be
+identified, Stem will create a Stem Cell to perform it. Then Stem's
+users can easily integrate that new Cell without programming or needing
+to reinvent that wheel.
+
+Who Created Stem?
+
+Stem was created by Uri Guttman, and is the culmination of his long
+career in systems architecture and development. He has combined a
+quarter century of experience designing and implementing event-driven
+systems with his love of Perl to produce a powerful, yet easy-to-use
+framework for network applications. For more on Stem's creator, see the
+Stem Team biographies.
+
+How (and why) was Stem conceived?
+
+Stem was created when its designer was contracted to a network
+application development group where they were constantly putting out
+fires and creating a massive ad hoc system.  It was clear that they
+needed a framework to give structure to their network application
+design. Stem was the result.
+
+What Does Stem Stand For?
+
+Stem is not an acronym or an abbreviation, rather it is named from the
+real word 'stem' which has several dictionary meanings that map well to Stem.
+
+v. tr.
+  1.  To stop or hold back by or as if by damming; as in "stem the tide"
+
+n.
+  1. A slender stalk supporting or connecting another plant part, such
+     as a leaf or flower.
+  2. A connecting or supporting part.
+
+n.
+  1. A line of ancestry : STOCK; especially : a fundamental line from
+     which others have arisen. as in "stem cell" in biology.
diff --git a/Doc/HISTORY-2001 b/Doc/HISTORY-2001
new file mode 100644 (file)
index 0000000..e0a7012
--- /dev/null
@@ -0,0 +1,13 @@
+                  STEM CODE HISTORY 2001
+
+$Id: HISTORY-2001,v 1.1 2001/02/07 10:09:09 uri Exp $
+
+20010109:
+
+    mengwong@pobox.com created a repository of stem-0.04.
+
+20010110:
+    amused@pobox.com verified cvs operation
+    uri@stemsystems.com verified cvs operation
+    mengwong@pobox.com added loginfo mailing to devel@stemsystems.com
+
diff --git a/FAQ/faq.text b/FAQ/faq.text
new file mode 100644 (file)
index 0000000..f5e3d38
--- /dev/null
@@ -0,0 +1,407 @@
+S: About M<Stem>
+
+
+Q: What is M<Stem>?
+
+A: M<Stem> is a general purpose networking toolkit and a suite of ready
+to use network applications. Its goal is to transform common network
+programming to configuration and make uncommon network programming much
+simpler. Some M<Stem> users will use its suite of applications and
+modules and just configure them to perform their needed network
+tasks. Others will create new M<Stem> modules to perform their specific
+tasks and integrate them with M<Stem>'s standard modules using
+configurations.  In both cases, M<Stem> will speed up network
+application development, simplify maintenance, and lower lifetime costs.
+
+
+Q: Where can I get M<Stem>?
+A: M<Stem> can now be downloaded by the general public. Go to the 
+<A HREF="download.html">download</A> page to get the latest version.
+
+
+Q: What Can I Do With M<Stem>?
+A: M<Stem> is a general purpose networking toolkit. As such, it can
+be the backbone or framework of almost any networked (distributed)
+application you can imagine. M<Stem> is designed with standard modules 
+that are aimed at specific application niches. Some M<Stem> users
+want a simple solution to common problems while others need a way to 
+accelerate network application development. Via this modular design, 
+M<Stem> can satisfy the differing needs of both groups. 
+
+
+Q: What Are the Top M<Stem> Applications?
+A: The most commonly requested applications of 
+M<Stem> are log management, file distribution, 
+monitoring and remote command execution. For more on 
+M<Stem> applications, see the <A href="products.html">niches</A> page.
+
+QUOTE<
+       Stem Monitoring. \
+       Overloaded Server Appears. \
+       Remediation. \
+       -- U.G. \
+     >
+
+Q: Why Should I Use M<Stem>?
+A: You should use M<Stem> if:
+<UL>
+       <LI><P>You are tired of reinventing the wheel for each
+       network application you build. M<Stem> provides you with all
+       the common services that a network application needs and
+       makes them very simple to use.</P></LI>
+
+       <LI><P>You don't want to be forced to rely on a commercial
+       monolithic network tool that everyone hates to use. M<Stem> is
+       low cost and Open Source. You can easily write new modules
+       to customize M<Stem> to your needs.</P></LI>
+
+       <LI><P>You want to dramatically reduce the development
+       and maintenance costs of developing your new
+       network applications. M<Stem> tranforms common network
+       programming to configuration. Even if you create new M<Stem>
+       modules, it is very simple to connect them together with
+       M<Stem> configurations. This lowers your development time and
+       costs in many ways.</P></LI>
+
+       <LI><P>You just want to use a well-designed network system that
+       will make your profession much more enjoyable. M<Stem> is
+       architected to be easy to use at both the configuration and
+       coding levels. Our goal is for all M<Stem> users to enjoy
+       working with it and reduce the frustrations and stress of
+       network management.</P></LI>
+</UL>
+
+<P>These are just a few of the many reasons why 
+M<Stem> should be in place on your network.  
+If you have questions on how M<Stem> will benefit 
+your individual needs, <A HREF="mailto:faq@stemsystems.com">please contact us</A>.
+
+
+Q: Who Is Using M<Stem>?
+A: A start up firm hired M<Stem Systems> to implement a specialized
+web crawler.  M<Stem> will be used as
+the communications backbone for this multiprocessor system and
+it will interconnect and manage all of its components. Some of
+those components include subprocesses doing the page fetches,
+site objects, html page parsers, custom filters, crawl management
+and a database.
+
+QUOTE<
+       Divers search limpid pools. \
+       Precious Perl is summer's find, \
+       But Stem is loved more. \
+       -- D.G. \
+>
+
+Q: How can I be notified about M<Stem> updates?
+A: M<Stem Systems> has a email list dedicated to M<Stem> updates.  To
+subscribe, send an empty email to: news-subscribe@stemsystems.com.  This
+is a low volume list used for sending important news regarding M<Stem>
+(ie - new releases).
+
+S: M<Stem> Design
+
+Q: Do I Need To Get Rid Of [Commercial Application] To Use M<Stem>?
+A: No, M<Stem> can run in parallel with all of 
+your existing applications. As long as M<Stem> 
+can have access to its own TCP ports (which can be configured to
+any available ones), it has no need to conflict with any of your current
+systems.
+
+QUOTE<
+       superglue the net \
+       tie the machines together \
+       stem keeps it all sane \
+       -- U.G. \
+>
+
+
+Q: What is a M<Stem> Hub?
+A: A M<Stem> Hub is a single Perl process running 
+M<Stem>. It can function as a complete standalone 
+application or be connected to other M<Stem> Hubs in a
+networked application. M<Stem> Hubs contain the 
+M<Stem> core system and M<Stem>
+Cells that have been created by configuration files. 
+
+<P>For more on this, read the 
+<A href="technotes/arch_notes.html">architecture notes</A> document or find it 
+in the Design directory of the tarball.
+
+Q: What is a M<Stem> Cell?
+A: A M<Stem> Cell is a Perl object that has 3 major characteristics:
+
+<OL>
+   <LI><P>First, it is registered as a Cell in this Hub with a cell name and 
+       an optional target name</P></LI>
+   <LI><P>It has a set of methods designated to handle messages directed at 
+       the Cell</P></LI>
+   <LI><P>It sends messages to other cells.</P></LI>
+</OL>
+
+
+S: M<Stem> Features
+
+
+Q: What is a M<Stem> Message?
+A: M<Stem> Messages carry commands and data between 
+M<Stem> Cells. They are addressed with a 
+Hub/Cell/Target triplet. 
+
+<P>Messages can be any one of a wide range of types including command, 
+data, log, stderr etc. They can carry any form of data to a maximum 
+(to be designated later) size. 
+
+<P>See
+<A HREF="technotes/message_notes.html">message notes</A> document for more.
+
+QUOTE<
+       Nets catch more than fish. \
+       Data's trapped beneath the waves. \
+       Stem frees; packets breathe. \
+       -- D.G. \
+>
+
+
+Q: How Hard Is M<Stem> To Install/Configure?
+A: M<Stem> is very easy to install. It only requires 
+Perl 5.005_03 and one code module Event.pm that you can get from 
+<A HREF="http://search.cpan.org">CPAN</A>. The rest of
+M<Stem> is comprised of pure Perl modules.
+
+<p>M<Stem> has a step by step installation
+script that guides you through the installation process and any
+required modules that are necessary for the normal functioning of M<Stem>.
+It is easy to use and allows you to customize the installation to
+your liking or take the default install.
+
+<P>Much more on this can be found in the 
+<A href="technotes/config_notes.html">config notes</A> 
+document or in the Design directory of the tarball.
+
+
+Q: Can M<Stem> Do Guaranteed Messaging?
+A: M<Stem>'s core design does not directly support 
+guaranteed message delivery. It was designed this way, as the 
+technical market that M<Stem> targets
+doesn't have a strong requirement for this feature as the
+commercial/e-business markets have. 
+
+<P>Future releases of M<Stem> will support guaranteed delivery via a 
+separate module that is now under development.
+
+
+
+Q: Does M<Stem> Use XML?
+A: M<Stem>'s messages are designed to not be specific 
+to any format or encoding. They can carry data structure in any format the 
+designer wants,        including XML.  In fact, the message itself can also be in 
+any format as long as it is properly identified and a module to decode that 
+format is installed. 
+
+<p>M<Stem> is currently in the process of including
+<a href="http://www.yaml.org" name="YAML">YAML</a> as its primary format for
+messages, configuration files, and logs, but as demand arises other message formats
+including XML will be supported.
+
+<P>One longer-term goal is that message formats from many other
+systems will be supported by gateways modules, which will translate
+between M<Stem> and external messages.
+
+Q: What Kind Of Security Does M<Stem> Use?
+A: M<Stem> doesn't do direct authentication and secure
+transmissions. Currently it relies upon industry standard utilities such as ssh and
+stunnel to provide the secure socket connections between 
+M<Stem> hubs (processes).  Security is a great concern to
+everyone and M<Stem> development is looking into the possibilities
+of supporting various mechanisms and levels of security (i.e. - ssh, ssl, etc)
+through configurable modules.
+
+QUOTE<
+       Information rain \
+       Can drown the sleepy server, \
+       But for Stem's shelter. \
+       -- D.G. \
+>
+
+
+Q: Can M<Stem> Do Central Configuration Management?
+A: The log management project under development has this requirement. The
+lists of log files on each system that need to be monitored are
+maintained on the central system. Configuration files are autogenerated
+from those lists and distributed to all the M<Stem> hubs in this network
+application. The same mechanism can be used to distribute configuration
+files for other applications and M<Stem> can also notify them to reload the
+new files.
+
+Q: Can M<Stem> Handle My Content Distribution?
+A: Distributing content is similar to distributing configuration files and
+will use the same mechanisms. What content is distributed to where can
+all be easily controlled by M<Stem> configurations without any additional
+programming.
+
+
+S: Developing with M<Stem>
+
+
+Q: Can I Extend M<Stem> Myself To Suit Our Needs?
+A: M<Stem> is designed to be modified and extended very
+easily. You can copy existing modules or use design ideas from them to
+created new Cells for your own needs.
+
+<P>The high level Cell API hides the complexities of
+network communication and message transfer, leaving you free to focus on
+the specific design and coding of your custom Cells.
+
+
+QUOTE<
+       Just one message lost \
+       could stop your business blooming. \
+       Fear not: grasp the stem! \
+       -- D.G. \
+>
+
+Q: Is There a M<Stem> Tutorial?
+A: M<Stem> Development has created a <a href="cookbook.html" name="cookbook">cookbook</a> of examples that demonstrates the
+design of M<Stem> cells from the simplest form up
+to various levels of complexity.
+
+
+Q: Should I Modify M<Stem> On My Own Or Use M<Stem>'s Development Team?
+A: If your requirements are simple, and you have skilled staff to do the
+work, there's no reason not to develop and maintain your own M<Stem>-based
+systems. If your applications are complex applications, your staff new
+to M<Stem>, or your time-frame short, you will probably find it more
+cost-effective to let M<Stem Systems>' expert team develop your system for
+you.
+
+<P>Otherwise, M<Stem Systems> can assist you in the design, development and
+configuration of your application. How much assistance can be provided
+depends on the customer subscription deal and any other contracts. If
+you do not have a subscription, our support will be limited to bug fixes
+and email and you will have to maintain your modified code on your own.
+
+Q: Which Operating Systems Support M<Stem>?
+A: M<Stem> runs runs without any modification under any Unix flavor (Solaris,
+Linix, FreeBSD, etc.)  Support for WinX is in the planning stage and is
+expected to be released in the near future.
+
+Q: What Is M<Stem>'s License?
+A: M<Stem> will be released under the <a
+href="http://www.gnu.org/copyleft/gpl.html" name="GPL">GNU General
+Public License</a> starting with version 0.06.  Our intent is for
+M<Stem> to be free for non-commercial use.  Commercial licenses can be
+purchased through M<Stem Systems>.  Please contact <a
+href="mailto:info@stemsystems.com">Stem Systems</a> for more
+information regarding commercial license.
+
+
+S: M<Stem Systems>
+
+
+Q: What Is M<Stem Systems>?
+A: M<Stem Systems> is the company which is developing and maintaining M<Stem>. It
+owns the copyright to the M<Stem> codebase and is releasing it to its
+customers and the open source community. M<Stem Systems> also sells support
+subscriptions to the M<Stem> user community.
+
+QUOTE<
+       Black chips nurture life. \
+       Data shoots out, seeking Spring. \
+       Stem makes all fertile. \
+       -- D.G. \
+>
+
+Q: What Is The Business Model Of M<Stem Systems>?
+A: M<Stem Systems> has three business models,
+<ol>
+       <li>Development Projects</li>
+       <li>Support Subscriptions</li>
+       <li>3rd Party products and VAR's</li>
+</ol>
+
+Q: How Much Does M<Stem> Cost?
+A: 0.06 will be under the
+<a href="http://www.gnu.org/copyleft/gpl.html" name="GPL">GNU General
+Public License</a>.  M<Stem> can be used freely for non-commercial
+use.  For commercial and acedemic licenses please contact us at <a
+href="mailto:sales@stemsystems.com">Stem Systems</a> for more
+information.
+
+
+Q: Who Are Your Technical Advisors?
+A: Our technical advisors are listed <A HREF="company.html">here</A>.
+
+S: Miscellaneous
+
+
+Q: How Can I Learn M<Stem>?
+A: The best way right now is to start with the technical notes and get a
+basic understanding of M<Stem>, its architecture and
+its nomenclature. Then run the various demo scripts and read their 
+configuration files. Developers will go next to the Cell documentation that 
+describes each Cell's function, their attributes and how to configure it. 
+Once some insight into each Cell is attained the next step would be to go
+through M<Stem>'s <a href="cookbook.html" name="cookbook">cookbook</a>
+of examples put together by the developers of M<Stem>.  This will
+show you how to extend M<Stem> with your own Cells.
+
+<P>We are discussing the
+creation of classes with a training firm when the demand arises.
+
+Q: What Is The Future Of M<Stem>?
+A: M<Stem> will constantly be growing its Cell library and adding support for
+new features. Whenever any repeated networked operations can be
+identified, M<Stem> will create a M<Stem> Cell to 
+perform it. Then M<Stem>'s
+users can easily integrate that new Cell without programming or needing
+to re-invent that wheel.
+
+Q: Who Created M<Stem>?
+A: M<Stem> was created by Uri Guttman, and is the 
+culmination of his long career in systems architecture and development. He has combined a
+quarter century of experience designing and implementing event-driven
+systems with his love of Perl to produce a powerful, yet easy-to-use
+framework for network applications. 
+
+<P>For more on M<Stem>'s creator, see the 
+<A HREF="http://stemsystems.com/company.html">M<Stem> Team biographies</A>.
+
+Q: How (and why) Was M<Stem> Conceived?
+A: M<Stem> was created when its designer was contracted 
+to a network application development group who were constantly 
+putting out fires and creating a massive ad hoc system. It was clear that they needed a
+framework to give structure to their network application design. 
+M<Stem> was the result.
+
+QUOTE<
+       Coders or firemen? \
+       System in conflagration. \
+       From ashes rose Stem. \
+       -- U.G. \
+>
+
+
+Q: What Does M<Stem> Stand For?
+A: M<Stem> is not an acronym or an abbreviation, rather it is named from the
+real word 'stem' which has several dictionary meanings that map well to M<Stem>.
+
+<DL><DT><B>v. tr.</B></DT>
+<DD><OL><LI>To stop or hold back by or as if by damming; as in "stem the tide"</OL></DD></DL>
+
+<DL><DT><B>n.</B></DT>
+<DD><OL><LI>A slender stalk supporting or connecting another plant part, such as a leaf or flower.
+       <LI>A connecting or supporting part.
+</OL></DD></DL>
+
+<DL><DT><B>n.</B></DT>
+<DD><OL><LI>A line of ancestry : STOCK; especially : a fundamental line from
+which others have arisen. as in "stem cell" in biology.</OL></DD></DL>
+
+
+QUOTE<
+       To keep your network \
+       flowering when the heat's on, \
+       it needs a strong stem. \
+       -- D.C. \
+>
diff --git a/FAQ/faq_maker.pl b/FAQ/faq_maker.pl
new file mode 100644 (file)
index 0000000..9a8bbc9
--- /dev/null
@@ -0,0 +1,346 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+use Carp ;
+
+use YAML ;
+
+my @markup = (
+
+             {
+              'search'  => 'M<[^<>]+>',
+              'replace' => sub {
+                      my ( $text ) = @_;
+
+                      $text =~ s|M<([^<>]+)>|<SPAN CLASS="stem">$1</SPAN>|sg;
+
+                      $text;
+              },
+
+             },
+
+             {
+              'search'  => 'QUOTE<(.*?)>',
+              'replace' => sub {
+                      my ( $text ) = @_;
+
+                      $text =~ /QUOTE<(.*?)>/gs;
+
+                      my $before = $`;
+                      my $after  = $';
+                      my $quote  = $1;
+
+                      $quote =~ s/\\/<BR>/sg;
+
+                      $before .
+                      "<P><TABLE BORDER='0' ALIGN='CENTER' CELLPADDING='3'" .
+                      " CELLSPACING='0' BGCOLOR='FORESTGREEN'><TR><TD>" .
+                      "<TABLE WIDTH='100%' CELLPADDING='3' CELLSPACING='2'" .
+                      " BORDER='0' BGCOLOR='#CFE7CF'><TR><TH> $quote" .
+                      "</TH></TR></TABLE></TD></TR></TABLE>" .
+                      $after;
+              },
+             },
+
+            );
+
+
+my (
+    @sections,
+
+    $header_text,
+
+    $page_title_base
+);
+
+set_header_text() ;
+
+process_faq_text() ;
+
+process_sections() ;
+
+print_section_page() ;
+
+exit ;
+
+
+sub process_faq_text {
+
+       my ( $section, $quest_text, $answer_text, $curr_faq ) ;
+
+       while( <> ) {
+
+               next if /^\s*$/ ;
+               s/\n/ /;
+
+               if ( /^([SQ]):\s*(.+)$/ ) {
+
+
+                       if ( $curr_faq ) {
+
+
+                               $curr_faq->{'answer'} = 
+                                   markup_text( $answer_text ) ;
+
+                               $answer_text = '' ;
+
+                               unless ( $curr_faq->{'question'} &&
+                                        $curr_faq->{'answer'} ) {
+
+
+                                       die
+
+                                "bad FAQ entry before line $. in $ARGV\n" ;
+                               }
+
+                               push( @{$section->{'faqs'}}, $curr_faq ) ;
+                               $curr_faq = undef ;
+                       }
+
+                       if ( $1 eq 'S' ) {
+
+                               my $section_title = $2 ;
+
+                               push( @sections, $section ) if $section ;
+
+                               $section = {
+
+                                       'plain_title' => $section_title,
+                                       'title'       => markup_text( $section_title ),
+                               } ;
+
+                               next ;
+                       }
+
+                       $quest_text = $2 ;
+
+                       next ;
+               }
+
+               if ( /^A:\s*(.+)$/ ) {
+
+                       $answer_text = markup_text( $1 ) ;
+
+                       $curr_faq = {
+                               'question' => markup_text( $quest_text ),
+                       } ;
+
+                       $quest_text = '' ;
+                       next ;
+               }
+
+               if ( $quest_text ) {
+
+                       $quest_text .= $_ ;
+                       next ;
+               }
+
+               $answer_text .= $_ ;
+       }
+
+       push( @sections, $section ) ;
+}
+
+
+sub process_sections {
+
+
+       my $sect_num = 1 ;
+
+       foreach my $sect_ref ( @sections ) {
+
+
+               my $title = $sect_ref->{'title'} ;
+
+               $sect_ref->{'num'} = $sect_num ;
+
+               my $link = <<LINK ;
+$sect_num <A HREF="faq$sect_num.html">$title</A>
+LINK
+
+               $sect_ref->{'link'} = $link ;
+
+               my $quest_num = 1 ;
+
+               foreach my $faq_ref ( @{$sect_ref->{'faqs'}} ) {
+
+                       my $quest  = $faq_ref->{'question'} ;
+
+                       my $answer = $faq_ref->{'answer'} ;
+
+                       $faq_ref->{'num'} = $quest_num ;
+                       $faq_ref->{'index'} = "$sect_num.$quest_num" ;
+
+                       $faq_ref->{'link'} = <<LINK ;
+$sect_num.$quest_num <A HREF="faq$sect_num.html#FAQ$quest_num">$quest</A>
+LINK
+
+                       $quest_num++ ;
+               }
+
+               $sect_num++ ;
+       }
+}
+
+
+sub print_section_page {
+
+       my $page_text = <<HTML ;
+<%attr>
+        title => "$page_title_base"
+</%attr>
+
+<A HREF="index.html">Home</A> &gt <B>FAQ</B>
+
+<HR CLASS="sep">
+
+<H1>Frequently Asked Questions</H1>
+
+<UL STYLE="list-style-type:none">
+HTML
+
+       foreach my $sect_ref ( @sections ) {
+
+               my $link = $sect_ref->{'link'} ;
+
+               $page_text .= "<LI>$link" ;
+
+               print_faq_pages( $sect_ref ) ;
+       }
+
+       $page_text .= "</UL>";
+
+       write_file( 'faq.html', $page_text ) ;
+
+}
+
+sub print_faq_pages {
+
+       my ( $sect_ref ) = @_ ;
+
+       my $quest_list ;
+
+       my $faq_text ;
+
+       my $plain_title = $sect_ref->{'plain_title'} ;
+       my $title = $sect_ref->{'title'} ;
+       my $sect_num = $sect_ref->{'num'} ;
+
+       my $page_text = <<HTML ;
+<%attr>
+        title => "$page_title_base &gt; $plain_title"
+</%attr>
+
+<A HREF="index.html">Home</A> &gt <A HREF="faq.html">FAQ</A> &gt; <B>$title</B>
+
+<HR CLASS="sep">
+
+<H1><A NAME="top">$title</A></H1>
+
+<HR CLASS="sep">
+
+HTML
+
+
+       $quest_list .= <<HTML ;
+<UL STYLE="list-style-type:none">
+HTML
+
+       foreach my $faq_ref ( @{$sect_ref->{'faqs'}} ) {
+
+               my $quest = $faq_ref->{'question'} ;
+               my $answer = $faq_ref->{'answer'} ;
+
+               my $faq_num = $faq_ref->{'num'} ;
+               my $faq_ind = $faq_ref->{'index'} ;
+
+               $quest_list .= <<HTML ;
+<LI>$faq_ref->{'link'}
+HTML
+
+
+               $faq_text .= <<HTML ;
+
+<A NAME="FAQ$faq_num"></A>
+
+<H3>$quest</H3>
+    <BLOCKQUOTE>
+$answer
+    </BLOCKQUOTE>
+
+<DIV CLASS="toplink"><A HREF="#top">Top</A></DIV>
+
+<HR CLASS="sep">
+
+HTML
+
+       }
+
+       $quest_list .= "</UL>" ;
+
+
+       my $section_list = '<UL STYLE="list-style-type:none">' ;
+
+       foreach my $s_ref ( @sections ) {
+
+               $section_list .= <<HTML ;
+<LI>$s_ref->{'link'}
+HTML
+
+               if ( $s_ref == $sect_ref ) {
+
+                       $section_list .= $quest_list ;
+               }
+
+       }
+
+       $section_list .= "</UL>" ;
+
+       $page_text .= $section_list ;
+
+       $page_text .= $faq_text ;
+
+       write_file( "faq$sect_num.html", $page_text ) ;
+}
+
+
+sub set_header_text {
+
+       $page_title_base = 'Stem Systems, Inc. &gt; Stem &gt; FAQ'
+}
+
+
+sub write_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+
+       open( FH, ">$file_name" ) || carp "can't create $file_name $!" ;
+
+       print FH @_ ;
+}
+
+
+
+sub markup_text {
+
+       my ( $text ) = @_;
+
+       map {
+
+               if ($text =~ /$_->{'search'}/s) {
+
+                       $text = $_->{'replace'}->($text);
+               }
+
+       } @markup;
+
+       return $text;
+
+}
+
+
+__END__
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..70d24cd
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,73 @@
+
+                         Installation Of Stem
+
+Installing Stem is simple as you can use the classic module install
+commmands:
+
+`      perl Makefile.PL
+       make
+       make test
+       make install
+
+The installation uses Module::Build so you have to have that. You can
+get it from CPAN. The Makefile actually just is a passthrough that calls
+the Build program and is there for compatibility. You can bypass make
+and use the Build commands directly:
+
+on UNIX flavors do this:
+
+       perl Build.PL
+       Build
+       Build test
+       Build install
+
+on Windows do this:
+
+       perl Build.PL
+       perl Build
+       perl Build test
+       perl Build install
+
+The perl Makefile.PL or perl Build.PL command will query you for a
+short series of answers. These include where is perl, where to store the
+Stem executable scripts, where to store the Stem configuration files,
+etc. Each question will be explained in detail and has a reasonable
+default value.
+
+Stem has a set of demonstration applications and you will be asked if
+you want to install them. The driver scripts use xterm to
+bring up multiple windows so you can interact with the
+demonstrations. The Build.PL script will find xterms on most UNIX
+flavors that have X on them. OSX doesn't come with X by default but you
+can install the X cdrom if you want.
+
+NOTE: The xterm program is NOT required to run the demostrations. You
+can run the scripts and the commands which create the xterms will be
+printed. If you don't have xterms, they will fail but you can copy the
+Stem command (the part after the -e) and run them in terminal windows
+that you bring up yourself. This works on windows and OSX which doesn't
+have X installed. Just run those Stem commands in the order they are
+printed and each one in its own terminal window.
+
+Another option for the demo scripts is called ssfe (split screen front
+end). It is a general purpose C (UNIX only) utility that runs any
+command and provides command line editing and history. It is a nice
+little utility and it make running the demos a bit nicer. It is bundled
+in a IRC application call sirc and if you ask for it to be installed,
+the whole sirc package (just ssfe and sirc) will be built and
+installed. This build/install will be run in its own xterm. When it is
+done installing, it will sleep for a while. You can kill the xterm
+window or ^C in it and the rest of the Stem installation will continue.
+
+The installation answers you give will be stored in the module
+Stem::InstallConfig for use by any Stem application. This module is used
+by Build.PL to override the default installation answers. So the next
+time you do perl Build.PL you will see the previous choices you made as
+the defaults. Doing Build realclean will remove this module and you will
+see the original default answers.
+
+If you know the default answers are fully acceptable, you can do this:
+
+       perl Build.PL use_defaults=1
+
+and all the defaults will be used and no questions will be asked.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..7e50a41
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,34 @@
+###############################################################################
+                 Copyright and Licensing Information for Stem
+
+The copyright and license on the Stem software system is as follows:
+
+   Copyright (C) 1999-2004 Stem Systems, Inc.
+
+   Stem is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 2 of the License, or (at your
+   option) any later version.
+
+   Stem is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Stem in the file "COPYING" in this directory.  If not, write to
+   the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA 02111-1307 USA
+
+
+   Stem Systems, Inc. provides other licensing options for Stem as part of
+   our business.  For a license to use the Stem under conditions other
+   than those described here, to purchase support for this software, or to
+   purchase a commercial warranty contract, please contact Stem Systems
+   at:
+
+        Stem Systems, Inc.             781-643-7504
+       79 Everett St.                  info@stemsystems.com
+       Arlington, MA 02474
+       USA
+###############################################################################
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..141fa26
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,174 @@
+Makefile.PL
+Build.PL
+BuildStem.pm
+META.yml
+MANIFEST
+CHANGELOG
+COPYING
+CREDITS
+README
+INSTALL
+LICENSE
+TODO
+DEMO
+DEMO_CHAT
+DEMO_INETD
+DEMO_TAIL
+Cookbook/cookbook.txt
+Cookbook/World1.pm
+Cookbook/World2.pm
+Cookbook/World3.pm
+Cookbook/World4.pm
+Cookbook/World5.pm
+Cookbook/worlds.stem
+Design/asyncio_notes
+Design/Stem-Mon
+Design/arch_notes
+Design/config_notes
+Design/cell_notes
+Design/console_notes
+Design/cron_notes
+Design/debug_notes
+Design/env_notes
+Design/event_notes
+Design/id_notes
+Design/index
+Design/log_notes
+Design/logtail_notes
+Design/message_notes
+Design/portal_notes
+Design/proc_notes
+Design/registry_notes
+Design/security_notes
+Design/sock_msg_notes
+Design/socket_notes
+Design/switch_notes
+Doc/FAQ.txt
+Doc/HISTORY-2001
+FAQ/faq_maker.pl
+FAQ/faq.text
+bin/run_stem
+bin/boot_stem
+bin/stem_msg
+bin/stem2pod
+bin/cgi2stem.pl
+bin/hello_demo
+bin/chat_demo
+bin/chat2_demo
+bin/inetd_demo
+bin/tail_demo
+bin/quote_serve
+bin/cli
+conf/chat_client.stem
+conf/archive.stem
+conf/boot.stem
+conf/chat.stem
+conf/chat_label.stem
+conf/chat_server.stem
+conf/cron.stem
+conf/cli.stem
+conf/hello.stem
+conf/hello_client.stem
+conf/hello_server.stem
+conf/hello_shell.stem
+conf/hello_yaml.stem
+conf/inetd.stem
+conf/load_driver.stem
+conf/load_echo.stem
+conf/monitor.stem
+conf/proc.stem
+conf/slave.stem
+conf/tail.stem
+conf/test_udp.stem
+conf/test_flow.stem
+conf/test_packet_io.stem
+conf/ticker.stem
+conf/ttysock.stem
+conf/type.stem
+extras/sirc-2.211.tar.gz
+lib/Stem.pm
+lib/Stem/Event.pm
+lib/Stem/Event/EventPM.pm
+lib/Stem/Event/Perl.pm
+lib/Stem/Event/Signal.pm
+lib/Stem/Event/Queue.pm
+lib/Stem/Event/Tk.pm
+lib/Stem/Event/Wx.pm
+lib/Stem/Msg.pm
+lib/Stem/Route.pm
+lib/Stem/Class.pm
+lib/Stem/Conf.pm
+lib/Stem/Boot.pm
+lib/Stem/Portal.pm
+lib/Stem/Hub.pm
+lib/Stem/Vars.pm
+lib/Stem/Cell.pm
+lib/Stem/Cell/Sequence.pm
+lib/Stem/Cell/Clone.pm
+lib/Stem/Cell/Flow.pm
+lib/Stem/Cell/Pipe.pm
+lib/Stem/Cell/Work.pm
+lib/Stem/Log.pm
+lib/Stem/Log/Entry.pm
+lib/Stem/Log/File.pm
+lib/Stem/Log/Tail.pm
+lib/Stem/Socket.pm
+lib/Stem/SockMsg.pm
+lib/Stem/UDPMsg.pm
+lib/Stem/Proc.pm
+lib/Stem/Switch.pm
+lib/Stem/Debug.pm
+lib/Stem/Trace.pm
+lib/Stem/Inject.pm
+lib/Stem/Cron.pm
+lib/Stem/DBI.pm
+lib/Stem/File.pm
+lib/Stem/Console.pm
+lib/Stem/Id.pm
+lib/Stem/Gather.pm
+lib/Stem/Util.pm
+lib/Stem/AsyncIO.pm
+lib/Stem/WorkQueue.pm
+lib/Stem/ChatLabel.pm
+lib/Stem/TtySock.pm
+lib/Stem/Packet.pm
+lib/Stem/Codec.pm
+lib/Stem/Codec/Data/Dumper.pm
+lib/Stem/Codec/Storable.pm
+lib/Stem/Codec/YAML.pm
+lib/Stem/Load/Driver.pm
+lib/Stem/Load/Ticker.pm
+lib/Stem/Test/ConfTypes.pm
+lib/Stem/Test/Echo.pm
+lib/Stem/Test/Flow.pm
+lib/Stem/Test/PacketIO.pm
+lib/Stem/Test/UDP.pm
+lib/Stem/Demo/World.pm
+lib/Stem/Demo/CLI.pm
+t/event/event_test.pl
+t/event/perl.t
+t/event/event.t
+t/event/poe.t
+t/event/tk.t
+t/event/wx.t
+t/event/gtk.t
+t/event/qt.t
+t/socket/plain.t
+t/socket/plain_fork.t
+t/socket/ssl_fork.t
+t/socket/udp.t
+t/socket/SockFork.pm
+t/cell/flow.t
+t/io/packet.t
+sessions/client.pl
+sessions/mid_event.pl
+sessions/mid_event_async.pl
+sessions/backend.pl
+certs/client-cert.pem
+certs/client-key.enc
+certs/client-key.pem
+certs/my-ca.pem
+certs/server-cert.pem
+certs/server-key.enc
+certs/server-key.pem
+certs/test-ca.pem
diff --git a/META.yml b/META.yml
new file mode 100644 (file)
index 0000000..e007605
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,132 @@
+--- #YAML:1.0
+name: Stem
+version: 0.11
+author: ~
+abstract: ~
+license: gpl
+dynamic_config: 1
+provides:
+  Stem:
+    file: lib/Stem.pm
+    version: 0.11
+  Stem::AsyncIO:
+    file: lib/Stem/AsyncIO.pm
+  Stem::Boot:
+    file: lib/Stem/Boot.pm
+  Stem::Cell:
+    file: lib/Stem/Cell/Sequence.pm
+  Stem::ChatLabel:
+    file: lib/Stem/ChatLabel.pm
+  Stem::Class:
+    file: lib/Stem/Class.pm
+  Stem::Codec:
+    file: lib/Stem/Codec.pm
+  Stem::Codec::Data::Dumper:
+    file: lib/Stem/Codec/Data/Dumper.pm
+  Stem::Codec::Storable:
+    file: lib/Stem/Codec/Storable.pm
+  Stem::Codec::YAML:
+    file: lib/Stem/Codec/YAML.pm
+  Stem::Conf:
+    file: lib/Stem/Conf.pm
+  Stem::Console:
+    file: lib/Stem/Console.pm
+  Stem::Cron:
+    file: lib/Stem/Cron.pm
+  Stem::DBI:
+    file: lib/Stem/DBI.pm
+  Stem::Debug:
+    file: lib/Stem/Debug.pm
+  Stem::Demo::CLI:
+    file: lib/Stem/Demo/CLI.pm
+  Stem::Demo::World:
+    file: lib/Stem/Demo/World.pm
+  Stem::Event:
+    file: lib/Stem/Event.pm
+  Stem::Event::EventPM:
+    file: lib/Stem/Event/EventPM.pm
+  Stem::Event::IO:
+    file: lib/Stem/Event.pm
+  Stem::Event::Perl:
+    file: lib/Stem/Event/Perl.pm
+  Stem::Event::Plain:
+    file: lib/Stem/Event/Perl.pm
+  Stem::Event::Queue:
+    file: lib/Stem/Event/Queue.pm
+  Stem::Event::Read:
+    file: lib/Stem/Event.pm
+  Stem::Event::Signal:
+    file: lib/Stem/Event/Signal.pm
+  Stem::Event::Timer:
+    file: lib/Stem/Event.pm
+  Stem::Event::Tk:
+    file: lib/Stem/Event/Tk.pm
+  Stem::Event::Write:
+    file: lib/Stem/Event.pm
+  Stem::Event::Wx:
+    file: lib/Stem/Event/Wx.pm
+  Stem::Event::Wx::App:
+    file: lib/Stem/Event/Wx.pm
+  Stem::Event::Wx::Timer:
+    file: lib/Stem/Event/Wx.pm
+  Stem::File:
+    file: lib/Stem/File.pm
+  Stem::Gather:
+    file: lib/Stem/Gather.pm
+  Stem::Hub:
+    file: lib/Stem/Hub.pm
+  Stem::Id:
+    file: lib/Stem/Id.pm
+  Stem::Inject:
+    file: lib/Stem/Inject.pm
+  Stem::Load::Driver:
+    file: lib/Stem/Load/Driver.pm
+  Stem::Load::Ticker:
+    file: lib/Stem/Load/Ticker.pm
+  Stem::Log:
+    file: lib/Stem/Log.pm
+  Stem::Log::Entry:
+    file: lib/Stem/Log/Entry.pm
+  Stem::Log::File:
+    file: lib/Stem/Log/File.pm
+  Stem::Log::Tail:
+    file: lib/Stem/Log/Tail.pm
+  Stem::Msg:
+    file: lib/Stem/Msg.pm
+  Stem::Packet:
+    file: lib/Stem/Packet.pm
+  Stem::Portal:
+    file: lib/Stem/Portal.pm
+  Stem::Proc:
+    file: lib/Stem/Proc.pm
+  Stem::Route:
+    file: lib/Stem/Route.pm
+  Stem::SockMsg:
+    file: lib/Stem/SockMsg.pm
+  Stem::Socket:
+    file: lib/Stem/Socket.pm
+  Stem::Switch:
+    file: lib/Stem/Switch.pm
+  Stem::Test::ConfTypes:
+    file: lib/Stem/Test/ConfTypes.pm
+  Stem::Test::Echo:
+    file: lib/Stem/Test/Echo.pm
+  Stem::Test::Flow:
+    file: lib/Stem/Test/Flow.pm
+  Stem::Test::PacketIO:
+    file: lib/Stem/Test/PacketIO.pm
+  Stem::Test::UDP:
+    file: lib/Stem/Test/UDP.pm
+  Stem::Trace:
+    file: lib/Stem/Trace.pm
+  Stem::TtySock:
+    file: lib/Stem/TtySock.pm
+  Stem::UDPMsg:
+    file: lib/Stem/UDPMsg.pm
+  Stem::Util:
+    file: lib/Stem/Util.pm
+  Stem::Vars:
+    file: lib/Stem/Vars.pm
+  Stem::WorkQueue:
+    file: lib/Stem/WorkQueue.pm
+generated_by: Module::Build version 0.2611
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..696da33
--- /dev/null
@@ -0,0 +1,31 @@
+# Note: this file was auto-generated by Module::Build::Compat version 0.03
+    
+    unless (eval "use Module::Build::Compat 0.02; 1" ) {
+      print "This module requires Module::Build to install itself.\n";
+      
+      require ExtUtils::MakeMaker;
+      my $yn = ExtUtils::MakeMaker::prompt
+       ('  Install Module::Build now from CPAN?', 'y');
+      
+      unless ($yn =~ /^y/i) {
+       die " *** Cannot install without Module::Build.  Exiting ...\n";
+      }
+      
+      require Cwd;
+      require File::Spec;
+      require CPAN;
+      
+      # Save this 'cause CPAN will chdir all over the place.
+      my $cwd = Cwd::cwd();
+      my $makefile = File::Spec->rel2abs($0);
+      
+      CPAN::Shell->install('Module::Build::Compat')
+       or die " *** Cannot install without Module::Build.  Exiting ...\n";
+      
+      chdir $cwd or die "Cannot chdir() back to $cwd: $!";
+    }
+    eval "use Module::Build::Compat 0.02; 1" or die $@;
+    use lib '_build/lib';
+    Module::Build::Compat->run_build_pl(args => \@ARGV);
+    require BuildStem;
+    Module::Build::Compat->write_makefile(build_class => 'BuildStem');
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..19dec7c
--- /dev/null
+++ b/README
@@ -0,0 +1,84 @@
+This is the README file for the Stem system.  Stem is released under
+the GNU General Public License (http://www.gnu.org/copyleft/gpl.html).
+For more information regarding commercial licenses please visit the
+Stem Systems website at http://www.stemsystems.com.
+
+The information below assumes the Stem tarball unpacks into a dir with
+the name stem-0.NN (where NN is the version number). That directory path
+will be referred to as $STEM.
+
+You can find more text (FAQ, products) on the Stem web site:
+http://www.stemsystems.com. The technical notes are also on the web site
+but may be more up to date in the tarball.
+
+
+Stem Installation.
+
+The INSTALL file has complete instructions on how to install Stem on
+your system. 
+
+Stem Demonstrations
+
+There are 4 Stem demonstration scripts. Read the DEMO file first to
+learn about their common features and then read the individual DEMO_*
+files for each one.
+
+Manifest
+
+README (this file) is in:
+
+       $STEM/README
+
+Stem installation instructions are in:
+
+       $STEM/INSTALL
+
+Stem demonstration script instructions are in:
+
+       $STEM/DEMO
+       $STEM/DEMO_CHAT
+       $STEM/DEMO_INETD
+       $STEM/DEMO_TAIL
+
+Event.pm is in the tarball (You can also it from CPAN):
+
+       $STEM/modules/Event-0.77.tar.gz
+
+Stem modules are in:
+
+       $STEM/lib/Stem.pm
+       $STEM/lib/Stem/
+
+Stem Example Configuration files are in:
+
+       $STEM/conf/
+
+Stem Executable scripts are in:
+
+       $STEM/bin/
+
+Technical Design Notes are in:
+
+       $STEM/Design/
+
+Documentation is in:
+
+       $STEM/Docs/
+
+       The latest FAQ is on the web site (http://www.stemsystems.com)
+
+Stem Cookbook is in:
+
+       $STEM/Cookbook/
+
+ssfe (split screen front end) is found in the sirc tarball:
+
+       $STEM/extras/sirc-2.211.tar.gz
+
+Test scripts are in:
+
+       $STEM/t/
+
+Utility scripts are in:
+
+       $STEM/utils/
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..3324809
--- /dev/null
+++ b/TODO
@@ -0,0 +1,140 @@
+                      Stem Development TODO List
+
+Feel free to tackle any of these tasks. Email uri@stemsystems.com if you
+want design/code ideas or to discuss any of these projects.
+
+Last edited Fri Jan 16 17:07:14 EST 2004
+
+Stem::Event::*
+
+       Add support for more event loops (Tk, Qt, tcl, WxWindows,
+       POE). See Stem::Event::EventPM for an example of how to wrap an
+       event loop in Stem.
+
+Stem::Msg, Stem::Route
+
+       enable tracing of delivered messages
+
+       basic source routing needs design work. new client hubs will
+       flood upstream who they are. master servers can respond
+       downstream which will create a fully aware tree networks.
+
+       add support for a forward. it takes a message, clones it and
+       changes the to address.
+
+       maybe move Stem::Route to Stem::Msg::Route. it is only used by
+       Stem::Msg and that better describes its name.
+
+Stem::Portal
+
+       finish and test Portal forking
+
+       maybe convert to use Class clone/piped support. not sure if this
+       is reasonable or worth it.
+
+Stem::Log
+
+       add support for single method ref to do all the filtering. it is
+       passed the log entry and is a stem::log::entry object. it can
+       call all the action methods. there is no need for the current
+       design of a list of key/values. it will be deprecated.
+
+       timestamp is set with a template (strftime like but numbers only).
+
+       add timer based filters. single shot cron entries to toggle
+       state are fine. but how do you set the state at startup? if it
+       is between start and end times, the filter should be
+       enabled. this needs design work.
+
+       define and add more actions
+
+               email
+               msg
+
+run_stem
+
+       write up run_stem man page in pod
+
+       write tech notes for run_stem
+
+Stem::Proc
+
+       needs much more testing
+
+       test pseudo tty
+
+Stem::File
+
+       (some of this is done. a good simple project to pick up)
+
+       design parent cell and how it spawns targeted file cells.
+
+       code file stem based transfer stuff.
+
+       add file compare options - size, timestamp, MD5
+
+       add single directory support.
+       filename filters
+
+       add dir tree copy support
+
+       add throttle support? don't want to slurp entire large file in
+       and clog the system. throttle with reply messages and/or timing
+
+       add support for ftp and scp
+
+Stem::Util
+
+       replace read_file/write_file with File::Slurp. might as well eat
+       my own dog food. this needs to be added to the required modules
+       list in Build.PL
+
+Stem::Expect
+
+       use proc or socket and use async IO
+
+       hook in Stem::Cell::Flow
+
+       expect handles timeouts from async IO. then it drives the state
+       machine with a timeout method. regular input is sent to the
+       state machine as data. 
+
+       if no input matches but not timed out yet, we wait for more
+       input or the timeout.
+
+       do we need code callbacks? i think the higher level object
+       (protocol::ftp?) would want callbacks itself in some cases. 
+
+Testing
+       need tests for higher level cells.
+
+       need load testing of various subsystems.
+
+       need cross platform testing.
+
+Documentation
+
+       full pods for all modules
+
+       accurate docs for all class fields. this can be autogenerated
+       from the $field_info lists. we could parse only that code out
+       easily and eval the string. the either edit the internal pod or
+       some other text file. it would generate a nicely formatted
+       description of all the class fields.
+
+Tracing
+
+       mark by name which message is currently being delivered
+
+       use the hub/cell/target in the 'to' address.
+
+       any newly dispatched messages get a from/origin of that to
+
+       just copy the to?
+
+       fix registry lookup by object to get cell/name and target
+
+       save current event cell name and target
+
+       create trace file to be written by events and message delivery
+       (or dispatch?)
diff --git a/bin/boot_stem b/bin/boot_stem
new file mode 100755 (executable)
index 0000000..ba841b4
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+
+use Getopt::Std ;
+
+use YAML ;
+
+my %opts ;
+
+getopts( 'v', \%opts ) ;
+
+my $cmds = Load do{ local $/ ; <> } ;
+
+foreach my $boot ( @{$cmds} ) {
+
+       if ( my $skip = $boot->{'skip'} ) {
+
+               next if $skip eq 'yes' ;
+       }
+       my $wrap = $boot->{'wrap'} || '/bin/sh -c' ;
+       my $cd = $boot->{'cd'} || '.' ;
+       my $cmd = $boot->{'cmd'} ;
+
+       my $user = $boot->{'user'} || '';
+
+       my $env = $boot->{'env'} || {} ;
+       my $stem_env = $boot->{'stem_env'} || {} ;
+
+       local( %ENV ) = ( %ENV, %{$env} ) ;
+
+       my $cmd_env = join ' ', map( "$_='$stem_env->{$_}'", keys %$stem_env ) ;
+
+       $cmd =~ s/run_stem/run_stem $cmd_env/ ;
+
+       my $system;
+       $system = "su - $user;" if $user && getpwuid($<) ne $user;
+       $system .= qq|$wrap "cd $cd ; $cmd" &| ;
+
+       print "$system\n" if $opts{'v'} ;
+
+       system $system ;
+
+       my $delay = $boot->{'delay'} || 3 ;
+
+       print "waiting $delay seconds\n" if $opts{'v'};
+
+       sleep $delay ;
+}
diff --git a/bin/cgi2stem.pl b/bin/cgi2stem.pl
new file mode 100755 (executable)
index 0000000..4ab7577
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/local/bin/perl -wT
+
+# This is a CGI script that interfaces to stem. it collects all the
+# CGI data and sends it to a Stem::SockMsg cell as a single
+# Stem::Packet.  It reads a single Stem::Packet back from the socket
+# and uses the data in there to generate a response page.
+
+$|++ ;
+
+use strict ;
+use lib '/wrk/stem/src/stem/lib/' ;
+
+use CGI ;
+use CGI::Carp qw(fatalsToBrowser) ;
+use IO::Socket ;
+
+use Stem::Packet ;
+
+my $cgi = CGI->new() ;
+
+my %cgi_data ;
+
+# get all the cgi data we can
+
+$cgi_data{ 'params' }  = get_cgi_data( 'param' ) ;
+$cgi_data{ 'cookies' } = get_cgi_data( 'cookie' ) ;
+#$cgi_data{ 'env' }    = { %ENV } ;
+#$cgi_data{ 'self_url' }       = $cgi->self_url() ;
+$cgi_data{ 'url' }     = $cgi->url() ;
+#$cgi_data{ 'cgi' }    = $cgi ;
+
+# todo: handle default host:port here
+
+my $data = send_and_get_packet( \%cgi_data ) ;
+
+# use Data::Dumper ;
+
+# print $cgi->header() ;
+# # print "<PRE>\n", Dumper( \%cgi_data ), "\n</PRE>\n" ;
+# print "<PRE>\n", Dumper( $data ), "\n</PRE>\n" ;
+
+# exit ;
+
+if ( ref $data eq 'SCALAR' ) {
+
+       print $$data ;
+       exit ;
+}
+
+print $cgi->header(), <<HTML ;
+<HTML>
+cgi2stem error: $data
+</HTML>
+HTML
+
+
+
+# this works for both cookies and params as their APIs are the same
+
+sub get_cgi_data {
+
+       my( $type ) = @_ ;
+
+       my %cgi_info ;
+
+       foreach my $name ( $cgi->$type() ) { ;
+
+               my @values = $cgi->$type( $name ) ;
+
+               if ( @values > 1 ) {
+                       $cgi_info{ $type } = \@values ;
+                       next ;
+               }
+
+               $cgi_info{ $name } = shift @values ;
+       }
+
+       return \%cgi_info ;
+}
+
+sub send_and_get_packet {
+
+       my( $in_data, $host, $port ) = @_ ;
+
+       $port ||= 9999 ;
+       $host ||= 'localhost' ;
+
+       my $sock = IO::Socket::INET->new( "$host:$port" ) ;
+
+       $sock or return "can't connect to $host:$port\n" ;
+
+       my $packet = Stem::Packet->new( codec => 'Storable' ) ;
+
+       my $write_buf = $packet->to_packet($in_data) ;
+
+       syswrite( $sock, $$write_buf ) ;
+
+       my $read_buf  ;
+
+       while( 1 ) {
+
+               my $bytes_read = sysread( $sock, $read_buf, 8192 ) ;
+
+               return "sysread error $!" unless defined $bytes_read ;
+               return "sysread closed" if $bytes_read == 0 ;
+
+               my $result = $packet->to_data( $read_buf ) ;
+
+               return $result if $result ;
+       }
+}
diff --git a/bin/chat2_demo b/bin/chat2_demo
new file mode 100755 (executable)
index 0000000..a2006a9
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/local/bin/perl -s
+
+$line_cnt = 10 ;
+$offset = 175 ;
+$base_off = 0 ;
+$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
+
+my @children ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+if ( $s ) {
+
+       $ssfe = 'ssfe' ;
+       $prompt = '-prompt Stem:' ;
+       $prompt2 = '-prompt Chat:' ;
+       $echo = 'console_echo=1'
+}
+
+foreach $cmd ( split /\n/, <<EOT ) {
+xterm -T Chat1 -n Chat1 -geometry 80x25+XSKIP+0   -e $ssfe $prompt run_stem $echo  chat_server
+xterm -T Chat2 -n Chat2 -geometry 80x25+XSKIP+250 -e $ssfe $prompt run_stem $echo  chat_client
+xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
+xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
+xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6668
+xterm -T D -n D -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6669
+EOT
+
+       $cmd =~ s/XSKIP/$xskip/ ;
+
+       $cmd =~ s/LINE/$line_cnt/ ;
+       $cmd =~ s/OFF/$base_off/ and $base_off += $offset ;
+
+print "$cmd\n" ;
+
+       @cmd = split ' ', $cmd ;
+
+       s/:/: / for @cmd ;
+
+       fork_exec( @cmd ) ;
+       sleep 2 unless $cmd =~ /localhost/ ;
+}
+
+while( <STDIN> ) {
+
+       next unless /^q/i ;
+
+       cleanup() ;
+}
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( @exec ) = @_ ;
+
+       if ( $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec @exec ;
+}
diff --git a/bin/chat_demo b/bin/chat_demo
new file mode 100755 (executable)
index 0000000..8437913
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/local/bin/perl -s
+
+$line_cnt = 10 ;
+$offset = 175 ;
+$base_off = 0 ;
+$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
+
+print "CHAT DEMO\n" ;
+
+my @children ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+if ( $s ) {
+
+       $ssfe = 'ssfe' ;
+       $prompt = '-prompt Stem:' ;
+       $prompt2 = '-prompt Chat:' ;
+       $echo = 'console_echo=1'
+}
+
+foreach $cmd ( split /\n/, <<EOT ) {
+xterm -T Chat -n Chat -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem $echo chat
+xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
+xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
+xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6668
+xterm -T D -n D -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6669
+EOT
+
+       $cmd =~ s/XSKIP/$xskip/ ;
+
+       $cmd =~ s/LINE/$line_cnt/ ;
+       $cmd =~ s/OFF/$base_off/ and $base_off += $offset ;
+
+print "$cmd\n" ;
+
+       @cmd = split ' ', $cmd ;
+
+       s/:/: / for @cmd ;
+
+       fork_exec( @cmd ) ;
+       sleep 4 unless $cmd =~ /localhost/ ;
+}
+
+
+while( <STDIN> ) {
+
+       next unless /^q/i ;
+
+       cleanup() ;
+}
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( @exec ) = @_ ;
+
+       if ( $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec @exec ;
+}
diff --git a/bin/cli b/bin/cli
new file mode 100644 (file)
index 0000000..833982a
--- /dev/null
+++ b/bin/cli
@@ -0,0 +1,53 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+
+use IO::Socket ;
+use Data::Dumper ;
+
+use Stem::Packet ;
+
+$| = 1 ;
+
+my $host = 'localhost' ;
+
+my $port = shift || 8888 ;
+
+my $sock = IO::Socket::INET->new( "$host:$port" ) ;
+$sock or die "can't connect to $host:$port\n" ;
+
+#my $packet = Stem::Packet->new( codec => 'YAML' ) ;
+my $packet = Stem::Packet->new() ;
+
+print "type 'help' for help\n\n" ;
+
+while( 1 ) {
+
+       print "CLI > " ;
+
+       chomp( my $line = <> ) ;
+       next unless $line =~ /\S/ ;
+
+#my $line = "foo bar bazz" ;
+
+       my %data ;
+       @data{ qw( op key value ) } = split( ' ', $line, 3 ) ;
+
+       my $write_buf = $packet->to_packet( \%data) ;
+#print "WRITE [$$write_buf]\n" ;
+
+       syswrite( $sock, "${$write_buf}"  ) ;
+
+# this should be a proper non-blocking read loop but it is fine for this 
+# demo.
+
+       my $bytes_read = sysread( $sock, my $read_buf, 8192 ) ;
+       last unless defined $bytes_read and $bytes_read > 0 ;
+
+       my $result = $packet->to_data( \$read_buf ) ;
+
+#      print "RESULT [$$result]\n" ;
+       print Dumper $result ;
+
+#exit ;
+}
diff --git a/bin/hello_demo b/bin/hello_demo
new file mode 100644 (file)
index 0000000..5b525c1
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl -s
+
+use strict ;
+use warnings ;
+our $s ;
+
+if ( -d 'conf' && -e 'bin/run_stem' ) {
+
+       $ENV{PERL5LIB} = 'lib' ;
+       $ENV{PATH} =  "bin:$ENV{PATH}" ;
+}
+
+print "HELLO DEMO\n" ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+my @children ;
+
+my $ssfe = $s ? 'ssfe -prompt Stem:' : '' ;
+my $echo = $s ? 'console_echo=1' : '' ;
+
+my $cmd = <<CMD ;
+xterm -T Hello -n Hello -geometry 80x40+0+0 -e $ssfe run_stem $echo hello
+CMD
+
+print "$cmd\n" ;
+
+my @cmd = split ' ', $cmd ;
+s/:/: / for @cmd ;
+
+fork_exec( @cmd ) ;
+
+while( <STDIN> ) {
+
+       next unless /^q/i ;
+
+       cleanup() ;
+}
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( @exec ) = @_ ;
+
+       if ( my $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec @exec ;
+}
diff --git a/bin/inetd_demo b/bin/inetd_demo
new file mode 100755 (executable)
index 0000000..9116feb
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/local/bin/perl -s
+
+$line_cnt = 10 ;
+$offset = 175 ;
+$base_off = 0 ;
+$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
+
+my @children ;
+
+print "INETD: $ENV{PATH}\n" ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+if ( $s ) {
+
+       $ssfe = 'ssfe' ;
+       $prompt = '-prompt Stem:' ;
+       $echo = 'console_echo=1'
+#      $prompt2 = '-prompt Chat:' ;
+}
+
+foreach $cmd ( split /\n/, <<EOT ) {
+xterm -T Stem -n Stem -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem $echo inetd
+xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
+xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
+xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
+xterm -T D -n D -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
+EOT
+
+       $cmd =~ s/XSKIP/$xskip/ ;
+
+       $cmd =~ s/LINE/$line_cnt/ ;
+       $cmd =~ s/OFF/$base_off/ and $base_off += $offset ;
+
+print "CMD $cmd\n" ;
+
+       @cmd = split ' ', $cmd ;
+
+       s/:/: / for @cmd ;
+
+       fork_exec( @cmd ) ;
+       sleep 4 unless $cmd =~ /localhost/ ;
+}
+
+
+while( <STDIN> ) {
+
+       next unless /^q/i ;
+
+       cleanup() ;
+}
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( @exec ) = @_ ;
+
+       if ( $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec @exec ;
+}
diff --git a/bin/quote_serve b/bin/quote_serve
new file mode 100755 (executable)
index 0000000..940389f
Binary files /dev/null and b/bin/quote_serve differ
diff --git a/bin/run_stem b/bin/run_stem
new file mode 100755 (executable)
index 0000000..1d20a3f
--- /dev/null
@@ -0,0 +1,283 @@
+#!/usr/local/bin/perl -w
+#  File: bin/run_stem
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+$Data::Dumper::Indent = 1 ;
+$Data::Dumper::Purity = 1 ;
+$Data::Dumper::Useqq = 1 ;
+
+$| = 1 ;
+
+#print "RUN STEM $0\n" ;
+
+my @conf_args ;
+
+# we set Stem's default environment before we load any Stem modules so
+# they can use those values
+
+use Stem::InstallConfig ;
+
+BEGIN {
+       my $env_text ;
+       my $stem_lib_dir = $Stem::InstallConfig::Config{'conf_path'} ;
+
+       my $is_win32 = $^O =~ /Win32/i ;
+
+# get the site env and home env files
+
+       my @env_files = "$stem_lib_dir/env" ;
+
+       unless ( $is_win32 ) {
+
+               push @env_files, ( $ENV{HOME} ||
+                                  $ENV{LOGDIR} ||
+                                  (getpwuid($>))[7] ) . '/.stem_env' ;
+       }
+
+       foreach my $env_file ( @env_files ) {
+
+               next unless -r $env_file ;
+
+# shut up a dumb warning
+               use vars '*ARGVOUT' ;
+               $env_text .= 
+                       do { local( @ARGV, $/ ) = $env_file ; <> } ;
+       }
+
+
+# set the starting %env from the files
+
+       %Stem::Vars::Env = $env_text =~ /^([^=]+)=(.+)$/mg if $env_text ;
+
+
+# set the %Stem::Vars::Env from %ENV any %ENV name starting with STEM_
+# is used. the STEM_ is deleted and the rest of the lower case name is
+# used with the %ENV value
+
+       /^STEM_(\w+)/ and $Stem::Vars::Env{ lc $1 } = $ENV{ $_ } for keys %ENV ;
+
+# set %Stem::Vars::Env from 'name=value' command line args
+# all other args are assumed to be conf file names.
+# we do this after we process %ENV so the command line args can override
+# any shell environment values
+
+       while( @ARGV ) {
+
+               my $arg = shift ;
+
+               if ( $arg =~ /([^=]+)=(.*)/ ) {
+
+                       $Stem::Vars::Env{ $1 } = $2 ;
+                       next ;
+               }
+
+               push @conf_args, $arg ;
+       }
+
+# set the default config search path. this will be changed by the install
+# script.
+
+       $Stem::Vars::Env{ 'conf_path' } ||= 'conf:.' ;
+
+# set the trace levels
+
+#      $Stem::Vars::Env{ 'MainTraceStatus'    } ||= 1 ;
+#      $Stem::Vars::Env{ 'MainTraceError'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'ProcTraceStatus'    } ||= 1 ;
+#      $Stem::Vars::Env{ 'ProcTraceError'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'PortalTraceStatus'  } ||= 1 ;
+#      $Stem::Vars::Env{ 'PortalTraceError'   } ||= 1 ;
+#      $Stem::Vars::Env{ 'SockMsgTraceStatus' } ||= 1 ;
+#      $Stem::Vars::Env{ 'SockMsgTraceError'  } ||= 1 ;
+#      $Stem::Vars::Env{ 'ConfTraceStatus'    } ||= 1 ;
+#      $Stem::Vars::Env{ 'ConfTraceError'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'LogTraceStatus'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'LogTraceError'      } ||= 1 ;
+#      $Stem::Vars::Env{ 'CellTraceStatus'    } ||= 0 ;
+#      $Stem::Vars::Env{ 'CronTraceStatus'    } ||= 1 ;
+#      $Stem::Vars::Env{ 'CronTraceError'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'EventTraceStatus'   } ||= 0 ;
+#      $Stem::Vars::Env{ 'EventTraceError'    } ||= 0 ;
+#      $Stem::Vars::Env{ 'GatherTraceStatus'  } ||= 1 ;
+#      $Stem::Vars::Env{ 'GatherTraceError'   } ||= 1 ;
+#      $Stem::Vars::Env{ 'HubTraceStatus'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'HubTraceError'      } ||= 1 ;
+#      $Stem::Vars::Env{ 'TailTraceStatus'    } ||= 1 ;
+#      $Stem::Vars::Env{ 'TailTraceError'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'MsgTraceError'      } ||= 1 ;
+#      $Stem::Vars::Env{ 'MsgTraceStatus'     } ||= 1 ;
+#      $Stem::Vars::Env{ 'MsgTraceMsg'        } ||= 1 ;
+#      $Stem::Vars::Env{ 'SwitchTraceStatus'  } ||= 1 ;
+#      $Stem::Vars::Env{ 'SwitchTraceError'   } ||= 1 ;
+#      $Stem::Vars::Env{ 'AsynchIOTraceStatus'} ||= 1 ;
+#      $Stem::Vars::Env{ 'AsynchIOTraceError' } ||= 1 ;
+#      $Stem::Vars::Env{ 'TtyMsgTraceStatus'  } ||= 1 ;
+#      $Stem::Vars::Env{ 'TtyMsgTraceError'   } ||= 1 ;
+
+}
+
+# we load Stem after we process the command line args and %ENV so the
+# modules can use those values
+
+use Stem ;
+
+use Stem::Trace 'log' => 'stem_status',
+               'sub' => 'TraceStatus',
+               'env' => 'MainTraceStatus' ;
+
+my $prog_name = $0 ;
+
+$prog_name =~ s|.+/|| ;
+
+unless ( @conf_args ) {
+
+       $prog_name eq 'run_stem' &&
+                       die "run_stem must be passed a stem config file" ;
+
+       @conf_args = $prog_name ;
+}
+
+# always start with the site config file
+# this defines site wide configuration settings that are internal
+# to Stem
+
+my $err = Stem::Conf::load_confs( 'site' ) ;
+
+# ignore a missing site config
+
+die $err if defined $err && $err !~ /Can't find config/ ;
+
+$err = Stem::Conf::load_confs( @conf_args ) ;
+
+TraceStatus "Stem startup" ;
+
+TraceStatus $err if $err;
+
+die $err if $err ;
+
+###############
+# this should use Stem::Event
+###############
+$SIG{ 'INT' } = sub {
+       TraceStatus "INT signal received" ;
+       Stem::Event::stop_loop()
+} ;
+
+Stem::Event::start_loop() ;
+
+TraceStatus "Stem shutdown" ;
+
+exit;
+
+=head1 run_stem - Start up Stem and load configuration files
+
+=head2 Synopsis
+
+       run_stem foo=bar stem_conf_file
+
+This script is the way most Stem applications are started. It does
+several important things so you don't have to create your own top
+level scripts. It is not required to execute run_stem to use Stem but
+it makes it much easier to get it going in most cases. The following
+are the steps that 'run_stem' does when bringing up Stem.
+
+=head2 * Load Stem Environment
+
+Many Stem modules and cells look at the Stem environment for default
+configuration values or global flags. This allows you to control how
+many of the cells and modules behave when loaded and instantiated.  If
+a Stem attribute in a specification has its 'env' name description
+set, it will use that name (optionally prefixed with the cell's
+registration name) as a key to lookup in the Stem Environement. If
+found there, that value becomes is used and overrides the default and
+any value set in a configuration file. This allows the user to
+override default setting from the outside without modifying Stem
+configuration files. See Stem::Class for more on this.  The Stem
+environment is set from these sources in the following order:
+
+=over 4
+
+=item Global Site Environment File
+
+'run_stem' initially looks for a file named 'env' in the first
+configuration directory (set at install time) and loads it if
+found. These site and user files both have a simple key=value format
+with one entry per line.
+
+=item User Environment File
+
+'run_stem' then will look in your home directory (not supported on
+windows) for a file named .stem_env and loads it if found.
+
+=item Shell Environment
+
+Any shell environment variable with the form 'STEM_*' will
+have the 'STEM_' part deleted and the rest of its name
+converted to lower case. That will become the key in the Stem
+environment with the value set to the shell variable's value.
+
+=item Command Line
+
+Any command line arguments of the form key=value will be
+parsed out and used to set a Stem environment variable.
+
+=back
+
+=head2 * Load Stem Core Modules
+
+       'run_stem' then loads all the core Stem modules with a use
+       Stem line.
+
+=head2 * Load Configuration Files
+
+Any arguments left in @ARGV are assumed to be Stem configuration
+files. Typically there is only one configuration file but you can have
+pass in as many as you want.  The config file arguments can have a
+.stem suffix or no suffix. The configuration directory list is
+searched in order for the file and it is loaded and all of its entries
+are constructed.
+
+You can override the default configuration directory list (set at
+install time) by setting the 'conf_path' Stem environment variable
+from the shell environment or on the 'run_stem' command line. The
+following are equivilent:
+
+       export STEM_CONF_PATH=/etc/stem/conf:/home/foo/.stem
+       run_stem bar
+
+       run_stem conf_path=/etc/stem/conf:/home/foo/.stem bar
+
+=head2 * Start Event Loop
+
+The final operation 'run_stem' does is start the main event
+loop. If no events were created by the loaded configuration
+files, this will fail and 'run_stem will exit immediately. If
+all the created events eventually get canceled, the event loop
+will exit and 'run_stem' will exit too.
+
+=cut
diff --git a/bin/stem2pod b/bin/stem2pod
new file mode 100755 (executable)
index 0000000..97143d5
--- /dev/null
@@ -0,0 +1,410 @@
+#!/usr/local/bin/perl -w
+#
+#      stem2pod
+#
+#      takes filename (a stem module) arguments and it updates their
+#      pod from their attribute descriptions. it also will insert pod
+#      templates for methods, subs and standard pod sections.
+#
+#      if a file is changed, it is written out over itself. unchanged
+#      files are not touched.
+
+use strict;
+
+use Carp qw( carp cluck ) ;
+use Data::Dumper;
+
+#use Test::More tests => 1 ;
+
+#$SIG{__WARN__} = sub { cluck } ;
+
+my $changed ;
+my $package ;
+
+my %is_attr_part = map { $_ => 1 } qw(
+       name
+       type
+       help
+       default
+       required
+       class
+       class_args
+) ;
+
+foreach my $file_name ( @ARGV ) {
+
+       process_source_file( $file_name ) ;
+}
+
+exit ;
+
+sub process_source_file {
+
+       my ( $file_name ) = @_ ;
+
+       my $code_text = read_file( $file_name ) ;
+
+       my $new_code_text = process_code_text( $file_name, $code_text ) ;
+
+#print $new_code_text ;
+
+       if ( $new_code_text eq $code_text ) {
+
+               print "$file_name SAME\n" ;
+               return ;
+       }
+
+       print "$file_name CHANGED\n" ;
+
+       write_file( "$file_name.new, $new_code_text ) ;
+
+#      write_file( "$file_name.bak, $code_text ) ;
+#      write_file( $file_name, $new_code_text ) ;
+
+}
+
+sub process_code_text {
+
+       my ( $file_name, $text ) = @_ ;
+
+       $text =~ s{
+                 (
+                    ^package                   # start at package line
+                    .+?                        # the middle stuff
+                    ^sub                       # start of constructor
+                 )
+               }
+               {
+                 update_attr_spec( $1, $file_name )
+               }mgsex ;
+
+       $text =~ s{
+                       (.{0,20}?)
+                       ^sub
+                       \s+
+                       (\w+)
+                       \s*
+               }
+               { update_sub_pod( $1, $2 ) }mgsex ;
+
+       unless( $text =~ /^=cut\s*^\s*1\s*;\s*/m ) {
+       
+               $text =~ s{^\s*1\s*;\s*$}{ update_trailing_pod() }mex ;
+       }
+
+       return $text ;
+}
+
+
+sub update_attr_spec {
+
+       my( $attr_text, $file_name ) = @_ ;
+
+#print "U1 <$attr_text>\n" ;
+
+       ( $package ) = $attr_text =~ /^package\s+([\w:]+)/ ;
+
+       $attr_text =~ s/\n*^\#{5,}\n.+?^\#{5,}\n*//ms ;
+# and print "DELETED OLD POD\n" ;
+
+#print "U3 <$attr_text>\n" ;
+
+       $attr_text =~ s{ (^my\s+\$attr_spec.+?^]\s*;\s*) }
+                      { attr_spec_to_pod( $1, $file_name ) }gmsex ;
+
+#dump_attr( 'ATTR', $attr_text ) ;
+#print "ATTR [", substr( $attr_text, -40 ), "]\n" ;
+#print "U2 [$attr_text]\n" ;
+
+       return $attr_text ;
+}
+
+sub attr_spec_to_pod {
+
+       my ( $attr_text, $file_name ) = @_ ;
+
+       my $pod ;
+
+#print "ATTR [$attr_text]\n" ;
+#print "ATTR END1 [", substr( $attr_text, -30), "]\n" ;
+
+       $attr_text =~ s/\s*\z// ;
+
+       my( $attr_list_text ) =
+               $attr_text =~ /^my\s+\$attr_spec.+?=(.+?^\])/ms ;
+       $attr_list_text or die
+             "can't parse out attr list from file $file_name class $package" ;
+
+#print "ATTR2 [$attr_list_text]\n" ;
+       my $attr_list = eval $attr_list_text ;
+
+       $pod .= <<POD ;
+###########
+# This POD section is autogenerated. Any edits to it will be lost.
+
+=head2 Class Attributes for $package
+
+=over 4
+
+POD
+
+#print "POD [$pod]\n" ;
+
+
+       foreach my $attr_ref ( @{$attr_list} ) {
+
+               my $name = $attr_ref->{name} ;
+
+               if ( $name ) {
+
+                       $pod .= <<POD ;
+
+=item * Attribute - B<$name>
+
+=over 4
+
+POD
+               }
+               else {
+
+                       warn <<WARN ;
+Missing attribute name in Class $package in file $file_name
+WARN
+
+                       next ;
+               }
+
+               my $help = $attr_ref->{help} ;
+
+               if ( defined( $help ) ) {
+
+                       $pod .= <<POD ;
+
+=item Description:
+
+$help
+POD
+               }
+               else {
+
+                       warn <<WARN ;
+Missing help in attribute $name in Class $package in file $file_name
+WARN
+               }
+
+               if ( my $attr_class = $attr_ref->{class} ) {
+
+                       my $class_args = '<' .
+                               join( ', ', @{$attr_ref->{class_args} || []} )
+                                . '>' ;
+
+                       $pod .= <<POD ;
+
+=item Class Attribute:
+
+'$name' is an object of class $attr_class and constructed with:
+$class_args
+POD
+               }
+
+
+               exists( $attr_ref->{type} ) and $pod .= <<POD ;
+
+=item The type of '$name' is:
+
+$attr_ref->{type}
+POD
+
+               if ( exists( $attr_ref->{default} ) ) {
+
+                       my $default = $attr_ref->{default} ;
+
+                       if( ref($default) eq "ARRAY" ) {
+
+                               $default =
+                                       '(' . join( ', ', @{$default} ) . ')' ;
+                       }
+
+                       $pod .= <<POD
+
+=item B<Default> value:
+
+$default
+POD
+               }
+
+               exists( $attr_ref->{required} ) and $pod .= <<POD ;
+
+=item It is B<required>.
+POD
+
+               foreach my $attr ( sort keys %{ $attr_ref } ) {
+                       next if $is_attr_part{ $attr } ;
+                       $pod .= "Unknown attribute $attr\n" ;
+               }
+
+               $pod .= <<POD ;
+
+=back
+
+POD
+       }
+
+       $pod .= <<POD ;
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+POD
+
+#print "[$pod]" ;
+#print "POD2 [", substr($pod, 0, 40), "]\n" ;
+
+       return "$attr_text\n\n$pod" ;
+}
+
+sub update_sub_pod {
+
+       my( $cut_text, $name ) = @_ ;
+
+#print "SUB [$cut_text][$name]\n" ;
+
+       if ( $cut_text =~ /^=cut\s*$/m || $name =~ /^_/ ) {
+
+#print "SUB1 [${cut_text}sub $name ]\n"  if $name eq 'new' ;
+#dump_new( 'POD FOUND', $cut_text ) ;
+
+               return "${cut_text}sub $name " ;
+       }
+
+#print "NO SUB POD for $name\n" ;
+
+       my $desc = get_sub_pod( $name ) ;
+
+#dump_new( 'CUT', $cut_text ) ;
+#dump_new( 'DESC', $desc ) ;
+#print "CUT2 [$cut_text]\nDESC [$desc]\n" if $name eq 'new' ;
+
+       my $pod = <<POD ;
+$cut_text$desc
+=cut
+
+sub $name 
+POD
+
+       chomp $pod ;
+
+#print "SUB2 [$pod]\n" if $name eq 'new' ;
+
+       return $pod ;
+}
+
+sub get_sub_pod {
+
+       my ( $name ) = @_ ;
+
+       return <<POD if $name eq 'new' ;
+=head3 Constructor - B<new>
+
+The B<new> method creates an object of the class B<$package>. 
+
+POD
+
+       return <<POD if $name eq 'msg_in' ;
+=head3 Message Handler - B<msg_in>
+
+The B<msg_in> method is effectively a default method for message
+delivery. If any message to this cell can't be delivered to another
+method, then it will be delivered to the B<msg_in> method. If a
+command message is delivered and a value is returned by B<msg_in>, a
+response message is sent back to the originating cell with that value.
+POD
+
+       return <<POD if $name =~ /(\w+)_in$/ ;
+=head3 Message Handler - $name
+
+B<$1> type messages are delivered to this method. Its return value is
+ignored by the message delivery system.
+POD
+
+       return <<POD if $name =~ /(\w+)_cmd$/ ;
+=head3 Command Message Handler - $name
+
+B<$1> command messages are delivered to this method. If any value is
+returned, the message delivery system will create a response type
+message and dispatch it back to the sending cell.
+POD
+
+       return <<POD ;
+=head3 Method - $name
+POD
+
+}
+
+sub update_trailing_pod {
+
+       my( $tail_text ) = @_ ;
+
+#      return $tail_text if $tail_text =~ /=cut/ ;
+
+#print "1 [$tail_text]\n" ;
+
+       return <<POD ;
+
+=head1 Bugs
+
+=head1 Todo
+
+=head1 See Also
+
+=head1 Author
+
+Uri Guttman, E<lt>uri\@stemsystems.comE<gt>
+
+=cut
+
+1 ;
+POD
+
+}
+
+sub read_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+       open( FH, $file_name ) || carp "can't open $file_name $!" ;
+
+       return <FH> if wantarray ;
+
+       my $buf ;
+
+       sysread( FH, $buf, -s FH ) ;
+       return $buf ;
+}
+
+sub write_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+
+       open( FH, ">$file_name" ) || carp "can't create $file_name $!" ;
+
+       print FH @_ ;
+}
+
+sub dump_attr {
+
+       my( $key, $text ) = @_ ;
+
+       $text =~ /(;\s+#{3,})/s or return ;
+
+       print "$key [$1]\n" ;
+}
+
+__END__
diff --git a/bin/stem_msg b/bin/stem_msg
new file mode 100755 (executable)
index 0000000..be0f70c
--- /dev/null
@@ -0,0 +1,191 @@
+#!/usr/local/bin/perl
+
+use Getopt::Long ;
+use strict ;
+
+use Stem ;
+
+my %args ;
+my $hub_name ;
+my $portal ;
+
+parse_args() ;
+
+setup_hub() ;
+
+send_msg() ;
+
+Stem::Event::start_loop() ;
+
+# no return from here.
+######################
+
+sub setup_hub {
+
+       $hub_name = "Stem_msg_$$" ;
+
+       Stem::Route::register_class( __PACKAGE__ ) ;
+
+       Stem::Hub->new( 'reg_name' => $hub_name ) ;
+
+       my @portal_args ;
+
+       push @portal_args, ( 'host' => $args{'host'} ) if $args{'host'} ;
+       push @portal_args, ( 'port' => $args{'port'} ) if $args{'port'} ;
+
+#print "portal args: @portal_args\n" ;
+
+       $portal = Stem::Portal->new( @portal_args ) ;
+
+       die "Can't create Portal: $portal" if $portal ;
+}
+
+sub send_msg {
+
+       my ( @msg_args, @target ) ;
+
+       if ( $args{'cmd'} ) {
+
+               @msg_args = ( 'type' => 'cmd', 'cmd' => $args{'cmd'} ) ;
+       }
+       else {
+
+               @msg_args = ( 'type' => 'data' ) ;
+       }
+
+       @target = ( 'to_target' => $args{'target'} ) if $args{'target'} ;
+
+       push( @msg_args, ( 'ack_req' => 1 ) ) if $args{'ack'} ;
+
+       my $data = exists( $args{'data'} ) ? $args{'data'} : '' ;
+
+       my $msg = Stem::Msg->new( 
+                       'to_hub'        => 'DEFAULT',
+                       'to_cell'       => $args{'cell'},
+                       @target,
+                       'from_cell'     => __PACKAGE__,
+                       'from_hub'      => $hub_name,
+                       'data'          => \$data,
+                       @msg_args,
+               ) ;
+
+       $msg->dispatch() ;
+}
+
+# this is the class method that gets back the response and ack messages.
+
+sub msg_in {
+
+       my( $class, $msg ) = @_ ;
+
+       if( $msg->type() eq 'msg_ack' ) {
+
+#              print "ACK\n" ;
+               exit ;
+       }
+
+       if ( my $data = $msg->data() ) {
+
+               print ${$data} ;
+       }
+
+#      $portal->shut_down() ;
+
+       exit unless $args{'ack'} ;
+
+       return ;
+}
+
+
+sub parse_args {
+
+       Getopt::Long::Configure( 'no_ignore_case' ) ;
+
+       GetOptions( \%args,
+                   'cell|C=s',
+                   'hub|H=s',
+                   'target|T=s',
+                   'cmd|c=s',
+                   'data|d=s',
+                   'ack|a',
+                   'host|h=s',
+                   'port|p=s',
+                   'help|?',
+       ) ;
+
+#print map "$_ => $args{$_}\n", sort keys %args ;
+
+       usage( '' ) if $args{ 'help' } ;
+
+       usage( 'Missing Cell address' ) unless $args{ 'cell' } ;
+}
+
+sub usage {
+
+       my $err_msg = shift ;
+
+       my $usage = <<'=cut' ;
+=pod
+
+=head1 NAME
+
+stem_msg - Inject a message into a Stem Hub
+
+=head1 SYNOPSIS
+
+stem_msg -cell <cell> [-hub <hub>] [-target <target>]
+        [-cmd <cmd>] [-data <data>] [-ack]
+        [-host <host>] [-port <port>]
+
+       -C <cell>               The Stem Cell to send this message to.
+       -cell <cell>            This is required.
+
+       -H <hub>                The hub which has the addressed Stem Cell.
+       -hub <hub>              
+
+       -T <target>             The target address of the Stem Cell
+       -target <target>
+
+       -c <cmd>                The cmd type to send in the message
+       -cmd <cmd>              If no cmd is set, it will be a data type
+                               message. 
+
+       -d <data>               The data to be sent in the message.
+       -data <data>            Default is an empty string.
+
+       -a                      Wait for an acknowledge message before
+       -ack                    exiting.
+
+       -h <host>               The host which the Stem Hub is on.
+       -host <host>            Default: localhost
+
+       -p <port>               The port which the Stem Portal is listening
+       -port <port>            to.
+                               Default: 10,000 (probably will change)
+
+=head1 DESCRIPTION
+
+This program is meant to inject a single message into a Stem Hub. You
+set the Cell address with the command line options and then which
+command to execute in that Cell. If you don't set a command, then a
+data message will be sent. You can send data in the message as well.
+
+If the Cell generates a response message, then its data will be
+printed on stdout.
+
+If the -ack option is set, then the message will have the ack_req flag
+will be set in the outgoing message. This will cause an 'ack' type
+message to be sent back after the original message has been
+delivered. This is meant for when you send a message to a Cell which
+doesn't generate a response. It lets this program know that it can
+exit.
+
+=cut
+
+       $usage =~ s/^=\w+.*$//mg ;
+
+       $usage =~ s/\n{2,}/\n\n/ ;
+       $usage =~ s/\A\n+/\n/ ;
+
+       die "$err_msg\n$usage" ;
+}
diff --git a/bin/tail_demo b/bin/tail_demo
new file mode 100755 (executable)
index 0000000..ce04d7e
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/local/bin/perl -s
+
+$line_cnt = 10 ;
+$offset = 175 ;
+$base_off = 0 ;
+$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
+
+my @children ;
+
+my $tail_dir = 'tail' ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+if ( $s ) {
+
+       $ssfe = 'ssfe' ;
+       $prompt = '-prompt Stem:' ;
+       $echo = 'console_echo=1'
+}
+
+-d $tail_dir or mkdir $tail_dir, 0722 or
+               die "can't create $tail_dir working directory" ;
+
+foreach my $log ( qw( foo bar bar_status ) ) {
+       unlink "$tail_dir/$log.log" ;
+}
+
+
+foreach $cmd ( split /\n/, <<EOT ) {
+xterm -T Archive -n Archive -geometry 80x35+0+0 -e $ssfe $prompt run_stem $echo archive
+xterm -T Monitor -n Monitor -geometry 80x35+$xskip+0 -e $ssfe $prompt run_stem $echo monitor
+xterm -T Tail -n Tail -geometry 80x20+275+420
+EOT
+
+       print "$cmd\n" ;
+
+       @cmd = split ' ', $cmd ;
+
+       s/:/: / for @cmd ;
+
+       chdir $tail_dir unless $cmd =~ /run_stem/ ;
+
+       fork_exec( @cmd ) ;
+
+       sleep 2 ;
+}
+
+while( <STDIN> ) {
+
+       next unless /^q/i ;
+
+       cleanup() ;
+}
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( @exec ) = @_ ;
+
+       if ( $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec @exec ;
+}
diff --git a/certs/client-cert.pem b/certs/client-cert.pem
new file mode 100644 (file)
index 0000000..e35a3ce
--- /dev/null
@@ -0,0 +1,43 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Some-State, O=Dummy IO::Socket::SSL Certificate Authority, CN=Dummy IO::Socket::SSL Certificate Authority
+        Validity
+            Not Before: Jul 20 16:06:19 2002 GMT
+            Not After : Dec  5 16:06:19 2029 GMT
+        Subject: C=US, ST=Some-State, O=IO::Socket::SSL Dummy Certificate, CN=IO::Socket::SSL Dummy Certificate
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:cd:65:60:3d:8f:55:1f:7a:9e:87:a8:72:a0:53:
+                    9d:e9:31:9e:bc:f1:27:d3:ba:e9:ab:ca:28:a5:b5:
+                    48:a2:24:d8:ed:01:ec:ae:69:b1:91:7b:68:e6:d1:
+                    15:57:7e:b0:06:62:33:d5:88:3e:fc:dc:fb:db:8a:
+                    c9:9b:bb:50:9f
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        0b:61:be:f8:12:9a:82:92:63:ed:57:f8:f9:dd:79:1b:46:a2:
+        0c:7c:a0:80:01:88:38:0f:a1:c0:b3:2d:57:0e:ad:3c:a8:72:
+        e3:d9:50:7f:11:9a:af:2a:e3:d8:66:de:b2:18:3e:cf:c5:4a:
+        71:00:b0:05:49:a3:83:9a:53:f9:83:92:2f:c7:f7:d3:df:8f:
+        3d:92:5c:e5:3e:5a:f8:b6:55:71:5e:c8:85:a4:0c:0f:e7:1b:
+        1f:b9:c9:db:c4:9e:d8:6a:fc:33:da:10:36:de:73:34:2f:ea:
+        0d:29:e1:2b:90:89:a3:a9:74:8c:57:e3:ee:50:00:b4:0c:69:
+        1a:f2
+-----BEGIN CERTIFICATE-----
+MIICNDCCAZ0CAQEwDQYJKoZIhvcNAQEEBQAwgY4xCzAJBgNVBAYTAlVTMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMTQwMgYDVQQKEytEdW1teSBJTzo6U29ja2V0OjpTU0wg
+Q2VydGlmaWNhdGUgQXV0aG9yaXR5MTQwMgYDVQQDEytEdW1teSBJTzo6U29ja2V0
+OjpTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDcyMDE2MDYxOVoXDTI5
+MTIwNTE2MDYxOVowejELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUx
+KjAoBgNVBAoTIUlPOjpTb2NrZXQ6OlNTTCBEdW1teSBDZXJ0aWZpY2F0ZTEqMCgG
+A1UEAxMhSU86OlNvY2tldDo6U1NMIER1bW15IENlcnRpZmljYXRlMFwwDQYJKoZI
+hvcNAQEBBQADSwAwSAJBAM1lYD2PVR96noeocqBTnekxnrzxJ9O66avKKKW1SKIk
+2O0B7K5psZF7aObRFVd+sAZiM9WIPvzc+9uKyZu7UJ8CAwEAATANBgkqhkiG9w0B
+AQQFAAOBgQALYb74EpqCkmPtV/j53XkbRqIMfKCAAYg4D6HAsy1XDq08qHLj2VB/
+EZqvKuPYZt6yGD7PxUpxALAFSaODmlP5g5Ivx/fT3489klzlPlr4tlVxXsiFpAwP
+5xsfucnbxJ7Yavwz2hA23nM0L+oNKeErkImjqXSMV+PuUAC0DGka8g==
+-----END CERTIFICATE-----
diff --git a/certs/client-key.enc b/certs/client-key.enc
new file mode 100644 (file)
index 0000000..69eddc0
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,A24532ADA3AB45E3
+
+sltzmMEb/cKphkOkMQsMJHyqc17pxXf3z3QrvktJRvZQtMU6dY0eisbW3gilU+80
+qgiMkkD9TEufYQ3oLRAF8hefS+iM+Mx3F5Ml5bhArXQBRN6Gd0QkepWnrrko8Y6N
+6uGT9ndJHhLyOArDWgWkzfuT7NEVoYrLhf8x6E43v/6By5GmBZs5mbwygYr6vpzZ
+6+KHgpVFYPNQvhJTKJTe2XO+kTQtOcYmrapTr6SG4i9i7x2Q21CGhum8492KA+3N
++EvmGAp0MMwZVTuCRyquLRfF6/NsPANb97yWsz3pe05I/p8CSzwU7ivNQZA51pyx
+zlwzjoWElSzMTssIegcCctdjeTHhiFRfvW1YDinvXPiLfAjBOv37JDvLC6vclqev
+yPXtne0iOn8hZc0QAnWESAAPz6PE0jIecqcZkXKQHfc=
+-----END RSA PRIVATE KEY-----
diff --git a/certs/client-key.pem b/certs/client-key.pem
new file mode 100644 (file)
index 0000000..fc5baf2
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOQIBAAJBAM1lYD2PVR96noeocqBTnekxnrzxJ9O66avKKKW1SKIk2O0B7K5p
+sZF7aObRFVd+sAZiM9WIPvzc+9uKyZu7UJ8CAwEAAQJAdCtxWoAWCh7lSR8J7go9
+Fya8fGr9NrDR9xr5EHhPI30lmzpj1Tst2VRymN8ojUu/3AgBAOshpqS5Ve38o2mU
+yQIhAPd44uw37QTwmefTWV+REtaGHaRwbc5qemSV6WNpgei9AiEA1HlOzr1BxqCo
+ZwlizSn5ox8YdJ6LAOA+1GOkvOyV2osCIGT26w4Y2xiy2PfeII5+78KaQSm/vO0E
+QB8dknS+rQO5AiBwv6KjMGjsFyrl6mQkjOasuf6HO+51W4nbuLidjEoE+wIgN00d
+/WcCSyj8HlfLgHPe8ZlEyMe6Hq7LfX5lShUq0qI=
+-----END RSA PRIVATE KEY-----
diff --git a/certs/my-ca.pem b/certs/my-ca.pem
new file mode 100644 (file)
index 0000000..36bf8e4
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBjjELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTClNvbWUtU3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6
+OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpT
+b2NrZXQ6OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNzIwMTYwNTU0
+WhcNMjkxMjA1MTYwNTU0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt
+U3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALQm
+bgkEUWImNkjWcO6qn5NZ7rCFbtrzqEYbqciy+1qlWuoBgU44n9ykD1c/BcmBPsDT
+bIOfLzjcdJj38taXu7kcRclchJ+/c6o/SmDv7UqcL6QgVSZRvRrK7TDypMqe3sW8
+zCvTF8WtSsgFy5f9qlUdx4NowMzVV7OFl+6x4YlpAgMBAAGjge4wgeswHQYDVR0O
+BBYEFDU4SrHVMHDjd2kBgFM/qyC3DPxFMIG7BgNVHSMEgbMwgbCAFDU4SrHVMHDj
+d2kBgFM/qyC3DPxFoYGUpIGRMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29t
+ZS1TdGF0ZTE0MDIGA1UEChMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENlcnRpZmlj
+YXRlIEF1dGhvcml0eTE0MDIGA1UEAxMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENl
+cnRpZmljYXRlIEF1dGhvcml0eYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
+BAUAA4GBAIbCsK/qUXiIsRvg1ptaLNM6VsuR8ifNrmo9A4zk1h4OCixys6Hmoow6
+3MndnLpD3rh3UCYh0M20+fiHcwSmHZvBo3dfSSvYnH0gFSBjKp/wgGcb3Cvl3dRX
+aeWZGrKQKLI6DrHqAiSu9rv+2kfzgmRLt0K+gdb2GkQqCBwT8Gjr
+-----END CERTIFICATE-----
diff --git a/certs/server-cert.pem b/certs/server-cert.pem
new file mode 100644 (file)
index 0000000..0fc5c24
--- /dev/null
@@ -0,0 +1,44 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Some-State, O=Dummy IO::Socket::SSL Certificate Authority, CN=Dummy IO::Socket::SSL Certificate Authority
+        Validity
+            Not Before: Jul 20 16:06:37 2002 GMT
+            Not After : Dec  5 16:06:37 2029 GMT
+        Subject: C=US, ST=Some-State, O=IO::Socket::SSL Dummy Server Certificate, CN=IO::Socket::SSL Dummy Server Certificate
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:9f:27:5f:4a:8a:35:4a:7f:3f:d1:80:25:96:26:
+                    0a:da:af:9a:6d:bc:23:ba:71:91:5b:40:d1:2d:2b:
+                    c8:60:2a:ef:e9:54:e5:a2:64:0a:57:90:35:bf:cd:
+                    b6:36:f3:25:53:68:65:2c:d8:d0:f9:b7:f3:7f:2e:
+                    f8:e2:3d:e0:dd
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        57:a7:2d:91:cc:e9:11:16:bb:c1:cd:b5:a5:e1:26:99:8f:ee:
+        8c:b0:2d:b6:54:f4:8a:8e:fd:8f:45:9a:68:d8:0e:ef:d6:a5:
+        38:6a:48:d0:08:da:a8:87:3c:70:05:18:69:a1:c8:ee:94:a7:
+        87:40:f5:4f:64:b4:b0:c6:d3:d2:ed:f9:cc:d1:fe:da:4d:99:
+        4d:22:02:f6:0e:9b:c0:cc:42:59:50:2f:5c:fc:5b:70:f9:0b:
+        ec:6e:5b:eb:d7:6f:a1:b8:67:57:b1:4f:99:bd:ad:03:9d:b5:
+        f3:44:5c:36:1c:fa:33:82:87:0b:99:aa:f5:39:5c:63:23:6b:
+        48:2d
+-----BEGIN CERTIFICATE-----
+MIICQzCCAawCAQIwDQYJKoZIhvcNAQEEBQAwgY4xCzAJBgNVBAYTAlVTMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMTQwMgYDVQQKEytEdW1teSBJTzo6U29ja2V0OjpTU0wg
+Q2VydGlmaWNhdGUgQXV0aG9yaXR5MTQwMgYDVQQDEytEdW1teSBJTzo6U29ja2V0
+OjpTU0wgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDcyMDE2MDYzN1oXDTI5
+MTIwNTE2MDYzN1owgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRl
+MTEwLwYDVQQKEyhJTzo6U29ja2V0OjpTU0wgRHVtbXkgU2VydmVyIENlcnRpZmlj
+YXRlMTEwLwYDVQQDEyhJTzo6U29ja2V0OjpTU0wgRHVtbXkgU2VydmVyIENlcnRp
+ZmljYXRlMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ8nX0qKNUp/P9GAJZYmCtqv
+mm28I7pxkVtA0S0ryGAq7+lU5aJkCleQNb/NtjbzJVNoZSzY0Pm3838u+OI94N0C
+AwEAATANBgkqhkiG9w0BAQQFAAOBgQBXpy2RzOkRFrvBzbWl4SaZj+6MsC22VPSK
+jv2PRZpo2A7v1qU4akjQCNqohzxwBRhpocjulKeHQPVPZLSwxtPS7fnM0f7aTZlN
+IgL2DpvAzEJZUC9c/Ftw+Qvsblvr12+huGdXsU+Zva0DnbXzRFw2HPozgocLmar1
+OVxjI2tILQ==
+-----END CERTIFICATE-----
diff --git a/certs/server-key.enc b/certs/server-key.enc
new file mode 100644 (file)
index 0000000..36a2d7f
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,25B674CF19D8A5D7
+
+fLLIyUYuJLP0YzaBTMrdHSN9ROApAIyDdZNUM3qOW19IfER87Rw90pTJLTIe2r9d
+tDIBY0w3MCYHOKEC+g9R8nsgLJNEkXG/Zi6fbhufnPAk343X8mDm+hjtQVzZONVc
+VE8v4EA6qYHm54y/UlXnK9fJ9OhFru+btueQ+8z2pYZJEZ9ktAEJBj+jeD9nOcw4
+lZ2vgpGwyj5pzNSS/4QlujRSB4gddPlJyig+STN2iom7BetPUVJg0XOW74ezZSr9
+WU/c8Ghbg/efzah56WUYzBdIhXjpQr5zDmXi6uj8zCLSSFjdR48KQVfbXULoeqeA
+8Nru2tXv34C5UdDgOkKbZSHar0n3o6t73B3WS9i90/1A4VLHvCvNxoBet7JdkEx8
+yv0b3A6wBHtAI5LRryaHAVN7bkIpXCfXGeMGFoCUpdO2jCeg/j54AQ==
+-----END RSA PRIVATE KEY-----
diff --git a/certs/server-key.pem b/certs/server-key.pem
new file mode 100644 (file)
index 0000000..b7a165f
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAJ8nX0qKNUp/P9GAJZYmCtqvmm28I7pxkVtA0S0ryGAq7+lU5aJk
+CleQNb/NtjbzJVNoZSzY0Pm3838u+OI94N0CAwEAAQJAf/DavcVVCco5t2TY0ldK
+qno4Hrb70cmyHDWC8lkb/5HAGbCGxpsstXxVKczRO201vcFUKm6PX5moUnFCINpg
+UQIhAM+ooHbD0eLL0K6limEnW7GId/+DFI/6KFXk2Nzm//XXAiEAxDQbWQvZS8DO
+HJ5JV8flvMhH30KLeH+zpsvBjWJK4GsCIQCUF7woNsquJZBznNctJjZ8S8jYThES
+BONTLluCXrNYDQIhAJFnsHDQqCxM6jMpV193pJnAsAsUbPpTYZeWX43hL26bAiEA
+jNB3PPNvTNr5tICkO/lMZcN87eUn4ZAtrNzCVF5ilEo=
+-----END RSA PRIVATE KEY-----
diff --git a/certs/test-ca.pem b/certs/test-ca.pem
new file mode 100644 (file)
index 0000000..36bf8e4
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQQFADCBjjELMAkGA1UEBhMCVVMx
+EzARBgNVBAgTClNvbWUtU3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6
+OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpT
+b2NrZXQ6OlNTTCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNzIwMTYwNTU0
+WhcNMjkxMjA1MTYwNTU0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt
+U3RhdGUxNDAyBgNVBAoTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxNDAyBgNVBAMTK0R1bW15IElPOjpTb2NrZXQ6OlNTTCBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALQm
+bgkEUWImNkjWcO6qn5NZ7rCFbtrzqEYbqciy+1qlWuoBgU44n9ykD1c/BcmBPsDT
+bIOfLzjcdJj38taXu7kcRclchJ+/c6o/SmDv7UqcL6QgVSZRvRrK7TDypMqe3sW8
+zCvTF8WtSsgFy5f9qlUdx4NowMzVV7OFl+6x4YlpAgMBAAGjge4wgeswHQYDVR0O
+BBYEFDU4SrHVMHDjd2kBgFM/qyC3DPxFMIG7BgNVHSMEgbMwgbCAFDU4SrHVMHDj
+d2kBgFM/qyC3DPxFoYGUpIGRMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29t
+ZS1TdGF0ZTE0MDIGA1UEChMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENlcnRpZmlj
+YXRlIEF1dGhvcml0eTE0MDIGA1UEAxMrRHVtbXkgSU86OlNvY2tldDo6U1NMIENl
+cnRpZmljYXRlIEF1dGhvcml0eYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
+BAUAA4GBAIbCsK/qUXiIsRvg1ptaLNM6VsuR8ifNrmo9A4zk1h4OCixys6Hmoow6
+3MndnLpD3rh3UCYh0M20+fiHcwSmHZvBo3dfSSvYnH0gFSBjKp/wgGcb3Cvl3dRX
+aeWZGrKQKLI6DrHqAiSu9rv+2kfzgmRLt0K+gdb2GkQqCBwT8Gjr
+-----END CERTIFICATE-----
diff --git a/conf/archive.stem b/conf/archive.stem
new file mode 100644 (file)
index 0000000..dddb304
--- /dev/null
@@ -0,0 +1,66 @@
+# archive.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'archive',
+       'args'  =>      [],
+],
+[
+       'class' =>      'Stem::Portal',
+       'args'  =>      [
+               'server'        => 1,
+       ],
+],
+[
+       'class' =>      'Stem::Log',
+       'args'  =>      [
+
+               'name'          => 'bar',
+
+               'file'          => [
+                       'path'          => 'tail/bar.log',
+               ],
+
+               'filters'       => [
+                       file    => 1,
+                       forward => [ 'bar_stdout' ],
+               ],
+
+       ],
+],
+[
+       'class' =>      'Stem::Log',
+       'args'  =>      [
+
+               'name'          => 'bar_stdout',
+               'format'        => '%f [%L][%l] %T',
+               'strftime'      => '%D %T',
+               'filters'       => [
+                       'env_gt_level'  => 'bar_stdout',
+                       stdout  => 1,
+               ],
+       ],
+],
+[
+       'class' =>      'Stem::Log',
+       'args'  =>      [
+
+               'name'          => 'bar_status',
+
+               'file'          => [
+                       'path'          => 'tail/bar_status.log',
+               ],
+
+               'format'        => '[%f]%h:%H:%P - %T',
+               'strftime'      => '%T',
+               'filters'       => [
+                       file    => 1,
+                       'env_gt_level'  => 'bar_status',
+#                      stdout  => 1,
+                       console => 1,
+               ],
+       ],
+],
diff --git a/conf/boot.stem b/conf/boot.stem
new file mode 100644 (file)
index 0000000..8208b21
--- /dev/null
@@ -0,0 +1,16 @@
+[      class   =>      'Stem::Log',
+       args    =>      [
+
+               name    =>      'stdout',
+               filters =>      [
+                       'stdout'        => 1,
+               ],
+       ],
+],
+[
+       class   =>      'Stem::Boot',
+       name    =>      'test',
+       args    =>      [
+               boot_file       => 'test/test.boot',
+       ]
+],
diff --git a/conf/chat.stem b/conf/chat.stem
new file mode 100644 (file)
index 0000000..6e2a147
--- /dev/null
@@ -0,0 +1,71 @@
+# chat.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:a'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'B',
+       args    =>      [
+               port            => 6667,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:b'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'C',
+       args    =>      [
+               port            => 6668,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:c'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'D',
+       args    =>      [
+               port            => 6669,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:d'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::Switch',
+       name    =>      'sw',
+       args    =>      [
+
+               in_map => [
+
+                       a => [ qw( a b c d ) ],
+                       b => 'a',
+                       c => [ qw( b d ) ],
+                       d => 'c',
+               ],
+
+               out_map => [
+
+                       a => 'A',
+                       b => 'B',
+                       c => 'C',
+                       d => 'D',
+               ],
+       ],
+],
diff --git a/conf/chat_client.stem b/conf/chat_client.stem
new file mode 100644 (file)
index 0000000..aef3e40
--- /dev/null
@@ -0,0 +1,36 @@
+# chat_client.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Hub',
+       name    =>      'client',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:a'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'B',
+       args    =>      [
+               port            => 6667,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:b'
+               ],
+       ],
+],
diff --git a/conf/chat_label.stem b/conf/chat_label.stem
new file mode 100644 (file)
index 0000000..27147bc
--- /dev/null
@@ -0,0 +1,84 @@
+# chat.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Demo::Cmd',
+       name    =>      'cmd',
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':label:a'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'B',
+       args    =>      [
+               port            => 6667,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':label:b'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'C',
+       args    =>      [
+               port            => 6668,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':label:c'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'D',
+       args    =>      [
+               port            => 6669,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':label:d'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::ChatLabel',
+       name    =>      'label',
+       args    =>      [
+               sw_addr         => 'sw',
+       ],
+],
+
+
+[
+       class   =>      'Stem::Switch',
+       name    =>      'sw',
+       args    =>      [
+
+               in_map => [
+
+                       a => [ qw( a b c d ) ],
+                       b => 'a',
+                       c => [ qw( b d ) ],
+                       d => 'c',
+               ],
+
+               out_map => [
+
+                       a => 'A',
+                       b => 'B',
+                       c => 'C',
+                       d => 'D',
+               ],
+       ],
+],
diff --git a/conf/chat_server.stem b/conf/chat_server.stem
new file mode 100644 (file)
index 0000000..84a72fc
--- /dev/null
@@ -0,0 +1,58 @@
+# chat_server.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Hub',
+       name    =>      'server',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      ['server' => 1 ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'C',
+       args    =>      [
+               port            => 6668,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:c'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'D',
+       args    =>      [
+               port            => 6669,
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => ':sw:d'
+               ],
+       ],
+],
+[
+       class   =>      'Stem::Switch',
+       name    =>      'sw',
+       args    =>      [
+
+               in_map => [
+
+                       a => [ qw( a b c d ) ],
+                       b => 'a',
+                       c => [ qw( b d ) ],
+                       d => 'c',
+               ],
+
+               out_map => [
+
+                       a => 'client:A',
+                       b => 'client:B',
+                       c => 'C',
+                       d => 'D',
+               ],
+       ],
+],
diff --git a/conf/cli.stem b/conf/cli.stem
new file mode 100644 (file)
index 0000000..ab96791
--- /dev/null
@@ -0,0 +1,27 @@
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'cli_sock',
+       args    =>      [
+               port            => 8888,
+               server          => 1,
+               host            => '',
+               cell_attr       => [
+                       'cloneable'     => 1,
+                       'data_addr'     => 'cli',
+                       'codec'         => 'Data::Dumper',
+               ],
+       ],
+],
+[
+       class     =>    'Stem::Demo::CLI',
+       name      =>    'cli',
+       args      =>    [
+               cell_attr => [
+                       cloneable       => 1,
+                       no_io           => 1,
+               ],
+       ],
+],
diff --git a/conf/cron.stem b/conf/cron.stem
new file mode 100644 (file)
index 0000000..832f390
--- /dev/null
@@ -0,0 +1,25 @@
+# cron.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Demo::Cmd',
+       name    =>      'cmd',
+],
+[
+       class   =>      'Stem::Cron',
+       name    =>      'cron',
+       args    =>      [
+
+               'hours'         => [ 0 .. 8 ],
+               'month_days'    => [ '11-15' ],
+               'months'        => [ '12' ],
+
+               'msg'   => [
+
+                       'to_cell'       => 'console',
+                       'data'          => "foo\n",
+               ]
+       ],
+],
diff --git a/conf/hello.stem b/conf/hello.stem
new file mode 100644 (file)
index 0000000..6c01843
--- /dev/null
@@ -0,0 +1,9 @@
+# hello.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Demo::World',
+       name    =>      'world',
+],
diff --git a/conf/hello_client.stem b/conf/hello_client.stem
new file mode 100644 (file)
index 0000000..8e5b041
--- /dev/null
@@ -0,0 +1,24 @@
+# hello_client.stem
+#
+[
+       class   =>      'Stem::Hub',
+       name    =>      'system_B',
+       args    =>      [
+               'host' => 'localhost',
+       ],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::Proc',
+       name    =>      'hello',
+       args    =>      [
+               path            => 'bin/hello.sh',
+               proc_args       => ['client'],
+               cell_attr       => [
+                       'data_addr'     => 'system_A:console',
+               ],
+       ],
+],
diff --git a/conf/hello_server.stem b/conf/hello_server.stem
new file mode 100644 (file)
index 0000000..d2dadc6
--- /dev/null
@@ -0,0 +1,43 @@
+# hello_server.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Hub',
+       name    =>      'system_A',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      [
+               'server' => 1,
+               'host' => 'localhost',
+       ],
+],
+[
+       class   =>      'Stem::Switch',
+       name    =>      'sw',
+       args    =>      [
+               in_map => [
+
+                       h => [ qw( h h3 ) ],
+               ],
+               out_map => [
+
+                       h => 'hello',
+                       h3 => 'system_B:hello',
+               ],
+       ],
+],
+[
+       class   =>      'Stem::Proc',
+       name    =>      'hello',
+       args    =>      [
+               path            => 'bin/hello.sh',
+               proc_args       => ['server'],
+               cell_attr       => [
+                       'data_addr'     => 'console',
+               ],
+       ],
+],
diff --git a/conf/hello_shell.stem b/conf/hello_shell.stem
new file mode 100644 (file)
index 0000000..1d176b6
--- /dev/null
@@ -0,0 +1,14 @@
+# hello_shell.stem
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Proc',
+       name    =>      'hello',
+       args    =>      [
+               path            => 'bin/hello.sh',
+               cell_attr       => [
+                       'data_addr'     => 'console',
+               ],
+       ],
+],
diff --git a/conf/hello_yaml.stem b/conf/hello_yaml.stem
new file mode 100644 (file)
index 0000000..ad38628
--- /dev/null
@@ -0,0 +1,12 @@
+--- #YAML:1.0
+-
+ class: Stem::Console
+-
+ class: Stem::Proc
+ name: hello
+ args:
+    path: bin/hello.sh
+    proc_args: 
+     - Athena Health
+    cell_attr:
+     data_addr: console
diff --git a/conf/inetd.stem b/conf/inetd.stem
new file mode 100644 (file)
index 0000000..dc124e9
--- /dev/null
@@ -0,0 +1,60 @@
+# inetd.stem
+#
+# Emulate an inetd daemon
+#
+#
+# Load the consols module so we can enter commands to stem
+[
+       class   =>      'Stem::Console',
+],
+
+# these two cells are both SockMsg's. they are servers listening for
+# connections on different ports. they have different Cell addresses (A,
+# B). Both use the Stem::Cell attributes to handle cloning upon
+# connection and the logical pipe to the Stem::Proc cell. Note that B
+# has the pipe_args option which sends extra arguments to the other side
+# of the pipe. this enables line numbering in the output of the
+# proc_serv script.
+
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'A',
+       args    =>      [
+               port            => 6666,
+               server          => 1,
+               cell_attr       => [
+                       'cloneable'     => 1,
+# this name maps to the cell name of the Stem::Proc below
+                       'pipe_addr'     => 'quote',
+               ],
+       ],
+],
+[
+       class   =>      'Stem::SockMsg',
+       name    =>      'B',
+       args    =>      [
+               port            => 6667,
+               server          => 1,
+               cell_attr       => [
+                       'cloneable'     => 1,
+# this name maps to the cell name of the Stem::Proc below
+                       'pipe_addr'     => 'quote',
+                       'pipe_args'     => '-n',
+               ],
+       ],
+],
+
+# this is the Stem::Proc cell actually forks the program. its name is
+# refered to by the SockMsg cells. 
+
+[
+       class   =>      'Stem::Proc',
+       name    =>      'quote',
+       args    =>      [
+               path            => 'quote_serve',
+               use_stderr      => 1,
+               cell_attr       => [
+                       'cloneable'     => 1,
+               ],
+       ],
+],
diff --git a/conf/load_driver.stem b/conf/load_driver.stem
new file mode 100644 (file)
index 0000000..16c9404
--- /dev/null
@@ -0,0 +1,24 @@
+[
+       class   =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'Load',
+       'args'  =>      [],
+],
+[
+       'class' =>      'Stem::Portal',
+       'name'  =>      'load',
+       'args'  =>      [
+               'port'          => 10001,
+       ],
+],
+[
+       class   =>      'Stem::Load::Driver',
+       name    =>      'driver',
+       args    =>      [
+                       'load_addr'     => 'Echo:echo',
+                       'max_loads_per_hub' => 1,
+                       'max_load_msgs' => 100,
+       ],
+],
diff --git a/conf/load_echo.stem b/conf/load_echo.stem
new file mode 100644 (file)
index 0000000..26b2e8d
--- /dev/null
@@ -0,0 +1,15 @@
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Load::Driver',
+       name    =>      'driver',
+       args    =>      [
+               load_addr       => 'echo',
+       ],
+],
+[
+       class   =>      'Stem::Test::Echo',
+       name    =>      'echo',
+       args    =>      [],
+],
diff --git a/conf/monitor.stem b/conf/monitor.stem
new file mode 100644 (file)
index 0000000..aee3340
--- /dev/null
@@ -0,0 +1,26 @@
+# monitor.stem
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'monitor',
+       'args'  =>      [],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      [
+       ],
+],
+[
+       'class' =>      'Stem::Log::Tail',
+       'name'  =>      'foo',
+       'args'  =>      [
+               'path'          => 'tail/foo.log',
+               'interval'      => 3,
+               'delay'         => 0,
+               'data_log'      => 'archive:bar',
+               'status_log'    => 'archive:bar_status',
+       ],
+],
diff --git a/conf/proc.stem b/conf/proc.stem
new file mode 100644 (file)
index 0000000..61830c0
--- /dev/null
@@ -0,0 +1,30 @@
+[
+       class   =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'proc',
+       'args'  =>      [],
+],
+
+# this portal listens for the driver hub
+[
+       'class' =>      'Stem::Portal',
+       'name'  =>      'Echo',
+       'args'  =>      [
+               'host'          => '',
+               'port'          => 10001,
+               'server'        => 1
+       ],
+],
+[
+       class   =>      'Stem::Proc',
+       name    =>      'echo',
+       args    =>      [
+               path            => './bin/echo_worker.pl',
+               spawn_now       => 1,
+               cell_attr       => [
+                       'worker_mode'           => 1,
+               ],
+       ],
+],
diff --git a/conf/slave.stem b/conf/slave.stem
new file mode 100644 (file)
index 0000000..a448d8d
--- /dev/null
@@ -0,0 +1,17 @@
+# slave.stem
+# this is used for slave Hubs which get their configs from a master Hub
+#
+[
+       class   =>      'Stem::Console',
+],
+[
+       class   =>      'Stem::Hub',
+       name    =>      'slave',
+       args    =>      [],
+],
+[
+       class   =>      'Stem::Portal',
+       args    =>      [
+               'server'        => 1
+       ],
+],
diff --git a/conf/tail.stem b/conf/tail.stem
new file mode 100644 (file)
index 0000000..67bd740
--- /dev/null
@@ -0,0 +1,41 @@
+# tail.stem
+#
+[
+       'class' =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'tail',
+       'args'  =>      [],
+],
+[
+       'class' =>      'Stem::Log::Tail',
+       'name'  =>      'foo',
+       'args'  =>      [
+               'path'          => 'tail/foo',
+               'interval'      => 5,
+               'data_log'      => 'bar',
+               'status_log'    => 'bar_status',
+       ],
+],
+[
+       'class' =>      'Stem::Log',
+       'args'  =>      [
+
+               'name'          => 'bar',
+               'path'          => 'tail/bar',
+       ],
+],
+[
+       'class' =>      'Stem::Log',
+       'args'  =>      [
+
+               'name'          => 'bar_status',
+               'path'          => 'tail/bar_status',
+
+               'filters'       => [
+                       stdout  => 1,
+                       file    => 1,
+               ],
+       ],
+],
diff --git a/conf/test_flow.stem b/conf/test_flow.stem
new file mode 100644 (file)
index 0000000..009d3f2
--- /dev/null
@@ -0,0 +1,6 @@
+# test_flow.stem
+[
+       class   =>      'Stem::Test::Flow',
+       name    =>      'flow',
+       args    =>      [],
+],
diff --git a/conf/test_packet_io.stem b/conf/test_packet_io.stem
new file mode 100644 (file)
index 0000000..88a8ca0
--- /dev/null
@@ -0,0 +1,12 @@
+[
+        class   =>      'Stem::Test::Echo',
+        name    =>      'echo',
+        args    =>      [],
+],
+[
+       class   =>      'Stem::Test::PacketIO',
+       name    =>      'packet_io',
+       args    =>      [
+               write_addr => 'client_sock',
+       ],
+],
diff --git a/conf/test_udp.stem b/conf/test_udp.stem
new file mode 100644 (file)
index 0000000..7097851
--- /dev/null
@@ -0,0 +1,28 @@
+# test_udp.stem
+[
+       class   =>      'Stem::UDPMsg',
+       name    =>      'send_only',
+       args    =>      [
+#              send_host       => 'localhost',
+       ],
+],
+[
+       class   =>      'Stem::UDPMsg',
+       name    =>      'recv_only',
+       args    =>      [
+               bind_port       => 9999,
+               bind_host       => 'localhost',
+               data_addr       => 'udp_test',
+               timeout_addr    => 'udp_test',
+               server          => 1,
+               timeout         => 1,
+       ],
+],
+[
+       class   =>      'Stem::Test::UDP',
+       name    =>      'udp_test',
+       args    =>      [
+               send_addr => 'send_only',
+               send_port => '9999',
+       ],
+],
diff --git a/conf/ticker.stem b/conf/ticker.stem
new file mode 100644 (file)
index 0000000..4e70874
--- /dev/null
@@ -0,0 +1,29 @@
+[
+       class   =>      'Stem::Console',
+],
+[
+       'class' =>      'Stem::Hub',
+       'name'  =>      'tick_driver',
+       'args'  =>      [],
+],
+[
+       'class' =>      'Stem::Portal',
+       'name'  =>      'tick_server',
+       'args'  =>      [
+               'server'        => 1,
+       ],
+],
+[
+       class   =>      'Stem::WorkQueue',
+       name    =>      'dbi_queue',
+       args    =>      [],
+],
+
+
+[
+       'class' =>      'Stem::Load::Ticker',
+       'name'  =>      'tick',
+       'args'  =>      [
+               dbi_addr        => 'dbi_queue',
+       ],
+],
diff --git a/conf/ttysock.stem b/conf/ttysock.stem
new file mode 100644 (file)
index 0000000..b4d501d
--- /dev/null
@@ -0,0 +1,8 @@
+# ttysock.stem
+#
+# drive this from the command line with
+# tty_host and tty_port
+[
+       class   =>      'Stem::TtySock',
+       args    =>      [],
+],
diff --git a/conf/type.stem b/conf/type.stem
new file mode 100644 (file)
index 0000000..6587288
--- /dev/null
@@ -0,0 +1,104 @@
+--- #YAML:1.0
+# -
+#  class: Stem::Test::ConfTypes
+#  name: boolean
+#  args:
+#     bool_attr: yes
+# -
+#  class: Stem::Test::ConfTypes
+#  name: scalar
+#  args:
+#     list_attr: scalar_val
+# -
+#  class: Stem::Test::ConfTypes
+#  name: list
+#  args:
+#     list_attr: [ list, of, three ]
+# -
+#  class: Stem::Test::ConfTypes
+#  name: hash_to_list
+#  args:
+#     list_attr: 
+#      hash: of
+#      key: value
+# -
+#  class: Stem::Test::ConfTypes
+#  name: hash
+#  args:
+#     hash_attr:
+#       foo: bar
+# -
+#  class: Stem::Test::ConfTypes
+#  name: list_to_hash
+#  args:
+#     hash_attr:
+#       - foo
+#       - bar
+# -
+#  class: Stem::Test::ConfTypes
+#  name: lol_to_lol
+#  args:
+#     lol_attr:
+#       - [ 1, 2 ]
+#       - [ 3, 4 ]
+# -
+#  class: Stem::Test::ConfTypes
+#  name: loh_to_lol
+#  args:
+#     lol_attr:
+#       - 
+#         foo: 1
+#       -
+#      bar: 2
+# -
+#  class: Stem::Test::ConfTypes
+#  name: hol_to_lol
+#  args:
+#     lol_attr:
+#       foo: [ 1, 2 ]
+#       bar: 3
+-
+ class: Stem::Test::ConfTypes
+ name: hoh_to_lol
+ args:
+    lol_attr:
+      foo: [ 1, 2 ]
+      bar: 3
+-
+ class: Stem::Test::ConfTypes
+ name: hoh_to_hol
+ args:
+    hol_attr:
+      foo: [ 1, 2 ]
+      bar: 3
+-
+ class: Stem::Test::ConfTypes
+ name: lol_to_hol
+ args:
+    hol_attr:
+      - foo
+      - [ 1, 2 ]
+      - bar
+      - 3
+-
+ class: Stem::Test::ConfTypes
+ name: lolh_to_loh
+ args:
+    loh_attr:
+      - 
+       foo: faaa
+      - [ 1, 2 ]
+      -
+       bar: jejej
+      - [ 3, 5, 4, 0 ]
+-
+ class: Stem::Test::ConfTypes
+ name: lolh_to_hol
+ args:
+    hol_attr:
+      - 
+       foo: faaa
+      - [ 1, 2 ]
+      -
+       bar: jejej
+      - [ 3, 5, 4, 0 ]
diff --git a/extras/sirc-2.211.tar.gz b/extras/sirc-2.211.tar.gz
new file mode 100644 (file)
index 0000000..0c65d6a
Binary files /dev/null and b/extras/sirc-2.211.tar.gz differ
diff --git a/lib/Stem.pm b/lib/Stem.pm
new file mode 100644 (file)
index 0000000..a37d9d6
--- /dev/null
@@ -0,0 +1,75 @@
+#  File: Stem.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem ;
+
+=head1 Stem.pm - A Network Application Toolkit and Framework
+
+This module load these core Stem modules:
+
+       Stem::Util ;            # support utilities
+       Stem::Class ;           # object constructor
+       Stem::Event ;           # event loop
+       Stem::Msg ;             # message creation and delivery
+       Stem::Route ;           # message routing
+       Stem::Vars ;            # stem environment
+       Stem::Conf ;            # configuration file handling
+       Stem::Log ;             # logging services
+
+You can create a script and just use Stem.pm (see the tests in t/ for
+some examples) but most Stem applications will use the startup script
+<B>run_stem</B> which loads this module and your configuration files.
+
+=cut
+
+
+
+use strict ;
+use vars qw( $VERSION ) ;
+
+$VERSION = 0.12 ;
+
+use Stem::Util ;
+use Stem::Class ;
+use Stem::Event ;
+use Stem::Msg ;
+use Stem::Route qw( :cell ) ;
+use Stem::Vars ;
+use Stem::Conf ;
+use Stem::Log ;
+#use Stem::Hub ;
+
+register_class( __PACKAGE__, 'stem' ) ;
+
+
+sub version_cmd {
+
+       return "Stem Version: $VERSION" ;
+}
+
+1 ;
diff --git a/lib/Stem/AsyncIO.pm b/lib/Stem/AsyncIO.pm
new file mode 100644 (file)
index 0000000..5b011e4
--- /dev/null
@@ -0,0 +1,527 @@
+#  File: Stem/AsyncIO.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::AsyncIO ;
+
+use strict ;
+use Data::Dumper ;
+
+use Stem::Vars ;
+
+
+my $attr_spec = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'read_method',
+               'default'       => 'async_read_data',
+               'help'          => <<HELP,
+Method called with the data read from the read handle. It is only called if the        
+data_addr attribute is not set.
+HELP
+       },
+
+       {
+               'name'          => 'stderr_method',
+               'default'       => 'async_stderr_data',
+               'help'          => <<HELP,
+Method called with the data read from the stderr handle. It is only
+called if the stderr_addr attribute is not set.
+HELP
+       },
+
+       {
+               'name'          => 'closed_method',
+               'default'       => 'async_closed',
+               'help'          => <<HELP,
+Method used when this object is closed.
+HELP
+       },
+       {
+               'name'          => 'fh',
+               'help'          => <<HELP,
+File handle used for reading and writing.
+HELP
+       },
+       {
+               'name'          => 'read_fh',
+               'help'          => <<HELP,
+File Handle used for reading.
+HELP
+       },
+       {
+               'name'          => 'write_fh',
+               'help'          => <<HELP,
+File handle used for standard output.
+HELP
+       },
+       {
+               'name'          => 'stderr_fh',
+               'help'          => <<HELP,
+File handle used for Standard Error.
+HELP
+       },
+       {
+               'name'          => 'data_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+The address of the Cell where the data is sent.
+HELP
+       },
+       {
+               'name'          => 'stderr_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+The address of the Cell where the stderr is sent.
+HELP
+       },
+       {
+               'name'          => 'data_msg_type',
+               'default'       => 'data',
+               'help'          => <<HELP,
+This sets the type of the data message.
+HELP
+       },
+       {
+               'name'          => 'codec',
+               'help'          => <<HELP,
+Use this codec to encode/decode the I/O data. Each write is encoded to
+one packet out. Each packet read in will be decoded and either send a
+data message or generate a callback.
+HELP
+       },
+       {
+               'name'          => 'stderr_msg_type',
+               'default'       => 'stderr_data',
+               'help'          => <<HELP,
+This sets the type of the stderr data message.
+HELP
+       },
+       {
+               'name'          => 'from_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+The address used in the 'from' field of data and stderr messages.
+HELP
+       },
+       {
+               'name'          => 'send_data_on_close',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Buffer all read data and send it when the read handle is closed.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+
+################
+## add support to log all AIO
+################
+
+       {
+               'name'          => 'log_label',
+               'default'       => 'AIO',
+               'help'          => <<HELP,
+HELP
+       },
+       {
+               'name'          => 'log_level',
+               'default'       => 5,
+               'help'          => <<HELP,
+HELP
+       },
+       {
+               'name'          => 'read_log',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'stderr_log',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'write_log',
+               'help'          => <<HELP,
+HELP
+       },
+
+
+] ;
+
+use Carp 'cluck' ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+#cluck "NEW $self" ;
+
+       if ( $self->{'data_addr'} && ! $self->{'from_addr'} ) {
+
+               return "Using 'data_addr in AsyncIO requires a 'from_addr'" ;
+       }
+
+       if ( my $codec = $self->{'codec'} ) {
+
+               require Stem::Packet ;
+               my $packet = Stem::Packet->new( 'codec' => $codec ) ;
+               return $packet unless ref $packet ;
+
+               $self->{'packet'} = $packet ;
+       }
+
+       $self->{'stderr_addr'} ||= $self->{'data_addr'} ;
+
+       $self->{'buffer'} = '' if $self->{'send_data_on_close'} ;
+
+       $self->{ 'read_fh' } ||= $self->{ 'fh' } ;
+       $self->{ 'write_fh' } ||= $self->{ 'fh' } ;
+
+       if ( my $read_fh = $self->{'read_fh'} ) {
+
+               my $read_event = Stem::Event::Read->new(
+                                       'object'        => $self,
+                                       'fh'            => $read_fh,
+               ) ;
+
+               return $read_event unless ref $read_event ;
+
+               $self->{'read_event'} = $read_event ;
+       }
+
+       if ( my $stderr_fh = $self->{'stderr_fh'} ) {
+
+               my $stderr_event = Stem::Event::Read->new(
+                                       'object'        => $self,
+                                       'fh'            => $stderr_fh,
+                                       'method'        => 'stderr_readable',
+               ) ;
+
+               return $stderr_event unless ref $stderr_event ;
+
+               $self->{'stderr_event'} = $stderr_event ;
+       }
+
+       if ( my $write_fh = $self->{'write_fh'} ) {
+
+               my $write_event = Stem::Event::Write->new(
+                                       'object'        => $self,
+                                       'fh'            => $write_fh,
+               ) ;
+
+               return $write_event unless ref $write_event ;
+
+               $self->{'write_event'} = $write_event ;
+
+               $self->{'write_buf'} = '' ;
+       }
+
+       return $self ;
+}
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+#cluck "SHUT $self\n" ;
+
+
+       if ( $self->{'shut_down'} ) {
+
+               return ;
+       }
+
+       $self->{'shutting_down'} = 1 ;
+
+       $self->read_shut_down() ;
+
+       $self->write_shut_down() ;
+
+       if ( my $event = delete $self->{'stderr_event'} ) {
+
+               $event->cancel() ;
+               close( $self->{'stderr_fh'} ) ;
+       }
+
+       $self->{'shut_down'} = 1 ;
+
+#print "DELETE OBJ", caller(), "\n" ;
+
+       delete $self->{'object'} ;
+}
+
+sub read_shut_down {
+
+       my( $self ) = @_ ;
+
+       if ( my $event = delete $self->{'read_event'} ) {
+
+               $event->cancel() ;
+       }
+
+       shutdown( $self->{'read_fh'}, 0 ) ;
+}
+
+sub write_shut_down {
+
+       my( $self ) = @_ ;
+
+       if ( exists( $self->{'write_buf'} ) && 
+            length( $self->{'write_buf'} ) ) {
+
+#print "write handle shut when empty\n" ;
+               $self->{'shut_down_when_empty'} = 1 ;
+
+               return ;
+       }
+
+       if ( my $event = delete $self->{'write_event'} ) {
+
+               shutdown( $self->{'write_fh'}, 1 ) ;
+               $event->cancel() ;
+       }
+}
+
+sub readable {
+
+       my( $self ) = @_ ;
+
+       my( $read_buf ) ;
+
+       return if $self->{'shut_down'} ;
+
+       my $bytes_read = sysread( $self->{'read_fh'}, $read_buf, 8192 ) ;
+
+#print "READ: $bytes_read [$read_buf]\n" ;
+
+       unless( defined( $bytes_read ) && $bytes_read > 0 ) {
+
+               $self->read_shut_down() ;
+
+               if ( $self->{'send_data_on_close'} &&
+                    length( $self->{'buffer'} ) ) {
+
+                       $self->send_data() ;
+
+# since we sent the total read buffer, we don't do a closed callback.
+
+                       return ;
+               }
+
+               $self->_callback( 'closed_method' ) ;
+
+               return ;
+       }
+
+# decode the packet if needed
+
+       if ( my $packet = $self->{packet} ) {
+
+               my $buf_ref = \$read_buf ;
+
+               while( my $data_ref = $packet->to_data( $buf_ref ) ) {
+
+                       $self->send_data( $data_ref ) ;
+                       $buf_ref = undef ;
+               }
+
+               return ;
+       }
+
+       if ( $self->{'send_data_on_close'} ) {
+
+               $self->{'buffer'} .= $read_buf ;
+               return ;
+       }
+
+       $self->send_data( \$read_buf ) ;
+}
+
+sub send_data {
+
+       my( $self, $buffer ) = @_ ;
+
+       my $buf_ref = $buffer || \$self->{'buffer'} ;
+
+       $self->_send_data_msg( 'data_addr', 'data_msg_type', $buf_ref ) ;
+       $self->_callback( 'read_method', $buf_ref ) ;
+
+       return ;
+}
+
+sub stderr_readable {
+
+       my( $self ) = @_ ;
+
+       my( $read_buf ) ;
+
+       my $bytes_read = sysread( $self->{'stderr_fh'}, $read_buf, 8192 ) ;
+
+# no callback on stderr close. let the read handle close deal with the
+# shutdown
+
+       return if $bytes_read == 0 ;
+
+#print "STDERR READ [$read_buf]\n" ;
+
+       $self->_send_data_msg( 'stderr_addr', 'stderr_msg_type', \$read_buf ) ;
+       $self->_callback( 'stderr_method', \$read_buf ) ;
+}
+
+sub _send_data_msg {
+
+       my( $self, $addr_attr, $type_attr, $data_ref ) = @_ ;
+
+       my $to_addr = $self->{$addr_attr} or return ;
+
+       my $msg = Stem::Msg->new(
+                       'to'            => $to_addr,
+                       'from'          => $self->{'from_addr'},
+                       'type'          => $self->{$type_attr},
+                       'data'          => $data_ref,
+       ) ;
+
+#print $msg->dump( 'SEND DATA' ) ;
+       $msg->dispatch() ;
+}
+
+sub _callback {
+
+       my ( $self, $method_attr, @data ) = @_ ;
+
+       my $obj = $self->{'object'} or return ;
+
+       my $method = $self->{$method_attr} ;
+
+       my $code = $obj->can( $method ) or return ;
+
+       return $obj->$code( @data, $self->{'id'} ) ;
+}
+
+sub write {
+
+       my( $self ) = shift ;
+
+       return unless @_ ;
+
+       return unless exists( $self->{'write_buf'} ) ;
+
+       my $buffer = shift ;
+
+       return if $self->{'shut_down'} ;
+
+# encode the data in a packet if needed
+
+       if ( my $packet = $self->{packet} ) {
+
+               my $buf_ref = $packet->to_packet( $buffer ) ;
+
+               $self->{'write_buf'} .= ${$buf_ref} ;
+       }
+       else {
+
+               $self->{'write_buf'} .= ref $buffer eq 'SCALAR' ?
+                       ${$buffer} : $buffer ;
+       }
+
+       $self->{'write_event'}->start() ;
+}
+
+sub final_write {
+
+       my( $self ) = @_ ;
+
+       $self->write( $_[1] ) ;
+
+       $self->write_shut_down() ;
+}
+
+
+sub writeable {
+
+       my( $self ) = @_ ;
+
+       return if $self->{'shut_down'} ;
+
+       my $buf_ref = \$self->{'write_buf'} ;
+       my $buf_len = length $$buf_ref ;
+
+#print "BUFLEN [$buf_len]\n" ;
+
+       unless ( $buf_len ) {
+
+#print "AIO W STOPPING\n" ;
+               $self->{'write_event'}->stop() ;
+               return ;
+       }
+
+       my $bytes_written = syswrite( $self->{'write_fh'}, $$buf_ref ) ;
+
+       unless( defined( $bytes_written ) ) {
+
+# do a SHUTDOWN
+               return ;
+       }
+
+# remove the part of the buffer that was written 
+
+       substr( $$buf_ref, 0, $bytes_written, '' ) ;
+
+       return if length( $$buf_ref ) ;
+
+       $self->write_shut_down() if $self->{'shut_down_when_empty'} ;
+}
+
+
+# DESTROY {
+
+#      my( $self ) = @_  ;
+
+# print "DESTROY $self\n" ;
+
+# }
+
+1 ;
diff --git a/lib/Stem/Boot.pm b/lib/Stem/Boot.pm
new file mode 100644 (file)
index 0000000..3b34d5b
--- /dev/null
@@ -0,0 +1,291 @@
+#  File: Stem/Boot.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Boot ;
+
+use strict ;
+use Carp ;
+use Symbol ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+This is the name under which this Cell was registered.
+HELP
+       },
+       {
+               'name'          => 'boot_file',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the file that describes the processes to bootstrap
+HELP
+       },
+       {
+               'name'          => 'name',
+               'help'          => <<HELP,
+Name of this boot entry
+HELP
+       },
+       {
+               'name'          => 'cmd',
+               'help'          => <<HELP,
+Path to command that will be booted
+HELP
+       },
+       {
+               'name'          => 'log',
+               'help'          => <<HELP,
+Default Name of logical log to send all status and process output
+HELP
+       },
+       {
+               'name'          => 'delay',
+               'help'          => <<HELP,
+Default delay (in seconds) between spawning processes
+HELP
+       },
+       {
+               'name'          => 'user',
+               'help'          => <<HELP,
+Default user id to run the processes
+HELP
+       },
+       {
+               'name'          => 'wrap',
+               'default'       => '/bin/sh -c',
+               'help'          => <<HELP,
+Default command wrapper for each process
+HELP
+       },
+       {
+               'name'          => 'chdir',
+               'help'          => <<HELP,
+Default dir to chdir to before running each process
+HELP
+       },
+       {
+               'name'          => 'boot_now',
+               'type'          => 'boolean',
+               'default'       => 1,
+               'help'          => <<HELP,
+Boot this program when this object is created
+HELP
+       },
+       {
+               'name'          => 'restart',
+               'help'          => <<HELP,
+Restart this program when it exits
+HELP
+       },
+] ;
+
+my %name2boot ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $boot_info = Stem::Util::load_file( $self->{'boot_file'} ) ;
+       return $boot_info unless ref $boot_info ;
+
+       foreach my $boot ( @{$boot_info} ) {
+
+               die "boot entry is not a hash\n" unless ref $boot eq 'HASH' ;
+
+               if ( my $skip = $boot->{'skip'} ) {
+
+                       next if lc $skip eq 'yes' ;
+               }
+
+               my $boot_obj = Stem::Class::parse_args( $attr_spec,
+                                       %{$self},
+                                       %{$boot}
+               ) ;
+
+               die "boot entry error: $boot_obj\n" unless ref $boot_obj ;
+
+               my $cmd = $boot_obj->{'cmd'} ;
+               die "boot entry is missing 'cmd'\n" unless $cmd ;
+
+               my $name = $boot_obj->{'name'} ;
+               die "boot entry is missing 'name'\n" unless $name ;
+
+               $name2boot{ $name } = $boot_obj ;
+
+               if ( $boot_obj->{'boot_now'} ) {
+
+                       $boot_obj->run_cmd() ;
+               }
+       }
+
+       return ;
+}
+
+
+sub run_cmd {
+
+       my( $self ) = @_ ;
+
+#print Store $self ;
+
+       my $cmd ;
+
+       if ( my $user = $self->{'user'} ) {
+
+               if ( getpwuid($<) ne $user ) {
+
+                       $cmd .= "su - $user ; " ;
+               }
+       }
+
+       if ( my $wrap = $self->{'wrap'} ) {
+
+               $cmd .= qq{$wrap "} ;
+               $self->{'wrap_end'} ||= '"' ;
+       }
+
+       if ( my $chdir = $self->{'chdir'} ) {
+
+               $cmd .= "cd $chdir ; " ;
+       }
+
+       if ( my $stem_env = $self->{'stem_env'} ) {
+
+               my $cmd_env = join ' ', map(
+                               "$_='$stem_env->{$_}'", keys %{$stem_env} ) ;
+
+               $cmd =~ s/run_stem/run_stem $cmd_env/ ;
+       }
+
+       $cmd .= $self->{'cmd'} ;
+
+       $cmd .= $self->{'wrap_end'} if $self->{'wrap_end'} ;
+
+       my $handle = gensym ;
+
+#print "$cmd\n" ;
+
+       if ( my $pid = open( $handle, '-|' ) ) {
+
+#print "pid $pid\n" ;
+               $self->{'pid'} = $pid ;
+               $self->{'handle'} = $handle ;
+       }
+       elsif ( defined( $pid ) ) {
+
+               local( %ENV ) = ( %ENV, %{ $self->{'env'} || {} } ) ;
+
+               open( STDERR, '>&STDOUT' ) ;
+
+               exec $cmd ;
+               die "Couldn't exec [$cmd]\n" ;
+       }
+       else {
+
+               die "couldn't fork\n" ;
+       }
+
+       my $aio = Stem::AsyncIO->new(
+
+                       'object'        => $self,
+                       'read_fh'       => $handle,
+                       'read_method'   => 'boot_read',
+                       'closed_method' => 'boot_closed',
+       ) ;
+
+       $self->{'aio'} = $aio ;
+
+       if ( my $log = $self->{'log'} ) {
+
+               Stem::Log::Entry->new(
+                       'logs'  => $log,
+                       'label' => 'boot',
+                       'text'  =>
+                       "Booting $self->{'name'} PID = $self->{'pid'}: $cmd\n",
+               ) ;
+       }
+
+       return ;
+}
+
+sub boot_read {
+
+       my( $self, $data ) = @_ ;
+
+#print "BOOT READ [$$data]\n" ;
+
+       if ( my $log = $self->{'log'} ) {
+
+               Stem::Log::Entry->new(
+                       'logs'  => $log,
+                       'label' => 'boot',
+                       'text'  => "Output for $self->{'name'}\n[${$data}]\n",
+               ) ;
+       }
+
+       return ;
+}
+
+sub boot_closed {
+
+       my( $self ) = @_ ;
+
+#print "BOOT closed\n" ;
+
+       $self->{'aio'}->shut_down() ;
+       delete $self->{'aio'} ;
+
+       my $boot_pid = $self->{'pid'} ;
+       my $pid = waitpid( $boot_pid, 0 ) ;
+
+#print "WAIT [$pid]\n" ;
+
+       if ( my $log = $self->{'log'} ) {
+
+               Stem::Log::Entry->new(
+                       'logs'  => $log,
+                       'label' => 'boot',
+                       'text'  => "Boot $self->{'name'} exited PID = $pid",
+               ) ;
+       }
+
+# do restart if needed
+
+
+
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Cell.pm b/lib/Stem/Cell.pm
new file mode 100644 (file)
index 0000000..07e6f29
--- /dev/null
@@ -0,0 +1,712 @@
+#  File: Stem/Cell.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+use Data::Dumper ;
+use Carp qw( cluck ) ;
+
+use Stem::Route qw( :cell ) ;
+use Stem::AsyncIO ;
+use Stem::Id ;
+use Stem::Gather ;
+use Stem::Cell::Clone ;
+use Stem::Cell::Pipe ;
+use Stem::Cell::Flow ;
+use Stem::Cell::Work ;
+
+use Stem::Trace 'log' => 'stem_status' , 'sub' => 'TraceStatus' ;
+
+my %class_to_attr_name ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+The registered address of the owner Cell
+HELP
+       },
+       {
+               'name'          => 'cloneable',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+The parent Cell will be cloned upon triggering
+HELP
+       },
+       {
+               'name'          => 'data_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+Cell address to send any data read in. If not set here it must come
+from a trigger message.
+HELP
+       },
+       {
+               'name'          => 'status_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+Cell address to send Cell status to
+HELP
+       },
+       {
+               'name'          => 'send_data_on_close',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Buffer all read data and only send it when the I/O is closed
+HELP
+       },
+       {
+               'name'          => 'no_io',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Don't do any I/O for the Cell. Either there is none or the owner Cell must
+do its own I/O
+HELP
+       },
+       {
+               'name'          => 'pipe_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+Cell address to open a pipe to
+HELP
+       },
+       {
+               'name'          => 'pipe_args',
+               'help'          => <<HELP,
+This is list of arguments or a single argument which is passed to the
+cell at the remote end of the pipe.
+HELP
+       },
+       {
+               'name'          => 'aio_args',
+               'type'          => 'hash',
+               'help'          => <<HELP,
+This is a list of arguments passed to the Stem::AsyncIO module constructor
+HELP
+       },
+       {
+               'name'          => 'errors_to_output',
+               'env'           => 'errors_to_output',
+               'help'          => <<HELP,
+Any received error messages will be sent to the output.
+HELP
+       },
+
+############
+# change this to max_clones
+############
+       {
+               'name'          => 'id_size',
+               'default'       => 3,
+               'help'          => <<HELP,
+Size of unique ID space for clones. Range is 26**N
+HELP
+       },
+       {
+               'name'          => 'trigger_method',
+               'default'       => 'triggered_cell',
+               'help'          => <<HELP,
+Method to callback in owner object when cell is triggered
+HELP
+       },
+
+# the below attributes are not permanent yet
+# unused so far.
+       {
+               'name'          => 'shut_down_method',
+               'default'       => 'shut_down_cell',
+               'help'          => <<HELP,
+Method to callback in owner object when cell is shutdown
+HELP
+       },
+       {
+               'name'          => 'activated_method',
+               'default'       => 'activate_cell',
+               'help'          => <<HELP,
+Method to call in owner Cell when the cell is activated. UNSUPPORTED
+HELP
+       },
+       {
+               'name'          => 'sequence_done_method',
+               'help'          => <<HELP,
+Method to call in owner Cell when the executing sequence completes.
+HELP
+       },
+       {
+               'name'          => 'codec',
+               'help'          => <<HELP,
+This sets the codec that converts data packets to/from a byte stream.
+HELP
+       },
+       {
+               'name'          => 'work_ready_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+This is the address of the Cell that this Cell sends a message to
+when work can be done (i.e. a work message can now be sent here).
+HELP
+       },
+       {
+               'name'          => 'stderr_log',
+               'help'          => <<HELP,
+This sets the log that will get the stderr output of the process
+HELP
+       },
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+#print $self->_dump( 'NEW' ) ;
+
+       return( $self ) ;
+}
+
+# this is only called in Stem::Conf for this class.
+# it initializes the cell info object inside its owner object.
+
+sub cell_init {
+
+       my( $self, $owner_obj, $cell_name, $cell_info_attr ) = @_ ;
+
+# the $owner_obj is the cell that owns this Stem::Cell object
+
+       $self->{'owner_obj'} = $owner_obj ;
+       $self->{'cell_name'} = $cell_name ;
+#      $self->{'from_addr'} = $cell_name ;
+                                       
+       $self->{'from_addr'} = Stem::Msg::make_address_string( 
+                                       $Stem::Vars::Hub_name,
+                                       $cell_name
+       ) ;
+
+       $self->{'cell_info_attr'} = $cell_info_attr ;
+
+# save the attribute name that the owner class uses for the cell info.
+# this is how a cell info object can be found given an owner cell object.
+# also keep this name in the info itself
+
+#print "OWNER [$owner_obj]\n" ;
+       $class_to_attr_name{ ref $owner_obj } ||= $cell_info_attr ;
+
+       if ( $self->{'cloneable'} ) {
+
+               $self->{'id_obj'} = Stem::Id->new(
+                                       'size'  => $self->{'id_size'} ) ;
+               $self->{'is_parent'} = 1 ;
+               $self->{'target'} = '' ;
+       }
+}
+
+# get the cell info whether we were called from the owner object or
+# the cell info itself ;
+
+sub _get_cell_info {
+
+       my ( $self ) = @_ ;
+
+       my $class = ref $self ;
+
+       return "can't get cell info from '$self'\n" unless $class ;
+
+       return $self if $class eq __PACKAGE__ ;
+
+#print "CLASS [$class][$class_to_attr_name{ $class }]\n" ;
+
+       return $self->{ $class_to_attr_name{ $class } } ;
+}
+
+sub cell_trigger {
+
+       my ( $self, @args ) = @_ ;
+
+       my $self_info = $self->_get_cell_info() ;
+
+       return $self_info unless ref $self_info ;
+
+       return if $self_info->{'triggered'} ;
+
+# clone this cell and its info if needed
+# $cell will either be $self or a clone of $self
+
+       my $cell = $self_info->_clone() ;
+
+       my $cell_info = $cell->_get_cell_info() ;
+
+       $cell_info->{'triggered'} = 1 ;
+
+#print $cell_info->_dump( 'TRIGGER' ) ;
+
+# set any args (e.g. from trigger message) into this cell 
+
+       $cell_info->cell_set_args( @args ) ;
+
+       $cell_info->_cell_pipe() ;
+
+       if ( my $err = $cell_info->_gather_io_args() ) {
+               $cell_info->cell_shut_down( $err ) ;
+               return $err ;
+       }
+
+# do the callback into the (possibly cloned) cell
+
+       if ( my $err = $cell_info->_callback( 'trigger_method' ) ) {
+
+#print "CALLBACK $err\n" ;
+
+               $cell_info->cell_shut_down( $err ) ;
+               return $err ;
+       }
+
+#      return $cell_info ;
+       return ;
+}
+
+sub cell_trigger_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my @args ;
+
+       if ( my $data = $msg->data() ) {
+
+               $data = ${$data} if ref $data eq 'SCALAR' ;
+
+               my $ref = ref $data ;
+
+               if ( ! $ref && defined $data ) {
+
+                       unless ( @args = $data =~ /(\S+)=(\S+)/g ) {
+
+                               @args = ( 'args' => $data ) ;
+                       }
+               }
+               elsif ( $ref eq 'HASH' ) {
+
+                       @args = %{$data} ;
+               }
+               elsif ( $ref eq 'ARRAY' ) {
+
+                       @args = @{$data} ;
+               }
+       }
+
+       push( @args, triggering_msg => $msg ) ;
+
+       my $err = $self->cell_trigger( @args ) ;
+
+print "TRIG ERR [$err]\n" if $err ;
+
+       return $err if ref $err ;
+       return ;
+}
+
+
+sub cell_shut_down {
+
+       my( $self, $error ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#cluck "CELL SHUT\n" ;
+
+#print $cell_info->_dump( 'SHUT' ) ;
+
+
+       return unless $error || $cell_info->{'active'} ;
+
+       $cell_info->{'error'} = $error ;
+
+#print $cell_info->_dump( "SHUT $error" ) ;
+
+       if ( my $aio = delete $cell_info->{'aio'} ) {
+
+               $aio->shut_down() ;
+       }
+
+       if ( my $gather = delete $cell_info->{'gather'} ) {
+
+               $gather->shut_down() ;
+       }
+
+       $cell_info->_close_pipe() ;
+
+       $cell_info->_clone_delete() ;
+
+       delete $cell_info->{'args'} ;
+#      delete $cell_info->{'data_addr'} ;
+
+       $cell_info->{'active'} = 0 ;
+       $cell_info->{'triggered'} = 0 ;
+
+       TraceStatus "cell shut down done" ;
+
+       return ;
+}
+
+
+sub cell_set_args {
+
+       my( $self, %args ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       @{$cell_info->{'args'}}{ keys %args } = values %args ;
+
+       if ( my $gather = $cell_info->{'gather'} ) {
+
+               my $err = $gather->gathered( keys %args ) ;
+               return $err if $err ;
+       }
+
+       return ;
+}
+
+sub cell_get_args {
+
+       my( $self, @arg_keys ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       return( @{$cell_info->{'args'}}{@arg_keys } ) ;
+}
+
+sub cell_info {
+
+       my( $self ) = shift ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'info'} = shift if @_ ;
+
+       return $cell_info->{'info'} ;
+}
+
+sub _gather_io_args {
+
+       my( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       return if $cell_info->{'no_io'} ;
+
+       my @gather_keys = 'aio_args' ;
+
+       push( @gather_keys, 'data_addr' ) if
+                       $cell_info->{'piped'} &&
+                       ! $cell_info->{'data_addr'} ;
+
+       my $gather = Stem::Gather->new(
+                       'object'        => $cell_info,
+                       'keys'          => \@gather_keys,
+                       'gathered_method' => '_cell_activate_io',
+       ) ;
+
+       return $gather unless ref $gather ;
+
+       $cell_info->{'gather'} = $gather ;
+
+       my $err = $gather->gathered( keys %{$cell_info->{'args'}} ) ;
+
+       return $err if $err ;
+}
+
+sub _cell_activate_io {
+
+       my ( $self ) = @_ ;
+
+       TraceStatus "cell activated" ;
+
+       $self->{'active'} = 1 ;
+
+#print $self->_dump( "BEFORE AIO" ) ;
+
+       my @aio_args ;
+
+# get any config args
+
+       if ( my $aio_args = $self->{'aio_args'} ) {
+
+               push( @aio_args, %{$aio_args} ) ;
+       }
+
+# args from a trigger message override any config args
+
+       if ( my $msg_aio_args = $self->{'args'}{'aio_args'} ) {
+
+               ref $msg_aio_args eq 'ARRAY' or return <<ERR ;
+aio_args is not an ARRAY ref
+ERR
+               push( @aio_args, @{$msg_aio_args} ) ;
+       }
+
+       my $data_addr = $self->{'args'}{'data_addr'} || $self->{'data_addr'} ;
+
+       my $aio = Stem::AsyncIO->new(
+
+               'object'                => $self->{'owner_obj'},
+               'data_addr'             => $data_addr,
+               'from_addr'             => $self->{'from_addr'},
+               'send_data_on_close'    => $self->{'send_data_on_close'},
+               'codec'                 => $self->{'codec'},
+               @aio_args,
+       ) ;
+
+print "AIO ERR [$aio]\n" unless ref $aio ;
+       return $aio unless ref $aio ;
+
+       $self->{'aio'} = $aio ;
+
+#print $self->_dump( "AFTER AIO" ) ;
+
+       return ;
+}
+
+sub cell_activate {
+
+       my( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'active'} = 1 ;
+}
+
+*cell_status_cmd = \&status_cmd ;
+
+sub status_cmd {
+
+       my( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       my $info = $cell_info->{'info'} || $cell_info->{'args'}{'info'} || '' ;
+
+       $info =~ s/^/\t\t/mg ;
+       my $class = ref $cell_info->{'owner_obj'} ;
+
+#      my $data_addr = Stem::Msg::address_string( 
+       my $data_addr = $cell_info->{'data_addr'} ||
+                       $cell_info->{'args'}{'data_addr'} ||
+                       '[NONE]' ;
+
+       my $active = ( $cell_info->{'active'} ) ? 'Active' : 'Inactive' ;
+
+       my $codec = $cell_info->{codec} || 'NONE' ;
+
+print "CELL STATUS\n" ;
+
+#my $dump = $cell_info->_dump( 'STATUS' ) ;
+my $dump = '' ;
+
+       return <<STATUS ;
+Cell Status for:
+Class:         $class
+Addr:          $cell_info->{'from_addr'}
+Status:                $active
+Data Addr:     $data_addr
+Codec:         $codec
+Info:$info
+
+SELF: $self
+CELL: $cell_info
+AIO: $cell_info->{aio}
+FH: $cell_info->{fh}
+
+$dump
+
+STATUS
+
+}
+
+sub data_in {
+
+       my( $self, $msg ) = @_ ;
+
+#print "DATA SELF $self\n" ;
+
+#print $msg->dump( 'CELL IN' ) ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       if ( $cell_info->{'is_parent'} ) {
+
+#print "PARENT\n" ;
+               TraceStatus "parent cell $cell_info->{'from_addr'} ignoring msg" ;
+
+               return ;
+       }
+
+       unless( $cell_info->{'active'} ) {
+#print "INACTIVE\n" ;
+
+               TraceStatus "cell not active. msg ignored FOO" ;
+
+               return ;
+       }
+
+#print $cell_info->_dump( "DATA IN" ) ;
+
+       $cell_info->{data_in_msg} = $msg ;
+       $cell_info->cell_write( $msg->data() ) ;
+}
+
+sub cell_write {
+
+       my( $self, $data ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'aio'}->write( $data ) ;
+}
+
+sub _cell_write_sync {
+
+       my( $self, $data ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#print "SYNC $$data\n" ;
+
+#print $cell_info->_dump( 'SYNC' ) ;
+
+       if ( my $aio_args = $cell_info->{'args'}{'aio_args'} ) {
+
+               my %aio_args = @{$aio_args} ;
+
+               if ( my $fh = $aio_args{'fh'} ) {
+
+#                      $fh->blocking( 1 ) ;
+
+                       $fh->syswrite( (ref $data) ? $$data : $data ) ;
+               }
+       }
+}
+
+# handle stderr data as plain data
+
+*stderr_data_in = \&data_in ;
+
+
+# $cell_info is the Stem::Cell object of the parent cell. the name is
+# not self as it is differentiated from $clone_info.
+
+
+
+sub _callback {
+
+       my ( $self, $method_name, @data ) = @_ ;
+
+       my $method = $self->{$method_name} ;
+
+       my $owner_obj = $self->{'owner_obj'} ;
+
+       if ( $owner_obj->can( $method ) ) {
+
+               return $owner_obj->$method( @data ) ;
+       }
+
+       TraceStatus "can't call $method in $owner_obj" ;
+
+       return ;
+}
+
+sub cell_from_addr {
+
+       my ( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       return( $cell_info->{'from_addr'} ) ;
+}
+
+use Stem::Debug qw( dump_data ) ;
+
+sub _dump {
+
+       my ( $self, $text ) = @_ ;
+
+return $text . dump_data( $self ) ;
+
+       $text ||= 'CELL' ;
+
+       my $dump = "$text =\n" ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#      my $owner_obj = $cell_info->{owner_obj} ;
+#      my @names = lookup_cell_name( $owner_obj ) ;
+#      $dump .= "\nNames: @names\n" ;
+
+       foreach my $key ( sort keys %{$cell_info} ) {
+
+               my $val = $cell_info->{$key} ;
+               next unless defined $val ;
+
+               if ( $key eq 'args' ) {
+
+                       $dump .= "\targs = {\n" ;
+
+                       foreach my $arg ( sort keys %{$val} ) {
+
+                               my $arg_val = $val->{$arg} || '';
+
+                               $dump .= "\t\t$arg = '$arg_val'\n" ;
+                       }
+
+                       $dump .= "\t}\n" ;
+
+                       next ;
+               }
+
+               $dump .= "\t$key = '$val'\n" ;
+       }
+
+       $dump .= "\n\n" ;
+
+       return $dump ;
+}
+
+sub dump_cmd {
+
+       my ($self) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       return $cell_info->_dump() . Dumper $cell_info ;
+}
+
+1 ;
diff --git a/lib/Stem/Cell/Clone.pm b/lib/Stem/Cell/Clone.pm
new file mode 100644 (file)
index 0000000..000cc60
--- /dev/null
@@ -0,0 +1,161 @@
+#  File: Stem/Cell/Clone.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+sub cell_cloneable {
+
+       my( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       return $cell_info->{'cloneable'} ;
+}
+
+#####################
+#####################
+# add check of max clone count
+#####################
+#####################
+
+my @clone_fields = qw(
+
+       no_io
+       data_addr
+       errors_to_output
+       pipe_addr
+       pipe_args
+       codec
+       work_ready_addr
+       trigger_method
+       sequence_done_method
+       send_data_on_close
+       stderr_log
+) ;
+
+sub _clone {
+
+       my( $cell_info ) = @_ ;
+
+       my $owner_obj = $cell_info->{'owner_obj'} ;
+
+       return $owner_obj unless $cell_info->{'cloneable'} ;
+
+# copy the object
+
+       my $clone = bless { %{$owner_obj} }, ref $owner_obj ;
+
+# get a new target id and the cell name
+
+       my $target = $cell_info->{'id_obj'}->next() ;
+
+       my $cell_name = $cell_info->{'cell_name'} ;
+
+# keep track of the clone in the parent and register it
+
+       $cell_info->{'clones'}{$target} = $clone ;
+
+       my $err = register_cell( $clone, $cell_name, $target ) ;
+
+       die $err if $err ;
+
+# the parent loses its args to the clone. parent cells never do real work
+
+##################
+## add parent private INFO/ARGS for use by status command
+##################
+
+       my $args = delete $cell_info->{'args'} ;
+
+# create the clone info and save it in the cloned object
+
+       my $cell_info_attr = $cell_info->{'cell_info_attr'} ;
+
+       my $from_addr = Stem::Msg::make_address_string( 
+                       $Stem::Vars::Hub_name,
+                       $cell_name,
+                       $target
+       ) ;
+
+#print "FROM ADDR $cell_info->{'from_addr'}\n" ;
+       my $clone_info = bless {
+
+               'owner_obj'             => $clone,
+               'parent_obj'            => $owner_obj,
+               'cell_name'             => $cell_name,
+               'target'                => $target,
+               'from_addr'             => $from_addr,
+               'args'                  => $args,
+               'cell_info_attr'        => $cell_info_attr,
+               map { $_ => $cell_info->{$_} } @clone_fields,
+       } ;
+
+# save the new clone info into the clone itself ;
+
+       $clone->{$cell_info_attr} = $clone_info ;
+
+       return $clone ;
+}
+
+sub _clone_delete {
+
+       my ( $self ) = @_ ;
+
+       my $parent_obj          = $self->{'parent_obj'} ;
+
+       return unless $parent_obj ;
+
+       my $owner_obj           = $self->{'owner_obj'} ;
+
+       my $cell_info_attr      = $self->{'cell_info_attr'} ;
+
+#print $self->cell_status_cmd() ;
+
+# break all circular links
+# delete the refs to the parent and parent objects in the cell info
+# and the owner object ref to this cell info
+
+       delete @{$self}{ qw( owner_obj parent_obj ) } ;
+       delete $owner_obj->{$cell_info_attr} ;
+
+       delete $self->{'args'} ;
+
+# clean up the parent clones hash and the registry
+
+       my $parent_info         = $parent_obj->{$cell_info_attr} ;
+       my $target              = $self->{'target'} ;
+
+       delete $parent_info->{'clones'}{$target} ;
+       $parent_info->{'id_obj'}->delete( $target ) ;
+
+       my $err = unregister_cell( $owner_obj ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Cell/Flow.pm b/lib/Stem/Cell/Flow.pm
new file mode 100644 (file)
index 0000000..79e62fb
--- /dev/null
@@ -0,0 +1,442 @@
+#  File: Stem/Cell/Flow.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+my $grammar = <<'GRAMMAR' ;
+
+flow           :       list /\s*\Z/ { $item[1] }
+
+top_list       :       list  
+
+list           :       statement(s) 
+
+statement      :       ifelse  |
+                       if      |
+                       while   |
+                       delay   |
+                       next    |
+                       stop    |
+                       method  |
+                       <error>
+
+ifelse         :       /(?:if|unless)\b/i method_call block /else/i block {
+                               {
+                                       op      => 'IF',
+                                       not     => lc $item[1] eq 'unless' ?
+                                                       1 : 0,
+                                       cond    => $item[2],
+                                       then    => $item[3],
+                                       else    => $item[5]
+                               }
+                       }
+
+if             :       /(?:if|unless)\b/i method_call block {
+                               {
+                                       op      => 'IF',
+                                       not     => lc $item[1] eq 'unless' ?
+                                               1 : 0,
+                                       cond    => $item[2],
+                                       then    => $item[3],
+                               }
+                       }
+
+while          :       label(?) /(?:while|until)\b/i method_call block {
+                               {
+                                       op      => 'WHILE',
+                                       label   => $item[1][0] || '',
+                                       not     => lc $item[2] eq 'until' ?
+                                               1 : 0,
+                                       cond    => $item[3],
+                                       block   => $item[4]
+                               }
+                       }
+
+next           :       /(?:next|last)\b/i name(?) ';' {
+                               {
+                                       op      => 'NEXT',
+                                       last    => lc $item[1] eq 'last',
+                                       label   => $item[2][0] || ''
+                               }
+                       }
+
+delay          :       /delay\b/i ( delay_value | delay_method ) ';' {
+                               {
+                                       op      => 'DELAY',
+                                       @{$item[2]}
+                               }
+                        }
+
+
+stop           :       /stop/i ';' {
+                               {
+                                       op      => 'STOP',
+                               }
+                       }
+
+label          :       name ':' { $item[1] }
+
+delay_value    :       /\d+/ {
+                               [value  => $item[1]]
+                       }
+
+delay_method   :       method_call {
+                               [method => $item[1]]
+                       }
+
+method         :       method_call ';' { $item[1] }
+
+method_call    :       args_method | plain_method
+
+plain_method   :       name {
+                               {
+                                       op      => 'METHOD',
+                                       method  => $item[1],
+                               }
+                       }
+
+args_method    :       name '(' arg(s /,/) ')' {
+                               {
+                                       op      => 'METHOD',
+                                       method  => $item[1],
+                                       args    => $item[3],
+                               }
+                       }
+
+arg            :       /\w+/
+
+name           :       /[^\W\d]\w*/
+
+block          :       '{' list '}' { $item[2] }
+
+GRAMMAR
+
+my $flow_parser ;
+my %flows ;
+
+my %flow_ops = (
+
+       WHILE   => \&flow_while_op,
+       IF      => \&flow_if_op,
+       NEXT    => \&flow_next_op,      
+       METHOD  => \&flow_method_op,
+       DELAY   => \&flow_delay_op,
+       STOP    => \&flow_stop_op,
+) ;
+
+
+$::RD_HINT = 1 ;
+$::RD_ERRORS = 1 ;
+
+use Data::Dumper ;
+
+sub cell_flow_init {
+
+       my( $self, $name, $source ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       unless( $flow_parser ) {
+
+               require Parse::RecDescent ;
+
+               $flow_parser = Parse::RecDescent->new( $grammar ) or
+                               die 'bad flow grammar' ;
+       }
+
+       my $tree = $flows{$name}{'tree'} ;
+
+       unless( $tree ) {
+
+               $source =~ s/#.+$//mg ;
+
+               $tree = $flow_parser->flow( $source ) ;
+
+#print Dumper $tree ;
+
+               $flows{$name} = {
+
+                       'tree'  => $tree,
+                       'source' => $source,
+               } ;
+       }
+
+       $cell_info->{'flow'} = {
+
+                       'name'  => $name,
+                       'tree'  => $tree,
+                       'pc'    => [ $tree, 0 ],
+       } ;
+
+       return ;
+}
+
+sub cell_flow_go_in {
+
+       my( $self, $msg ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#print $msg->dump( 'GO') if $msg ;
+
+#print "GO\n" ;
+       my $flow = $cell_info->{'flow'} ;
+
+#print Dumper $flow ;
+
+       while( 1 ) {
+
+               my ( $pc_ref, $pc_index ) = @{$flow->{'pc'}} ;
+
+#print "IND $pc_index ", Dumper $pc_ref ;
+
+               if ( $pc_index >= @{$pc_ref} ) {
+
+#print "LIST END\n" ;
+
+                       my $old_pc = pop( @{$flow->{'stack'}} ) ;
+
+                       $old_pc or die "FELL off FLOW STACK" ;
+
+#print "POP\n" ;
+
+                       $flow->{'pc'} = $old_pc ;
+                       next ;
+               }
+
+               my $op = $pc_ref->[$pc_index] ;
+
+               my $op_name = $op->{'op'} ;
+
+#print "OP $op_name\n" ;
+
+               my $code = $flow_ops{$op_name} ;
+
+               $code or die "unknown flow op code [$code]" ;
+
+               my $meth_val = $code->( $flow, $op, $self, $msg ) ;
+
+               $msg = undef ;
+
+               next unless $meth_val ;
+
+               return if $meth_val && $meth_val eq 'FLOW_STOP' ;
+
+# check for a message
+
+               if ( ref $meth_val eq 'Stem::Msg' ) {
+
+                       $meth_val->reply_type( 'cell_flow_go' ) ;
+
+                       $meth_val->dispatch() ;
+
+                       return ;
+               }
+
+               return ;
+       }
+
+       return ;
+}
+
+sub flow_stop_op {
+
+       my( $flow ) = @_ ;
+
+       my $pc = $flow->{'pc'} ;
+
+# always go to the next op
+
+       $pc->[1]++ ;
+       return 'FLOW_STOP' ;
+}
+
+sub flow_method_op {
+
+       my( $flow, $op, $obj, $msg ) = @_ ;
+
+       my $pc = $flow->{'pc'} ;
+
+# always go to the next op
+
+       $pc->[1]++ ;
+
+#print Dumper $pc ;
+
+       return( flow_call_method( $op, $obj, $msg ) ) ;
+}
+
+sub flow_while_op {
+
+       my( $flow, $op, $obj ) = @_ ;
+
+       my $pc = $flow->{'pc'} ;
+
+       my $cond_val = flow_cond( $op, $obj ) ;
+
+       unless( $cond_val ) {
+
+#print "WHILE END\n" ;
+
+               $pc->[1]++ ;
+               return ;
+       }
+
+#print "WHILE LOOP\n" ;
+
+       push( @{$flow->{'stack'}}, $pc ) ;
+
+       $flow->{'pc'} = [ $op->{'block'}, 0 ] ;
+
+       return ;
+}
+
+sub flow_if_op {
+
+       my( $flow, $op, $obj ) = @_ ;
+
+       my $cond_val = flow_cond( $op, $obj ) ;
+
+       my $block = $cond_val ? $op->{'then'} : $op->{'else'} ;
+
+       my $pc = $flow->{'pc'} ;
+
+# always go to the next op
+
+       $pc->[1]++ ;
+
+       if ( $block ) {
+
+               push( @{$flow->{'stack'}}, $pc ) ;
+
+               $flow->{'pc'} = [ $block, 0 ] ;
+       }
+
+       return ;
+}
+
+sub flow_next_op {
+
+       my( $flow, $op, $obj ) = @_ ;
+
+       my $label = $op->{'label'} ;
+
+       while( 1 ) {
+
+               my $pc = pop( @{$flow->{'stack'}} ) ;
+
+               $pc or die "can't find label '$label' in flow stack" ;
+
+#print "PC: ", Dumper $pc ;
+
+               my $prev_op = $pc->[0][$pc->[1]] ;
+
+#print "PREV: ", Dumper $prev_op ;
+
+               next unless $prev_op && $prev_op->{'op'} eq 'WHILE' ;
+
+#print "FOUND WHILE\n" ;
+
+               next unless $prev_op->{'label'} eq $label ;
+
+               $pc->[1]++ if $op->{'last'} ;
+
+#print "LAST PC: ", Dumper $pc ;
+
+               $flow->{'pc'} = $pc ;
+
+               return ;
+       }
+}
+
+
+sub flow_delay_op {
+
+       my( $flow, $op, $obj ) = @_ ;
+
+#print Dumper $op ;
+
+       my $pc = $flow->{'pc'} ;
+       $pc->[1]++ ;
+
+       my $delay = $op->{'value'} ;
+
+       unless ( defined $delay ) {
+
+               $delay = flow_call_method( $op->{'method'}, $obj ) ;
+       }
+
+       $flow->{'timer'} = Stem::Event::Timer->new( 
+                               'object'        => $obj,
+                               'method'        => 'cell_flow_go_in',
+                               'delay'         => $delay, 
+                               'hard'          => 1,
+                               'single'        => 1,
+       ) ;
+
+#      print "D $delay EVT $flow->{'timer'}\n" ;
+
+       return 1 ;
+}
+
+sub flow_cond {
+
+       my( $op, $obj ) = @_ ;
+
+       my $cond = $op->{'cond'} ;
+
+       return 1 if $cond eq '1' ;
+
+       my $cond_val = flow_call_method( $cond, $obj ) ? 1 : 0 ;
+
+       return( $cond_val ^ $op->{'not'} ) ;
+}
+
+sub flow_call_method {
+
+       my( $call, $obj, $msg ) = @_ ;
+
+       my $method = $call->{'method'} ;
+
+       my @args = @{$call->{'args'} || []} ;
+
+       unshift( @args, $msg ) if $msg ;
+
+# flow methods are always called in scalar context
+
+#print "METHOD $method ( @args )\n" ;
+
+       my $val = $obj->$method( @args ) ;
+
+       return $val ;
+}
+
+
+1 ;
diff --git a/lib/Stem/Cell/Pipe.pm b/lib/Stem/Cell/Pipe.pm
new file mode 100644 (file)
index 0000000..d5eca52
--- /dev/null
@@ -0,0 +1,153 @@
+#  File: Stem/Cell/Pipe.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+sub _cell_pipe {
+
+       my( $self ) = @_ ;
+
+       if ( $self->{'args'}{'pipe_open'} ) {
+
+               $self->{'piped'} = 1 ;
+
+# return the connection handshake
+
+               my $addr_msg = Stem::Msg->new(
+                       'cmd'           => 'cell_pipe_addr',
+                       'to'            => $self->{'args'}{'data_addr'},
+                       'from'          => $self->{'from_addr'},
+               ) ;
+
+               $addr_msg->dispatch() ;
+
+               return ;
+       }
+
+       my $pipe_addr = $self->{'args'}{'pipe_addr'} || $self->{'pipe_addr'} ;
+
+       return unless $pipe_addr ;
+
+       $self->{'piped'} = 1 ;
+
+# start the pipe connection handshake
+
+       my $open_msg = Stem::Msg->new(
+                       'cmd'           => 'cell_trigger',
+                       'to'            => $pipe_addr,
+                       'from'          => $self->{'from_addr'},
+                       'data'          => {
+                               'args'          => $self->{'pipe_args'},
+                               'pipe_open'     => 1,
+                               'data_addr'     => $self->{'from_addr'},
+                       },
+       ) ;
+
+       $open_msg->dispatch() ;
+}
+
+# this command sub sets the data address at the end of a pipe handshake
+
+sub cell_pipe_addr_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'data_addr'} = $msg->from() ;
+
+       my $err = $cell_info->{'gather'}->gathered( 'data_addr' ) ;
+       return $err if $err ;
+
+       return ;
+}
+
+sub cell_pipe_close_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'PIPE' ) ;
+
+#      TraceStatus "pipe closed cmd" ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'close_cmd_seen'} = 1 ;
+
+       my $data = $msg->data() ;
+
+# see if we dump the errors to the output handle
+
+       if ( $data && $cell_info->{'errors_to_output'} ) {
+
+               $data = <<ERR ;
+Cell::Pipe Error
+$data
+ERR
+               $cell_info->_cell_write_sync( \$data ) ;
+       }
+
+
+
+       $self->cell_shut_down() ;
+
+       return ;
+}
+
+sub _close_pipe {
+
+       my( $self ) = @_ ;
+
+       return if $self->{'close_cmd_seen'} ;
+
+use Carp qw( cluck ) ;
+#cluck() ;
+#print $self->_dump( 'CLOSE PIPE' ) ;
+
+       return unless $self->{'piped'} ;
+
+#      TraceStatus "pipe closing" ;
+
+       my $to_addr = $self->{'args'}{'data_addr'} ||
+                       $self->{'data_addr'} ;
+
+       my $close_msg = Stem::Msg->new(
+               'cmd'           => 'cell_pipe_close',
+               'to'            => $to_addr,
+               'from'          => $self->{'from_addr'},
+               'data'          => $self->{'error'},
+       ) ;
+
+#print $close_msg->dump( '_close PIPE' ) ;
+
+       $close_msg->dispatch() ;
+}
+
+1 ;
diff --git a/lib/Stem/Cell/Sequence.pm b/lib/Stem/Cell/Sequence.pm
new file mode 100644 (file)
index 0000000..249f18f
--- /dev/null
@@ -0,0 +1,219 @@
+#  File: Stem/Cell/Sequence.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+sub cell_set_sequence {
+
+       my( $self, @sequence ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#print "@sequence\n" ;
+
+       $cell_info->{'sequence'} = [ @sequence ] ;
+       $cell_info->{'sequence_left'} = [ @sequence ] ;
+
+       return ;
+}
+
+
+sub cell_reset_sequence {
+
+       my( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'sequence_left'} = [ @{$cell_info->{'sequence'}} ] ;
+
+       return ;
+}
+
+sub cell_replace_next_sequence {
+
+        my( $self, $method ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->{'sequence_left'}[0] = $method;
+
+       return ;
+}
+
+#
+# This method lets you basically set up loops.  For example, method X
+# could insert itself as the next next method in the sequence.  Then,
+# when it is called again it can decide whether or not to insert
+# itself again.
+#
+# A more complex example might see method X might say "now execute Y,
+# Z, M, and X", which allows you to create loops.  Then method Z might
+# say "now execute Q and Z".
+#
+# Obviously, most loops will also need a break condition where method
+# X decides _not_ to insert itself into the sequence.
+#
+sub cell_insert_next_sequence {
+
+        my( $self, @sequence ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       unshift @{ $cell_info->{'sequence_left'} }, @sequence;
+
+       return ;
+}
+
+sub cell_skip_next_sequence {
+
+        my( $self, $count ) = @_ ;
+
+       $count ||= 1 ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       shift @{ $cell_info->{'sequence_left'} } for 1..$count;
+
+       return ;
+}
+
+sub cell_skip_until_method {
+
+        my( $self, $method ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       my $seq_left = $cell_info->{'sequence_left'} ;
+
+       while( @{$seq_left} ) {
+
+               return if $seq_left->[0] eq $method ;
+               shift @{$seq_left} ;
+       }
+
+       die "skip sequence method $method is not found" ;
+}
+
+
+sub cell_next_sequence_in {
+
+       my( $self, $msg ) = @_ ;
+
+#print $msg->dump( "NEXT IN" ) if $msg ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       $cell_info->cell_next_sequence( $msg ) ;
+}
+
+sub cell_next_sequence {
+
+       my( $self, $in_msg ) = @_ ;
+
+#print caller(), "\n" ;
+
+#print $in_msg->dump('SEQ IN') if $in_msg ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       my $owner_obj = $cell_info->{'owner_obj'} ;
+
+
+       while( my $next_sequence = shift @{$cell_info->{'sequence_left'}} ) {
+
+#print "LEFT @{$cell_info->{'sequence_left'}}\n" ;
+
+               die "cannot call sequence method $next_sequence"
+                       unless $owner_obj->can( $next_sequence ) ;
+
+#print "SEQ: $next_sequence\n" ;
+
+               my $seq_val = $owner_obj->$next_sequence( $in_msg ) ;
+
+# don't pass in the message more than once.
+
+               $in_msg = undef ;
+
+               next unless $seq_val ;
+
+               if ( ref $seq_val eq 'Stem::Msg' ) {
+
+
+#print caller() ;
+#print $seq_val->dump( 'SEQ: MSG' ) ;
+                       $seq_val->reply_type( 'cell_next_sequence' ) ;
+
+                       $seq_val->dispatch() ;
+
+                       return ;
+               }
+
+               if ( ref $seq_val eq 'HASH' ) {
+
+                       my $delay = $seq_val->{'delay'} ;
+
+                       if ( defined( $delay ) ) {
+
+                               $cell_info->cell_sequence_delay( $delay ) ;
+                               return ;
+                       }
+               }
+       }
+
+       if ( my $seq_done_method = $cell_info->{'sequence_done_method'} ) {
+
+               $owner_obj->$seq_done_method() ;
+
+               return ;
+       }
+
+#warn "FELL off end of sequence" ;
+
+       return ;
+}
+
+sub cell_sequence_delay {
+
+       my( $self, $delay ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+#print "SEQ DELAY $delay\n" ;
+
+       $cell_info->{'timer'} = Stem::Event::Timer->new(
+                               'object'        => $cell_info,
+                               'method'        => 'cell_next_sequence',
+                               'delay'         => $delay, 
+                               'hard'          => 1,
+                               'single'        => 1,
+       ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Cell/Work.pm b/lib/Stem/Cell/Work.pm
new file mode 100644 (file)
index 0000000..dfff2f5
--- /dev/null
@@ -0,0 +1,78 @@
+#  File: Stem/Cell/Work.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001, 2002 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cell ;
+
+use strict ;
+
+
+sub cell_worker_ready {
+
+       my ( $self ) = @_ ;
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       my $ready_addr = $cell_info->{'work_ready_addr'} ;
+
+       return unless $ready_addr ;
+
+#print "READY addr [$ready_addr]\n" ;
+
+       my $worker_msg = Stem::Msg->new(
+                               'to' => $ready_addr,
+                               'type' => 'worker',
+                               'from' => $cell_info->{'from_addr'},
+       ) ;
+
+#print $worker_msg->dump('worker ready') ;
+
+       $worker_msg->dispatch() ;
+
+       return ;
+}
+
+sub cell_work_in {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'WORK MSG' ) ;
+
+##################
+# handle error: work when work in progress. check 'work_msg'
+#################
+
+       my $cell_info = $self->_get_cell_info() ;
+
+       my $obj = $msg->data() ;
+
+       my $packet_text = $cell_info->{'packet'}->to_packet( $obj ) ;
+
+       $cell_info->cell_write( $packet_text ) ;
+}
+
+1 ;
diff --git a/lib/Stem/ChatLabel.pm b/lib/Stem/ChatLabel.pm
new file mode 100644 (file)
index 0000000..34fbe0f
--- /dev/null
@@ -0,0 +1,42 @@
+package Stem::ChatLabel ;
+
+use strict ;
+
+
+my $attr_spec = [
+
+       {
+               'name'          => 'sw_addr',
+               'help'          => <<HELP,
+This is the address of the chat switch
+HELP
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return $self ;
+}
+
+sub data_in {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+#print "$$data" ;
+
+       substr( $$data, 0, 0, $msg->from_cell() . ': ' ) ;
+
+       $msg->data( $data ) ;
+       $msg->to_cell( $self->{'sw_addr'} ) ;
+
+       $msg->dispatch() ;
+}
+
+1 ;
diff --git a/lib/Stem/Class.pm b/lib/Stem/Class.pm
new file mode 100644 (file)
index 0000000..8d4861c
--- /dev/null
@@ -0,0 +1,420 @@
+#  File: Stem/Class.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Class ;
+
+use strict ;
+
+#use Data::Dumper ;
+
+# dispatch table for attribute 'type' checking and conversion
+
+my %type_to_code = (
+
+       'boolean'       => \&_type_boolean,
+       'hash'          => \&_type_hash,
+       'list'          => \&_type_list,
+       'HoL'           => \&_type_hash_of_list,
+       'LoL'           => \&_type_list_of_list,
+       'HoH'           => \&_type_hash_of_hash,
+       'LoH'           => \&_type_list_of_hash,
+       'addr'          => \&_type_address,
+       'address'       => \&_type_address,
+       'obj'           => \&_type_object,
+       'object'        => \&_type_object,
+       'cb_object'     => \&_type_object,
+       'handle'        => \&_type_handle,
+) ;
+
+sub parse_args {
+
+       my( $attr_spec, %args_in ) = @_ ;
+
+       my( $package ) = caller ;
+
+#print "PACK $package\n" ;
+
+       my $obj = bless {}, $package ;
+
+#print Dumper( $attr_spec ) ;
+#print "class args ", Dumper( \%args_in ) ;
+
+       my( $cell_info_obj, $cell_info_name ) ;
+
+       my $reg_name = $args_in{ 'reg_name' } || '' ;
+
+       foreach my $field ( @{$attr_spec} ) {
+
+               my $field_name = $field->{'name'} or next ;
+
+               my $field_val = $args_in{ $field_name } ;
+
+               if ( my $class = $field->{'class'} ) {
+
+# optinally force a sub-object build by passing a default empty list
+# for its value
+# Stem::Cell is always built
+
+                       if ( $field->{'always_create'} ||
+                            $class eq 'Stem::Cell' ) {
+
+                               $field_val ||= [] ;
+                       }
+
+                       my @class_args ;
+
+                       if ( ref $field_val eq 'HASH' ) {
+
+                               @class_args = %{$field_val} ;
+                       }
+                       elsif ( ref $field_val eq 'ARRAY' ) {
+
+                               @class_args  = @{$field_val} ;
+                       }
+                       else {
+                               next ;
+                       }
+
+                       my $class_args = $field->{'class_args'} ;
+
+                       if ( $class_args && ref $class_args eq 'HASH' ) {
+
+                               push( @class_args, %{$class_args} ) ;
+                       }
+                       elsif ( $class_args && ref $class_args eq 'ARRAY' ) {
+
+                               push( @class_args, @{$class_args} ) ;
+                       }
+
+# Stem::Cell wants to know its owner's cell name
+
+                       push( @class_args, 'reg_name' => $reg_name )
+                               if $class eq 'Stem::Cell' ;
+
+                       $field_val = $class->new( @class_args ) ;
+
+                       return <<ERR unless $field_val ;
+Missing attribute class object for '$field_name' for class $package
+ERR
+
+                       return $field_val unless ref $field_val ;
+
+# track the field info for Stem::Cell for use later
+
+                       if ( $class eq 'Stem::Cell' ) {
+
+                               $cell_info_obj = $field_val ;
+                               $cell_info_name = $field_name ;
+                       }
+               }
+
+# handle a callback type attribute. it does all the parsing and object stuffing
+# the callback should return 
+
+               if ( my $callback = $field->{'callback'} and $field_val ) {
+
+
+                       my $cb_err = $callback->( $obj,
+                                                 $field_name, $field_val ) ;
+
+                       return $cb_err if $cb_err ;
+
+                       next ;
+               }
+
+               if ( my $env_name = $field->{'env'} ) {
+
+                       my @prefixes = ( $reg_name ) ?
+                                       ( "${reg_name}:", "${reg_name}_", '' ) :
+                                       ( '' ) ;
+
+                       foreach my $prefix ( @prefixes ) {
+
+#print "ENV NAME [$prefix$env_name]\n" ;
+
+                               my $env_val =
+                                       $Stem::Vars::Env{"$prefix$env_name"} ;
+
+                               next unless defined $env_val ;
+
+                               $field_val = $env_val ;
+#print "ENV field $field_name [$env_val]\n" ;
+                               last ;
+                       }
+               }
+
+               unless( defined $field_val ) {
+
+                       if ( $field->{'required'} ) {
+
+                               return <<ERR ;
+Missing required field '$field_name' for class $package
+ERR
+                       }
+
+                       $field_val = $field->{'default'}
+                                       if exists $field->{'default'} ;
+               }
+
+#print "field $field_name [$field_val]\n" ;
+
+               next unless defined $field_val ;
+
+               if ( my $type = $field->{'type'} ) {
+                       
+                       my $type_code = $type_to_code{$type} ;
+                       return "Unknown attribute type '$type'"
+                                                       unless $type_code ;
+                       
+                       my $err = $type_code->(
+                               \$field_val, $type, $field_name ) ;
+#print "ERR $err\n" ;
+                       return $err if $err ;
+               }
+
+               $obj->{$field_name} = $field_val ;
+       }
+
+       if ( $cell_info_obj ) {
+
+               return <<ERR unless $reg_name ;
+Missing 'name' in configuration for class $package.
+It is required for use by Stem::Cell
+ERR
+
+               $cell_info_obj->cell_init( $obj,
+                                          $reg_name,
+                                          $cell_info_name
+               ) ;
+       }
+
+#print "class obj ", Dumper( $obj ) ;
+
+       return $obj ;
+}
+
+sub _type_boolean {
+
+       my ( $val_ref, $type ) = @_ ;
+
+       return if ${$val_ref} =~ s/^(?:|1|Y|Yes)$/1/i || 
+                 ${$val_ref} =~ s/^(?:|0|N|No)$/0/i ;
+
+       return "Attribute value '${$val_ref}' is not boolean"
+}
+
+sub _type_object {
+
+       my ( $val_ref, $type ) = @_ ;
+
+       return if ref ${$val_ref} ;
+
+       return "Attribute value '${$val_ref}' is not an object"
+}
+
+sub _type_address {
+
+       my ( $val_ref, $type, $name ) = @_ ;
+
+       my( $to_hub, $cell_name, $target ) =
+                       Stem::Msg::split_address( ${$val_ref} ) ;
+
+       return if $cell_name ;
+
+       return "Attribute $name: value '${$val_ref}' is not a valid Stem address"
+}
+
+sub _type_handle {
+
+       my ( $val_ref, $type ) = @_ ;
+
+       return if defined fileno( ${$val_ref} ) ;
+
+       return "Attribute value '${$val_ref}' is not an open IO handle"
+}
+
+sub _type_list {
+
+       my ( $val_ref, $type ) = @_ ;
+
+       my $err = _convert_to_list( $val_ref ) ;
+
+       return unless $err ;
+
+       return "Attribute value '${$val_ref}' is not a list\n$err" ;
+}
+
+sub _type_hash {
+
+       my ( $val_ref, $type ) = @_ ;
+
+       my $err = _convert_to_hash( $val_ref ) ;
+
+       return unless $err ;
+
+       return "Attribute value '${$val_ref}' is not a hash\n$err" ;
+}
+
+sub _type_list_of_list {
+
+       my ( $val_ref, $type ) = @_ ;
+
+#print Dumper $val_ref ;
+       my $err = _convert_to_list( $val_ref ) ;
+
+#print Dumper $val_ref ;
+
+       return $err if $err ;
+
+       foreach my $sub_val ( @{$$val_ref}) {
+
+               $err = _convert_to_list( \$sub_val ) ;
+               return <<ERR if $err ;
+Attribute's secondary value '$sub_val' can't be converted to a list\n$err" ;
+ERR
+       }
+
+#print Dumper $val_ref ;
+
+       return ;
+}
+
+sub _type_list_of_hash {
+
+       my ( $val_ref, $type ) = @_ ;
+
+#print Dumper $val_ref ;
+       my $err = _convert_to_list( $val_ref ) ;
+
+#print Dumper $val_ref ;
+
+       return $err if $err ;
+
+       foreach my $sub_val ( @{$$val_ref}) {
+
+               $err = _convert_to_hash( \$sub_val ) ;
+               return <<ERR if $err ;
+Attribute's secondary value '$sub_val' can't be converted to a hash\n$err" ;
+ERR
+       }
+
+#print Dumper $val_ref ;
+
+       return ;
+}
+
+
+sub _type_hash_of_list {
+
+       my ( $val_ref, $type ) = @_ ;
+
+#print Dumper $val_ref ;
+       my $err = _convert_to_hash( $val_ref ) ;
+
+#print Dumper $val_ref ;
+
+       return $err if $err ;
+
+       foreach my $val ( values %{$$val_ref}) {
+
+               $err = _convert_to_list( \$val ) ;
+               return <<ERR if $err ;
+Attribute's secondary value '$val' can't be converted to a list\n$err" ;
+ERR
+       }
+
+#print Dumper $val_ref ;
+
+       return ;
+}
+
+sub _type_hash_of_hash {
+
+       my ( $val_ref, $type ) = @_ ;
+
+#print Dumper $val_ref ;
+       my $err = _convert_to_hash( $val_ref ) ;
+
+#print Dumper $val_ref ;
+
+       return $err if $err ;
+
+       foreach my $val ( values %{$$val_ref}) {
+
+               $err = _convert_to_hash( \$val ) ;
+               return <<ERR if $err ;
+Attribute's secondary value '$val' can't be converted to a hash\n$err" ;
+ERR
+       }
+
+#print Dumper $val_ref ;
+
+       return ;
+}
+
+sub _convert_to_list {
+
+       my ( $val_ref ) = @_ ;
+
+       my $val_type = ref ${$val_ref} ;
+
+       return if $val_type eq 'ARRAY' ;
+
+       unless ( $val_type ) {
+
+               ${$val_ref} = [ ${$val_ref} ] ;
+               return ;
+       }
+
+       if ( $val_type eq 'HASH' ) {
+
+               ${$val_ref} = [ %{${$val_ref}} ] ;
+               return ;
+       }
+
+       return 'It must be a scalar or a reference to an array or hash' ;
+}
+
+sub _convert_to_hash {
+
+       my ( $val_ref ) = @_ ;
+
+       my $val_type = ref ${$val_ref} ;
+
+       return if $val_type eq 'HASH' ;
+
+       if ( $val_type eq 'ARRAY' ) {
+
+               ${$val_ref} = { @{${$val_ref}} } ;
+               return ;
+       }
+
+       return 'It must be a reference to an array or hash' ;
+}
+
+1 ;
diff --git a/lib/Stem/Codec.pm b/lib/Stem/Codec.pm
new file mode 100644 (file)
index 0000000..d441ef5
--- /dev/null
@@ -0,0 +1,204 @@
+#  File: Stem/Codec/.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Codec ;
+
+use strict ;
+
+use Stem::Class ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'codec',
+               'default'       => 'Data::Dumper',
+               'help'          => <<HELP,
+This is the name of the Codec:: subclass that will be used in this cell
+HELP
+       },
+       {
+               'name'          => 'object',
+               'type'          => 'object',
+               'help'          => <<HELP,
+If an object is passed in, the filter will use it for callbacks
+HELP
+       },
+
+       {
+               'name'          => 'encode_method',
+               'default'       => 'encoded_data',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'decode_method',
+               'default'       => 'decoded_data',
+               'help'          => <<HELP,
+HELP
+       },
+
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::Codec::Data::Dumper
+
+=over 4
+
+
+=item * Attribute - B<object>
+
+=over 4
+
+
+=item Description:
+If an object is passed in, the filter will use it for callbacks
+
+
+=item Its B<type> is: object
+
+=back
+
+=item * Attribute - B<encode_method>
+
+=over 4
+
+
+=item It B<defaults> to: encoded_data
+
+=back
+
+=item * Attribute - B<decode_method>
+
+=over 4
+
+
+=item It B<defaults> to: decoded_data
+
+=back
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+my %loaded_codecs ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $err = $self->load_codec() ;
+       return $err if $err ;
+
+       return $self ;
+}
+
+sub load_codec {
+
+       my( $self ) = @_ ;
+
+       my $codec = $self->{codec} ;
+
+       return if $loaded_codecs{ $codec } ;
+
+       my $codec_class = "Stem::Codec::$codec" ;
+
+       eval "require $codec_class" ;
+
+       return "Can't load Stem codec '$codec_class' $@" if $@ ;
+
+       $loaded_codecs{ $codec } = {
+
+               encoder => $codec_class->make_encoder(),
+               decoder => $codec_class->make_decoder(),
+       } ;
+
+       return ;
+}
+
+sub encode {
+
+       my $self = shift ;
+
+       return unless @_ ;
+
+       my $encoder = $loaded_codecs{ $self->{codec} }{encoder} ;
+
+# make sure scalars and scalar refs have a ref taken to them as codecs
+# always take a ref. we do ref on scalar refs so we can tell at decode
+# time that REF is a scalar ref but SCALAR is a plain scalar
+
+#print "IN $_[0] REF ", ref $_[0], "\n" ;
+
+       my $data_ref = ( ! ref $_[0] || ref $_[0] eq 'SCALAR' ) ?
+               \$_[0] : $_[0] ;
+
+#print "DATA REF $data_ref\n" ;
+
+       my $encoded_text = $encoder->( $data_ref ) ;
+
+       if ( my $obj = $self->{'object'} ) {
+
+               my $method = $self->{'encode_method'} ;
+               $obj->$method( $encoded_text ) ;
+       }
+
+       return $encoded_text ;
+}
+
+sub decode {
+
+       my $self = shift ;
+
+       my $decoder = $loaded_codecs{ $self->{codec} }{decoder} ;
+
+       my $decoded_data = $decoder->( $_[0] ) ;
+
+       $decoded_data = ${$decoded_data} if
+               ref $decoded_data eq 'SCALAR' ||
+               ref $decoded_data eq 'REF' ;
+
+       if ( my $obj = $self->{'object'} ) {
+
+               my $method = $self->{'decode_method'} ;
+               $obj->$method( $decoded_data ) ;
+       }
+
+       return( $decoded_data ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Codec/Data/Dumper.pm b/lib/Stem/Codec/Data/Dumper.pm
new file mode 100644 (file)
index 0000000..5af7fb2
--- /dev/null
@@ -0,0 +1,49 @@
+#  File: Stem/Codec/Data/Dumper.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Codec::Data::Dumper ;
+
+use strict ;
+use Data::Dumper ;
+
+$Data::Dumper::Purity = 1 ;
+
+sub make_encoder {
+
+# strip out the '$VAR = ' stuff 
+
+       return sub { \substr( Dumper( $_[0] ), 8 ) } ;
+#      return sub { \Dumper( $_[0] ) } ;
+}
+
+sub make_decoder {
+
+       return sub { eval $_[0] } ;
+}
+
+1 ;
diff --git a/lib/Stem/Codec/Storable.pm b/lib/Stem/Codec/Storable.pm
new file mode 100644 (file)
index 0000000..60a7dca
--- /dev/null
@@ -0,0 +1,44 @@
+#  File: Stem/Codec/Storable.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Codec::Storable ;
+
+use strict ;
+use Storable () ;
+
+sub make_encoder {
+
+       return sub { \Storable::freeze( $_[0] ) } ;
+}
+
+sub make_decoder {
+
+       return sub { Storable::thaw( $_[0] ) } ;
+}
+
+1 ;
diff --git a/lib/Stem/Codec/YAML.pm b/lib/Stem/Codec/YAML.pm
new file mode 100644 (file)
index 0000000..2fee821
--- /dev/null
@@ -0,0 +1,44 @@
+#  File: Stem/Codec/YAML.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Codec::YAML ;
+
+use strict ;
+use YAML () ;
+
+sub make_encoder {
+
+       return sub { \YAML::Dump( $_[0] ) } ;
+}
+
+sub make_decoder {
+
+       return sub { YAML::Load( $_[0] ) ; } ;
+}
+
+1 ;
diff --git a/lib/Stem/Conf.pm b/lib/Stem/Conf.pm
new file mode 100644 (file)
index 0000000..8eed0c4
--- /dev/null
@@ -0,0 +1,328 @@
+#  File: Stem/Conf.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Conf ;
+
+use Data::Dumper ;
+use strict ;
+
+use Stem::Vars ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError'  ;
+
+Stem::Route::register_class( __PACKAGE__, 'conf' ) ;
+
+my @conf_paths = split ':', $Env{ 'conf_path' } || '' ;
+if ( my $add_conf_path = $Env{ 'add_conf_path' } ) {
+
+       push @conf_paths, split( ':', $add_conf_path ) ;
+}
+
+my $attr_spec = [
+
+       {
+               'name'          => 'path',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the full path of the configuration file.
+HELP
+       },
+
+       {
+               'name'          => 'to_hub',
+               'help'          => <<HELP,
+This is the Hub that this configuration will be sent to.
+HELP
+       },
+] ;
+
+# this does not construct anything. just loads a conf file locally or remotely
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       if ( my $to_hub = $self->{'to_hub'} ) {
+
+               my $conf_data = load_conf_file( $self->{'path'} ) ;
+
+               return $conf_data unless ref $conf_data ;
+
+               my $msg = Stem::Msg->new(
+                               'to_hub'        => $to_hub,
+                               'to_cell'       => __PACKAGE__,
+                               'from_cell'     => __PACKAGE__,
+                               'type'          => 'cmd',
+                               'cmd'           => 'remote',
+                               'data'          => $conf_data,
+               ) ;
+
+               $msg->dispatch() ;
+
+               return ;
+       }
+
+       my $err = load_conf_file( $self->{'path'}, 1 ) ;
+
+TraceError $err if $err ;
+
+       return $err if $err ;
+
+       return ;
+}
+
+
+sub load_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       my @conf_names ;
+
+       push( @conf_names, @{$data} ) if ref $data eq 'ARRAY' ;
+       push( @conf_names, ${$data} ) if ref $data eq 'SCALAR' ;
+
+       my $err = load_confs( @conf_names ) ;
+
+TraceError $err if $err ;
+
+       return $err if $err ;
+
+       return ;
+}
+
+sub remote_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my $err = configure( $msg->data() ) ;
+
+TraceError $err if $err ;
+
+       return $err if $err ;
+
+       return ;
+}
+
+sub load_conf_file {
+
+       my( $conf_path, $do_conf ) = @_ ;
+
+       -r $conf_path or return "$conf_path can't be read: $!" ;
+
+       my $conf_data = Stem::Util::load_file( $conf_path ) ;
+
+       return "Stem::Conf load error:\n$conf_data" unless ref $conf_data ;
+
+       return $conf_data unless $do_conf ;
+
+       my $conf_err = configure( $conf_data ) ;
+
+       return <<ERR if $conf_err ;
+Configuration error in '$conf_path'
+$conf_err
+ERR
+
+#      TraceStatus "$conf_path configuration loaded." ;
+
+       return ;
+}
+
+
+sub load_confs {
+
+       my ( @conf_names ) = @_ ;
+
+       NAME:
+       foreach my $conf_name ( @conf_names ) {
+
+               $conf_name =~ s/\.stem$// ;
+
+               for my $path ( @conf_paths ) {
+
+                       my $conf_path = "$path/$conf_name.stem" ;
+
+                       next unless -e $conf_path ;
+
+                       my $err = load_conf_file( $conf_path, 1 ) ;
+
+                       return $err if $err ;
+
+                       next NAME ;
+               }
+
+               local( $" ) = "\n\t" ;
+
+               return <<ERR ;
+Can't find config file '$conf_name.stem' in these directories:
+       @conf_paths
+ERR
+       }
+
+       return ;
+}
+
+my $eval_error ;
+
+sub configure {
+
+       my ( $conf_list_ref ) = @_ ;
+
+       my $class ;
+       my @notify_done; # list of objects/packages to call config_done on
+
+       foreach my $conf_ref ( @{$conf_list_ref} ) {
+
+               my %conf ;
+
+               if ( ref $conf_ref eq 'HASH' ) {
+
+                       %conf = %{$conf_ref} ;
+               }
+               elsif ( ref $conf_ref eq 'ARRAY' ) {
+
+                       %conf = @{$conf_ref} ;
+               }
+               else {
+                       return "config entry is not an HASH or ARRAY ref\n" .
+                                       Dumper($conf_ref). "\n" ;
+               }
+
+               unless ( $class = $conf{'class'} ) {
+
+                       return "Missing class entry in conf\n" .
+                            Dumper($conf_ref) . "\n" ;
+               }
+
+# get the config name for registration
+
+               my $reg_name = $conf{'name'} || '' ;
+
+               no strict 'refs' ;
+
+               unless ( %{"::${class}"} ) {
+
+                       my $module = $class ;
+                       $module =~ s{::}{/}g ;
+                       $module .= '.pm' ;
+
+                       while( 1 ) {
+
+                               my $err = eval { require $module } ;
+
+                               return <<ERR if $err && $err !~ /^1/ ;
+Configure error FOO in Cell '$reg_name' from class '$class' FOO
+$eval_error
+$err
+ERR
+                               last if $err ;
+
+                               if ( $@ =~ /Can't locate $module/ ) {
+
+# this could be a subclass so try to load the parent class
+# is this used?
+                                       next if $module =~ s{/\w+\.pm$}{.pm} ;
+
+                                       die
+                                "Conf: can't find module for class $class" ;
+                               }
+
+                               return "eval $@\n" if $@ ;
+                       }
+
+               }
+
+# if arguments, call the method or new to get a possible object
+
+               if ( my $args_ref = $conf{'args'} ) {
+
+                       my @args ;
+
+                       if ( ref $args_ref eq 'HASH' ) {
+
+                               @args = %{$args_ref} ;
+                       }
+                       elsif ( ref $args_ref eq 'ARRAY' ) {
+
+                               @args = @{$args_ref} ;
+                       }
+                       else {
+                               return
+                                "args entry is not an HASH or ARRAY ref\n" .
+                                       Dumper($args_ref). "\n" ;
+                       }
+
+                       my $method = $conf{'method'} || 'new' ;
+
+
+# register if we have an object
+
+#print "NAME: $reg_name\n" ;
+
+                       if ( my $obj = $class->$method(
+                                               'reg_name' => $reg_name,
+                                               @args ) ) {
+
+                               return <<ERR unless ref $obj ;
+Configure error in Cell '$reg_name' from class '$class'
+$obj
+ERR
+
+# register the object by the conf name or the class
+
+                               my $err = Stem::Route::register_cell(
+                                               $obj,
+                                               $reg_name || $class ) ;
+
+                               return $err if $err ;
+                               push @notify_done, $obj if $obj->can('config_done');
+                               next;
+                       }
+
+                    }
+# or else register the class if we have a name
+
+               my $err = Stem::Route::register_class( $class, $reg_name ) ;
+
+               return $err if $err ;
+               push @notify_done, $class if $class->can('config_done');
+       }
+
+       foreach my $class (@notify_done) {
+          $class->config_done();
+       }
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Console.pm b/lib/Stem/Console.pm
new file mode 100644 (file)
index 0000000..cf62156
--- /dev/null
@@ -0,0 +1,362 @@
+#  File: Stem/Console.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Console ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+use strict ;
+
+use Data::Dumper ;
+use Symbol ;
+use Socket ;
+
+use Stem::AsyncIO ;
+use Stem::Vars ;
+
+my $console_obj ;
+my $line ;
+
+my( $read_fh, $write_fh, $parent_fh, $child_fh ) ;
+
+if ( $^O =~ /Win32/ ) {
+
+
+       $parent_fh = gensym ;
+       $child_fh = gensym ;
+
+       socketpair( $parent_fh, $child_fh, AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ;
+       start_reader() ;
+       start_writer() ;
+
+#      close $child_fh ;
+
+       $read_fh = $parent_fh ;
+       $write_fh = $parent_fh ;
+}
+else {
+
+       $read_fh = \*STDIN ;
+       $write_fh = \*STDOUT ;
+}
+
+return init() unless $Env{'console_disable'} || $Env{'tty_disable'} ;
+
+
+sub start_reader {
+
+# back to parent
+
+       return if fork() ;
+
+       close $parent_fh ;
+
+#syswrite( \*STDERR, "reader started\n" ) ;
+#warn "reader started2\n" ;
+
+       while( 1 ) {
+
+               my $buf ;
+
+               my $cnt = sysread( \*STDIN, $buf, 1000 ) ;
+
+#syswrite( \*STDERR, $buf ) ;
+
+               syswrite( $child_fh, $buf ) ;
+       }
+}
+
+sub start_writer {
+
+# back to parent
+
+       return if fork() ;
+
+#      close $parent_fh ;
+
+       while( 1 ) {
+
+               my $buf ;
+
+               my $cnt = sysread( $child_fh, $buf, 1000 ) ;
+
+               syswrite( \*STDOUT, $buf ) ;
+       }
+}
+
+sub init {
+
+       Stem::Route::register_class( __PACKAGE__, 'cons', 'console', 'tty' ) ;
+
+       $Env{'has_console'} = 1 ;
+
+       my $self = bless {} ;
+
+       my $aio = Stem::AsyncIO->new(
+
+                       'object'        => $self,
+                       'read_fh'       => $read_fh,
+                       'write_fh'      => $write_fh,
+                       'read_method'   => 'stdin_read',
+                       'closed_method' => 'stdin_closed',
+       ) ;
+
+       return $aio unless ref $aio ;
+
+       $self->{'aio'} = $aio ;
+
+       $self->{'prompt'} = $Env{'prompt'} || "\nStem > " ;
+
+       $console_obj = $self ;
+
+       $self->write( "\nEnter 'help' for help\n\n" ) ;
+       $self->prompt() ;
+
+       return 1 ;
+}
+
+sub stdin_read {
+
+       my( $self, $line_ref ) = @_ ;
+
+       $line = ${$line_ref} ;
+
+       chomp( $line ) ;
+
+       if ( $line =~ /^\s*$/ ) {
+
+               $self->prompt() ;
+               return ;
+       }
+
+       if ( $line =~ /^quit\s*$/i ) {
+
+               TraceStatus "quitting" ;
+
+               exit ;
+       }
+
+       if ( $line =~ /^\s*help\s*$/i ) {
+
+               $self->help() ;
+               $self->prompt() ;
+               return ;
+       }
+
+       if ( my( $key, $val ) = $line =~ /^\s*(\w+)\s*=\s*(.+)$/ ) {
+
+               $val =~ s/\s+$// ;
+
+               $self->echo() ;
+
+               $self->write( "Setting Environment '$key' to '$val'\n" ) ;
+               $Env{ $key } = $val ;
+
+               $self->prompt() ;
+
+               return ;
+       }
+
+       unless ( $line =~ /^\s*(\S+)\s+(.*)$/ ) {
+
+               $self->write( <<ERR ) ;
+Console commands must be in the form
+<Cell Address> command [args ...]
+
+ERR
+               $self->prompt() ;
+
+               return ;
+       }
+
+       my $addr = $1 ;
+
+       my( $cmd_name, $cmd_data ) = split( ' ', $2, 2 ) ;
+
+# allow a leading : on the command to make it a regular message instead
+
+       my $msg_type = ( $cmd_name =~ s/^:// ) ? 'type' : 'cmd' ;
+
+       my $msg = Stem::Msg->new(
+                       'to'            => $addr,
+                       'from'          => 'console',
+                       $msg_type       => $cmd_name,
+                       'data'          => \$cmd_data,
+       ) ;
+
+       if( ref $msg ) {
+
+               $self->echo() ;
+
+               $msg->dispatch() ;
+       }
+       else {
+               $self->write( "Bad console command message: $msg\n" ) ;
+       }
+
+       $self->prompt() ;
+
+       return ;
+}
+
+sub stdin_closed {
+
+       my( $self ) = @_ ;
+
+       *STDIN->clearerr() ;
+
+       $self->write( "EOF (ignored)\n" ) ;
+
+       $self->prompt() ;
+}
+
+sub data_in {
+
+       goto &response_in ;
+}
+
+sub response_in {
+
+       my( $self, $msg ) = @_ ;
+
+       $self = $console_obj unless ref $self ;
+
+       return unless $self ;
+
+       my $data = $msg->data() ;
+
+       $self->write( "\n\n" ) ;
+
+       if ( $Env{'console_from'} ) {
+
+               my $from = $msg->from() ;
+
+               $self->write( "[From: $from]\n" ) ;
+       }
+
+       if ( ref $data eq 'SCALAR' ) {
+
+               $self->write( ${$data} ) ;
+       }
+       elsif( ref $data ) {
+
+               $self->write( Dumper( $data ) ) ;
+       }
+       else {
+
+               $self->write( $data ) ;
+       }
+
+       $self->prompt() ;
+}
+
+sub write {
+
+       my( $self, $text ) = @_ ;
+
+       $self = $console_obj unless ref $self ;
+
+       $self->{'aio'}->write( $text ) ;
+}
+
+
+sub prompt {
+
+       my( $self ) = @_ ;
+
+       return unless $self->{'prompt'} ;
+
+       $self->write( $self->{'prompt'} ) ;
+}
+
+sub echo {
+
+       my( $self ) = @_ ;
+
+       return unless $Env{'console_echo'} ;
+
+       $self->write( "->$line\n" ) ;
+}
+
+sub help {
+
+       my( $self ) = @_ ;
+
+       $self->write( <<HELP ) ;
+
+Stem::Console Help:
+
+You can enter various commands to Stem here. 
+
+If the line is of the form:
+
+key=value
+
+then the global command args hash %Stem::Vars::Env has that key set to
+the value. Stem environment variables can be used to control log filters,
+set cell behavior, set default values for cell attributes and other purposes
+
+If the line is of the form:
+
+address cmd data_text
+
+it is parsed and a command message is created and sent.
+
+The address can be in one of these forms:
+
+       cell
+       hub:cell
+       hub:cell:target
+       :cell:target
+
+The cmd token is the name of the command for the message. If it is
+prefixed with a :, then this string becomes the message type instead.
+
+The rest of the line is sent as the data of the message.
+
+Examples:
+
+reg status
+
+will send a 'status' command message to the 'reg' cell which is the
+Stem::Route class. A listing of all registered Cells will be returned
+and printed.
+
+server:sw map a c d
+
+That will send a 'map' command message to the Cell named 'sw' in the
+Hub named 'server'. The data will be the string 'a c d'. That is used
+to change the mapping of target 'a' to c, d in the Switch Cell in the
+chat and chat2 demos.
+
+HELP
+
+}
+
+1 ;
diff --git a/lib/Stem/Cron.pm b/lib/Stem/Cron.pm
new file mode 100644 (file)
index 0000000..7844495
--- /dev/null
@@ -0,0 +1,278 @@
+#  File: Stem/Cron.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Cron ;
+
+use strict ;
+use Data::Dumper ;
+
+use Stem::Vars ;
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+Stem::Route::register_class( __PACKAGE__, 'cron' ) ;
+
+my %cron_entries ;
+my $cron_timer ;
+my $last_time ;
+
+
+my @set_names = qw( minutes hours month_days months week_days ) ;
+
+{
+       my $t = time ;
+
+       my $interval = 60 ;
+       my $delay = 59 - $t % 60 ;
+
+       if ( $Env{ 'cron_interval' } ) {
+
+               $interval = $Env{ 'cron_interval' } ;
+               $delay = 0 ;
+       }
+
+#      my $lt = localtime $t ;
+#      print "$t $lt ",  $t % 60, "\n" ;
+
+       $cron_timer = Stem::Event::Timer->new(
+               'object'        => __PACKAGE__,
+               'method'        => 'cron_triggered',
+               'interval'      => $interval,
+               'delay'         => $delay,
+               'repeat'        => 1,                   
+               'hard'          => 1,
+       ) ;
+}
+
+die "Stem::Cron $cron_timer" unless ref $cron_timer ;
+
+
+my $attr_spec = [
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'msg',
+               'class'         => 'Stem::Msg',
+               'required'      => 1,
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'minutes',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'hours',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'month_days',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'months',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'week_days',
+               'help'          => <<HELP,
+HELP
+       },
+
+] ;
+
+my %ranges = (
+
+       'minutes'       => [0, 59],
+       'hours'         => [0, 23],
+       'month_days'    => [1, 31],
+       'months'        => [1, 12],
+       'week_days'     => [0, 6],
+) ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $self->{'msg'}->from_cell( $self->{'reg_name'} || 'cron' ) ;
+
+# make sets for each time part. if one isn't created because it is
+# empty, it is a wild card with behaves as if all the slots are set.
+
+       foreach my $set_name ( @set_names ) {
+
+               $self->_make_cron_set( $set_name, @{$ranges{$set_name}} )
+       }
+
+# keep track of all the active cron entries.
+
+       $cron_entries{ $self } = $self ;
+
+       TraceStatus Dumper($self) ;
+
+####################
+####################
+# why return cron entry? it should not be registered as you can't send
+# it messages.  do we need a way to cancel a cron entry? could we
+# register in internally to cron and not need external registration?
+####################
+####################
+
+       return $self ;
+}
+
+sub _make_cron_set {
+
+       my( $self, $set_name, $min, $max ) = @_ ;
+
+       my $cron_list = $self->{$set_name} ;
+
+       return unless ref $cron_list eq 'ARRAY' ;
+
+       my( @cron_vals ) ;
+
+       foreach my $cron_val ( @{$cron_list} ) {
+
+               if ( $cron_val =~ /^(\d+)$/ &&
+                    $min <= $1 && $1 <= $max ) {
+
+                       push @cron_vals, $1 ;
+                       next ;
+               }
+
+               if ( $cron_val =~ /^(\d+)-(\d+)$/ &&
+                    $min <= $1 && $1 <= $2 && $2 <= $max ) {
+
+                       push @cron_vals, $1 .. $2 ;
+                       next ;
+               }
+
+##################
+##################
+##################
+# this is for normal cron entries with names like days of week and
+# months.  the name translation tables will be passed in or defaulted
+# to american names. it needs work.
+#
+# also to be done is fancy entries like first thursday of month or
+# weekend days, etc. it will be a filter to run when the numeric days
+# of week or month days filter is run.
+##################
+##################
+##################
+
+#              if ( $convert_to_num &&
+#                   exists( $convert_to_num->{$cron_val} ) ) {
+
+#                      push @cron_vals, $convert_to_num->{$cron_val} ;
+#                      next ;
+#              }
+
+               TraceError "bad cron value '$cron_val'" ;
+       }
+
+       if ( @cron_vals ) {
+
+               my @cron_set ;
+
+               @cron_set[@cron_vals] = (1) x @cron_vals ;
+
+               $self->{"${set_name}_set"} = \@cron_set ;
+       }
+}
+
+
+sub cron_triggered {
+
+       my $this_time = time() ;
+
+       my %set_times ;
+
+       TraceStatus scalar localtime( $this_time ) ;
+
+# get the current time part into a hash
+
+       @set_times{ @set_names } = (localtime( $this_time ))[ 1, 2, 3, 4, 6 ] ;
+
+# one base the months
+
+       $set_times{'months'}++ ;
+
+       my( $set ) ;
+
+# loop over all the entries
+
+       CRON:
+       foreach my $cron ( values %cron_entries ) {
+
+# loop over all the possible time sets
+
+               foreach my $name ( @set_names ) {
+
+#  my $s = $cron->{"${name}_set"} || [] ;
+#  print "C $name $set_times{ $name } @$s\n" ;
+
+# we don't trigger unless we have a set with data and the time slot
+# for the current time is true
+
+                       next CRON if $set = $cron->{"${name}_set"} and
+                                  ! $set->[$set_times{ $name }] ;
+               }
+
+#print "C disp $cron\n" ;
+
+# we must have passed all the time filters, so send the message
+
+               $cron->{'msg'}->dispatch() ;
+       }
+}
+
+sub status_cmd {
+
+Dumper(\%cron_entries) ;
+
+}
+
+1 ;
diff --git a/lib/Stem/DBI.pm b/lib/Stem/DBI.pm
new file mode 100644 (file)
index 0000000..b4403ef
--- /dev/null
@@ -0,0 +1,400 @@
+#  File: Stem/DBI.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::DBI ;
+
+use strict ;
+
+use DBI ;
+
+use base 'Stem::Cell' ;
+use Stem::Route qw( :cell ) ;
+
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'port',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'host',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'db_type',
+               'required'      => 1,
+               'help'          => <<HELP,
+HELP
+       },
+
+        # db_name must be something that can go after "dbi:mysql:" so
+        # something like "dbname=foo" or "database=foo" depending on
+        # the driver.
+       {
+               'name'          => 'db_name',
+               'required'      => 1,
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'user_name',
+               'env'           => 'dbi_user_name',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'password',
+               'env'           => 'dbi_password',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'dsn_extras',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'statements',
+               'help'          => <<HELP,
+HELP
+       },
+       {
+               'name'          => 'error_log',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'default_return_type',
+               'default'       => 'list_of_hashes',
+               'help'          => <<HELP,
+HELP
+       },
+       {
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+               'help'          => <<HELP,
+This value is the attributes for the included Stem::Cell which handles
+cloning, async I/O and pipes.
+HELP
+       },
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return "statements is not an ARRAY ref"
+                       unless ref $self->{'statements'} eq 'ARRAY' ;
+
+       if ( my $err = $self->db_connect() ) {
+
+               return $err ;
+       }
+
+       if ( my $err = $self->prepare() ) {
+
+               return $err ;
+       }
+
+       $self->cell_worker_ready() ;
+
+       return $self ;
+}
+
+sub db_connect {
+
+       my ( $self ) = @_ ;
+
+       my $db_type = $self->{'db_type'} ;
+       my $db_name = $self->{'db_name'} ;
+       my $host = $self->{'host'} ;
+       my $port = $self->{'port'} ;
+       my $user_name = $self->{'user_name'} ;
+       my $password = $self->{'password'} ;
+       my $extras = $self->{'dsn_extras'} ;
+
+       my $dsn = "dbi:$db_type:$db_name" ;
+       $dsn .= ";host=$host" if defined $host ;
+       $dsn .= ";port=$port" if defined $port ;
+       $dsn .= ";$extras" if defined $extras ;
+
+#print "DSN [$dsn]\n" ;
+       my $dbh = DBI->connect( $dsn, $user_name, $password,
+                               { 'PrintError' => 0,
+                                 'FetchHashKeyName' => 'NAME_lc' } )
+           or return DBI->errstr ;
+
+       $self->{'dbh'} = $dbh ;
+
+       return ;
+}
+
+
+sub prepare {
+
+       my ( $self ) = @_ ;
+
+       my %name2statement ;
+
+       my $dbh = $self->{'dbh'} ;
+
+       my $statements = $self->{'statements'} ;
+
+       foreach my $statement ( @{$statements} ) {
+
+               # Hey, this is ugly.  I guess we need parameter type
+               # coercion ;)
+               $statement = { @{$statement} };
+               my $name = $statement->{'name'} ;
+
+               return "statement is missing a name" unless $name ;
+
+               my $sql = $statement->{'sql'} ;
+
+               return "statement '$name' is missing sql" unless defined $sql ;
+
+               $statement->{'return_type'} ||= $self->{'default_return_type'};
+
+               unless ( $self->can( $statement->{'return_type'} ) ) {
+
+                       return
+               "No such return type for $name: $statement->{'return_type'}";
+               }
+
+               my $sth = $dbh->prepare( $sql )
+                   or return $dbh->errstr ;
+
+               $statement->{'sth'} = $sth ;
+
+               $name2statement{ $name } = $statement ;
+       }
+
+       $self->{'name2statement'} = \%name2statement ;
+
+       return ;
+}
+
+sub execute_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+#print "EXEC\n" ;
+
+# why not tell the queue ready before we start this operation. since
+# it blocks we will handle that new work until this is done.
+
+       $self->cell_worker_ready() ;
+
+       my $data = $msg->data() ;
+
+       return $self->log_error( "No message data" )
+               unless $data ;
+       return $self->log_error( "Message data is not a hash " )
+               unless ref $data eq 'HASH' ;
+
+       my $sth ;
+       my $statement ;
+
+       if ( exists $data->{'sql'} ) {
+
+           return "Must provide return type" unless exists $data->{'return_type'} ;
+
+               $statement = $data->{'sql'} ;
+
+           $sth = $self->{'dbh'}->prepare( $statement ) ;
+
+           return $self->log_error( $self->{'dbh'}->errstr . "\n$statement" )
+               if $self->{'dbh'}->errstr ;
+       }
+       else {
+
+           $statement = $data->{'statement'} ;
+
+           if ( my $in_cnt = $data->{'in_cnt'} ) {
+
+                   my $sql = $self->{'name2statement'}{$statement}{'sql'} ;
+
+                   my @qmarks = ('?') x $in_cnt ;
+                   local( $" ) = ',' ;
+                   $sql =~ s/IN\(\)/IN( @qmarks )/i ;  
+
+                   $sth = $self->{'dbh'}->prepare( $sql ) ;
+
+                   return $self->log_error(
+                               $self->{'dbh'}->errstr . "\n$statement" )
+                                       if $self->{'dbh'}->errstr ;
+           }
+           else {
+
+                   $sth = $self->{'name2statement'}{$statement}{'sth'} ;
+                   return $self->log_error(
+                           "Unknown statement name: $statement" ) unless $sth ;
+           }
+       }
+
+
+       $self->{'statement'} = $statement ;
+
+       my $bind = $data->{'bind'} || [] ;
+       return $self->log_error( "Statement arguments are not a list " )
+                                unless ref $bind eq 'ARRAY' ;
+
+       my $dbh = $self->{'dbh'} ;
+
+       my $return_type = $data->{'return_type'} ||
+               $self->{'name2statement'}{$statement}{'return_type'} ;
+
+       unless ( $self->can( $return_type ) ) {
+
+               return $self->log_error( 
+                       "No such return type: $data->{'return_type'}" ) ;
+       }
+
+       my $dbi_result = $self->$return_type( $sth, $bind ) ;
+
+       if ( $dbi_result && ! ref $dbi_result ) {
+
+               return( $self->log_error( "[$statement] $dbi_result" ) ) ;
+       }
+
+       return $dbi_result ;
+}
+
+sub list_of_hashes {
+
+    return shift->_fetch( 'fetchall_arrayref', @_, {} );
+}
+
+sub list_of_arrays {
+
+    return shift->_fetch( 'fetchall_arrayref', @_, [] );
+}
+
+sub one_hashref {
+
+    return shift->_fetch( 'fetchrow_hashref', @_ );
+}
+
+sub column_as_array {
+
+    my( $self, $sth, $bind ) = @_;
+
+    my @column;
+
+    $sth->finish if $sth->{'Active'} ;
+
+    $sth->execute( @{$bind} ) or return $sth->errstr ;
+
+    while ( my @row = $sth->fetchrow_array ) {
+
+       push @column, $row[0];
+    }
+
+    return $sth->errstr() if $sth->errstr() ;
+
+    return \@column;
+}
+
+sub _fetch {
+
+    my( $self, $method, $sth, $bind, @args ) = @_ ;
+
+    $sth->finish if $sth->{'Active'} ;
+
+    $sth->execute( @{$bind} ) or return $sth->errstr ;
+
+    my $data = $sth->$method( @args ) ;
+
+    return $sth->errstr if $sth->errstr ;
+
+    return $data ;
+}
+
+sub rows_affected {
+
+    my( $self, $sth, $bind ) = @_;
+
+    $sth->execute( @{$bind} );
+
+    return $sth->errstr if $sth->errstr ;
+
+    return { 'rows' => $sth->rows };
+}
+
+sub insert_id {
+
+    my( $self, $sth, $bind ) = @_;
+
+    my $err = $sth->execute( @{$bind} );
+
+    return $sth->errstr if $sth->errstr ;
+
+#print "ID: [$self->{'dbh'}{'mysql_insertid'}]\n" ;
+
+    return { 'insert_id' => $self->{'dbh'}{'mysql_insertid'} } ;
+}
+
+sub log_error {
+
+       my ( $self, $err ) = @_;
+
+       my $log = $self->{'error_log'} ;
+
+       return $err unless $log ;
+
+       Stem::Log::Entry->new (
+              'logs'   => $log,
+              'level'  => 5,
+              'label'  => 'Stem::DBI',
+              'text'   => "Statement: $self->{'statement'} - $err\n",
+       ) ;
+
+       return \$err ;
+}
+
+1 ;
diff --git a/lib/Stem/Debug.pm b/lib/Stem/Debug.pm
new file mode 100644 (file)
index 0000000..6178fcb
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/local/bin/perl
+
+package Stem::Debug ;
+
+use strict ;
+use Data::Dumper ;
+use Scalar::Util qw( openhandle ) ;
+
+use base 'Exporter' ;
+our @EXPORT_OK = qw ( dump_data dump_socket dump_owner ) ;
+
+sub dump_data {
+
+       my( $data ) = @_ ;
+
+       local $Data::Dumper::Sortkeys = \&dump_filter ;
+
+       return Dumper $data ;
+}
+
+sub dump_filter {
+
+       my( $href ) = @_ ;
+
+       my @keys ;
+
+       my %fh_dumps ;
+
+       while( my( $key, $val ) = each %{$href} ) {
+
+               if( my $fh_val = dump_socket( $val ) ) {
+
+                       my $fh_key = "$key.FH" ;
+                       $fh_dumps{$fh_key} = $fh_val ;
+                       push @keys, $fh_key ;
+                       next ;
+               }
+
+               push @keys, $key ;
+       }
+
+       @{$href}{ keys %{fh_dumps} } = values %{fh_dumps} ;
+
+#print "KEYS [@keys]\n" ;
+
+       return [ sort @keys ] ;
+}
+
+sub dump_socket {
+
+       my ( $sock ) = @_ ;
+
+       return 'UNDEF' unless defined $sock ;
+       return 'EMPTY' unless $sock ;
+       return 'NOT REF' unless ref $sock ;
+
+       return 'NOT GLOB' unless $sock =~ /GLOB/ ;
+
+warn "SOCK [$sock]\n" ;
+
+       my $fdnum = fileno( $sock ) ;
+
+       return 'NO FD' unless defined $fdnum ;
+
+       my $opened = openhandle( $sock ) ? 'OPEN' : 'CLOSED' ;
+
+#      return "CLOSED $sock" if $opened eq 'CLOSED' ;
+
+#      $fdnum = 'NONE' unless defined $fdnum ;
+
+#      my $fdnum = "FOO" ;
+
+#      return "FD [$fdnum]" unless $sock->isa('IO::Socket') ;
+
+       return "FD [$fdnum] *$opened*   $sock" ;
+}
+
+
+
+sub dump_owner {
+
+       my ( $owner ) = @_ ;
+
+       my $owner_dump = "$owner" ;
+
+       while( $owner->{object} ) {
+
+               $owner = $owner->{object} ;
+               $owner_dump .= " -> $owner " ;
+       }
+
+       return $owner_dump ;
+}
+
+1 ;
diff --git a/lib/Stem/Demo/CLI.pm b/lib/Stem/Demo/CLI.pm
new file mode 100644 (file)
index 0000000..e5f339c
--- /dev/null
@@ -0,0 +1,171 @@
+package Stem::Demo::CLI ;
+
+print "LOAD\n" ;
+
+use strict;
+
+use base 'Stem::Cell' ;
+
+my $attr_spec = [
+                {
+                 name      => 'reg_name',
+                 help      => <<HELP,
+Name this Cell was registered with.
+HELP
+        },
+                {
+                 name      => 'cell_attr',
+                 class     => 'Stem::Cell',
+                 help      => <<HELP,
+This value is the attributes for the included Stem::Cell which handles
+cloning and sequencing.
+HELP
+        },
+];
+
+sub new {
+
+       my( $class ) = shift ;
+#print "ARGS [@_]\n" ;
+       my $self = Stem::Class::parse_args( $attr_spec, @_ );
+       return $self unless ref $self;
+
+       return $self ;
+}
+
+sub triggered_cell {
+
+       my( $self ) = @_ ;
+
+print "TRIGGERED\n" ;
+
+       $self->cell_activate;
+
+#print $self->SUPER::_dump( "CLI TRIGGERED\n" ) ;
+
+       return;
+}
+
+my %op_to_code = (
+
+       set     => \&_set,
+       get     => \&_get,
+       dump    => \&_dump,
+       clear   => \&_clear,
+       help    => \&_help,
+) ;
+
+sub data_in {
+
+       my( $self, $msg ) = @_;
+
+#print $msg->dump( 'IN' ) ;
+
+       $self->{data_in_msg} = $msg ;
+
+       my $data = $msg->data() ;
+
+       my $op = $data->{op} ;
+
+       if( my $code = $op_to_code{ $op } ) {
+
+               $self->$code( $data ) ;
+       }
+       else {
+
+               $self->send_reply( "unknown CLI op '$op'" ) ;
+       }
+}
+
+sub send_reply {
+
+       my ( $self, $data ) = @_;
+
+       my $in_msg = delete $self->{data_in_msg} ;
+
+       my $reply_msg = $in_msg->reply( type => 'data', data => $data ) ;
+
+#print $reply_msg->dump( 'REPLY' ) ;
+
+       $reply_msg->dispatch() ;
+}
+
+sub _set {
+
+       my( $self, $data ) = @_;
+
+       my $key = $data->{key} ;
+       if ( defined( $key ) ) {
+
+               my $value = $data->{value} ;
+
+               $self->{data}{$key} = $value ;
+
+               $self->send_reply( "set '$key' to '$value'" ) ;
+       }
+       else {
+               $self->send_reply( "set is missing a key" ) ;
+       }
+}
+
+sub _get {
+
+       my( $self, $data ) = @_;
+
+       my $key = $data->{key} ;
+       if ( defined( $key ) ) {
+
+               my $value = $self->{data}{$key} ;
+
+               $self->send_reply( "'$key' was set to '$value'" ) ;
+       }
+       else {
+               $self->send_reply( "get is missing a key" ) ;
+       }
+}
+
+sub _clear {
+
+       my( $self ) = @_;
+
+       $self->{data} = {} ;
+       $self->send_reply( "cleared your data" ) ;
+}
+
+sub _dump {
+
+       my( $self ) = @_;
+
+       my $text = join '', map "\t$_ => $self->{data}{$_}\n",
+                                       sort keys %{$self->{data}} ;
+
+       $self->send_reply( "your data is:\n$text\n" ) ;
+}
+
+sub _help {
+
+       my( $self ) = @_;
+
+       my $text = <<TEXT ;
+
+These are the commands supported in Stem::Demo::CLI
+
+set <name> <value>
+get <name>
+dump
+clear
+help
+
+set sets a value in the CLI session hash
+get gets a value in the CLI session hash
+dump returns a dump of the session hash
+clear will empty the the session hash
+help prints this text
+
+TEXT
+
+       $self->send_reply( $text ) ;
+}
+
+
+1 ;
diff --git a/lib/Stem/Demo/World.pm b/lib/Stem/Demo/World.pm
new file mode 100644 (file)
index 0000000..bf5b3b3
--- /dev/null
@@ -0,0 +1,3 @@
+package Stem::Demo::World ;
+sub hello_cmd { return "Hello World!\n" }
+1 ;
diff --git a/lib/Stem/Event.pm b/lib/Stem/Event.pm
new file mode 100644 (file)
index 0000000..bd80857
--- /dev/null
@@ -0,0 +1,923 @@
+#  File: Stem/Event.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+# this is the base class for all of the other event classes. it
+# provides common services and also stubs for the internal _methods so
+# the other classes don't need to declare them if they don't use them.
+
+package Stem::Event ;
+
+use Stem::Class ;
+
+use strict ;
+
+# this will hold the hashes of events for each event type.
+
+my %all_events = (
+
+       plain   => {},
+       signal  => {},
+       timer   => {},
+       read    => {},
+       write   => {},
+) ;
+
+# table of loop types to the Stem::Event::* class name
+
+my %loop_to_class = (
+
+       event   => 'EventPM',
+       perl    => 'Perl',
+       tk      => 'Tk',
+       wx      => 'Wx',
+#      gtk     => 'Gtk',
+#      qt      => 'Qt',
+) ;
+
+# use the requested event loop and default to perl on windows and
+# event.pm elsewhere.
+
+my $loop_class = _get_loop_class() ;
+
+init_loop() ;
+
+
+sub init_loop {
+
+       $loop_class->_init_loop() ;
+
+Stem::Event::Queue::_init_queue() if defined &Stem::Event::Queue::_init_queue ;
+
+}
+
+sub start_loop {
+
+       $loop_class->_start_loop() ;
+}
+
+sub stop_loop {
+
+       $loop_class->_stop_loop() ;
+}
+
+sub trigger {
+
+       my( $self, $method ) = @_ ;
+
+# never trigger inactive events
+
+       return unless $self->{active} ;
+
+
+       $method ||= $self->{'method'} ;
+#print "METHOD [$method]\n" ;
+
+       $self->{'object'}->$method( $self->{'id'} ) ;
+
+       Stem::Msg::process_queue() if defined &Stem::Msg::process_queue;
+
+       return ;
+}
+
+#################
+# all the stuff below is a rough cell call trace thing. it needs work
+# it would be put inside the trigger method
+# 'log_type' attribute is set or the event type is used.
+#_init subs need to set event_log_type in the object
+#use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+#use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+#      $log_type = $self->{'log_type'} || $self->{'event_type'} ;
+#      TraceStatus "[$log_type] [$object] [$method]\n" ;
+#      $Stem::Event::current_object = $object ;
+#      my ( $cell_name, $target ) = Stem::Route::lookup_cell_name( $object ) ;
+#      if ( $cell_name ) {
+# #            Debug 
+# #                "EVENT $event to $cell_name:$target [$object] [$method]\n" ;
+#      }
+#      else {
+# #            Debug "EVENT $event to [$object] [$method]\n" ;
+#      }
+#################
+
+
+# get all the event objects for an event type
+# this is a class sub.
+
+sub _get_events {
+
+       my( $event_type ) = @_ ;
+
+       my $events = $all_events{ $event_type } ;
+
+       return unless $events ;
+
+       return values %{$events} if wantarray ;
+
+       return $events ;
+}
+
+# initialize the subclass object for this event and store generic event
+# info.
+
+sub _build_core_event {
+
+#print "BAZ\n" ;
+
+       my( $self, $event_type ) = @_ ;
+
+
+#print "EVT [$self] [$event_type]\n" ;
+
+# call and and check the return of the core event constructor
+
+       if ( my $core_event = $self->_build() ) {
+
+# return the error if it was an error string
+
+               return $core_event unless ref $core_event ;
+
+# save the core event
+
+               $self->{core_event} = $core_event ;
+       }
+       
+# mark the event type and track it
+
+       $self->{event_type} = $event_type ;
+       $all_events{ $event_type }{ $self } = $self ;
+
+       return ;
+}
+
+# these are the public versions of the support methods.
+# subclasses can provide a _method to override the stub ones in this class.
+
+sub cancel {
+
+       my( $self ) = @_ ;
+
+       $self->{'active'} = 0 ;
+       delete $self->{'object'} ;
+
+# delete the core object
+
+       if ( my $core_event = delete $self->{core_event} ) {
+
+       # call the core cancel
+
+               $self->_cancel( $core_event ) ;
+       }
+
+# delete this event from the tracking hash
+
+       delete $all_events{ $self->{event_type} }{ $self } ;
+
+       return ;
+}
+
+sub start {
+       my( $self ) = @_ ;
+
+       $self->{'active'} = 1 ;
+       $self->_start( $self->{core_event} ) ;
+
+       return ;
+}
+
+sub stop {
+       my( $self ) = @_ ;
+
+       $self->{'active'} = 0 ;
+       $self->_stop( $self->{core_event} ) ;
+
+       return ;
+}
+
+# stubs for the internal methods that subclasses should override if needed.
+
+sub _init_loop {}
+sub _build {}
+sub _start {}
+sub _stop {}
+sub _reset {}
+sub _cancel {}
+
+use Stem::Debug qw( dump_socket dump_owner dump_data ) ;
+
+sub dump_events {
+
+       print dump_data( \%all_events ) ;
+}
+
+sub dump {
+
+       my( $self ) = @_ ;
+
+       my $event_text = <<TEXT ;
+EV:    $self
+ACT:   $self->{'active'}
+TEXT
+
+       my $obj_dump = dump_owner $self->{'object'} ;
+       $event_text .= <<TEXT ;
+OBJ:   $obj_dump
+METH:  $self->{'method'}
+TEXT
+
+       if ( my $fh = $self->{'fh'} ) {
+
+               my $fh_text = dump_socket( $self->{'fh'} ) ;
+               $event_text .= <<TEXT ;
+FH:    $fh_text
+TEXT
+       }
+
+       if ( $self->{event_type} eq 'timer' ) {
+
+               my $delay = $self->{delay} || 'NONE' ;
+               my $interval = $self->{interval} || 'NONE' ;
+               $event_text .= <<TEXT ;
+DELAY: $delay
+INT:   $interval
+TEXT
+       }
+
+       if ( my $io_timer_event = $self->{'io_timer_event'} ) {
+
+               $event_text = "IO TIMER: >>>>>\n" . $io_timer_event->dump() .
+                               "END\n";
+       }
+
+       return <<DUMP ;
+
+>>>
+$event_text<<<
+
+DUMP
+
+}
+
+#############
+# change this to a cleaner loop style which can handle more event loops and 
+# try them in sequence
+#############
+
+sub _get_loop_class {
+
+       my $loop_type = $Stem::Vars::Env{ 'event_loop' } ||
+                       ($^O =~ /win32/i ? 'perl' : 'event' );
+
+       $loop_type = 'perl' unless $loop_to_class{ $loop_type } ;
+       my $loop_class = "Stem::Event::$loop_to_class{ $loop_type }" ;
+
+       unless ( eval "require $loop_class" ) {
+               die "can't load $loop_class: $@" if $@ && $@ !~ /locate/ ;
+
+               $loop_type = 'perl' ;
+               eval { require Stem::Event::Perl } ;
+               die "can't load event loop Stem::Event::Perl $@" if $@ ;
+       }
+
+       # save the event loop that we loaded.
+
+       #print "using event loop [$loop_type]\n" ;
+       $Stem::Vars::Env{ 'event_loop' } = $loop_type ;
+
+       return $loop_class ;
+}
+
+
+############################################################################
+
+package Stem::Event::Plain ;
+
+BEGIN {
+       @Stem::Event::Plain::ISA = qw( Stem::Event ) ;
+}
+
+=head2 Stem::Event::Plain::new
+
+This class creates an event that will trigger a callback after all
+other pending events have been triggered.
+
+=head2 Example
+
+       $plain_event = Stem::Event::Plain->new( 'object' => $self ) ;
+
+=cut
+
+my $attr_spec_plain = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This object gets the method callbacks
+HELP
+       },
+       {
+               'name'          => 'method',
+               'default'       => 'triggered',
+               'help'          => <<HELP,
+This method is called on the object when the plain event is triggered
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_plain, @_ ) ;
+       return $self unless ref $self ;
+
+       my $err = $self->_core_event_build( 'plain' ) ;
+       return $err if $err ;
+
+       return $self ;
+}
+
+############################################################################
+
+package Stem::Event::Signal ;
+
+BEGIN { our @ISA = qw( Stem::Event ) } ;
+
+=head2 Stem::Event::Signal::new
+
+This class creates an event that will trigger a callback whenever
+its its signal has been received.  
+
+=head2 Example
+
+       $signal_event = Stem::Event::Signal->new( 'object' => $self,
+                                                 'signal' => 'INT' ) ;
+
+       sub sig_int_handler { die "SIGINT\n" }
+
+=cut
+
+my $attr_spec_signal = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This object gets the method callbacks
+HELP
+       },
+       {
+               'name'          => 'method',
+               'help'          => <<HELP,
+This method is called on the object when this event is triggered. The
+default method name for the signal NAME is 'sig_name_handler' (all lower case)
+HELP
+       },
+       {
+               'name'          => 'signal',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the name of the signal to handle. It is used as part of the
+default handler method name.
+HELP
+       },
+       {
+               'name'          => 'active',
+               'default'       => 1,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag marks the event as being active. It can be toggled with the
+start/stop methods.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_signal, @_ ) ;
+       return $self unless ref $self ;
+
+       my $signal = uc $self->{'signal'} ;
+
+       return "Unknown signal: $signal" unless exists $SIG{ $signal } ;
+
+       $self->{'method'} ||= "sig_\L${signal}_handler" ;
+       $self->{'signal'} = $signal ;
+
+       my $err = $self->_build_core_event( 'signal' ) ;
+       return $err if $err ;
+
+#print "SELF SIG $self\nPID $$\n" ;
+
+       return $self ;
+}
+
+
+############################################################################
+
+package Stem::Event::Timer ;
+
+BEGIN { our @ISA = qw( Stem::Event ) } ;
+
+=head2 Stem::Event::Timer::new
+
+This class creates an event that will trigger a callback after a time
+period has elapsed. The initial timer delay is set from the 'delay',
+'at' or 'interval' attributes in that order. If the 'interval'
+attribute is not set, the timer will cancel itself after its first
+triggering (it is a one-shot). The 'hard' attribute means that the
+next interval delay starts before the callback to the object is
+made. If a soft timer is selected (hard is 0), the delay starts after
+the callback returns. So the hard timer ignores the time taken by the
+callback and so it is a more accurate timer. The accuracy a soft timer
+is affected by how much time the callback takes.
+
+=head2 Example
+
+       $timer_event = Stem::Event::Timer->new( 'object' => $self,
+                                               'delay'  => 5,
+                                               'interval'  => 10 ) ;
+
+       sub timed_out { print "timer alert\n" } ;
+
+
+=cut
+
+BEGIN {
+
+my $attr_spec_timer = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This object gets the method callbacks
+HELP
+       },
+       {
+               'name'          => 'method',
+               'default'       => 'timed_out',
+               'help'          => <<HELP,
+This method is called on the object when the timeout is triggered
+HELP
+       },
+       {
+               'name'          => 'delay',
+               'help'          => <<HELP,
+Delay this amount of seconds before triggering the first time. If this
+is not set then the 'at' or 'interval' attributes will be used.
+HELP
+       },
+       {
+               'name'          => 'interval',
+               'help'          => <<HELP,
+Wait this time (in seconds) before any repeated triggers. If not set
+then the timer is a one-shot
+HELP
+       },
+       {
+               'name'          => 'at',
+               'help'          => <<HELP,
+Trigger in the future at this time (in epoch seconds). It will set the intial 
+delay to the different between the current time and the 'at' time.
+HELP
+       },
+       {
+               'name'          => 'hard',
+               'type'          => 'boolean',
+               'default'       => 0,
+               'help'          => <<HELP,
+If this is set, the interval time starts when the event is
+triggered. If it is not set, the interval time starts when the object
+callback has finished. So 'hard' timers repeat closer to equal
+intervals while without 'hard' the repeat time is dependant on how
+long the callback takes.
+HELP
+       },
+       {
+               'name'          => 'active',
+               'default'       => 1,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag marks the event as being active. It can be toggled with the
+start/stop methods.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_timer, @_ ) ;
+       return $self unless ref $self ;
+
+# the delay is either set, or at a future time or the interval
+
+       my $delay = exists( $self->{ 'delay' } ) ?
+                       $self->{ 'delay' } :
+                       exists( $self->{ 'at' } ) ?
+                               $self->{ 'at' } - time() :
+                               $self->{'interval'} ;
+
+#print "INT $self->{'interval'} DELAY $delay\n" ;
+
+# squawk if no delay value
+
+       return "No initial delay was specified for timer"
+               unless defined $delay ;
+
+       $self->{'delay'} = $delay ;
+       $self->{'time_left'} = $delay ;
+
+       my $err = $self->_build_core_event( 'timer' ) ;
+       return $err if $err ;
+
+##########
+# check on this logic
+#########
+
+       $self->_stop unless $self->{'active'} ;
+
+       return $self ;
+}
+
+}
+
+sub reset {
+
+       my( $self, $reset_delay ) = @_ ;
+
+       return unless $self->{'active'} ;
+
+# if we don't get passed a delay, use the interval or the delay attribute
+
+       $reset_delay ||= ($self->{'interval'}) ?
+                       $self->{'interval'} : $self->{'delay'} ;
+
+# track the new delay and reset the real timer (if we are using one)
+
+       $self->{'time_left'} = $reset_delay ;
+
+       $self->_reset( $self->{core_event}, $reset_delay ) ;
+
+       return ;
+}
+
+sub timer_triggered {
+
+       my( $self ) = @_ ;
+
+#print time(), " TIMER TRIG\n" ;
+#use Carp qw( cluck ) ;
+#cluck ;
+
+# check if this is a one-shot timer
+
+       $self->cancel() unless $self->{'interval'} ;
+
+# reset the timer count before the trigger code for hard timers
+#(trigger on fixed intervals)
+
+       $self->reset( $self->{'interval'} ) if $self->{'hard'};
+
+       $self->trigger() ;
+
+# reset the timer count before the trigger code for soft timers
+#(trigger on at least fixed intervals)
+
+       $self->reset( $self->{'interval'} ) unless $self->{'hard'};
+}
+
+############################################################################
+
+####################################################################
+# common methods for the Read/Write event classes to handle the optional
+# I/O timeouts.
+# these override Stem::Event's methods and then call those via SUPER::
+
+package Stem::Event::IO ;
+
+BEGIN { our @ISA = qw( Stem::Event ) } ;
+
+sub init_io_timeout {
+
+       my( $self ) = @_ ;
+
+       my $timeout = $self->{'timeout'} ;
+       return unless $timeout ;
+
+       $self->{'io_timer_event'} = Stem::Event::Timer->new(
+               'object'        => $self,
+               'interval'      => $timeout,
+       ) ;
+
+       return ;
+}
+
+sub cancel {
+
+       my( $self ) = @_ ;
+
+#print "IO CANCEL $self\n" ;
+
+       if ( my $io_timer_event = delete $self->{'io_timer_event'} ) {
+               $io_timer_event->cancel() ;
+       }
+
+       $self->SUPER::cancel() ;
+
+       delete $self->{'fh'} ;
+
+       return ;
+}
+
+sub start {
+
+       my( $self ) = @_ ;
+
+       if ( my $io_timer_event = $self->{'io_timer_event'} ) {
+               $io_timer_event->start() ;
+       }
+
+       $self->SUPER::start() ;
+
+       return ;
+}
+
+sub stop {
+
+       my( $self ) = @_ ;
+
+       $self->{'active'} = 0 ;
+
+       if ( my $io_timer_event = $self->{'io_timer_event'} ) {
+               $io_timer_event->stop() ;
+       }
+
+       $self->SUPER::stop() ;
+
+       return ;
+}
+
+sub timed_out {
+
+       my( $self ) = @_ ;
+
+#      $self->{log_type} = "$self->{'event_type'}_timeout" ;
+       $self->trigger( $self->{'timeout_method'} ) ;
+}
+
+#######################################################
+
+package Stem::Event::Read ;
+
+BEGIN { our @ISA = qw( Stem::Event::IO ) }
+
+=head2 Stem::Event::Read::new
+
+This class creates an event that will trigger a callback whenever
+its file descriptor has data to be read.  It takes an optional timeout
+value which will trigger a callback to the object if no data has been
+read during that period.
+
+Read events are active when created - a call to the stop method is
+needed to deactivate them.
+
+=cut
+
+BEGIN {
+
+my $attr_spec_read = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This object gets the method callbacks
+HELP
+       },
+       {
+               'name'          => 'fh',
+               'required'      => 1,
+               'type'          => 'handle',
+               'help'          => <<HELP,
+This file handle is checked if it has data to read
+HELP
+       },
+       {
+               'name'          => 'timeout',
+               'help'          => <<HELP,
+How long to wait (in seconds) without being readable before calling
+the timeout method
+HELP
+       },
+       {
+               'name'          => 'method',
+               'default'       => 'readable',
+               'help'          => <<HELP,
+This method is called on the object when the file handle has data to read
+HELP
+       },
+       {
+               'name'          => 'timeout_method',
+               'default'       => 'read_timeout',
+               'help'          => <<HELP,
+This method is called on the object when the hasn't been readable
+after the timeout period
+HELP
+       },
+       {
+               'name'          => 'active',
+               'default'       => 1,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag marks the event as being active. It can be toggled with the
+start/stop methods.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_read, @_ ) ;
+       return $self unless ref $self ;
+
+#      return <<ERR unless defined fileno $self->{fh} ;
+# Stem::Event::Read: $self->{fh} is not an open handle
+# ERR
+
+       my $err = $self->_build_core_event( 'read' ) ;
+       return $err if $err ;
+
+       $self->init_io_timeout() ;
+
+       return $self ;
+}
+
+}
+############################################################################
+
+package Stem::Event::Write ;
+
+BEGIN { our @ISA = qw( Stem::Event::IO ) } ;
+
+=head2 Stem::Event::Write::new
+
+This class creates an event that will trigger a callback whenever
+its file descriptor can be written to.  It takes an optional timeout
+value which will trigger a callback to the object if no data has been
+written during that period.
+
+Write events are stopped when created - a call to the start method is
+needed to activate them.
+
+=cut
+
+my $attr_spec_write = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This object gets the method callbacks
+HELP
+       },
+       {
+               'name'          => 'fh',
+               'required'      => 1,
+               'type'          => 'handle',
+               'help'          => <<HELP,
+This file handle is checked if it is writeable
+HELP
+       },
+       {
+               'name'          => 'timeout',
+               'help'          => <<HELP,
+How long to wait (in seconds) without being writeable before calling
+the timeout method
+HELP
+       },
+       {
+               'name'          => 'method',
+               'default'       => 'writeable',
+               'help'          => <<HELP,
+This method is called on the object when the file handle is writeable
+HELP
+       },
+       {
+               'name'          => 'timeout_method',
+               'default'       => 'write_timeout',
+               'help'          => <<HELP,
+This method is called on the object when the hasn't been writeable
+after the timeout period
+HELP
+       },
+       {
+               'name'          => 'active',
+               'default'       => 0,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag marks the event as being active. It can be toggled with the
+start/stop methods.
+NOTE: Write events are not active by default.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_write, @_ ) ;
+       return $self unless ref $self ;
+
+       my $err = $self->_build_core_event( 'write' ) ;
+       return $err if $err ;
+
+#print $self->dump_events() ;
+
+       $self->init_io_timeout() ;
+
+       $self->stop() unless $self->{'active'} ;
+
+#print $self->dump() ;
+
+       return $self ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/EventPM.pm b/lib/Stem/Event/EventPM.pm
new file mode 100644 (file)
index 0000000..425126d
--- /dev/null
@@ -0,0 +1,242 @@
+#  File: Stem/Event/EventPM.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+#print "required" ;
+
+=head1 Stem::Event::EventPM
+
+This module wraps the CPAN module Event.pm for use by the rest of
+Stem. It provides the common API for the standard Stem::Event classes:
+
+=over 4
+
+=item Stem::Event
+=item Stem::Event::Plain
+=item Stem::Event::Timer
+=item Stem::Event::Signal
+=item Stem::Event::Read
+=item Stem::Event::Write
+
+=back
+
+=cut
+
+package Stem::Event::EventPM ;
+
+use strict ;
+use Event ;
+
+@Stem::Event::EventPM::ISA = qw( Stem::Event ) ;
+
+# basic wrappers for top level Event.pm calls.
+
+sub _start_loop {
+       $Event::DIED = \&_died ;
+       Event::loop() ;
+}
+
+sub _died {
+       my( $event, $err ) = @_ ;
+        use Carp;
+       Carp::cluck( "Stem::Event died: $err", "die called in [$event]\n",
+                     map( "<$_>", caller() ), "\n" ) ;
+
+       exit;
+} ;
+
+
+sub _stop_loop {
+       Event::unloop_all( 1 ) ;
+}
+
+############################################################################
+
+package Stem::Event::Plain ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+       
+# create the plain event watcher
+
+       $self->{'idle_event'} = Event->idle(
+               'cb'            => [ $self, 'idle_triggered' ],
+               'repeat'        => 0
+       ) ;
+
+       return $self ;
+}
+
+sub idle_triggered {
+
+       my( $self ) = @_ ;
+
+       $self->trigger( 'plain' ) ;
+       my $idle_event = delete $self->{'idle_event'} ;
+       $idle_event->cancel() ;
+}
+
+############################################################################
+
+package Stem::Event::Signal ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+       my $signal = $self->{'signal'} ;
+
+# create the signal event watcher
+
+       return Event->signal(
+               'cb'            => sub { $self->trigger() },
+               'signal'        => $signal,
+       ) ;
+}
+
+sub _cancel {
+       my( $self, $signal_event ) = @_ ;
+       $signal_event->cancel() ;
+       return ;
+}
+
+############################################################################
+
+package Stem::Event::Timer ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+       return Event->timer(
+               'cb'            => [ $self, 'timer_triggered' ],
+               'hard'          => $self->{'hard'},
+               'after'         => $self->{'delay'},
+               'interval'      => $self->{'interval'},
+       ) ;
+}
+
+sub _reset {
+       my( $self, $timer_event, $delay ) = @_ ;
+       $timer_event->again() ;
+       return ;
+}
+
+sub _cancel {
+       my( $self, $timer_event ) = @_ ;
+       $timer_event->cancel() ;
+       return ;
+}
+
+sub _start {
+       my( $self, $timer_event ) = @_ ;
+       $timer_event->start() ;
+       return ;
+}
+
+sub _stop {
+       my( $self, $timer_event ) = @_ ;
+       $timer_event->stop() ;
+       return ;
+}
+
+############################################################################
+
+package Stem::Event::Read ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+# create the read event watcher
+
+       return Event->io(
+               'cb'    => sub { $self->trigger() },
+               'fd'    => $self->{'fh'},
+               'poll'  => 'r',
+       ) ;
+}
+
+sub _cancel {
+       my( $self, $read_event ) = @_ ;
+       $read_event->cancel() ;
+       return ;
+}
+
+sub _start {
+       my( $self, $read_event ) = @_ ;
+       $read_event->start() ;
+       return ;
+}
+
+sub _stop {
+       my( $self, $read_event ) = @_ ;
+       $read_event->stop() ;
+       return ;
+}
+
+############################################################################
+
+package Stem::Event::Write ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+# create the write event watcher
+
+# create the read event watcher
+
+       return Event->io(
+               'cb'    => sub { $self->trigger() },
+               'fd'    => $self->{'fh'},
+               'poll'  => 'w',
+       ) ;
+
+       return $self ;
+}
+
+sub _cancel {
+       my( $self, $write_event ) = @_ ;
+       $write_event->cancel() ;
+       return ;
+}
+
+sub _start {
+       my( $self, $write_event ) = @_ ;
+       $write_event->start() ;
+       return ;
+}
+
+sub _stop {
+       my( $self, $write_event ) = @_ ;
+       $write_event->stop() ;
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/Perl.pm b/lib/Stem/Event/Perl.pm
new file mode 100644 (file)
index 0000000..f614da7
--- /dev/null
@@ -0,0 +1,217 @@
+#  File: Stem/Event/Perl.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+=head1 Stem::Event::Perl
+
+This module is a pure Perl event loop. It requires Perl 5.8 (or
+better) which has safe signal handling.  It provides the common event
+API for the standard classes:
+
+=cut
+
+package Stem::Event::Perl ;
+
+use strict ;
+use Stem::Event::Signal ;
+
+@Stem::Event::Perl::ISA = qw( Stem::Event ) ;
+
+BEGIN {
+
+       unless ( eval { require Time::HiRes } ) {
+
+               Time::HiRes->import( qw( time ) ) ;
+       }
+}
+
+# get the hashes for each of the event types
+
+my ( $signal_events, $timer_events, $read_events, $write_events ) =
+       map scalar( Stem::Event::_get_events( $_ )), qw( signal timer
+       read write ) ;
+
+sub _start_loop {
+
+#print "PERL START\n" ;
+
+       while( keys %{$timer_events}  ||
+              keys %{$signal_events} ||
+              keys %{$read_events}   ||
+              keys %{$write_events} ) {
+
+               my $timeout = find_min_delay() ;
+
+#print "TIMEOUT [$timeout]\n" ;
+
+               my $time = time() ;
+
+               _one_time_loop( $timeout ) ;
+
+               my $delta_time = time() - $time ;
+               trigger_timer_events( $delta_time ) ;
+       }
+}
+
+sub _one_time_loop {
+
+       my( $timeout ) = @_ ;
+
+# force a no wait select call if no timeout was passed in
+
+       $timeout ||= 0 ;
+
+#print "ONE TIME $timeout\n" ;
+# use Carp qw( cluck ) ;
+# cluck ;
+
+# print "\n\n********EVENT LOOP\n\n" ;
+# print "READ EVENTS\n", map $_->dump(), values %{$read_events} ;
+# print "WRITE EVENTS\n", map $_->dump(), values %{$write_events} ;
+
+       my $read_vec = make_select_vec( $read_events ) ;
+       my $write_vec = make_select_vec( $write_events ) ;
+
+#print "R BEFORE ", unpack( 'b*', $read_vec), "\n" ;
+#print "W BEFORE ", unpack( 'b*', $write_vec), "\n" ;
+
+
+       my $cnt = select( $read_vec, $write_vec, undef, $timeout ) ;
+
+#print "SEL CNT [$cnt]\n" ;
+#print "R AFTER ", unpack( 'b*', $read_vec), "\n" ;
+#print "W AFTER ", unpack( 'b*', $write_vec), "\n" ;
+
+       trigger_select_vec( 'read',  $read_events, $read_vec ) ;
+       trigger_select_vec( 'write', $write_events, $write_vec,  ) ;
+
+#print "\n\n********END EVENT LOOP\n\n" ;
+
+}
+
+sub _stop_loop {
+
+       $_->cancel() for values %{$signal_events},
+                        values %{$timer_events},
+                        values %{$read_events},
+                        values %{$write_events} ;
+}
+
+sub find_min_delay {
+
+       my $min_delay = 0 ;
+
+       while( my( undef, $event ) = each %{$timer_events} ) {
+
+               if ( $event->{'time_left'} < $min_delay || $min_delay == 0 ) {
+
+                       $min_delay = $event->{'time_left'} ;
+
+#print "MIN [$min_delay]\n" ;
+               }
+       }
+
+       return unless $min_delay ;
+
+       return $min_delay ;
+}
+
+sub trigger_timer_events {
+
+       my( $delta ) = @_ ;
+
+#print "TIMER DELTA $delta\n" ;
+
+       while( my( undef, $event ) = each %{$timer_events} ) {
+
+#print $event->dump() ;
+
+               next unless $event->{'active'} ;
+
+               next unless ( $event->{'time_left'} -= $delta ) <= 0 ;
+
+               $event->timer_triggered() ;
+       }
+}
+
+sub make_select_vec {
+
+       my( $io_events ) = @_ ;
+
+       my $select_vec = '' ;
+
+       while( my( undef, $event ) = each %{$io_events} ) {
+
+#print "make F: [", fileno $event->{'fh'}, "] ACT [$event->{'active'}]\n" ;
+
+               unless ( defined fileno $event->{'fh'} ) {
+
+#print "BAD FH $event->{'fh'}\n" ;
+print "\n\n***EVENT BAD FH\n", $event->dump() ;
+
+                       $event->cancel() ;
+               }
+
+               next unless $event->{'active'} ;
+               vec( $select_vec, fileno $event->{'fh'}, 1 ) = 1 ;
+       }
+
+       return $select_vec ;
+}
+
+sub trigger_select_vec {
+
+       my( $event_type, $io_events, $select_vec ) = @_ ;
+
+       while( my( undef, $event ) = each %{$io_events} ) {
+
+               next unless $event->{'active'} ;
+               if ( vec( $select_vec, fileno $event->{'fh'}, 1 ) ) {
+
+                       $event->trigger() ;
+               }
+       }
+
+       return ;
+}
+
+############################################################################
+
+package Stem::Event::Plain ;
+
+######
+# right now we trigger plain events when they are created. this should
+# change to a queue and trigger after i/o and timer events
+######
+
+sub _build {
+       my( $self ) = @_ ;
+       $self->trigger() ;
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/Queue.pm b/lib/Stem/Event/Queue.pm
new file mode 100644 (file)
index 0000000..e6549d9
--- /dev/null
@@ -0,0 +1,110 @@
+#  File: Stem/Event/Queue.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+# this class provides a way to deliver certain events and messages
+# synchronously with the main event loop. this is done by queueing the
+# actual event/message and writing a byte down a special pipe used
+# only inside this process. the other side of the pipe has a read
+# event that when triggered will then deliver the queued
+# events/messages.
+
+# when using Stem::Event::Signal you need to use this module as
+# well. perl signals will be delivered (safely) between perl
+# operations but they could then be delivered inside an executing
+# event handler and that means possible corruption. so this module
+# allows those signal events to be delivered by the event loop itself.
+
+
+package Stem::Event::Queue ;
+
+use strict ;
+use warnings ;
+
+use Socket;
+use IO::Handle ;
+
+use base 'Exporter' ;
+our @EXPORT = qw( &mark_not_empty ) ;
+
+my( $queue_read, $queue_write, $queue_read_event ) ;
+
+my $self ;
+
+sub _init_queue {
+
+       socketpair( $queue_read, $queue_write,
+                AF_UNIX, SOCK_STREAM, PF_UNSPEC ) || die <<DIE ;
+can't create socketpair $!
+DIE
+
+#print fileno( $queue_read ), " FILENO\n" ;
+
+       $self = bless {} ;
+
+       $queue_read->blocking( 0 ) ;
+       $queue_read_event = Stem::Event::Read->new(
+               'object'        => $self,
+               'fh'            => $queue_read,
+       ) ;
+
+       ref $queue_read_event or die <<DIE ;
+can't create Stem::Event::Queue read event: $queue_read_event
+DIE
+
+}
+
+my $queue_is_marked ;
+
+sub mark_not_empty {
+
+       my( $always_mark ) = @_ ;
+
+# don't mark the queue if it is already marked and we aren't forced
+# the signal queue always marks the queue
+
+       return if $queue_is_marked && !$always_mark ;
+
+       syswrite( $queue_write, 'x' ) ;
+
+       $queue_is_marked = 1 ;
+}
+
+sub readable {
+
+       sysread( $queue_read, my $buf, 10 ) ;
+
+       $queue_is_marked = 0 ;
+
+#      Stem::Event::Plain::process_queue();
+       Stem::Event::Signal::process_signal_queue();
+#      Stem::Msg::process_queue() if defined &Stem::Msg::process_queue;
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/Signal.pm b/lib/Stem/Event/Signal.pm
new file mode 100644 (file)
index 0000000..c9d723e
--- /dev/null
@@ -0,0 +1,80 @@
+package Stem::Event::Signal ;
+
+use Stem::Event::Queue ;
+
+use strict ;
+use warnings ;
+
+use base 'Exporter' ;
+our @EXPORT = qw( process_signal_queue ) ;
+
+# this generic signal event code needs the safe signals of perl 5.8+
+
+use 5.008 ;
+
+my %signal2event ;
+
+my @signal_queue ;
+my %cached_handlers ;
+
+# this sub will cache the handler closures so we can reuse them. 
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+       my $signal = $self->{'signal'} ;
+
+       $self->{'method'} ||= "sig_\L${signal}_handler" ;
+
+# create the signal event handler and cache it.
+# we cache them so we can reuse these closures and never leak
+
+       $SIG{ $signal } = $cached_handlers{$signal} ||=
+               sub {
+                       mark_not_empty() ;
+#print "HIT $signal\n";
+                       push @signal_queue, $signal
+               } ;
+
+# track the event object for this signal
+
+       $signal2event{$signal} = $self ;
+
+#print "$signal = $SIG{ $signal }\n" ;
+       return ;
+}
+
+sub _cancel {
+
+       my( $self ) = @_ ;
+
+       $SIG{ $self->{'signal'} } = 'DEFAULT' ;
+
+       return ;
+}
+
+sub process_signal_queue {
+
+       my $sig_count = @signal_queue ;
+
+#print "PROCESS SIGNAL Q $sig_count\n" ;
+
+# return if we have no pending signals
+
+       return $sig_count unless $sig_count ;
+
+       while( my $signal = shift @signal_queue ) {
+
+               my $event = $signal2event{ $signal } ;
+
+               next unless $event ;
+               next unless $event->{'active'} ;
+
+               $event->trigger() ;
+       }
+
+       return $sig_count ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/Tk.pm b/lib/Stem/Event/Tk.pm
new file mode 100644 (file)
index 0000000..5b174d7
--- /dev/null
@@ -0,0 +1,210 @@
+#  File: Stem/Event/Tk.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+=head1 Stem::Event::Tk
+
+This module wraps the CPAN module Event.pm for use by the rest of
+Stem. It provides the common API for the standard Stem::Event classes:
+
+=over 4
+
+=item Stem::Event
+=item Stem::Event::Plain
+=item Stem::Event::Timer
+=item Stem::Event::Signal
+=item Stem::Event::Read
+=item Stem::Event::Write
+
+=back
+
+=cut
+
+package Stem::Event::Tk ;
+
+use strict ;
+use Tk ;
+
+use Stem::Event::Signal ;
+
+my $tk_main_window ;
+
+# basic wrappers for top level Tk.pm calls.
+
+sub _init_loop {
+
+       $tk_main_window ||= MainWindow->new() ;
+       $tk_main_window->withdraw() ;
+}
+
+sub _start_loop {
+       _init_loop() ;
+       MainLoop() ;
+}
+
+sub _stop_loop {
+
+#print "STOP INFO ", $tk_main_window->afterInfo(), "\n" ;
+
+       $tk_main_window->destroy() ;
+       $tk_main_window = undef ;
+}
+
+############################################################################
+
+package Stem::Event::Plain ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+       
+# create the plain event watcher
+
+       $self->{'idle_event'} = Event->idle(
+               'cb'            => [ $self, 'idle_triggered' ],
+               'repeat'        => 0
+       ) ;
+
+       return $self ;
+}
+
+sub idle_triggered {
+
+       my( $self ) = @_ ;
+
+       $self->trigger() ;
+       my $idle_event = delete $self->{'idle_event'} ;
+       $idle_event->cancel() ;
+}
+
+############################################################################
+
+package Stem::Event::Timer ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+
+Stem::Event::Tk::_init_loop() ;
+
+# tk times in milliseconds and stem times in floating seconds so
+# we convert to integer ms.
+
+       my $delay_ms = int( $self->{'delay'} * 1000 ) ;
+
+#      $self->{interval_ms} = int( ( $self->{'interval'} || 0 ) * 1000 ) ;
+
+       my $timer_method = $self->{'interval'} ? 'repeat' : 'after' ;
+
+       return $tk_main_window->$timer_method(
+                               $delay_ms,
+                               [$self => 'timer_triggered']
+       ) ;
+}
+
+sub _reset {
+
+       my( $self, $timer_event, $delay ) = @_ ;
+       my $delay_ms = int( $delay * 1000 ) ;
+       $timer_event->time( $delay_ms ) ;
+}
+
+sub _cancel {
+       my( $self, $timer_event ) = @_ ;
+       $timer_event->cancel() ;
+       return ;
+}
+
+############################################################################
+
+package Stem::Event::Read ;
+
+sub _build {
+
+       my( $self ) = @_ ;
+       goto &_start if $self->{active} ;
+       return ;
+}
+
+sub _start {
+
+       my( $self ) = @_ ;
+
+       return $tk_main_window->fileevent(
+               $self->{'fh'},
+               'readable',
+               [$self => 'trigger']
+       ) ;
+}
+
+sub _cancel { goto &_stop }
+
+sub _stop {
+       my( $self ) = @_ ;
+
+       $tk_main_window->fileevent(
+               $self->{'fh'},
+               'readable',
+               ''
+       ) ;
+}
+
+############################################################################
+
+package Stem::Event::Write ;
+
+sub _build {
+       my( $self ) = @_ ;
+       goto &_start if $self->{active} ;
+       return ;
+}
+
+sub _start {
+
+       my( $self ) = @_ ;
+
+       return $tk_main_window->fileevent(
+               $self->{'fh'},
+               'writable',
+               [$self => 'trigger']
+       ) ;
+}
+
+sub _cancel { goto &_stop }
+
+sub _stop {
+
+       my( $self ) = @_ ;
+
+       $tk_main_window->fileevent(
+               $self->{'fh'},
+               'writable',
+               ''
+       ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Event/Wx.pm b/lib/Stem/Event/Wx.pm
new file mode 100644 (file)
index 0000000..c71bcfe
--- /dev/null
@@ -0,0 +1,148 @@
+#  File: Stem/Event/Wx.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+=head1 Stem::Event::Wx
+
+This module is a pure Perl event loop. It requires Perl 5.8 (or
+better) which has safe signal handling.  It provides the common event
+API for the standard classes:
+
+=cut
+
+package Stem::Event::Wx ;
+
+use strict ;
+
+use base qw( Stem::Event ) ;
+use Stem::Event::Perl ;
+use Wx ;
+
+my $app = Stem::Event::Wx::App->new() ;
+my $wx_timer = Stem::Event::Wx::Timer->new() ;
+
+# this will call the io_poll_timer method in $wx_timer's class
+
+my $io_poll_timer = Stem::Event::Timer->new(
+       object          => $wx_timer,
+       interval        => 1,                   # .1 second poll
+       method          => 'io_poll_timer',
+) ;
+
+sub _start_loop {
+
+# _build just sets the min delay for the wx timer. this will make sure
+# any timer events get going when we start the loop.
+
+       Stem::Event::Timer::_build() ;
+       Wx::wxTheApp->MainLoop() ;
+}
+
+sub _stop_loop {
+
+       Wx::wxTheApp->ExitMainLoop() ;
+}
+
+
+package Stem::Event::Timer ;
+
+sub _build {
+
+       my $min_delay = Stem::Event::Perl::find_min_delay() ;
+       $wx_timer->set_wx_timer_delay( $min_delay ) ;
+       return ;
+}
+
+############################################################################
+
+# this class subclasses Wx::Timer and its Notify method will be called
+# after the current delay.
+
+package Stem::Event::Wx::Timer ;
+
+use base qw( Wx::Timer ) ;
+
+BEGIN {
+
+       unless ( eval { require Time::HiRes } ) {
+
+               Time::HiRes->import( qw( time ) ) ;
+       }
+}
+
+my $last_time ;
+
+sub set_wx_timer_delay {
+
+       my( $self, $delay ) = @_ ;
+
+#print "WX DELAY [$delay]\n" ;
+       if ( $delay ) {
+
+               $self->Start( int( $delay * 1000 ), 0 );
+               $last_time = time() ;
+               return ;
+       }
+
+       $self->Stop();
+}
+
+# Wx calls this method when its timers get triggered. this is the only
+# wx timer callback in this wrapper. all the others are handled with
+# perl in Event.pm and Event/Perl.pm
+
+sub Notify {
+
+#print "NOTIFY\n" ;
+       my $delta_time = time() - $last_time ;
+       my $min_delay = Stem::Event::Perl::find_min_delay() ;
+       $wx_timer->set_wx_timer_delay( $min_delay ) ;
+       Stem::Event::Perl::trigger_timer_events( $delta_time ) ;
+}
+
+sub io_poll_timer {
+
+#print "IO POLL\n" ;
+
+       Stem::Event::Perl::_one_time_loop() ;
+}
+
+
+############################################################################
+
+# this class is needed to subclass Wx::App and to make our own
+# WxApp. it needs to provide OnInit which is called at startup and has
+# to return true.
+
+package Stem::Event::Wx::App ;
+
+use base 'Wx::App' ;
+sub OnInit { return( 1 ) }
+
+1 ;
+
+__END__
diff --git a/lib/Stem/File.pm b/lib/Stem/File.pm
new file mode 100644 (file)
index 0000000..f057a40
--- /dev/null
@@ -0,0 +1,113 @@
+#  File: Stem/File.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::File ;
+
+use strict ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'path',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is just the path to the file. Given Unix conventions, the path
+may include the full file name from the root.  It's required.
+HELP
+       },
+
+       {
+               'name'          => 'mode',
+               'default'       => 'read',
+               'help'          => <<HELP,
+Can be read (default), write, or read/write.  Indicates how the file
+is to be opened using Unix conventions.
+HELP
+       },
+
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return $self ;
+}
+
+
+sub msg_in {
+
+       my( $self, $msg ) = @_ ;
+
+       my $type = $msg->type() ;
+
+#print $msg->dump( 'switch' ) ;
+
+       if ( $type eq 'cmd' ) {
+
+               $self->cmd_in( $msg ) ;
+               return ;
+       }
+}
+
+
+sub read {
+
+       my( $self, $read_size_wanted ) = @_ ;
+
+
+}
+
+sub read_line {
+
+       my( $self, $read_size_wanted ) = @_ ;
+
+       $self->{'handle'}->readline() ;
+}
+
+sub write {
+
+       my( $self, $write_data ) = @_ ;
+
+       $self->{'handle'}->write( $write_data ) ;
+}
+
+sub close {
+
+       my( $self ) = @_ ;
+
+       $self->{'handle'}->close() ;
+
+       delete( $self->{'handle'} ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Gather.pm b/lib/Stem/Gather.pm
new file mode 100644 (file)
index 0000000..b67eea1
--- /dev/null
@@ -0,0 +1,383 @@
+#  File: Stem/Gather.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Gather ;
+
+#use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+#use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+=head1 Description
+
+This is a object module used by Stem Cells and objects to detect when
+a set of asynchronous events have finished. It is constructed by an
+owner object which then stores it in itselt. Gather objects are
+initialized with a set of keys to be gathered. When the owner object
+is notified of an event, it calls the C<gathered> method of the gather
+object with a list of keys. When all of the keys are gathered, a
+callback is made to the owner object. An optional timeout is available
+which will also generate a callback if the keys are not gathered in
+time.
+
+=head1 Synopsis
+
+       use Stem::Gather ;
+
+        # $self is the owner object that has already been created
+
+       my $gather = Stem::Gather->new(
+               'object'        => $self,
+               'keys'          => [qw( msg1 msg2 )]
+       ) ;
+
+       $self->{'gather'} = $gather ;
+
+       sub msg1_in {
+
+               my( $self ) = @_ ;
+               $self->{'gather'}->gathered( 'msg1' ) ;
+       }
+
+       sub msg2_in {
+
+               my( $self ) = @_ ;
+               $self->{'gather'}->gathered( 'msg2' ) ;
+       }
+
+       sub gather_done {
+
+               my( $self ) = @_ ;
+
+               print "we have gathered\n" ;
+       }
+
+=cut
+
+use strict ;
+
+my %class_to_attr_name ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This is the owner object which has the methods that get called when Stem::Gather
+has either finished gathering all of the keys or it has timed out.
+HELP
+       },
+       {
+               'name'          => 'keys',
+               'required'      => 1,
+               'type'          => 'list',
+               'help'          => <<HELP,
+This is the list of keys to gather.
+HELP
+       },
+       {
+               'name'          => 'gathered_method',
+               'default'       => 'gather_done',
+               'help'          => <<HELP,
+This method is called in the owner object when all of the keys are gathered.
+HELP
+       },
+       {
+               'name'          => 'no_start',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+If set, then do not start the gather object upon creation. A call to
+the C<restart> must be made. This only meaningful if this gather has a
+timeout set.
+HELP
+       },
+       {
+               'name'          => 'timeout',
+               'help'          => <<HELP,
+This is an optional timeout period (in seconds) waiting for the gather
+to be completed
+HELP
+       },
+       {
+               'name'          => 'timeout_method',
+               'default'       => 'gather_timeout',
+               'help'          => <<HELP,
+This method is called in the owner object if the gather timed out
+before all keys were gathered.
+HELP
+       },
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::Gather
+
+=over 4
+
+
+=item * Attribute - B<object>
+
+=over 4
+
+
+=item Description:
+This is the owner object which has the methods that get called when Stem::Gather
+has either finished gathering all of the keys or it has timed out.
+
+
+=item Its B<type> is: object
+
+=item It is B<required>.
+
+=back
+
+=item * Attribute - B<keys>
+
+=over 4
+
+
+=item Description:
+This is the list of keys to gather.
+
+
+=item Its B<type> is: list
+
+=item It is B<required>.
+
+=back
+
+=item * Attribute - B<gathered_method>
+
+=over 4
+
+
+=item Description:
+This method is called in the owner object when all of the keys are gathered.
+
+
+=item It B<defaults> to: gather_done
+
+=back
+
+=item * Attribute - B<no_start>
+
+=over 4
+
+
+=item Description:
+If set, then do not start the gather object upon creation. A call to
+the C<restart> must be made. This only meaningful if this gather has a
+timeout set.
+
+
+=item Its B<type> is: boolean
+
+=back
+
+=item * Attribute - B<timeout>
+
+=over 4
+
+
+=item Description:
+This is an optional timeout period (in seconds) waiting for the gather
+to be completed
+
+
+=back
+
+=item * Attribute - B<timeout_method>
+
+=over 4
+
+
+=item Description:
+This method is called in the owner object if the gather timed out
+before all keys were gathered.
+
+
+=item It B<defaults> to: gather_timeout
+
+=back
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+
+
+
+=head2 Method new
+
+This is the constructor method for Stem::Gather. It uses the standard
+Stem key/value API with the
+
+=cut
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+#      return 'Stem::Gather "keys" is not an array reference'
+#              unless ref $self->{'keys'} eq 'ARRAY' ;
+
+       $self->restart() unless $self->{'no_start'} ;
+
+       return( $self ) ;
+}
+
+=head2 Method restart
+
+This method is called to start up the gather object when it has
+already gathered all the keys, it has timed out or it was never
+started (the no_start attribute was enabled). It takes no arguments.
+
+=cut
+
+
+sub restart {
+
+       my( $self ) = @_ ;
+
+       $self->{'gathered'} = 0 ;
+
+       $self->{'keys_left'} = { map { $_, 1 } @{$self->{'keys'}} } ;
+
+#      TraceStatus "GAT keys '@{$self->{'keys'}}'" ;
+
+       $self->_cancel_timeout() ;
+
+       if ( my $timeout = $self->{'timeout'} ) {
+
+               $self->{'timer_event'} = Stem::Event::Timer->new(
+                               'object'        => $self,
+                               'delay'         => $timeout, 
+                               'hard'          => 1,
+                               'repeat'        => 0 ) ;
+       }
+}
+
+=head2 Method add_keys
+
+This method is passed a list of keys which will be added to the list
+to be watched for by the Stem::Gather object. The new keys are not
+looked for until a call to the C<restart> method is made.
+
+=cut
+
+sub add_keys {
+
+       my( $self, @keys ) = @_ ;
+
+       push @{$self->{'keys'}}, @keys ;
+}
+
+=head2 Method gathered
+
+This method is called with a list of keys that are gathered. The keys
+that haven't been gathered before are marked as gathered. If there are
+no more keys to be gathered, the method in the C<gathered_method>
+attribute is called in the owner object. You have to call the
+C<restart> method on this gather object to use it again.You can pass
+this methods keys that have been gathered or are not even in the list
+to be gathered and they are ignored.
+
+=cut
+
+sub gathered {
+
+       my( $self, @keys ) = @_ ;
+
+#      TraceStatus "gathered: @keys" ;
+
+       return if $self->{'gathered'} ;
+
+       delete @{$self->{'keys_left'}}{@keys} ;
+
+       return if keys %{$self->{'keys_left'}} ;
+
+       $self->_cancel_timeout() ;
+       $self->{'gathered'} = 1 ;
+
+       my $method = $self->{'gathered_method'} ;
+
+#      TraceStatus "gathered done: calling $method" ;
+
+       return $self->{'object'}->$method() ;
+}
+
+sub timed_out {
+
+       my( $self ) = @_ ;
+
+       $self->_cancel_timeout() ;
+
+       my $method = $self->{'timeout_method'} ;
+       $self->{'object'}->$method() ;
+
+       return ;
+}
+
+sub _cancel_timeout {
+
+       my( $self ) = @_ ;
+
+       if ( my $timer = $self->{'timer_event'} ) {
+               $timer->cancel() ;
+
+               delete $self->{'timer_event'} ;
+       }
+}
+
+=head2 Method
+
+This method B<must> be called if the owner object is being shut down or
+destroyed. It will cancel any pending timeout and break the link back
+to the owner object. The owner object can then be destroyed without
+leaking memory.
+
+=cut
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+       $self->_cancel_timeout() ;
+
+       delete $self->{'object'} ;
+}
+
+1 ;
diff --git a/lib/Stem/Hub.pm b/lib/Stem/Hub.pm
new file mode 100644 (file)
index 0000000..936959c
--- /dev/null
@@ -0,0 +1,126 @@
+#  File: Stem/Hub.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Hub ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+use strict ;
+use Carp ;
+use Sys::Hostname ;
+
+use Stem::Vars ;
+
+$Stem::Vars::Hub_name = '' ;
+$Stem::Vars::Program_name = $0 ;
+$Stem::Vars::Host_name = hostname() ;
+
+Stem::Route::register_class( __PACKAGE__, 'hub' ) ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+The registration name is used to name this Hub.
+HELP
+       },
+
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::Hub
+
+=over 4
+
+
+=item * Attribute - B<reg_name>
+
+=over 4
+
+
+=item Description:
+The registration name is used to name this Hub.
+
+
+=back
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $Stem::Vars::Hub_name = $Env{ 'hub_name' } ||
+                               $self->{ 'reg_name' } ||
+                               $Stem::Vars::Program_name ;
+
+       TraceStatus "hub name is '$Stem::Vars::Hub_name'" ;
+
+###########################
+###########################
+# add code to open hub log 
+# 
+###########################
+###########################
+
+       return ;
+}
+
+sub status_cmd {
+
+       my $hub = $Stem::Vars::Hub_name || 'NONE' ;
+
+       return <<STATUS ;
+
+       Hub Status
+
+Name:          $hub
+Host:          $Stem::Vars::Host_name
+Program:       $Stem::Vars::Program_name
+
+STATUS
+
+}
+
+1 ;
diff --git a/lib/Stem/Id.pm b/lib/Stem/Id.pm
new file mode 100644 (file)
index 0000000..afb0aa9
--- /dev/null
@@ -0,0 +1,175 @@
+#  File: Stem/Id.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Id ;
+
+use strict ;
+
+=pod
+
+This module generates unique Id strings for use as names in Stem
+addresses. Its most common use is by parent Cells which clone
+themselves and need a unique Target. The parent Cell uses its Cell
+name and the new Target to register the cloned Cell.
+
+=cut
+
+my $attr_spec = [
+
+       {
+               'name'          => 'size',
+               'default'       => 6,
+               'help'          => <<HELP,
+This sets the number of characters in the Id.  It defaults to 6.
+HELP
+       },
+
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::Id
+
+=over 4
+
+
+=item * Attribute - B<size>
+
+=over 4
+
+
+=item Description:
+This sets the number of characters in the Id.  It defaults to 6.
+
+
+=item It B<defaults> to: 6
+
+=back
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+
+
+=head2 new
+
+The new method constructs a Stem::Id object. It initializes the Id
+string to a string of 'a's. The string size determines how long this
+object can go before it has to reuse previously deleted Id strings.
+
+=cut
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $size = $self->{'size'} ;
+
+       $self->{'start'}  = 'a' x $size ;
+       $self->{'next'}   = 'a' x $size ;
+       $self->{'end'}    = 'a' x ( $size + 1 ) ;
+       $self->{'in_use'} = {} ;
+
+       return $self ;
+}
+
+=head2 next
+
+The next method returns the next available Id in the object and marks
+that as in use.  It fails if all possible Id's are in use.
+
+=cut
+
+sub next {
+
+       my( $self ) = @_ ;
+
+       my $next = $self->{'next'} ;
+       my $curr_next = $next ;
+       my $end = $self->{'end'} ;
+       my $in_use = $self->{'in_use'} ;
+       
+       while( exists( $in_use->{$next} ) ) {
+
+               $next++ ;
+
+# fail if we looped around.
+
+#print "curr $curr_next $next\n" ;
+
+               return if $next eq $curr_next ;
+
+               $next = $self->{'start'} if $next eq $end ;
+       }
+
+       $in_use->{$next} = 1 ;
+       $self->{'next'} = $next ;
+
+       return $next ;
+}
+
+=head2 delete
+
+The delete method allows this Id to be reused by a call to the next
+method.
+
+=cut
+
+sub delete {
+
+       my( $self, $id ) = @_ ;
+
+       delete $self->{'in_use'}{ $id } ;
+}
+
+=head2 dump
+
+The dump method returns a the list of Ids that are in use.  used. It
+either returns the list of keys or an anonymous array with them
+depending on the calling context.
+
+=cut
+
+sub dump {
+
+       my( $self ) = @_ ;
+
+       return( wantarray ? keys %{ $self->{'in_use'} } :
+                         [ keys %{ $self->{'in_use'} } ] ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Inject.pm b/lib/Stem/Inject.pm
new file mode 100644 (file)
index 0000000..9546cde
--- /dev/null
@@ -0,0 +1,324 @@
+#  -*- mode: cperl; cperl-indent-level:8; tab-width:8; indent-tabs-mode:t; -*-
+
+#  File: Stem/Inject.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+#######################################################
+
+package Stem::Inject ;
+
+use strict ;
+
+use IO::Socket ;
+
+use Stem::Msg ;
+use Stem::Packet ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'host',
+               'required'      => 1,
+               'help'          => <<HELP,
+The hostname to use when connecting to the portal.
+HELP
+       },
+
+       {
+               'name'          => 'port',
+               'required'      => 1,
+               'help'          => <<HELP,
+The port to use when connecting to the portal.
+HELP
+       },
+
+       {
+               'name'          => 'to',
+               'required'      => 1,
+               'help'          => <<HELP,
+The cell to which the message is addressed.
+HELP
+       },
+
+       {
+               'name'          => 'type',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the type of the message. It is used to select the delivery method in
+the addressed Cell.
+HELP
+       },
+
+       {
+               'name'          => 'cmd',
+               'help'          => <<HELP,
+This is used for the delivery method if the message type is 'cmd'.
+HELP
+       },
+
+       {
+               'name'          => 'codec',
+               'help'          => <<HELP,
+The Stem::Codec module to use when creating packets.
+HELP
+       },
+
+       {
+               'name'          => 'data',
+               'help'          => <<HELP,
+This is the data the message is carrying. It should (almost) always be
+a reference.
+HELP
+       },
+
+       {
+               'name'          => 'timeout',
+               'default'       => 60,
+               'help'          => <<HELP,
+The timeout before giving up on getting a reply from the portal, in
+seconds.  Defaults to 60.
+HELP
+       },
+
+       {
+               'name'          => 'wait_for_reply',
+               'default'       => 1,
+               'help'          => <<HELP,
+Indicates whether or not a reply is expected.  Defaults to true.
+HELP
+       },
+
+] ;
+
+sub inject {
+
+       my $class = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $self->{'from'} = "Stem::Inject:inject$$";
+
+       $self->{'packet'} =
+           Stem::Packet->new( codec => $self->{'codec'} ) ;
+
+       local $SIG{'ALRM'} = sub { die 'Read or write to socket timed out' };
+
+       my $result;
+
+       eval {
+
+               my $address = "$self->{'host'}:$self->{'port'}";
+               $self->{'sock'} = IO::Socket::INET->new($address) ;
+               $self->{'sock'} or die "can't connect to $address\n" ;
+
+               alarm $self->{'timeout'} if $self->{'timeout'} ;
+
+               $self->_register() ;
+
+               $result = $self->_inject_msg() ;
+       } ;
+
+       alarm 0 ;
+
+       return $@ if $@ ;
+
+       return unless $self->{'wait_for_reply'};
+
+       return $result ;
+}
+
+sub _register {
+
+       my( $self, $data ) = @_ ;
+
+       my $reg_msg =
+           Stem::Msg->new( from => $self->{'from'},
+                           type => 'register',
+                         ) ;
+
+       die $reg_msg unless ref $reg_msg ;
+
+       my $reg = $self->{'packet'}->to_packet($reg_msg) ;
+
+       my $written = syswrite( $self->{'sock'}, $$reg ) ;
+       defined $written or die "can't write to socket\n" ;
+
+       my $read_buf ;
+       while (1) {
+
+               my $bytes_read = sysread( $self->{'sock'}, $read_buf, 8192 ) ;
+
+               defined $bytes_read or die "can't read from socket" ;
+               last if $bytes_read == 0 ;
+
+               my $data = $self->{'packet'}->to_data( $read_buf ) ;
+
+               last;
+       }
+}
+
+sub _inject_msg {
+
+       my( $self ) = @_;
+
+       my %msg_p =
+           ( 'to'   => $self->{'to'},
+             'from' => $self->{'from'},
+             'type' => $self->{'type'},
+           ) ;
+
+       $msg_p{'cmd'}  = $self->{'cmd'} if $self->{'type'} eq 'cmd';
+       $msg_p{'data'} = $self->{'data'},
+
+       my $data_msg = Stem::Msg->new(%msg_p) ;
+
+       die $data_msg unless ref $data_msg ;
+
+       my $data = $self->{'packet'}->to_packet($data_msg) ;
+
+       my $written = syswrite( $self->{'sock'}, $$data ) ;
+       defined $written or die "can't write to socket\n" ;
+
+       return unless $self->{'wait_for_reply'};
+
+       my $read_buf ;
+       while (1) {
+
+               my $bytes_read = sysread( $self->{'sock'}, $read_buf, 8192 ) ;
+
+               defined $bytes_read or die "can't read from socket" ;
+               last if $bytes_read == 0 ;
+
+               my $reply = $self->{'packet'}->to_data( $read_buf ) ;
+
+               return $reply->data ;
+       }
+}
+
+1 ;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Stem::Inject - Inject a message into a portal via a socket connection
+
+=head1 SYNOPSIS
+
+  my $return =
+      Stem::Inject->inject( to   => 'some_cell',
+                            type => 'do_something',
+                            port => 10200,
+                            host => 'localhost',
+                            data => { foo => 1 },
+                          );
+
+  # do something with data returned
+
+=head1 USAGE
+
+This class contains just one method, C<inject>, which can be used to
+inject a single message into a Stem hub, via a known server portal.
+
+This is very useful if you have a synchronous system which needs to
+communicate with a Stem system via messages.
+
+=head1 METHODS
+
+=over 4
+
+=item * inject
+
+This method is the sole interface provided by this class.  It accepts
+the following parameters:
+
+=over 8
+
+=item * host (required)
+
+This parameter specifies the host with which to connect.
+
+=item * port (required)
+
+The port with which to connect on the specified host.
+
+=item * to (required)
+
+The address of the cell to which the message should be delivered.
+
+=item * type (required)
+
+The type of the message to be delivered.
+
+=item * cmd
+
+The cmd being given.  This is only needed if the message's type is
+"cmd".
+
+=item * data
+
+The data that the message will carry, if any.
+
+=item * codec
+
+The codec to be used when communicating with the port.  This defaults
+to "Data::Dumper", but be careful to set this to whatever value the
+receiving port is using.
+
+=item * timeout (defaults to 60)
+
+The amount of time, in seconds, before giving up on message delivery
+or reply.  This is the I<total> amount of time allowed for message
+delivery and receiving a reply.
+
+=item * wait_for_reply (defaults to true)
+
+If this is true then the C<inject> method will expect a reply to the
+message it delivers.  If it doesn't receive one this will be
+considered an error.
+
+=back
+
+If there is an error in trying to inject a message, either with the
+parameters given, or with the socket connection, then this method will
+return an error string.
+
+If no reply was expected, this method simply returns false.
+Otherwise, it returns the reply message's data, which will always be a
+reference.
+
+=back
+
+=head1 AUTHOR
+
+Dave Rolsky <david@stemsystems.com>
+
+=cut
diff --git a/lib/Stem/Load/Driver.pm b/lib/Stem/Load/Driver.pm
new file mode 100644 (file)
index 0000000..3cea905
--- /dev/null
@@ -0,0 +1,124 @@
+
+package Stem::Load::Driver ;
+
+use strict ;
+
+use Time::HiRes qw( gettimeofday tv_interval ) ;
+
+my $attr_spec = [
+
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+Name this Cell was registered with.
+HELP
+       },
+       {
+               'name'          => 'load_addr',
+               'help'          => <<HELP,
+Address to send the load messages
+HELP
+       },
+       {
+               'name'          => 'load_data',
+               'help'          => <<HELP,
+Data string to send.
+HELP
+       },
+       {
+               'name'          => 'data_sizes',
+               'help'          => <<HELP,
+Range of data sizes to select from randomly
+HELP
+       },
+       {
+               'name'          => 'max_msg_cnt',
+               'default'       => 1000,
+               'help'          => <<HELP,
+Maximum number of messages to send
+HELP
+       },
+       {
+               'name'          => 'max_duration',
+               'default'       => 10,
+               'help'          => <<HELP,
+Maximum number of seconds to run
+HELP
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return $self ;
+}
+
+sub go_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       $self->{'echo_cnt'} = 0 ;
+
+       $self->{'start_time'} = gettimeofday() ;
+
+       $self->{'go_from_addr'} = $msg->from() ;
+
+       $self->send_load_msg() ;
+
+       return "Load Started\n" ;
+}
+
+sub response_in {
+
+       my( $self, $msg ) = @_ ;
+
+       my $time_delta = gettimeofday() - $self->{'start_time'} ;
+
+       if ( ++$self->{'echo_cnt'} >= $self->{'max_msg_cnt'} ||
+            $time_delta > $self->{'max_duration'} ) {
+
+               my $msgs_per_second = $self->{'echo_cnt'} / $time_delta ;
+
+               my $done_msg = Stem::Msg->new(
+                       'to'    => $self->{'go_from_addr'},
+                       'from'  => $self->{'reg_name'},
+                       'type'  => 'response',
+                       'data'  => <<DATA,
+sent $self->{'echo_cnt'} messages in $time_delta seconds
+$msgs_per_second messages per second
+DATA
+               ) ;
+
+               $done_msg->dispatch() ;
+
+               return ;
+       }
+
+       $self->send_load_msg() ;
+
+       return ;
+}
+
+
+sub send_load_msg {
+
+       my( $self ) = @_ ;
+
+       $self->{'echo_msg'} ||= Stem::Msg->new(
+                       'to'    => $self->{'load_addr'},
+                       'from'  => $self->{'reg_name'},
+                       'type'  => 'echo',
+                       'data'  => \'echo me',
+       ) ;
+
+       $self->{'echo_msg'}->dispatch() ;
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Load/Ticker.pm b/lib/Stem/Load/Ticker.pm
new file mode 100644 (file)
index 0000000..dd69dd8
--- /dev/null
@@ -0,0 +1,163 @@
+
+
+package Stem::Load::Ticker ;
+
+use strict ;
+
+use Time::HiRes qw( gettimeofday tv_interval ) ;
+
+my $attr_spec = [
+
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+Name this Cell was registered with.
+HELP
+       },
+       {
+               'name'          => 'dbi_addr',
+               'help'          => <<HELP,
+Address to send the insert messages
+HELP
+       },
+       {
+               'name'          => 'max_cnt',
+               'default'       => 20,
+               'help'          => <<HELP,
+Maximum number of rows to insert
+HELP
+       },
+       {
+               'name'          => 'parallel_cnt',
+               'default'       => 1,
+               'help'          => <<HELP,
+Number of inserts to do in parallel
+HELP
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return $self ;
+}
+
+sub go_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my %go_args ;
+
+       if ( my $data = $msg->data() ) {
+
+               %go_args = ${$data} =~ /(\S+)=(\S+)/g if $$data ;
+       }
+
+       $self->{'start_time'} = gettimeofday() ;
+       $self->{'go_from_addr'} = $msg->from() ;
+       $self->{'go_max_cnt'} = $go_args{'max_cnt'} || $self->{'max_cnt'} ;
+
+       $self->{'inserted_cnt'} = 0 ;
+       $self->{'send_cnt'} = $self->{'go_max_cnt'} ;
+       $self->{'parallel_cnt'} = $go_args{'para_cnt'} if $go_args{'para_cnt'} ;
+
+       $self->send_ticker_msgs( $self->{'parallel_cnt'} ) ;
+
+       return "Ticker Started\n" ;
+}
+
+sub send_ticker_msgs {
+
+       my( $self, $parallel_cnt ) = @_ ;
+
+#print "PARA $parallel_cnt\n" ;
+
+       while ( $parallel_cnt-- ) {
+
+               $self->insert_ticker_row() ;
+       }
+
+       return ;
+}
+
+sub insert_ticker_row {
+
+       my( $self ) = @_ ;
+
+       return if $self->{'send_cnt'} <= 0 ;
+       $self->{'send_cnt'}-- ;
+
+       my $ticker = join '', map ['A' .. 'Z']->[rand 26], 1 .. 3 ;
+
+       my $price = 100 + int rand 9900 ;
+
+       my $delta = -1000 + int rand 2000 ;
+
+       my $dbi_msg = Stem::Msg->new(
+
+               'to'            => $self->{'dbi_addr'},
+               'from'          => $self->{'reg_name'},
+               'type'          => 'cmd',
+               'cmd'           => 'execute',
+               'reply_type'    => 'insert_done',
+               'data'          => {
+                       statement       => 'insert_tick',
+                       bind            => [ $ticker, $price, $delta ],
+               },
+       );
+
+#print $dbi_msg->dump( 'SEND' ) ;
+       $dbi_msg->dispatch() ;
+
+       return ;
+}
+
+sub insert_done_in {
+
+       my( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'DONE' ) ;
+
+       if ( $self->{'send_cnt'} ) {
+
+               $self->send_ticker_msgs( 1 ) ;
+       }
+
+       if ( ++$self->{'inserted_cnt'} >= $self->{'go_max_cnt'} ) {
+
+               my $data = $msg->data() ;
+
+               die "insert_done_in: $$data" unless ref $data eq 'HASH' ;
+
+               my $time_delta = sprintf( "%8.4f",
+                                    gettimeofday() - $self->{'start_time'} ) ;
+
+               my $rows_per_second = $self->{'inserted_cnt'} / $time_delta ;
+
+               my $done_msg = Stem::Msg->new(
+                       'to'    => $self->{'go_from_addr'},
+                       'from'  => $self->{'reg_name'},
+                       'type'  => 'response',
+                       'data'  => <<DATA,
+inserted $self->{'inserted_cnt'} rows in $time_delta seconds
+$rows_per_second rows per second
+with $self->{'parallel_cnt'} inserts in parallel
+last row ID $data->{'insert_id'}
+DATA
+               ) ;
+
+               $done_msg->dispatch() ;
+
+               return ;
+       }
+
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Log.pm b/lib/Stem/Log.pm
new file mode 100644 (file)
index 0000000..fc9dd43
--- /dev/null
@@ -0,0 +1,475 @@
+#  File: Stem/Log.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+use strict ;
+
+use Stem::Log::Entry ;
+use Stem::Log::File ;
+
+my %logs ;
+
+package Stem::Log ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+
+use Data::Dumper ;
+
+use Stem::Vars ;
+
+Stem::Route::register_class( __PACKAGE__, 'log' ) ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'name',
+               'required'      => 1,
+               'help'          => <<HELP,
+Name of this logical log.
+HELP
+       },
+       {
+               'name'          => 'file',
+               'class'         => 'Stem::Log::File',
+               'help'          => <<HELP,
+The Stem::Log::File object that will create and manage a physical log file.
+HELP
+       },
+       {
+               'name'          => 'format',
+               'default'       => '%T',
+               'help'          => <<HELP,
+Format to print entries for this logical log. See elsewhere in this
+document for the details of the sprintf-like format'
+HELP
+       },
+       {
+               'name'          => 'strftime',
+               'default'       => '%C',
+               'help'          => <<HELP,
+Format passed to strftime to print the %f entry format.
+HELP
+       },
+       {
+               'name'          => 'use_gmt',
+               'default'       => 1,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Make strftime use gmtime instead of localtime to break the log entry
+timestamp into its parts.
+HELP
+       },
+       {
+               'name'          => 'filters',
+               'help'          => <<HELP,
+List of key/value pairs. The keys are either rules, actions or 'flag'.
+The value is passed to the function for the key. Use a list for complex values.
+HELP
+       },
+
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $logs{ $self->{'name'} } = $self ;
+
+       return ;
+}
+
+# table to convert filter keys to code refs to execute
+# these are all passed the $entry hash ref, the filter arg and the log object
+
+my %filter_to_code = (
+
+       'match_text'    => sub { $_[0]->{'text'}  =~ /$_[1]/ },
+       'match_label'   => sub { $_[0]->{'label'} =~ /$_[1]/ },
+
+       'eq_level'      => sub { $_[0]->{'level'} == $_[1] },
+       'lt_level'      => sub { $_[0]->{'level'} <  $_[1] },
+       'le_level'      => sub { $_[0]->{'level'} <= $_[1] },
+       'gt_level'      => sub { $_[0]->{'level'} >  $_[1] },
+       'ge_level'      => sub { $_[0]->{'level'} >= $_[1] },
+
+       'env_eq_level'  => sub { $_[0]->{'level'} == ( $Env{ $_[1] } || 0 ) },
+       'env_lt_level'  => sub { $_[0]->{'level'} >  ( $Env{ $_[1] } || 0 ) },
+       'env_le_level'  => sub { $_[0]->{'level'} >= ( $Env{ $_[1] } || 0 ) },
+       'env_gt_level'  => sub { $_[0]->{'level'} <  ( $Env{ $_[1] } || 0 ) },
+       'env_ge_level'  => sub { $_[0]->{'level'} <= ( $Env{ $_[1] } || 0 ) },
+
+       'file'          => \&_action_file,
+       'stdout'        => \&_action_stdout,
+       'stderr'        => \&_action_stderr,
+       'dev_tty'       => \&_action_dev_tty,
+       'console'       => \&_action_console,
+#      'msg'           => \&_action_msg,
+       'write'         => \&_action_write,
+       'wall'          => \&_action_wall,
+       'email'         => \&_action_email,
+       'page'          => \&_action_page,
+       'forward'       => \&_action_forward,
+
+       'custom'        => \&_custom_filter,
+) ;
+
+my %flag_to_code = (
+
+       'set'           => sub { $_[0]->{'flag'} = 1 },
+       'clear'         => sub { $_[0]->{'flag'} = 0 },
+       'invert'        => sub { $_[0]->{'flag'} = ! $_[0]->{'flag'} },
+       'inverted_test' => sub { $_[0]->{'invert_test'} = 1 },
+       'normal_test'   => sub { $_[0]->{'invert_test'} = 0 },
+       'or'            => sub { $_[0]->{'or'} = 1 },
+       'and'           => sub { $_[0]->{'or'} = 0 },
+) ;
+
+sub submit {
+
+       my( $self, $entry ) = @_ ;
+
+       $entry->{'format'} = $self->{'format'} ;
+       $entry->{'strftime'} = $self->{'strftime'} ;
+       $entry->{'use_gmt'} = $self->{'use_gmt'} ;
+
+       my $filter_list = $self->{'filters'} ;
+
+       unless ( $filter_list ) {
+
+# no filter so the default is to log to the file
+
+               _action_file( $entry, 0, $self ) ;
+
+               return ;
+       }
+
+# start with all actions enabled
+
+       $entry->{'flag'} = 1 ;
+
+# scan the filter list by pairs
+
+       for( my $i = 0 ; $i < @{$filter_list} ; $i += 2 ) {
+
+               my ( $filter_key, $filter_arg ) =
+                               @{$filter_list}[$i, $i + 1] ;
+
+# handle the flag operations first.
+
+               if ( $filter_key eq 'flag' ) {
+
+                       if ( my $code = $flag_to_code{ $filter_arg } ) {
+
+                               $code->( $entry ) ;
+                       }
+
+                       next ;
+               }
+
+# skip this filter rule/action if the flag is false
+
+               next unless $entry->{'flag'} && ! $entry->{'invert_test'} ;
+
+# check for and remove a 'not_' prefix
+
+               my $not = $filter_key =~ s/^not_(\w+)$/$1/ ;
+
+#print "FILT $filter_key $filter_arg\n" ;
+
+               my $code = $filter_to_code{ $filter_key } ;
+
+               next unless $code ;
+
+# execute the rule/action code
+
+               my $flag_val = $code->( $entry, $filter_arg, $self ) ;
+
+# don't mung the flag unless we get a boolean return
+
+               next unless defined( $flag_val ) ;
+
+# invert the returned flag value if needed
+
+               $flag_val = ! $flag_val if $not ;
+
+# do the right boolean op
+
+               if ( $entry->{'or'} ) {
+
+                       $entry->{'flag'} ||= $flag_val ;
+               }
+               else {
+
+                       $entry->{'flag'} &&= $flag_val ;
+               }
+       }
+}
+
+
+sub _format_entry {
+
+       my( $entry ) = @_ ;
+
+       my $formatted = $entry->{'format'} ;
+
+       $formatted =~ s/%(.)/_format_field( $entry, $1 )/seg ;
+
+       return $formatted ;
+}
+
+my %letter_to_key = (
+
+       'T'     => 'text',
+       't'     => 'time',
+       'L'     => 'label',
+       'l'     => 'level',
+       'H'     => 'hub_name',
+       'h'     => 'host_name',
+       'P'     => 'program_name',
+) ;
+
+sub _format_field {
+
+       my( $entry, $letter ) = @_ ;
+
+       if ( my $key = $letter_to_key{ $letter } ) {
+
+               return $entry->{$key} ;
+       }
+
+       if ( $letter eq 'f' ) {
+
+               require POSIX ;
+
+               $entry->{'formatted_time'} ||= do {
+
+                       my @times = ( $entry->{'use_gmt'} ) ?
+                                       gmtime( $entry->{'time'} ) :
+                                       localtime( $entry->{'time'} ) ;
+
+                       POSIX::strftime( $entry->{'strftime'}, @times ) ;
+               } ;
+
+               return $entry->{'formatted_time'} ;
+       }
+
+       return $letter ;
+}
+
+sub _action_file {
+
+       my( $entry, $arg, $log_obj ) = @_ ;
+
+       my $file = $log_obj->{'file'} ;
+
+       $file or return ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       $file->write( $entry->{'formatted'} ) ;
+
+       return ;
+}
+
+sub _action_stdout {
+
+       my( $entry ) = shift ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       print STDOUT $entry->{'formatted'} ;
+
+       return ;
+}
+
+sub _action_stderr {
+
+       my( $entry ) = shift ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       print STDERR $entry->{'formatted'} ;
+
+       return ;
+}
+
+sub _action_write {
+
+       my( $entry, $arg ) = @_ ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       my @users = ref $arg ? @{$arg} : $arg ;
+
+       foreach my $user ( @users ) {
+
+               system <<SYS ;
+/bin/echo '$entry->{'formatted'}' | write $user >/dev/null 2>&1 &
+SYS
+       }
+
+       return ;
+}
+
+sub _action_wall {
+
+       my( $entry ) = shift ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+
+       system <<SYS ;
+/bin/echo '$entry->{'formatted'}' | wall &
+SYS
+
+       return ;
+}
+
+# handle to write log entries to /dev/tty
+
+my $tty_fh ;
+
+sub _action_dev_tty {
+
+       my( $entry ) = shift ;
+
+       $tty_fh ||= IO::File->new( ">/dev/tty" ) ;
+
+       unless( $tty_fh ) {
+
+               warn "can't open log file /dev/tty $!" ;
+               return ;
+       }
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       print $tty_fh $entry->{'formatted'} ;
+
+       return ;
+}
+
+sub _action_console {
+
+       my( $entry ) = shift ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       return unless Stem::Console->can( 'write' ) ;
+
+       Stem::Console->write( $entry->{'formatted'} ) ;
+
+       return ;
+}
+
+sub _action_forward {
+
+       my( $entry, $arg ) = @_ ;
+
+       my @logs = ref $arg ? @{$arg} : $arg ;
+
+       my $entry_obj = $entry->{'entry_obj'} ;
+
+       $entry_obj->submit( @logs ) ;
+
+       return ;
+}
+
+sub _action_email {
+
+       my( $entry, $arg ) = @_ ;
+
+       $entry->{'formatted'} ||= _format_entry( $entry ) ;
+
+       my ( $email_addr, $subject ) = ( ref $arg ) ?
+                               @{$arg} : ( $arg, 'Stem::Log' ) ;
+
+#print "EMAIL  $email_addr: $subject\n" ;
+
+       require Mail::Send ;
+
+       my $mail = Mail::Send->new(
+                       'To'    => $email_addr,
+                       'Subject' => $subject
+       ) ;
+
+       my $fh = $mail->open();
+
+       $fh->print( $entry->{'formatted'} ) ;
+
+       $fh->close;
+
+       return ;
+}
+
+sub _custom_filter {
+
+       my( $entry, $arg ) = @_ ;
+
+#####
+# do this
+#####
+
+       return ;
+}
+
+sub find_log {
+
+       my ( $log_name ) = @_ ;
+
+       return( $logs{ $log_name } ) ;
+}
+
+sub status_cmd {
+
+       my $status_text .= sprintf( "%-20s%-40s%10s\n",
+                                               "Logical Log",
+                                               "Physical File",
+                                               "Size" ) ;
+       $status_text .= sprintf "-" x 70 . "\n";
+
+       foreach my $log_name ( sort keys %logs ) {
+
+               my $ref = $logs{$log_name} ;
+
+               $status_text .= sprintf "%-20s%-40s%10s\n",
+                                                     $log_name,
+                                                    $ref->{'file'}{'path'},
+                                                    $ref->{'file'}{'size'} ;
+       }
+
+       $status_text .= "\n\n" ;
+
+       return $status_text ;
+}
+
+1 ;
diff --git a/lib/Stem/Log/Entry.pm b/lib/Stem/Log/Entry.pm
new file mode 100644 (file)
index 0000000..c5edb43
--- /dev/null
@@ -0,0 +1,157 @@
+#  File: Stem/Log/Entry.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+use strict ;
+
+use Stem::Log ;
+
+package Stem::Log::Entry ;
+
+Stem::Route::register_class( __PACKAGE__, 'entry' ) ;
+
+
+my $attr_spec = [
+
+       {
+               'name'          => 'text',
+               'default'       => '',
+               'help'          => <<HELP,
+Text for this log entry. Can be filtered with the rule 'match_text'.
+HELP
+       },
+       {
+               'name'          => 'label',
+               'default'       => 'info',
+               'help'          => <<HELP,
+Label for this log entry. This is used to tag log entries from
+different sources. Can be filtered with the rule 'match_label'.
+HELP
+       },
+       {
+               'name'          => 'level',
+               'default'       => '1',
+               'help'          => <<HELP,
+Severity level for this log entry. It is an integer with 0 being the
+most severe level and 10 the lowest (this maps to the levels of
+syslog). There are several rules which can filter based on the level.
+HELP
+       },
+       {
+               'name'          => 'logs',
+               'type'          => 'list',
+               'help'          => <<HELP,
+This is a list of logical logs where this entry is submitted. The
+first one is considered the original log. If this is not passed, then
+the entry must be explicitly submitted by the submit method.
+HELP
+       },
+] ;
+
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $self->{'time'} = time() ;
+       $self->{'hub_name'} = $Stem::Vars::Hub_name ;
+       $self->{'host_name'} = $Stem::Vars::Host_name ;
+       $self->{'program_name'} = $Stem::Vars::Program_name ;
+
+       if ( my $logs_attr = $self->{'logs'} ) {
+
+               $self->submit( @{$logs_attr} ) ;
+       }
+
+       return $self ;
+}
+
+sub submit {
+
+       my( $self, @logs ) = @_ ;
+
+       foreach my $log_name ( @logs ) {
+
+#print "LOG [$log_name]\n" ;
+               if ( $log_name =~ /^(\w+):(\w+)$/ ) {
+
+                       my $to_hub = $1 ;
+                       my $to_log = $2 ;
+
+                       my $log_msg = Stem::Msg->new(
+                                       'to'    => "$to_hub:" . __PACKAGE__,
+                                       'from'          => __PACKAGE__,
+                                       'type'          => 'log',
+                                       'log'           => $to_log,
+                                       'data'          => $self,
+                       ) ;
+
+#print $log_msg->dump( 'LOG out' ) ;
+
+
+                       $log_msg->dispatch() ;
+
+                       next ;
+               }
+
+               my $log_obj = Stem::Log::find_log( $log_name ) ;
+
+               next unless $log_obj ;
+
+
+               my $entry_copy ||= { %{$self} } ;
+
+               $entry_copy->{'log_name'} = $log_name ;
+               $entry_copy->{'orig_log_name'} ||= $log_name ;
+               $entry_copy->{'entry_obj'} = $self ;
+
+               $log_obj->submit( $entry_copy ) ;
+       }
+}
+
+# this method is how a remote log message is delivered locally
+
+sub log_in {
+
+       my( $class, $msg ) = @_ ;
+
+       my $entry = $msg->data() ;
+
+       print "$entry\n" unless ref $entry ;
+
+#print $msg->dump( 'LOG in' ) ;
+
+       $entry->submit( $msg->log() ) ;
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Log/File.pm b/lib/Stem/Log/File.pm
new file mode 100644 (file)
index 0000000..3a3fcc6
--- /dev/null
@@ -0,0 +1,210 @@
+#  File: Stem/Log/File.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+use strict ;
+
+use IO::File ;
+use File::Basename ;
+
+
+package Stem::Log::File ;
+
+#########################
+#########################
+# add stuff for file rotation, number suffix, etc.
+#########################
+#########################
+
+my $attr_spec_log = [
+
+       {
+               'name'          => 'path',
+               'required'      => 1,
+               'help'          => <<HELP,
+The path for the physical log file
+HELP
+       },
+       {
+               'name'          => 'strftime',
+               'default'       => '%Y%m%d%H%M%S',
+               'help'          => <<HELP,
+Format passed to strftime to print the log file suffix timestamp
+HELP
+       },
+       {
+               'name'          => 'use_gmt',
+               'default'       => 1,
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Make strftime use gmtime instead of localtime for the suffix timestamp
+HELP
+       },
+
+       {
+               'name'          => 'rotate',
+               'type'          => 'hash',
+               'help'          => <<HELP,
+This is a list of option key/value pairs that can be applied to log rotation.
+HELP
+       },
+
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_log, @_ ) ;
+       return $self unless ref $self ;
+
+       if ( my $rotate_options = $self->{'rotate'} ) {
+
+               if ( ref $rotate_options eq 'ARRAY' ) {
+
+                       $self->{'rotate'} = { @{$rotate_options} } ;
+               }
+       }
+
+       $self->{'base_path'} = $self->{'path'} ;
+       ( $self->{'log_dir'}, $self->{'file_name'} ) =
+                                       File::Basename::fileparse( $self->{'path'} ) ;
+
+       my $err = $self->_open_file() ;
+       return $err if $err ;
+
+       return( $self ) ;
+}
+
+
+sub write {
+
+       my( $self, $text ) = @_ ;
+
+       $self->{'fh'}->print( $text ) ;
+
+       $self->{'size'} += length( $text ) ;
+
+       my $rotate_options = $self->{'rotate'} ;
+
+       if ( $rotate_options &&
+            $self->{'size'} >= $rotate_options->{'max_size'} ) {
+
+               $self->_rotate() ;
+       }
+}
+
+sub status_cmd {
+
+
+}
+
+sub rotate_cmd {
+
+       my ( $self ) = @_ ;
+
+       $self->_rotate() ;
+}
+
+sub _rotate {
+
+       my ( $self ) = @_ ;
+
+       my $fh = $self->{'fh'} ;
+
+       close( $fh ) ;
+
+       $self->_open_file() ;
+}
+
+
+sub _open_file {
+
+       my ( $self ) = @_ ;
+
+       my $open_path = $self->{'base_path'} ;
+
+       if ( $self->{'rotate'} ) {
+
+               my $suffix = $self->_get_last_suffix() ||
+                            $self->_generate_suffix() ;
+
+               
+               $open_path .= ".$suffix" ;
+       }
+
+       $self->{'open_path'} = $open_path ;
+
+       my $fh = IO::File->new( ">>$open_path" ) or
+                return "Can't append to log file '$open_path' $!" ;
+
+       $self->{'size'} = -s $fh ;
+
+       $fh->autoflush( 1 ) ;
+
+       $self->{'fh'} = $fh ;
+
+       return ;
+}
+
+sub _get_last_suffix {
+
+       my ( $self ) = @_ ;
+
+       my $log_dir = $self->{'log_dir'} ;
+       my $file_name = $self->{'file_name'} ;
+
+       local( *DH ) ;
+
+       opendir( DH, $log_dir ) || return '' ;
+
+       my @files = sort grep { /^$file_name/ } readdir(DH) ;
+
+# return the latest file suffix
+
+       if ( @files ) {
+
+               return $1 if $files[-1] =~ /\.(\d+$)/ ;
+       }
+
+       return '' ;
+}
+
+
+sub _generate_suffix {
+
+       my ( $self ) = @_ ;
+
+       require POSIX ;
+
+       my @time = ( $self->{'use_gmt'} ) ? gmtime : localtime ;
+
+       return POSIX::strftime( $self->{'strftime'}, @time ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Log/Tail.pm b/lib/Stem/Log/Tail.pm
new file mode 100644 (file)
index 0000000..6698f8f
--- /dev/null
@@ -0,0 +1,242 @@
+#  File: Stem/Log/Tail.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Log::Tail ;
+
+use strict ;
+use IO::Seekable ;
+use Data::Dumper ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'path',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the full path to the file we want to tail.
+HELP
+       },
+
+       {
+               'name'          => 'data_log',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the log which gets sent the data log entries.
+HELP
+       },
+
+       {
+               'name'          => 'status_log',
+               'help'          => <<HELP,
+This is the log which gets sent the status log entries.
+These include things like: Log has been rotated, deleted, moved, etc...
+HELP
+       },
+       {
+               'name'          => 'label',
+               'default'       => 'tail',
+               'help'          => <<HELP,
+Label to tag tailed log entry.
+HELP
+       },
+       {
+               'name'          => 'level',
+               'default'       => '5',
+               'help'          => <<HELP,
+Severity level for this tailed log entry.
+HELP
+       },
+       {
+               'name'          => 'interval',
+               'help'          => <<HELP,
+This specifies (in seconds) how often we check the log file for new
+data.  If this is not specified, you need to call the tail_cmd method
+to check for new data.
+HELP
+       },
+       {
+               'name'          => 'delay',
+               'default'       => 10,
+               'help'          => <<HELP,
+This specifies (in seconds) how long the delay is before the
+first repeated checking of the log file for new data.
+HELP
+       },
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+print "TAIL INT $self->{'interval'}\n" ;
+
+       if ( my $interval = $self->{'interval'} ) {
+
+               $self->{'timer'} = Stem::Event::Timer->new(
+                               'object'        => $self,
+                               'method'        => 'tail_cmd',
+                               'interval'      => $interval,
+                               'delay'         => $self->{'delay'},
+                               'repeat'        => 1,
+                               'hard'          => 1,
+               ) ;
+
+print "TIMER $self->{'timer'}\n" ;
+
+       }
+
+       $self->{'prev_size'} = 0 ;
+       $self->{'prev_mtime'} = 0 ;
+       $self->{'prev_inode'} = -1 ;
+
+       return( $self ) ;
+}
+
+sub tail_cmd {
+
+       my( $self ) = @_ ;
+
+print "TAILING\n" ;
+
+       local( *LOG ) ;
+
+       my $path = $self->{'path'} ;
+
+       unless( open( LOG, $path ) ) {
+
+               return if $self->{'open_failed'} ;
+               $self->{'open_failed'} = 1 ;
+
+               if ( my $status_log = $self->{'status_log'} ) {
+
+                       Stem::Log::Entry->new(
+                               'logs'  => $status_log,
+                               'label' => 'LogTail status',
+                               'text'  =>
+                                       "LogTail: missing log $path $!\n",
+                       ) ;
+               }
+               return ;
+       }
+
+       $self->{'open_failed'} = 0 ;
+
+       my( $inode, $size, $mtime ) = (stat LOG)[1, 7, 9] ;
+
+       TraceStatus "size $size mtime $mtime $inode" ;
+
+       my $prev_inode = $self->{'prev_inode'} ;
+       my $prev_size = $self->{'prev_size'} ;
+
+       if ( $prev_inode == -1 ) {
+
+               $self->{'prev_inode'} = $inode ;
+
+               if ( my $status_log = $self->{'status_log'} ) {
+
+                       Stem::Log::Entry->new(
+                               'logs'  => $status_log,
+                               'level' => 6,
+                               'label' => 'LogTail status',
+                               'text'  =>
+                                       "LogTail: first open of $path\n",
+                       ) ;
+               }
+       }
+       elsif ( $inode != $prev_inode ) {
+
+               $self->{'prev_inode'} = $inode ;
+
+               if ( my $status_log = $self->{'status_log'} ) {
+
+                       Stem::Log::Entry->new(
+                               'logs'  => $status_log,
+                               'level' => 6,
+                               'label' => 'LogTail status',
+                               'text'  =>
+                                       "LogTail: $path has moved\n",
+                       ) ;
+               }
+
+# tail the entire file as it is new
+
+               $prev_size = 0 ;
+
+       }
+       elsif ( $size < $prev_size ) {
+       
+               if ( my $status_log = $self->{'status_log'} ) {
+
+                       Stem::Log::Entry->new(
+                               'logs'  => $status_log,
+                               'level' => 6,
+                               'label' => 'LogTail status',
+                               'text'  =>
+                                       "LogTail: $path has shrunk\n",
+                       ) ;
+               }
+
+# tail the entire file as it has shrunk
+
+               $prev_size = 0 ;
+       }
+       elsif ( $size == $prev_size ) {
+
+               TraceStatus "no changes" ;
+               return ;
+       }
+
+       $self->{'prev_size'} = $size ;
+
+       my $delta_size = $size - $prev_size ;
+
+       return unless $delta_size ;
+
+       my $read_buf ;
+
+       sysseek( *LOG, $prev_size, SEEK_SET ) ;
+       sysread( *LOG, $read_buf, $delta_size ) ;
+
+       Stem::Log::Entry->new(
+               'logs'  => $self->{'data_log'},
+               'level' => $self->{'level'},
+               'label' => $self->{'label'},
+               'text'  => $read_buf,
+       ) ;
+
+       return ;
+}
+
+1 ;
diff --git a/lib/Stem/Msg.pm b/lib/Stem/Msg.pm
new file mode 100644 (file)
index 0000000..ddf468d
--- /dev/null
@@ -0,0 +1,636 @@
+#  File: Stem/Msg.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Msg ;
+
+use strict ;
+use Carp ;
+
+use Stem::Route qw( lookup_cell ) ;
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+use Stem::Trace 'log' => 'stem_msg'   , 'sub' => 'TraceMsg' ;
+
+my $msg_id = 0;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'type',
+               'help'          => <<HELP,
+This is the type of the message. It is used to select the delivery method in
+the addressed Cell.
+HELP
+       },
+
+       {
+               'name'          => 'cmd',
+               'help'          => <<HELP,
+This is used for the delivery method if the message type is 'cmd'.
+HELP
+       },
+       {
+               'name'          => 'reply_type',
+               'default'       => 'response',
+               'help'          => <<HELP,
+This is the type that will be used in a reply message.
+HELP
+       },
+
+       {
+               'name'          => 'data',
+               'help'          => <<HELP,
+This is the data the message is carrying. It should (almost) always be
+a reference.
+HELP
+       },
+
+       {
+               'name'          => 'log',
+               'help'          => <<HELP,
+This is the name of the log in a log type message.
+HELP
+       },
+
+       {
+               'name'          => 'status',
+               'help'          => <<HELP,
+This is the status in a status message.
+HELP
+       },
+
+       {
+               'name'          => 'ack_req',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means when this message is delivered, a 'msg_ack' message
+sent back as a reply.
+HELP
+       },
+
+       {
+               'name'          => 'in_portal',
+               'help'          => <<HELP,
+This is the name of the Stem::Portal that received this message.
+HELP
+       },
+       {
+               'name'          => 'msg_id',
+               'help'          => <<HELP,
+A unique id for the message.
+HELP
+       },
+       {
+               'name'          => 'reply_id',
+               'help'          => <<HELP,
+For replies, this is the msg_id of the message being replied to.
+HELP
+       },
+] ;
+
+# get the plain (non-address) attributes for the AUTOLOAD and the
+# message dumper
+
+my %is_plain_attr = map { $_->{'name'}, 1 } @{$attr_spec} ;
+
+# add the address types and parts to our attribute spec with callbacks
+# for parsing
+
+# lists of the address types and parts
+
+my @addr_types = qw( to from reply_to ) ;
+my @addr_parts = qw( hub cell target ) ;
+
+# these are used to grab the types and parts from the method names in AUTOLOAD
+
+my $type_regex = '(' . join( '|', @addr_types ) . ')' ;
+my $part_regex = '(' . join( '|', @addr_parts ) . ')' ;
+
+# build all the accessor methods as closures
+
+{
+       no strict 'refs' ;
+
+       foreach my $attr ( map $_->{'name'}, @{$attr_spec} ) {
+
+               *{$attr} = sub {
+
+                       $_[0]->{$attr} = $_[1] if @_ > 1 ;
+                       return $_[0]->{$attr}
+               } ;
+       }
+
+       foreach my $type ( @addr_types ) {
+
+               *{$type} = sub {
+                       my $self = shift ;
+                       $self->{ $type } = shift if @_ ;
+                       return $self->{ $type } ;
+               } ;
+
+##########
+# WORKAROUND
+# this array seems to be needed. i found a bug when i used
+# a scalar and bumped it. the closures all had the value of 3.
+##########
+
+               my @part_nums = ( 0, 1, 2 ) ;
+
+               foreach my $part ( @addr_parts ) {
+
+                       my $part_num = shift @part_nums ;
+
+                       *{"${type}_$part"} = sub {
+                               my $self = shift ;
+
+# split the address for this type of address (to,from,reply_to)
+
+                               my @parts = split_address( $self->{$type} ) ;
+
+
+                               if ( @_ ) {
+
+                                       $parts[ $part_num ] = shift ;
+
+                                       $self->{$type} =
+                                               make_address_string( @parts ) ;
+                               }
+#print "PART $type $part_num [$parts[ $part_num ]]\n" if $type eq 'from' ;
+
+                               return $parts[ $part_num ] ;
+                       } ;
+               }
+       }
+}
+
+# used for faster parsing.
+
+my @attrs = qw( to from reply_to type cmd reply_type log data ) ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+#      my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+#      return $self unless ref $self ;
+
+#print "A [$_]\n" for @_ ;
+
+       my %args = @_ ;
+
+#use YAML ;
+#print Dump \%args ;
+
+       my $self = bless { map { exists $args{$_} ?
+                               ( $_ => $args{$_} ) : () } @attrs } ;
+
+#print $self->dump( 'NEW' ) ;
+
+       $self->{'type'} = 'cmd' if exists $self->{'cmd'} ;
+
+        $self->{'msg_id'} ||= $class->_new_msg_id;
+
+#      TraceMsg "MSG: [$_] => [$args{$_}]\n" for sort keys %args ;
+
+#      TraceMsg $self->dump( 'new MSG' ) ;
+
+       return( $self ) ;
+}
+
+sub _new_msg_id {
+
+       my( $class ) = shift ;
+
+        $msg_id = 0 if $msg_id == 2 ** 31;
+
+        return ++$msg_id;
+}
+
+sub clone {
+
+       my( $self ) = shift ;
+
+       my $msg = Stem::Msg->new(
+                        ( map { exists $self->{$_} ?
+                               ( $_, $self->{$_} ) : () }
+                               @addr_types, keys %is_plain_attr ),
+                        @_
+       ) ;
+
+#      TraceMsg $self->dump( 'self' ) ;
+#      TraceMsg $msg->dump( 'clone' ) ;
+
+       return $msg ;
+}
+
+sub split_address {
+
+# return an empty address if no input
+
+       return( '', '', '' ) unless @_ && $_[0] ;
+
+# parse out the address parts so 
+
+# the cell part can be a token or a class name with :: between tokens.
+# delimiter can be /, @, -, or : with : being the convention
+# this is how triplets 
+# hub:cell:target
+
+#print "SPLIT IN [$_[0]]\n" ;
+
+       $_[0] =~ m{
+                       ^               # beginning of string
+                       (?:             # group /hub:/
+                          (\w*)        # grab /hub/
+                          ([:/@-])     # grab any common delimiter
+                       )?              # hub: is optional
+                       (               # grab /cell/
+                          (?:\w+|::)+  # group cell (token or class name)
+                       )               # /cell/ is required
+                       (?:             # group /:target/
+                          \2           # match first delimiter
+                          (\w*)        # grab /target/
+                       )?              # :target is optional
+                       $}x             # end of string
+
+# an bad address can be checked with @_ == 1 as a proper address is
+# always 3. 
+
+                or return "bad string address" ;
+
+# we return the list of hub, cell, target and give back nice null strings if 
+# needed.
+
+#print "SPLIT ", join( '--', $1 || '', $3, $4 || '' ), "\n" ;
+
+       return( $1 || '', $3, $4 || '' ) ;
+}
+
+# sub address_string {
+
+#      my( $addr ) = @_ ;
+
+# #use YAML ;
+# #print "ADDR [$addr]", Dump( $addr ) ;
+#      return $addr unless ref $addr ;
+
+#      return 'BAD ADDRESS' unless ref $addr eq 'HASH' ;
+
+#      return $addr->{'cell'} if keys %{$addr} == 1 && $addr->{'cell'} ;
+
+#      return join ':', map { $_ || '' } @{$addr}{qw( hub cell target ) } ;
+# }
+
+sub make_address_string {
+
+       my( $hub, $cell_name, $target ) = @_ ;
+
+       $hub = '' unless defined $hub ;
+       $target = '' unless defined $target ;
+
+       return $cell_name unless length $hub || length $target ;
+
+       return join ':', $hub, $cell_name, $target ;
+}
+
+sub reply {
+
+       my( $self ) = shift ;
+
+#      TraceMsg "Reply [$self]" ;
+
+#      TraceMsg $self->dump( 'reply self' ) ;
+
+#print $self->dump( 'reply self' ) ;
+
+       my $to = $self->{'reply_to'} || $self->{'from'} ;
+       my $from = $self->{'to'} ;
+
+       my $reply_msg = Stem::Msg->new(
+                               'to'    => $to,
+                               'from'  => $from,
+                               'type'  => $self->{'reply_type'} || 'response',
+                                'reply_id' => $self->{'msg_id'},
+                               @_
+       ) ;
+
+#      TraceMsg $reply_msg->dump( 'new reply' ) ;
+#$reply_msg->dump( 'new reply' ) ;
+
+       return( $reply_msg ) ;
+}
+
+#####################
+#####################
+# add forward method which clones the old msg and just updates the to address.
+#
+# work needs to be done on from/origin parts and who sets them
+#####################
+#####################
+
+
+sub error {
+
+       my( $self, $err_text ) = @_ ;
+
+#      TraceError "ERR [$self] [$err_text]" ;
+
+       my $err_msg = $self->reply( 'type' => 'error',
+                                   'data' => \$err_text ) ;
+
+#      TraceError $err_msg->dump( 'error' ) ;
+
+       return( $err_msg ) ;
+}
+
+
+########################################
+########################################
+# from/origin address will be set if none by looking up the cell that
+# is currently be called with a message. or use
+# Stem::Event::current_object which is set before every event
+# delivery.
+########################################
+########################################
+
+
+my @msg_queue ;
+
+sub dispatch {
+
+       my( $self ) = @_ ;
+
+warn( caller(), $self->dump() ) and die
+               'Msg: No To Address' unless $self->{'to'} ;
+warn( caller(), $self->dump() ) and die
+               'Msg: No From Address' unless $self->{'from'} ;
+
+
+#  $self->deliver() ;
+#  return ;
+
+#      unless ( @msg_queue ) {
+       unless ( ref ( $self ) ) {
+                $self = Stem::Msg->new( @_ ) ;
+       }
+#              Stem::Event::Plain->new( 'object' => __PACKAGE__,
+#                                       'method' => 'deliver_msg_queue' ) ;
+#      }
+       return "missing to attr in msg" unless $self ->{"to"} ;
+       return "missing from attr in msg" unless $self ->{"from"} ;
+       return "missing type attr in msg" unless $self ->{"type"} ;
+       push @msg_queue, $self ;
+}
+
+sub process_queue {
+
+       while( @msg_queue ) {
+
+               my $msg = shift @msg_queue ;
+
+#print $msg->dump( 'PROCESS' ) ;
+               my $err = $msg->_deliver() ;
+
+               if ( $err ) {
+
+                       my $err_text = "Undelivered:\n$err" ;
+#print $err_text, $msg->dump( 'ERR' ) ;
+                       TraceError $msg->dump( "$err_text" ) ;
+
+               }
+       }
+}
+
+sub _deliver {
+
+       my( $self ) = @_ ;
+
+#print $self->dump( "DELIVER" ) ;
+
+       my( $to_hub, $cell_name, $target ) = split_address( $self->{'to'} ) ;
+
+       unless( $cell_name ) {
+
+               return <<ERR ;
+Can't deliver to bad address: '$self->{'to'}'
+ERR
+       }
+
+#print "H [$to_hub] C [$cell_name] T [$target]\n" ;
+
+       if ( $to_hub && $Stem::Vars::Hub_name ) {
+
+               if ( $to_hub eq $Stem::Vars::Hub_name ) {
+
+                       if ( my $cell = lookup_cell( $cell_name, $target ) ) {
+
+                               return $self->_deliver_to_cell( $cell ) ;
+                       }
+
+                       return <<ERR ;
+Can't find cell $cell_name in local hub $to_hub
+ERR
+               }
+
+               return $self->send_to_portal( $to_hub ) ;
+       }
+
+# no hub, see if we can deliver to a local cell 
+
+       if ( my $cell = lookup_cell( $cell_name, $target ) ) {
+
+               return $self->_deliver_to_cell( $cell ) ;
+       }
+
+# see if this came in from a portal
+
+       if ( $self->{'in_portal'} ) {
+
+               return "message from another Hub can't be delivered" ;
+       }
+
+# not a local cell or named hub, send it to DEFAULT portal
+
+       my $err = $self->send_to_portal() ;
+       return $err if $err ;
+
+       return ;
+}
+
+sub send_to_portal {
+
+       my( $self, $to_hub ) = @_ ;
+
+       eval {
+
+               Stem::Portal::send_msg( $self, $to_hub ) ;
+       } ;
+
+       return "No Stem::Portal Cell was configured" if $@ ;
+
+       return ;
+}
+
+
+sub _find_local_cell {
+
+       my ( $self ) = @_ ;
+
+       my $cell_name   = $self->{'to'}{'cell'} ;
+       my $target      = $self->{'to'}{'target'} ;
+
+       return lookup_cell( $cell_name, $target ) ;
+}
+
+sub _deliver_to_cell {
+
+       my ( $self, $cell ) = @_ ;
+
+# set the method 
+
+       my $method = ( $self->{'type'} eq 'cmd' ) ?
+                               "$self->{'cmd'}_cmd" :
+                               "$self->{'type'}_in" ;
+
+#print "METH: $method\n" ;
+
+# check if we can deliver there or to msg_in
+
+       unless ( $cell->can( $method ) ) {
+
+               return $self->dump( <<DUMP ) unless( $cell->can( 'msg_in' ) ) ;
+missing message delivery methods '$method' and 'msg_in'
+DUMP
+
+               $method = 'msg_in' ;
+       }
+
+       TraceMsg "MSG to $cell $method" ;
+
+       my @response = $cell->$method( $self ) ;
+
+#print "RESP [@response]\n" ;
+
+# if we get a response then return it in a message
+
+       if ( @response && $self->{'type'} eq 'cmd' ) {
+
+# make the response data a reference
+
+               my $response = shift @response ;
+               my $data = ( ref $response ) ? $response : \$response ;
+
+#print $self->dump( 'CMD msg' ) ;
+               my $reply_msg = $self->reply(
+                                       'data' => $data,
+               ) ;
+
+#print $reply_msg->dump( 'AUTO REPONSE' ) ;
+
+               $reply_msg->dispatch() ;
+       }
+
+       if ( $self->{'ack_req'} ) {
+
+               my $reply_msg = $self->reply( 'type' => 'msg_ack' ) ;
+
+               $reply_msg->dispatch() ;
+       }
+
+       return ;
+}
+
+# dump a message for debugging
+
+sub dump {
+
+       my( $self, $label, $deep ) = @_ ;
+
+       require Data::Dumper ;
+
+       my $dump = '' ;
+       $label ||= 'UNKNOWN' ;
+
+       my( $file_name, $line_num ) = (caller)[1,2] ;
+
+       $dump .= <<LABEL ;
+
+>>>>
+MSG Dump at Line $line_num in $file_name
+$label = {
+LABEL
+
+       foreach my $type ( @addr_types ) {
+
+               my $addr = $self->{$type} ;
+
+               next unless $addr ;
+
+               my $addr_text = $addr || 'NONE' ;
+
+               $dump .= "\t$type\t=> $addr_text\n" ;
+       }
+
+       foreach my $attr ( sort keys %is_plain_attr ) {
+
+               next unless exists $self->{$attr} ;
+
+               my $tab = ( length $attr > 4 ) ? "" : "\t" ;
+
+               my( $val_text, $q, $ret ) ; 
+
+               if ( $deep || $attr eq 'data' ) {
+
+                       $val_text = Data::Dumper::Dumper( $self->{$attr} ) ;
+
+                       $val_text =~ s/^.+?=// ;
+                       $val_text =~ s/;\n?$// ;
+                       $val_text =~ s/^\s+/\t\t/gm ;
+                       $val_text =~ s/^\s*([{}])/\t$1/gm ;
+
+                       $q = '' ;
+                       $ret = "\n" ;
+               }
+               else {
+                       $val_text = $self->{$attr} ;
+                       $q = $val_text =~ /\D/ ? "'" : '' ;
+                       $ret = '' ;
+               }
+
+               $dump .= <<ATTR ;
+       $attr$tab       => $ret$q$val_text$q,
+ATTR
+
+       }
+
+       $dump .= "}\n<<<<\n\n" ;
+
+       return($dump) ;
+}
+
+1 ;
diff --git a/lib/Stem/Packet.pm b/lib/Stem/Packet.pm
new file mode 100644 (file)
index 0000000..57a167b
--- /dev/null
@@ -0,0 +1,270 @@
+#  File: Stem/Packet.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Packet ;
+
+use strict ;
+
+use Stem::Class ;
+use Stem::Codec ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'codec',
+               'env'           => 'packet_codec',
+               'default'       => 'Data::Dumper',
+               'help'          => <<HELP,
+This is the name of the Codec:: subclass that will be used in this cell
+HELP
+       },
+       {
+               'name'          => 'object',
+               'type'          => 'object',
+               'help'          => <<HELP,
+If an object is passed in, the filter will use it for callbacks
+HELP
+       },
+       {
+               'name'          => 'packet_method',
+               'default'       => 'packet_out',
+               'help'          => <<HELP,
+This method is called on the object when a packet has encoded from
+internal data
+HELP
+       },
+
+       {
+               'name'          => 'data_method',
+               'default'       => 'packet_data',
+               'help'          => <<HELP,
+This method is called on the object when a packet has been decoded
+from external data
+HELP
+       },
+
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::Packet
+
+=over 4
+
+
+=item * Attribute - B<codec>
+
+=over 4
+
+
+=item Description:
+This is the name of the Codec:: subclass that will be used in this cell
+
+
+=item It B<defaults> to: Storable
+Unknown attribute env
+
+=back
+
+=item * Attribute - B<object>
+
+=over 4
+
+
+=item Description:
+If an object is passed in, the filter will use it for callbacks
+
+
+=item Its B<type> is: object
+
+=back
+
+=item * Attribute - B<packet_method>
+
+=over 4
+
+
+=item Description:
+This method is called on the object when a packet has encoded from
+internal data
+
+
+=item It B<defaults> to: packet_out
+
+=back
+
+=item * Attribute - B<data_method>
+
+=over 4
+
+
+=item Description:
+This method is called on the object when a packet has been decoded
+from external data
+
+
+=item It B<defaults> to: packet_data
+
+=back
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+sub new {
+
+       my( $class ) = shift ;
+
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+#      my @codec_args = exists( $self->{codec} ) ?
+#                      ( codec => $self->{codec} ) : () ;
+
+#print "NEW PACKET CODEC $self->{codec}\n" ;
+
+       my $codec_obj = Stem::Codec->new( codec => $self->{codec} ) ;
+       return $codec_obj unless ref $codec_obj ;
+       $self->{'codec_obj'} = $codec_obj ;
+
+#print "CODEC OBJ [$codec_obj]\n" ;
+
+       return $self ;
+}
+
+my $END_MARK = "\012#END\012" ;
+my $end_mark_len = length $END_MARK ;
+
+
+sub to_packet {
+
+       my $self = shift ;
+
+       return unless @_ ;
+#use Data::Dumper ;
+#print Dumper $_[0] ;
+
+       my $codec_text = $self->{'codec_obj'}->encode( $_[0] ) ;
+
+#print Dumper $codec_text ;
+       my $size = length( ${$codec_text} ) ;
+
+# wrap the packet text with a size/codec/end pair of lines
+
+       my $packet_text = "#$size:$self->{'codec'}\012${$codec_text}$END_MARK" ;
+
+#print "PACKET TEXT [$packet_text]\n" ;
+
+       $self->_callback( 'packet_method', \$packet_text ) ;
+       return \$packet_text ;
+}
+
+sub to_data {
+
+       my( $self, $input ) = @_ ;
+
+#print "IN DATA [$input]\n" ;
+       my $buf_ref = \$self->{'buffer'} ;
+
+       ${$buf_ref} .= ( ref $input eq 'SCALAR' ) ? ${$input} : $input 
+               if defined $input ;
+
+       my $codec = $self->{'codec'} ;
+
+       while( 1 ) {
+
+               unless ( $self->{'packet_len'} ) {
+
+# grab the packet_len if we can from the header line
+
+                       return unless ${$buf_ref} =~
+                               s/\A#(\d+):$self->{'codec'}\012// ;
+
+                       $self->{'packet_len'} = $1 ;
+               }
+
+               my $packet_len = $self->{'packet_len'} || 0 ;
+
+#print "PACKET_LEN [$packet_len]\n" ;
+
+# see if we have a full packet with end line
+
+#print "IN BUF [${$buf_ref}]\n" ;
+
+               return if length( ${$buf_ref} ) < $packet_len ;
+
+# old regex method was limited to 64k bytes in a packet
+#              return unless ${$buf_ref} =~ s/^(.{$packet_len})$END_MARK//s ;
+
+               return unless
+               substr( ${$buf_ref}, $packet_len, $end_mark_len ) eq $END_MARK ;
+
+# grab the packet data and end marker and delete it from the buffer
+               my $packet = substr( ${$buf_ref}, 0,
+                                    $packet_len + $end_mark_len, '' ) ;
+
+# delete the end marker from the packet
+
+               substr( $packet, $packet_len, $end_mark_len, '' ) ;
+
+#print "IN PACKET [$packet]\n" ;
+
+               my $decoded_data = $self->{'codec_obj'}->decode( $packet ) ;
+
+#use Data::Dumper ;
+#print "DECODED: ", Dumper( $decoded_data ) ;
+
+               $self->{'packet_len'} = 0 ;
+
+#local( $SIG{'__WARN__'} ) = sub {} ;
+
+               next if $self->_callback( 'data_method', $decoded_data ) ;
+               return( $decoded_data ) ;
+       }
+}
+
+sub _callback {
+
+       my ( $self, $method_attr, @data ) = @_ ;
+
+       my $obj = $self->{'object'} or return ;
+       my $method = $self->{$method_attr} ;
+       my $code = $obj->can( $method ) or return ;
+
+       $obj->$code( @data ) ;
+
+       return 1 ;
+}
+
+1 ;
diff --git a/lib/Stem/Portal.pm b/lib/Stem/Portal.pm
new file mode 100644 (file)
index 0000000..c9cb2d4
--- /dev/null
@@ -0,0 +1,562 @@
+#  File: Stem/Portal.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Portal ;
+
+use strict ;
+use Carp ;
+use Data::Dumper ;
+
+use Stem::AsyncIO ;
+use Stem::Vars ;
+use Stem::Packet ;
+use Stem::Socket ;
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+my %name_to_portal ;
+my %portal_to_names ;
+
+my $default_portal ;
+
+
+Stem::Route::register_class( __PACKAGE__, 'port' ) ;
+
+my $attr_spec_portal = [
+
+       {
+               'name'          => 'reg_name',
+               'default'       => '',
+               'help'          => <<HELP,
+This is a unique name used to register this instance of a Portal.
+HELP
+       },
+       {
+               'name'          => 'server',
+               'env'           => 'server',
+               'help'          => <<HELP,
+This determines if we are a server or a client.
+If it is true, we are a server.  Otherwise, we are a client.
+HELP
+       },
+       {
+               'name'          => 'sync',
+               'type'          => 'boolean',
+               'default'       => 1,
+               'help'          => <<HELP,
+Mark this as a synchronously connecting Portal. Default is syncronous
+connections, set to 0 for asynchronous. In both cases the same method
+callbacks are used.
+HELP
+       },
+       {
+               'name'          => 'port',
+               'default'       => 10_000,
+               'env'           => 'port',
+               'help'          => <<HELP,
+This determines which port we bind to  if we are a server.
+This determines which port we connect to if we are a client. 
+The default value is 10,000.
+HELP
+       },
+       {
+               'name'          => 'host',
+               'default'       => 'localhost',
+               'env'           => 'host',
+               'help'          => <<HELP,
+This determines which host we attach to when we are a client.
+The default value is localhost.
+HELP
+       },
+
+       {
+               'name'          => 'spawn_conf_file',
+               'help'          => <<HELP,
+This tells the portal to fork another Stem Hub and pass this value as
+the configuration file argument to run_stem.  The new Hub will be
+connected to this Portal and be private to it.
+HELP
+       },
+
+       {
+               'name'          => 'spawn_conf_args',
+               'help'          => <<HELP,
+
+This tells the portal to fork another Stem Hub and pass (via a
+message) this data to the Stem::Conf as a configuration 
+The new Hub will be connected to this Portal and be private
+to it.
+
+HELP
+       },
+
+
+       {
+               'name'          => 'run_stem_args',
+               'help'          => <<HELP,
+These are the command line arguments to run_stem for the spawned Hub.
+HELP
+       },
+       {
+               'name'          => 'codec',
+               'help'          => <<HELP,
+This is the sub-class that is used to convert messages to/from a byte
+stream for this portal
+HELP
+       },
+       {
+               'name'          => 'disable',
+               'env'           => 'disable',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag will disable this Portal. It will not construct an object and
+no errors will be returned.
+HELP
+       },
+
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec_portal, @_ ) ;
+       return $self unless ref $self ;
+
+       return if $self->{ 'disable' } ;
+
+       my $name = $Stem::Vars::Hub_name ;
+
+       if ( $Env{'portal_use_stdio'} ) {
+
+               return $self->new_child_portal() ;
+       }
+
+       if ( $self->{'spawn_conf_file'} || $self->{'spawn_conf_args'} ) {
+
+               return $self->new_parent_portal() ;
+       }
+
+       if ( $self->{'server'} ) {
+
+               $self->{'type'} = 'listener' ;
+               $self->{'server_name'} = $name ;
+       }
+       else {
+
+               $self->{'type'} = 'client' ;
+               $self->{'name'} = $name ;
+       }
+
+#print "REG new [$self->{'reg_name'}]\n" ;
+
+       my $sock_obj = Stem::Socket->new(
+                               'object'        => $self,
+                               'host'          => $self->{'host'},
+                               'port'          => $self->{'port'},
+                               'server'        => $self->{'server'},
+                               'sync'          => $self->{'sync'},
+       ) ;
+
+       ref $sock_obj or return <<ERR ;
+Stem::Portal '$self->{'reg_name'}' tried to connect/listen to $self->{'host'}:$self->{'port'}
+$sock_obj
+ERR
+
+       $self->{'sock_obj'} = $sock_obj ;
+
+       return ;
+}
+
+sub connected {
+
+       my( $self, $connected_sock ) = @_ ;
+
+       my( $portal ) ;
+
+       TraceStatus "Portal Connected" ;
+
+       $self->{'read_fh'} = $connected_sock ;
+       $self->{'write_fh'} = $connected_sock ;
+
+       my $type = $self->{'type'} ;
+
+       if ( $type eq 'listener' ) {
+
+# fork off a new portal by making a clone of the listener portal
+
+               $portal = bless { %$self } ;
+               $portal->{'type'} = 'accepted' ;
+
+               my $name = $portal->{'server_name'} ;
+
+               $portal->{'name'} = $name ;
+
+               delete( $portal->{'sock_obj'} ) ;
+       }
+       else {
+
+#print "Portal Connected\n" ;
+
+# a client portal is just itself
+
+               $portal = $self ;
+
+#print "REG [$self->{'reg_name'}]\n" ;
+
+               if ( my $name = $self->{'reg_name'} ) {
+
+                       $portal->register( $name ) ;
+               }
+
+               unless ( $default_portal ) {
+
+                       $portal->register( 'DEFAULT' ) ;
+                       $default_portal = $portal ;
+               }
+       }
+
+       my $err = $portal->_activate() ;
+
+       die $err if $err ;
+}
+
+my $run_stem_path ;
+
+sub new_parent_portal {
+
+       my( $self ) = @_ ;
+
+       $run_stem_path ||= do {
+
+               require Stem::Proc ;
+               require Stem::InstallConfig ;
+
+               $Stem::InstallConfig{ run_stem_path } ;
+       } ;
+
+       my $conf_file = $self->{'spawn_conf_file'} || 'portal_child' ;
+
+       my @run_stem_args = @{$self->{'run_stem_args'} || []} ;
+
+       my $proc = Stem::Proc->new(
+
+                       path            => $run_stem_path,
+                       proc_args       => [
+                               'portal_use_stdio=1',
+                               @run_stem_args,
+                               $conf_file,
+                       ],
+                       spawn_now       => 1,
+                       cell_attr       => [
+                               no_io   => 1,
+                       ],
+       ) ;
+
+       $self->{'proc'} = $proc ;
+
+       TraceStatus "Portal Paren" ;
+
+       $self->{'read_fh'} = $proc->read_fh() ;
+       $self->{'write_fh'} = $proc->write_fh() ;
+
+#print "REG [$self->{'reg_name'}]\n" ;
+
+       my $err = $self->_activate() ;
+
+
+       die $err if $err ;
+###########
+#       $self->{'spawn_conf_args'} ) {
+#### when can we send the conf data?
+##########
+
+
+}
+
+sub new_child_portal {
+
+       my( $self ) = @_ ;
+
+       $self->{'type'} = 'child' ;
+
+
+       TraceStatus "Portal Child" ;
+
+       $self->{'read_fh'} = \*STDIN ;
+       $self->{'write_fh'} = \*STDOUT ;
+
+#print "REG [$self->{'reg_name'}]\n" ;
+
+       unless ( $default_portal ) {
+
+               $self->register( 'DEFAULT' ) ;
+               $default_portal = $self ;
+       }
+
+       if ( my $portal_name = $Env{'portal_name'} ) {
+
+               $self->register( $portal_name ) ;
+       }
+
+       my $err = $self->_activate() ;
+
+       die $err if $err ;
+}
+
+
+sub _activate {
+
+       my( $self ) = @_ ;
+
+       TraceStatus "Active portal" ;
+
+       my $aio = Stem::AsyncIO->new(
+
+                       'object'        => $self,
+                       'read_fh'       => $self->{'read_fh'},
+                       'write_fh'      => $self->{'write_fh'},
+                       'read_method'   => 'portal_data',
+                       'closed_method' => 'portal_closed',
+       ) ;
+
+       return $aio unless ref $aio ;
+
+       $self->{'aio'} = $aio ;
+
+       my $packet = Stem::Packet->new( 'codec' => $self->{'codec'} ) ;
+       return $packet unless ref $packet ;
+       $self->{'packet'} = $packet ;
+
+       my $msg = Stem::Msg->new( 'from' => "${Stem::Vars::Hub_name}:port",
+                                 'type'     => 'register',
+       ) ;
+
+       return $msg unless ref $msg ;
+
+       $self->write_msg( $msg ) ;
+
+       return ;
+}
+
+# this is not a method, but a class sub
+
+sub send_msg {
+
+       my ( $msg, $to_hub ) = @_ ;
+
+       $to_hub ||= 'DEFAULT' ;
+
+       my $self = $name_to_portal{ $to_hub } ;
+
+       return "unknown Portal '$to_hub'" unless $self ;
+
+       $msg->from_hub( $self->{'name'} ) unless $msg->from_hub() ;
+#      $msg->from_hub( $self->{'name'} ) ;
+
+       unless( $self->{'remote_hub'} ) {
+
+               push( @{$self->{'queued_msgs'}}, $msg ) ;
+
+               return ;
+       }
+
+       $self->write_msg( $msg ) ;
+
+       return ;
+}
+
+# this is a regular method called by the above sub.
+
+sub write_msg {
+
+       my( $self, $msg ) = @_ ;
+
+       my $packet_text = $self->{'packet'}->to_packet( $msg ) ;
+
+#print "PACK SEND [$packet_text]\n" ;
+
+       $self->{'aio'}->write( $packet_text ) ;
+}
+
+sub portal_data {
+
+       my( $self, $packet_text ) = @_ ;
+
+       my $packet = $self->{'packet'} ;
+
+# parse out all messages that may be in the input data
+
+       while( my $msg = $packet->to_data( $packet_text ) ) {
+
+               $self->_portal_msg_in( $msg ) ;
+
+# no more incoming data in this callback 
+
+               $packet_text = '' ;
+       }
+}
+
+sub _portal_msg_in {
+
+       my( $self, $msg ) = @_ ;
+
+       if ( $msg->type() eq 'register' ) {
+
+# register the other hub and mark this hub as connecting to it.
+
+               $self->{'remote_hub'} = $msg->from_hub() ;
+               warn( caller(), $msg->dump() ) and die
+                       'Msg Has No Remote Hub' unless $self->{'remote_hub'} ;
+               $self->register( $self->{'remote_hub'} ) ;
+
+# handle messages that got queued while the portal was down
+
+               while( my $queued_msg = shift @{$self->{'queued_msgs'}} ) {
+
+#print $queued_msg->dump( 'QUEUED' ) ;
+                       $self->write_msg( $queued_msg ) ;
+               }
+
+                return ;
+       }
+
+       $msg->in_portal( $self->{'remote_hub'} ) ;
+       $msg->dispatch() ;
+}
+
+
+sub portal_closed {
+
+       my( $self ) = @_ ;
+
+#TraceStatus "Portal closed" ;
+
+       Stem::Route::unregister_cell( $self ) ;
+       my $names = $self->unregister() ;
+
+       if ( $self->{'type'} eq 'accepted' ) {
+
+#              TraceStatus "client hub '$self->{'name'}' closed" ;
+
+               $self->shut_down() ;
+               return ;
+       }
+
+       my @hub_names = ref $names ? @{$names} : 'UNKNOWN' ;
+
+       Stem::Event::end_loop() ;
+
+       die "server hub [@hub_names] died" ;
+}
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+       TraceStatus "SHUT DOWN port : ". Dumper($self);
+
+       $self->{'aio'}->shut_down() ;
+       delete @{$self}{qw( object aio )} ;
+}
+
+# this is for messages directly to this portal. messages are sent out
+# the portal via the send class method
+#
+# UNUSED so far
+
+sub msg_in {
+
+       my( $self, $msg ) = @_ ;
+
+       TraceStatus "portal msg in" ;
+}
+
+sub register {
+
+       my( $self, $name ) = @_ ;
+
+#print "NAME [$name]: ", caller(), "\n" ;
+
+       TraceStatus "portal arg: [$self] [$name]\n\t",
+                                       map( "<$_>", caller() ), "\n" ;
+
+       $name_to_portal{ $name } = $self ;
+       push( @{$portal_to_names{ $self }}, $name ) ;
+}
+
+sub unregister {
+
+       my( $name ) = @_ ;
+
+# convert a name to its object ;
+
+       my $portal = ref $name ? $name : $name_to_portal{ $name } ;
+
+       if ( $portal ) {
+
+               delete $name_to_portal{ $portal } ;
+
+               my $names = delete $portal_to_names{ $portal } ;
+
+               return $names ;
+
+       }
+
+       return ;
+}
+
+sub status_cmd {
+
+       my ($self, $msg ) = @_ ;
+
+#print $msg->dump( 'PORT' ) ;
+
+       my $status = <<STATUS ;
+
+Portal Status for Hub '$Stem::Vars::Hub_name'
+
+STATUS
+
+       foreach my $port_name ( sort keys %name_to_portal ) {
+
+               my $portal = $name_to_portal{ $port_name } ;
+
+               $status .= <<STATUS ;
+$port_name
+       Hub:    $portal->{'remote_hub'}
+       Type:   $portal->{'type'}
+
+STATUS
+
+       }
+
+       return $status ;
+}
+
+1 ;
diff --git a/lib/Stem/Proc.pm b/lib/Stem/Proc.pm
new file mode 100644 (file)
index 0000000..8b8f9a3
--- /dev/null
@@ -0,0 +1,568 @@
+#  File: Stem/Proc.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Proc ;
+
+use Carp qw( cluck ) ;
+
+use strict ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+use IO::Socket ;
+use Symbol ;
+use Carp ;
+use POSIX qw( :sys_wait_h ) ;
+use constant EXEC_ERROR        => 199 ;
+
+use Stem::Route qw( :cell ) ;
+
+use base 'Stem::Cell' ;
+
+my %pid_to_obj ;
+
+my $child_event = Stem::Event::Signal->new(
+       'object' => bless({}),
+       'signal' => 'CHLD'
+) ;
+
+ref $child_event or return
+       "Stem::Proc can't create SIG_CHLD handler: $child_event\n" ;
+
+my $attr_spec = [
+
+
+###############
+# if you pass in an optional object, then that will be the base for
+# all the callback methods. the message and log options will not be
+# done as they work only using the callbacks internal to Stem::Proc.
+###############
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+This is the name under which this Cell was registered.
+HELP
+       },
+       {
+               'name'          => 'object',
+               'type'          => 'object',
+               'help'          => <<HELP,
+This is the owner object for this Cell and it will get the callbacks.
+HELP
+       },
+       {
+               'name'          => 'path',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the path to the program to run.
+HELP
+       },
+       {
+               'name'          => 'proc_args',
+               'default'       => [],
+               'type'          => 'list',
+               'help'          => <<HELP,
+This is a list of the arguments to the program to be run.
+HELP
+       },
+       {
+               'name'          => 'spawn_now',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means to spawn the process at constructor time. Default is to 
+spawn it when triggered via a message. 
+HELP
+       },
+       {
+               'name'          => 'no_io',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means the process will do no standard I/O and those pipes will
+not be created.
+HELP
+       },
+       {
+               'name'          => 'no_read',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means the Cell will not read from the process and that pipe
+will not be created. (unsupported)
+HELP
+       },
+       {
+               'name'          => 'no_write',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means the Cell will not write to the process and that pipe
+will not be created. (unsupported)
+HELP
+       },
+       {
+               'name'          => 'use_stderr',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag means the Cell will read from the stderr handle of the process.
+By default the stderr pipe is not created and its output comes in on stdout.
+HELP
+       },
+
+       {
+               'name'          => 'use_pty',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+This flag will cause the process to be run behind a pseudo-tty device.
+HELP
+       },
+       {
+               'name'          => 'exited_method',
+               'default'       => 'proc_ended',
+               'help'          => <<HELP,
+This method is called on the owner object when the process exits.
+HELP
+       },
+       {
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+               'help'          => <<HELP,
+This value is the attributes for the included Stem::Cell which handles
+cloning, async I/O and pipes.
+HELP
+       },
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+
+       my $err = $self->find_exec_path() ;
+       return $err if $err ;
+
+       $self->{ 'use_stderr' } = 0 if $self->{ 'use_pty' } ;
+
+       $err = $self->cell_set_args(
+                       'path'          => $self->{'path'},
+                       'proc_args'     => $self->{'proc_args'},
+       ) ;
+
+       return $err if $err ;
+
+       $self->cell_set_args( 'no_async' => 1 ) if  $self->{ 'no_io' } ;
+
+###########
+# cloneable and spawn_now should be mutually exclusive
+##########
+
+       if ( $self->{'spawn_now'} ) {
+
+TraceStatus "New Spawn" ;
+
+               my $err = $self->cell_trigger();
+               return $err unless ref $err ;
+
+               $err = $self->spawn() ;
+               return $err if $err ;
+       }
+
+       return $self ;
+}
+
+
+sub find_exec_path {
+
+       my( $self ) = shift ;
+
+       my $proc_path = $self->{'path'} ;
+
+       return if -x $proc_path ;
+
+       foreach my $path ( File::Spec->path() ) {
+
+               my $exec_path = File::Spec->catfile( $path, $proc_path ) ; 
+
+               next unless -f $exec_path ;
+
+               if ( -x $exec_path ) {
+
+                       $self->{'path'} = $exec_path ;
+                       return ;
+               }
+       }
+
+       return "$self->{'path'} is not found in $ENV{PATH}" ;
+}
+
+sub triggered_cell {
+
+       my( $self ) = @_ ;
+
+       my $err = $self->spawn() ;
+       return $err if $err ;
+
+#use Data::Dumper ;
+#print Dumper \%INC ;
+
+#print $self->status_cmd() ;
+
+       return ;
+}
+
+
+sub spawn {
+
+       my( $self ) = @_ ;
+
+       unless( $self->{'no_io'} ) {
+
+               $self->_parent_io() ;
+       }
+
+       $self->{'ppid'} = $$ ;  
+
+       my @exec_args = @{$self->{'proc_args'}} ;
+
+       if ( my $pipe_args_ref = $self->cell_get_args( 'args' ) ) {
+
+               push( @exec_args, (ref $pipe_args_ref) ?
+                               @{$pipe_args_ref} : $pipe_args_ref ) ;
+       }
+
+       my $pid = fork() ;
+       defined $pid or die "Stem::Proc can't fork $!" ;
+
+       if ( $pid ) {
+
+# in parent
+
+# must close the child fh in the parent so we will see a closed socket
+# when the child exits
+
+               unless( $self->{'no_io'} ) {
+
+                       close $self->{'child_fh'} ;
+                       close $self->{'child_err_fh'} if $self->{'use_stderr'} ;
+
+                       delete( $self->{'child_fh'} ) ;
+                       delete( $self->{'child_err_fh'} ) ;
+               }
+
+               TraceStatus "forked $pid" ;
+
+               $self->{'pid'} = $pid ; 
+               $pid_to_obj{ $pid } = $self ;
+
+               $self->cell_set_args( 'info' => <<INFO ) ;
+
+Path:  $self->{'path'}
+Args:  @exec_args
+Pid:   $pid
+
+INFO
+
+
+       }
+       else {
+
+# in child
+               unless( $self->{'no_io'} ) {
+
+                       $self->_child_io() ;
+               }
+
+###############
+###############
+## add support for setting local(%ENV)
+###############
+###############
+
+#TraceStatus "Exec'ing $self->{'path'}, @exec_args" ;
+
+               exec $self->{'path'}, @exec_args ;
+
+               exit EXEC_ERROR ;
+       }
+
+# back in parent (unless no exec -- FIX THAT!! unless path is
+# required) we could do a forked stem hub by execing stem with a new
+# config which has a portal with STDIN/STDOUT as fh's
+
+       my $err = $self->cell_set_args( 'aio_args' => [
+                       'read_fh'       => $self->{'parent_fh'},
+                       'write_fh'      => $self->{'parent_fh'},
+                       'stderr_fh'     => $self->{'parent_err_fh'},
+                       'closed_method' => $self->{'exited_method'},
+               ]
+       ) ;
+
+       return $err if $err ;
+
+       $self->cell_worker_ready() ;
+
+       return ;
+}
+
+
+sub _parent_io {
+
+       my( $self ) = @_ ;
+
+       my( $parent_fh, $child_fh ) ;
+
+
+       if ( $self->{'use_pty'} ) {
+
+               require IO::Pty ;
+               $parent_fh = IO::Pty->new() ;
+               $child_fh = $parent_fh->slave() ;
+       }
+       else {
+
+               $parent_fh = gensym ;
+               $child_fh = gensym ;
+
+               socketpair( $parent_fh, $child_fh,
+                                AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ||
+                                       die "can't make socket pair $!" ;
+       }
+
+       bless $parent_fh, 'IO::Socket' ;
+
+       $self->{'parent_fh'} = $parent_fh ;
+
+       $parent_fh->blocking( 0 ) ;
+
+       $self->{'child_fh'} = $child_fh ;
+
+#############
+# add pty support here
+#############
+
+       if ( $self->{'use_stderr'} ) {
+
+               my $parent_err_fh = gensym ;
+               my $child_err_fh = gensym ;
+
+               socketpair( $parent_err_fh, $child_err_fh,
+                                AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ||
+                               die "can't make socket pair $!" ;
+
+               $self->{'parent_err_fh'} = $parent_err_fh ;
+               $self->{'child_err_fh'} = $child_err_fh ;
+       }
+}
+
+sub _child_io {
+
+       my( $self ) = @_ ;
+
+       close $self->{'parent_fh'} ;
+       close $self->{'parent_err_fh'} if $self->{'use_stderr'} ;
+
+       my $child_fd = fileno( $self->{'child_fh'} ) ;
+
+       open( \*STDIN,  "<&$child_fd" ) ||
+                               croak "dup open of STDIN failed $!" ;
+
+       open( \*STDOUT, ">&$child_fd" ) ||
+                               croak "dup open of STDOUT failed $!" ;
+
+       if ( $self->{'use_stderr'} ) {
+
+               my $child_err_fd = fileno( $self->{'child_err_fh'} ) ;
+
+               open( \*STDERR,  ">&$child_err_fd" ) ||
+                               croak "dup open of STDERR failed $!" ;
+
+       }
+       else {
+               open( \*STDERR,  ">&$child_fd" ) ||
+                               croak "dup open of STDERR failed $!" ;
+       }
+}
+
+sub write {
+
+       my( $self, $data ) = @_ ;
+
+       $self->cell_write( $data ) ;
+}
+
+
+sub read_fh {
+
+       $_[0]->{'parent_fh'} ;
+}
+
+sub write_fh {
+
+       $_[0]->{'parent_fh'} ;
+}
+
+sub stderr_fh {
+
+       $_[0]->{'parent_err_fh'} ;
+}
+
+sub proc_ended {
+
+       my( $self ) = @_ ;
+
+#print "PROC ended, shutting down\n" ;
+
+       $self->shut_down() ;
+}
+
+sub signal_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+       return unless ref $data eq 'SCALAR' ;
+
+       my $signal = ${$data} ;
+
+       $self->signal( $signal ) ;
+
+       return ;
+}
+
+sub signal {
+
+       my( $self, $signal ) = @_ ;
+
+       $signal ||= 'SIGTERM' ;
+
+       TraceStatus "$self->{'pid'} received SIGTERM" ;
+
+       kill $signal, $self->{'pid'} ;
+}
+
+sub sig_chld_handler {
+
+       while ( 1 ) {
+
+               my $child_pid = waitpid( -1, WNOHANG ) ;
+
+               return if $child_pid == 0 || $child_pid == -1 ;
+
+               my $proc_status = $? ;
+
+               my ( $exit_code, $exit_signal ) ;
+
+               if ( WIFEXITED( $proc_status ) ) {
+
+                       $exit_code = WEXITSTATUS( $proc_status ) ;
+
+                       TraceStatus "EXIT: $exit_code" ;
+
+               }
+               else {
+                       $exit_signal = WTERMSIG( $proc_status ) ;
+
+                       TraceStatus "EXIT signal: $exit_signal" ;
+
+               }
+
+#print "EXIT CODE [$exit_code]\n" ;
+
+               if ( my $self = $pid_to_obj{ $child_pid } ) {
+
+                       $self->{'exit_code'} = $exit_code ;
+                       $self->{'exit_signal'} = $exit_signal ;
+
+                       if ( defined( $exit_code ) &&
+                            $exit_code == EXEC_ERROR ) {
+
+                               print <<ERR ;
+Stem::Proc exec failed on path '$self->{'path'}'
+ERR
+
+                       }
+
+                       $self->exited() ;
+               }
+               else {
+#### ERROR
+print "reaped unknown process pid $child_pid\n"
+               }
+
+       }
+}
+
+sub exited {
+
+       my( $self ) = @_ ;
+
+######################
+# handle watchdog here
+######################
+
+       $self->{'exited'} = 1 ;
+
+#print "EXITED\n" ;
+
+       $self->shut_down() if $self->{'no_io'} ;
+
+       TraceStatus "Proc $self->{'pid'} exited" ;
+}
+
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+#print "PROC SHUT\n" ;
+
+       unless( $self->{'exited'} ) {
+
+               kill 'SIGTERM', $self->{'pid'} ;
+
+               TraceStatus "kill of proc $self->{'pid'}" ;
+       }
+
+       return if $self->{'no_io'} ;
+
+       if ( my $pid = $self->{'pid'} ) {
+
+               delete( $pid_to_obj{ $pid } ) ;
+       }
+
+       $self->cell_shut_down() ;
+
+       close $self->{'parent_fh'} ;
+       close $self->{'parent_err_fh'} if $self->{'use_stderr'} ;
+}
+
+1 ;
diff --git a/lib/Stem/Route.pm b/lib/Stem/Route.pm
new file mode 100644 (file)
index 0000000..0dae3a6
--- /dev/null
@@ -0,0 +1,441 @@
+#  File: Stem/Route.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Route;
+
+#use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+#use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+use strict ;
+
+use base 'Exporter' ;
+use vars qw( %EXPORT_TAGS ) ;
+
+%EXPORT_TAGS = (
+       'cell' => [ qw(
+               register_cell
+               alias_cell
+               unregister_cell
+               lookup_cell
+               lookup_cell_name
+               register_class
+       ) ],
+       'filter' => [ qw(
+               push_filter_on_cell
+               pop_filter_from_cell
+       ) ],
+) ;
+
+Exporter::export_ok_tags( qw( cell filter ) );
+
+use constant DEBUG => 1;
+
+my %cell_info ;
+my %cell_name_to_obj ;
+
+register_class( __PACKAGE__, 'reg' ) ;
+
+#use diagnostics -verbose;
+
+## registration takes a minimum args of an object and a name.
+## an optional third arg of target is also accepted.
+##
+## the idea here is that when a portal connects, it's registered
+## with the local hub - which makes everyone aware of the new
+## portal.
+##
+## a couple remaining questions though .. should this registration
+## include the capabilities of the new portal?  should we add
+## an 'authentication' capability to the registration process?
+## ...just a few thoughts
+
+
+sub register_cell {
+
+       my( $obj, $name, $target ) = @_ ;
+
+       unless( $obj && $name ) {
+
+               my $err = <<ERR ;
+register() requires an object and a name, with an optional target.
+ERR
+
+#              TraceError $err ;
+
+               return $err ;
+       }
+
+       $target = '' unless defined $target ;
+
+       if ( $cell_name_to_obj{ $name }{ $target } ) {
+
+               my $err =
+                       "register_Cell $name:$target is already registered\n" ;
+
+               return $err ;
+       }
+
+       $cell_name_to_obj{ $name }{ $target } = $obj ;
+
+       $cell_info{ $obj }{'names'}{ $name }{ $target } = 1 ;
+       $cell_info{ $obj }{'primary_name'} ||= [ $name, $target ] ;
+
+       return ;
+}
+
+sub register_class {
+
+       my( $class, @nicks ) = @_ ;
+
+       foreach my $name ( $class, @nicks ) {
+
+               register_cell( $class, $name ) ;
+       }
+}
+
+sub alias_cell {
+
+       my( $obj, $name, $target ) = @_ ;
+
+       unless( $obj && $name ) {
+
+               my $err = <<ERR ;
+alias_cell() requires an object and a name, with an optional target.
+ERR
+
+#              TraceError $err ;
+
+               return $err ;
+       }
+
+       $target = '' unless defined $target ;
+
+
+       unless ( lookup_cell( $name, $target ) ) {
+
+               my $err = "Alias_cell: $name:$target is not registered\n" ;
+
+#              TraceError $err ;
+
+               return $err ;
+       }
+
+       $cell_name_to_obj{ $name }{ $target } = $obj ;
+       $cell_info{ $obj }{'names'}{ $name }{ $target } = 1 ;
+
+
+       return ;
+}
+
+sub unregister_cell {
+
+       my( $obj ) = shift ;
+
+       my $info_ref = $cell_info{ $obj } ;
+
+       unless ( $info_ref ) {
+
+               my $err = "unregister_cell: object [$obj] is not registered" ;
+               return $err ;
+       }
+
+       foreach my $name ( keys %{ $info_ref->{'names'} } ) {
+
+               foreach my $target (
+                       keys %{ $info_ref->{'names'}{$name} } ) {
+
+                       delete $cell_name_to_obj{ $name }{ $target } ;
+                       delete $cell_name_to_obj{ $name } if $target eq '' ;
+               }
+       }
+
+       delete $cell_info{ $obj } ;
+
+       return ;
+}
+
+
+# this sub returns a cell if it is registered. otherwise it returns a
+# proper false
+#
+# first check that the cell or parent cell exists.
+# if it is a targeted address then find the targeted cell or its parent cell
+# otherwise look for the regular cell with a null target.
+
+sub lookup_cell {
+
+       my( $name, $target ) = @_ ;
+
+#print "LOOK N [$name] T [$target]\n" ;
+       return unless exists( $cell_name_to_obj{ $name } ) ;
+
+# look for a targeted cell first and then for a configured or class cell
+
+       if ( defined $target ) {
+
+               my $obj = $cell_name_to_obj{ $name }{ $target } ;
+               return $obj if $obj ;
+       }
+
+       return $cell_name_to_obj{ $name }{''} ;
+}
+
+
+sub lookup_cell_name {
+
+       my( $obj ) = @_ ;
+
+       my $names_ref = $cell_info{ $obj }{'primary_name'} ;
+
+       return ( @{$names_ref} ) if $names_ref ;
+
+       return ;
+}
+
+
+sub push_filter_on_cell {
+
+       my( $obj, $filter ) = @_ ;
+
+       unless ( exists( $cell_info{ $obj } ) ) {
+
+               my $err = "push_filter_on_cell: object [$obj] is not registered" ;
+               return $err ;
+       }
+
+       push( @{ $cell_info{ $obj }{'filters'} }, $filter ) ;
+
+       return ;
+}
+
+sub pop_filter_on_cell {
+
+       my( $obj ) = @_ ;
+
+       unless ( exists( $cell_info{ $obj } ) ) {
+
+               my $err = "pop_filter_on_cell: object [$obj] is not registered" ;
+               return $err ;
+       }
+
+       pop( @{ $cell_info{ $obj }{'filters'} } ) ;
+
+       return ;
+}
+
+sub get_cell_filters {
+
+       my( $obj ) = @_ ;
+
+       return ( wantarray ) ?  @{ $cell_info{ $obj }{'filters'} } :
+                               $cell_info{ $obj }{'filters'} ;
+}
+
+
+sub status_cmd {
+
+       my( $class, $msg ) = @_ ;
+
+       my ( @cell_lines, %class_cell_texts ) ;
+
+#print map "$_ => $cell_name_to_obj{$_}\n", keys %cell_name_to_obj ;
+
+       foreach my $name ( keys %cell_name_to_obj ) {
+
+               my $cell = $cell_name_to_obj{$name}{''} ;
+
+#print "CELL $cell\n" ;
+
+# see if this is a Class Cell name
+
+               unless ( ref $cell ) {
+
+                       my $pad = "\t" x ( 3 - int( length( $cell ) / 8 ) ) ;
+
+                       $class_cell_texts{$cell} ||= "\t$cell$pad=>" ;
+
+                       next if $name eq $cell ;
+
+# it is a Class Cell alias
+                       $class_cell_texts{$cell} .= " $name" ;
+                       next ;
+               }
+
+               my $pad = "\t" x ( 4 - int( length( $name ) / 8 ) ) ;
+
+               my $cell_text = "\t$name$pad=> $cell\n" ;
+
+               foreach my $target ( keys %{ $cell_name_to_obj{$name} } ) {
+
+                       next if $target eq '' ;
+
+                       my $cell = $cell_name_to_obj{$name}{$target} ;
+
+                       my $pad = "\t" x (3 - int( length( ":$target" ) / 8 )) ;
+
+                       $cell_text .= "\t\t:$target$pad=> $cell\n" ;
+               }
+
+               push @cell_lines, $cell_text ;
+       }
+
+       @cell_lines = sort @cell_lines ;
+       my @class_lines = map { "$_\n" } sort values %class_cell_texts ;
+
+       my $hub_name = $Stem::Vars::Hub_name || '' ;
+
+       return <<STATUS ;
+
+Route Status for Hub '$hub_name'
+
+       Object Cells with Target names of their Cloned Cells
+
+@cell_lines
+       Class Cells with their Aliases
+
+@class_lines
+
+STATUS
+
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Stem::Route - Manages the Message Routing Tables and Cell Registry
+
+=head1 SYNPOSIS
+
+  use Stem::Route qw( :all );
+
+# $target is optional
+  register_cell( $object, $name, $target ) ;
+  unregister_cell($object);
+# or alternately...
+# again $target is optional
+  unregister_by_name($name, $target);
+
+=head1 DESCRIPTION
+
+The Stem::Route class manages the registry of Stem Cells and their
+names for a given Stem Hub (process). Any object which has selected
+methods which take Stem::Msg objects as arguments can be a registered
+cell. There are only 4 class methods in this module which work with
+the cell registry. They can be exported into a module individually or
+you can use the export tag :all to get them all.
+
+       register_cell( $object, $name )
+       register_cell( $object, $name, $target )
+
+       This class method takes the object to be registered and its
+       cell name and an optional target name. The object is
+       registered as the cell in this hub with this name/target
+       address. The cell address must be free to use - if it is in
+       used an error string is logged and returned. This address will be the
+       primary one for this cell. undef or () is returned upon
+       success.
+
+       alias_cell( $object, $alias )
+       alias_cell( $object, $alias, $target )
+
+       This class method takes the object and a cell alias for it and
+       an optional target name. The object will be registered as the
+       cell in this hub with this alias/target address. The object
+       must already be registered cell or an error string is logged
+       and returned.  undef or () is returned upon a success.
+
+       lookup_cell( $name, $target )
+
+       This class method takes a cell name and an optional target and
+       it looks up the cell registered under that address pair. It
+       returns the object if found or undef or () upon failure.
+
+       unregister_cell( $object )
+
+       This class method takes a object and deletes it and all of its
+       names and aliases from the cell registry. If the object is not
+       registered an error string is logged and returned.
+
+=head1 AUTHOR
+
+Originally started by Uri, current breakout by a-mused.
+
+=head1 STATUS
+
+Actively being developed.
+
+=head1 LAST-CHANGE
+
+Mon Jan 22 14:15:52 EST 2001
+
+=head1 NOTES
+
+  newest at the bottom
+
+  23 Jan 01
+  [01:09:34] <uri> here is a registry issue: i want to interpose cell in a
+           message stream. how do i do that without redoing all the configs?
+  [01:09:50] <uri> sorta like invisible renaming on the fly
+  [01:09:56] <amused> hrmm
+  [01:10:11] <uri> think about it. that will be big one day
+  [01:11:01] <uri> just like sysv streams. we push stuff onto the registry address. then messages get sent down
+           the list of pushed cells before being delivered to the real destination.
+  [01:11:29] <uri> so we need a way of moving messages from cell to cell without registering them globally but in
+           some sort of pipeline
+  [01:13:39] <amused> doesn't that violate a whole bunch of models and break distributed (multi-target) stuff?
+  [01:13:45] <uri> so instead of deliver, they RETURN a message. like status_cmd returns a status string
+  [01:14:12] <uri> no, only certain cells do that and only when they get
+           messages delivered that way.
+  [01:14:31] <uri> like stream_msg_in is called and it will return a message.
+  [01:14:44] <uri> insteead of msg_in or status_cmd.
+  [01:14:51] <amused> gotcha
+  [01:14:58] <uri> special input/output.
+  [01:15:00] <uri> same cell
+  [01:16:18] <uri> i like that. A LOT! very easy to do cell wise. and not much
+           work on the delivery side. some way to make the registry store a
+           stack of these under the name. make it a simple structure instead
+           of a cell you find with lookup.
+  [13:14:51] <uri> you push filter cells onto the destination cell (indexed by
+           its object ref). then any alias to it will have the same stack of
+           filters.
+  [13:15:52] <uri> when we deliver a message (the stuf you are touching), we
+           lookup the cell and then lookup via its ref) any filters. we then
+           loop over the filters passing in the message and getting one in
+           return and passint it to the next filter.
+  [13:16:02] <uri> just like sysV streams but unidirectional.
+  [13:16:38] <uri> we can interpose ANY set of filters before any named cell
+           transparently
+  [13:16:39] <uri> this is VERY cool.
+  [13:16:53] <uri> but not critical now. i just want to write up some notes on
+           it.
+
+=cut
+
diff --git a/lib/Stem/SockMsg.pm b/lib/Stem/SockMsg.pm
new file mode 100644 (file)
index 0000000..2accf18
--- /dev/null
@@ -0,0 +1,371 @@
+#  File: Stem/SockMsg.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::SockMsg ;
+
+use strict ;
+
+use Data::Dumper ;
+
+use Stem::Socket ;
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+use Stem::Route qw( :cell ) ;
+use base 'Stem::Cell' ;
+
+use Stem::Debug qw( dump_data dump_socket ) ;
+
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+The registration name for this Cell
+HELP
+       },
+
+       {
+               'name'          => 'host',
+               'env'           => 'host',
+               'help'          => <<HELP,
+Host address to listen on or connect to
+HELP
+       },
+
+       {
+               'name'          => 'port',
+               'env'           => 'port',
+               'required'      => 1,
+               'help'          => <<HELP,
+Port address to listen on or connect to
+HELP
+       },
+
+       {
+               'name'          => 'server',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Mark this Cell as a server (listens for connections)
+HELP
+       },
+
+       {
+               'name'          => 'connect_now',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Connect upon Cell creation
+HELP
+       },
+
+       {
+               'name'          => 'status_addr',
+               'type'          => 'address',
+               'help'          => <<HELP,
+Send status (connect/disconnect) messages to this address.
+HELP
+       },
+
+       {
+               'name'          => 'sync',
+               'type'          => 'boolean',
+               'default'       => 0,
+               'help'          => <<HELP,
+Mark this as a synchronously connecting socket. Default is asyncronous
+connections. In both cases the same method callbacks are used.
+HELP
+       },
+
+       {
+               'name'          => 'log_name',
+               'help'          => <<HELP,
+Log to send connection status to
+HELP
+       },
+
+       {
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+               'help'          => <<HELP,
+Argument list passed to Stem::Cell for this Cell
+HELP
+       },
+
+] ;
+
+#my $listener ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       if ( $self->{'server'} ) {
+               my $listen_obj = Stem::Socket->new( 
+                               'object'        => $self,
+                               'host'          => $self->{'host'},
+                               'port'          => $self->{'port'},
+                               'server'        => 1,
+               ) ;
+
+               return $listen_obj unless ref $listen_obj ;
+
+               my $host_text = $self->{'host'} ;
+
+               $host_text = 'localhost' unless defined $host_text ;
+
+               my $info = <<INFO ;
+SockMsg
+Type:  server
+Local: $host_text:$self->{'port'}
+INFO
+
+               $self->cell_info( $info ) ;
+
+               $self->{'listen_obj'} = $listen_obj ;
+
+#print "LISTEN $listen_obj\n" ;
+#$listener = $listen_obj ;
+
+               $self->cell_activate() ;
+       }
+       elsif ( $self->{'connect_now'} ) {
+
+               $self->connect() ;
+       }
+
+       $self->cell_set_args(
+                       'host'          => $self->{'host'},
+                       'port'          => $self->{'port'},
+                       'server'        => $self->{'server'},
+       ) ;
+
+#print  "Sock\n", Dumper( $self ) ;
+
+       return( $self ) ;
+}
+
+sub connect {
+
+       my( $self ) = @_ ;
+
+#print "MODE [$self->{connecting}]\n" ;
+
+#      return if $self->{connecting}++ ;
+
+       my $host = $self->cell_get_args( 'host' ) || $self->{'host'} ;
+       my $port = $self->cell_get_args( 'port' ) || $self->{'port'} ;
+       my $sync = $self->cell_get_args( 'sync' ) || $self->{'sync'} ;
+
+########################
+########################
+## handle connect timeouts
+########################
+########################
+
+#TraceStatus "Connecting to $host:$port" ;
+
+       my $sock_obj = Stem::Socket->new( 
+                       'object'        => $self,
+                       'host'          => $host,
+                       'port'          => $port,
+                       'sync'          => $sync,
+       ) ;
+
+       return $sock_obj unless ref $sock_obj ;
+
+       $self->{'sock_obj'} = $sock_obj ;
+
+       return ;
+}
+
+sub connected {
+
+       my( $self, $connected_sock ) = @_ ;
+
+#print "CONNECTED\n" ;
+
+       $self->{connected} = 1 ;
+
+       $self->send_status_msg( 'connected' ) ;
+
+       my $type = $self->{'sock_obj'} ?
+                       $self->{'sock_obj'}->type() :
+                       'sync connected' ;
+
+       my $info = sprintf( <<INFO,
+SockMsg connected
+Type:  $type
+Local: %s:%d
+Remote:        %s:%d
+INFO
+                               $connected_sock->sockhost(),
+                               $connected_sock->sockport(),
+                               $connected_sock->peerhost(),
+                               $connected_sock->peerport(),
+       ) ;
+
+       TraceStatus "\n$info" ;
+
+       if ( my $log_name = $self->{ 'log_name' } ) {
+
+#print "MSG LOG\n" ;
+
+               Stem::Log::Entry->new(
+                               'logs'  => $log_name,
+                               'text'  => "Connected\n$info",
+               ) ;
+       }
+
+       $self->cell_set_args(
+                       'fh'            => $connected_sock,
+                       'aio_args'      =>
+                               [ 'fh'  => $connected_sock ],
+                       'info'          => $info,
+       ) ;
+
+       my $err = $self->cell_trigger() ;
+#      print "TRIGGER ERR [$err]\n" unless ref $err ;
+}
+
+# this method is called after the cell is triggered. this cell can be
+# the original cell or a cloned one.
+
+sub triggered_cell {
+
+       my( $self ) = @_ ;
+
+#print "SockMsg triggered\n" ;
+       return if $self->{'server'} ;
+
+#      return "SockMsg: can't connect a server socket" if $self->{'server'} ;
+
+       return $self->connect() ;
+}
+
+# we handle the socket close method directly here so we can reconnect
+# if needed. the other async method callbacks are in Cell.pm
+
+sub async_closed {
+
+       my( $self ) = @_ ;
+
+# reconnect stuff. should be in Socket.pm
+
+#      my $sock = $self->cell_get_args( 'fh' ) ;
+#      $sock->close() ;
+#print "Sock MSG: closed name $self->{'reg_name'}\n" ;
+#      $self->{'sock_obj'}->connect_to() ;
+
+       $self->send_status_msg( 'disconnected' ) ;
+
+       if ( my $log_name = $self->{ 'log_name' } ) {
+
+               Stem::Log::Entry->new(
+                               'logs'  => $log_name,
+                               'text'  => "Closed\n$self->{'info'}",
+               )
+       }
+
+#      TraceStatus "Disconnected" ;
+
+       $self->cell_set_args( 'info' => 'SockMsg disconnected' ) ;
+
+######################
+######################
+# add support for reconnect.
+# it has a flag, delay, retry count.
+######################
+######################
+
+       $self->shut_down() ;
+}
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+#print "SOCKMSG SHUT $self\n", caller(), "\n", dump_data $self ;
+
+       $self->cell_shut_down() ;
+
+       unless ( $self->{'connected'} ) {
+
+use Carp 'cluck' ;
+#cluck "SOCKMSG SHUT SERVER $self\n" ;
+
+               my $sock_obj = $self->{'sock_obj'} ;
+
+               $sock_obj->shut_down() ;
+       }
+}
+
+sub send_status_msg {
+
+       my( $self, $status ) = @_ ;
+
+       my $status_addr = $self->{status_addr} or return ;
+
+       my $status_msg = Stem::Msg->new(
+               to      => $status_addr,
+               from    => $self->cell_from_addr(),
+               type    => 'status',
+               data    => {
+                       status  => $status,
+               },
+       ) ;
+
+       $status_msg->dispatch() ;
+}
+
+
+
+sub DESTROY {
+       my ( $self ) = @_ ;
+
+# print "SOCKMSG DESTROY", caller(), "\n" ;
+
+#print $self->_dump( "DESTROY") ;
+}
+
+
+# sub IO::Socket::INET::DESTROY {
+#      my ( $self ) = @_ ;
+
+# #    print "IO::DESTROY\n", dump_socket( $self ) ;
+
+# #warn "L $listener - S $self\n" if $listener == $self ;
+
+# # print "SOCKMSG DESTROY", caller(), "\n" ;
+# #cluck "IO::DESTROY $self\n" ;
+# }
+
+1 ;
diff --git a/lib/Stem/Socket.pm b/lib/Stem/Socket.pm
new file mode 100644 (file)
index 0000000..57b0e36
--- /dev/null
@@ -0,0 +1,487 @@
+#  File: Stem/Socket.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+#######################################################
+
+#print "LOADED\n" ;
+
+package Stem::Socket ;
+
+use strict ;
+
+use IO::Socket ;
+use Symbol ;
+use Errno qw( EINPROGRESS ) ;
+
+use Stem::Class ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'object',
+               'required'      => 1,
+               'type'          => 'object',
+               'help'          => <<HELP,
+This is the owner object which has the methods that get called when Stem::Socket
+has either connected, timed out or accepted a socket connection
+HELP
+       },
+       {
+               'name'          => 'server',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+If set, then this is a server socket.
+HELP
+       },
+       {
+               'name'          => 'sync',
+               'type'          => 'boolean',
+               'default'       => 0,
+               'help'          => <<HELP,
+Mark this as a synchronously connecting socket. Default is asyncronous
+connections. In both cases the same method callbacks are used.
+HELP
+       },
+       {
+               'name'          => 'port',
+               'required'      => 1,
+               'help'          => <<HELP,
+This is the TCP port number for listening or connecting.
+HELP
+       },
+       {
+               'name'          => 'host',
+               'default'       => 'localhost',
+               'help'          => <<HELP,
+Host to connect to or listen on. If a listen socket host is explicitly
+set to '', then the host will be INADDR_ANY which allows a server to
+listen on all host interfaces.
+HELP
+       },
+       {
+               'name'          => 'method',
+               'default'       => 'connected',
+               'help'          => <<HELP,
+This method is called in the owner object when when a socket
+connection or accept happens.
+HELP
+       },
+       {
+               'name'          => 'timeout_method',
+               'default'       => 'connect_timeout',
+               'help'          => <<HELP,
+This method is called in the owner object when when a socket
+connection times out.
+HELP
+       },
+       {
+               'name'          => 'timeout',
+               'default'       => 10,
+               'help'          => <<HELP,
+How long to wait (in seconds) before a connection times out.
+HELP
+       },
+       {
+               'name'          => 'max_retries',
+               'default'       => 0,
+               'help'          => <<HELP,
+The maximum number of connection retries before an error is returned.
+HELP
+       },
+       {
+               'name'          => 'listen',
+               'default'       => '5',
+               'help'          => <<HELP,
+This sets how many socket connections can be queued by a server socket.
+HELP
+       },
+       {
+               'name'          => 'ssl_args',
+               'type'          => 'list',
+               'help'          => <<HELP,
+This makes the socket use the IO::Socket::SSL module for secure sockets. The 
+arguments are passed to the new() method of that module.
+HELP
+       },
+       {
+               'name'          => 'id',
+               'help'          => <<HELP,
+The id is passed to the callback method as its only argument. Use it to
+identify different instances of this object.
+HELP
+
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       if ( $self->{ 'server' } ) {
+
+               $self->{'type'} = 'server' ;
+               my $listen_err = $self->listen_to() ;
+
+#print "ERR [$listen_err]\n" ;
+               return $listen_err if $listen_err ;
+       }
+       else {
+
+               $self->{'type'} = 'client' ;
+               my $connect_err = $self->connect_to() ;
+               return $connect_err if $connect_err ;
+       }
+
+       return( $self ) ;
+}
+
+use Carp 'cluck' ;
+
+sub shut_down {
+
+       my( $self ) = @_ ;
+
+#cluck "SOCKET SHUT" ;
+
+       if ( $self->{'type'} eq 'server' ) {
+
+#print "SOCKET SHUT server" ;
+
+               if ( my $read_event = delete $self->{'read_event'} ) {
+
+                       $read_event->cancel() ;
+               }
+
+               my $listen_sock = delete $self->{'listen_sock'} ;
+               $listen_sock->close() ;
+
+               return ;
+       }
+
+#print "SOCKET SHUT client" ;
+
+       $self->_write_cancel() ;
+
+       return ;
+}
+
+sub type {
+       $_[0]->{'type'} ;
+}
+
+sub connect_to {
+
+       my( $self ) = @_ ;
+
+       my $connect_sock = Stem::Socket::get_connected_sock(
+               $self->{'host'},
+               $self->{'port'},
+               $self->{'sync'},
+       ) ;
+
+       return $connect_sock unless ref $connect_sock ;
+
+       $self->{'connected_sock'} = $connect_sock ;
+
+       if( $self->{'sync'} ) {
+
+               $self->connect_writeable() ;
+               return ;
+       }
+
+# create and save the write event watcher
+
+       my $write_event = Stem::Event::Write->new(
+                       'object'        =>      $self,
+                       'fh'            =>      $connect_sock,
+                       'timeout'       =>      $self->{'timeout'},
+                       'method'        =>      'connect_writeable',
+                       'timeout_method' =>     'connect_timeout',
+       ) ;
+
+       return $write_event unless ref $write_event ;
+       $self->{'write_event'} = $write_event ;
+       $write_event->start() ;
+
+       return ;
+}
+
+# callback when a socket is connected (the socket is writeable)
+
+sub connect_writeable {
+
+       my( $self ) = @_ ;
+
+# get the connected socket
+
+       my $connected_sock = $self->{'connected_sock'} ;
+
+       if ( my $ssl_args = $self->{'ssl_args'} ) {
+
+               require IO::Socket::SSL ;
+               IO::Socket::SSL->VERSION(0.96);
+
+               my $err = IO::Socket::SSL->start_SSL(
+                       $connected_sock,
+                       @{$ssl_args}
+               ) ;
+
+               $err || die
+                       "bad ssl connect socket: " . IO::Socket::SSL::errstr() ;
+       }
+
+# the i/o for sockets is always non-blocking in stem.
+
+       $connected_sock->blocking( 0 ) ;
+
+# callback the owner object with the connected socket as the argument
+
+       my $method = $self->{'method'} ;
+       $self->{'object'}->$method( $connected_sock, $self->{'id'} );
+
+       $self->_write_cancel() ;
+
+       return ;
+}
+
+sub connect_timeout {
+
+       my( $self ) = @_ ;
+
+       $self->_write_cancel() ;
+
+       $self->{'connected_sock'}->close() ;
+       delete $self->{'connected_sock'} ;
+
+       if ( $self->{'max_retries'} && --$self->{'retry_count'} > 0 ) {
+
+               my $method = $self->{'timeout_method'} ;
+               $self->{'object'}->$method( $self->{'id'} );
+               return ;
+       }
+
+       $self->connect_to() ;
+
+       return ;
+}
+
+sub _write_cancel {
+
+       my( $self ) = @_ ;
+
+#      my $sock = delete $self->{'connected_sock'} ;
+#      $sock->close() ;
+
+       my $event = delete $self->{'write_event'} ;
+       return unless $event ;
+       $event->cancel() ;
+}
+
+sub get_connected_sock {
+
+       my( $host, $port, $sync ) = @_ ;
+
+       unless( $port ) {
+
+               my $err = "get_connected_sock Missing port" ;
+               return $err ;
+       }
+
+# get the host name or IP and convert it to an inet address
+
+       my $inet_addr = inet_aton( $host ) ;
+
+       unless( $inet_addr ) {
+
+               my $err = "get_connected_sock Unknown host [$host]" ;
+               return $err ;
+       }
+
+# check if it is a get the service name or numeric port and convert it
+# to a port number
+
+       if ( $port =~ /\D/ and not $port = getservbyname( $port, 'tcp' ) ) {
+
+               my $err = "get_connected_sock: unknown port [$port]" ;
+               return $err ;
+       }
+
+# prepare the socket address
+
+       my $sock_addr = pack_sockaddr_in( $port, $inet_addr ) ;
+
+       my $connect_sock = IO::Socket::INET->new( Domain => AF_INET) ;
+
+#print "connect $connect_sock [", $connect_sock->fileno(), "]\n" ;
+
+# set the sync (connect blocking) mode
+
+       $connect_sock->blocking( $sync ) ;
+
+       unless ( connect( $connect_sock, $sock_addr ) ) {
+
+# handle linux false error of EINPROGRESS
+
+               return <<ERR unless $! == EINPROGRESS ;
+get_connected_sock: connect to '$host:$port' error $!
+ERR
+       }
+
+       return $connect_sock ;
+}
+
+sub listen_to {
+
+       my( $self ) = @_ ;
+
+       my $listen_sock = get_listen_sock(
+               $self->{'host'},
+               $self->{'port'},
+               $self->{'listen'},
+       ) ;
+
+       return $listen_sock unless ref $listen_sock ;
+
+       $self->{'listen_sock'} = $listen_sock ;
+
+# create and save the read event watcher
+
+       my $read_event = Stem::Event::Read->new(
+                               'object'        => $self,
+                               'fh'            => $listen_sock,
+                               'method'        => 'listen_readable',
+       ) ;
+                                       
+       $self->{'read_event'} = $read_event ;
+
+       return ;
+}
+
+# callback when a socket can be accepted (the listen socket is readable)
+
+sub listen_readable {
+
+       my( $self ) = @_ ;
+
+# get the accepted socket
+
+       my $accepted_sock = $self->{'listen_sock'}->accept() ;
+
+# $accepted_sock || die "bad accept socket: ";
+my $fileno = fileno $accepted_sock ;
+#print "ACCEPT [$accepted_sock] ($fileno)\n" ;
+
+       if ( my $ssl_args = $self->{'ssl_args'} ) {
+
+               require IO::Socket::SSL ;
+               IO::Socket::SSL->VERSION(0.96);
+
+               my $err = IO::Socket::SSL->start_SSL(
+                       $accepted_sock,
+                       SSL_server      => 1,
+                       @{$ssl_args}
+               ) ;
+
+               $err || die
+                       "bad ssl accept socket: " . IO::Socket::SSL::errstr() ;
+       }
+
+# the i/o for sockets is always non-blocking in stem.
+
+       $accepted_sock->blocking( 0 ) ;
+
+# callback the object/method with the accepted socket as the argument
+
+       my $method = $self->{'method'} ;
+       $self->{'object'}->$method( $accepted_sock, $self->{'id'} );
+       return ;
+}
+
+sub stop_listening {
+
+       my( $self ) = @_ ;
+
+       my $read_event = $self->{'read_event'} ;
+       return unless $read_event ;
+       $read_event->stop() ;
+}
+
+sub start_listening {
+
+       my( $self ) = @_ ;
+
+       my $read_event = $self->{'read_event'} ;
+       return unless $read_event ;
+       $read_event->start() ;
+}
+
+sub get_listen_sock {
+
+       my( $host, $port, $listen ) = @_ ;
+
+       return "get_listen_sock Missing port" unless $port ;
+
+# get the host name or IP and convert it to an inet address
+# an empty host ('') will force INADDR_ANY
+
+       my $inet_addr = length( $host ) ? inet_aton( $host ) : INADDR_ANY ;
+
+#print "HOST [$host]\n" ;
+#print inet_ntoa( $inet_addr ), "\n" ;
+
+       return "get_listen_sock Unknown host [$host]" unless $inet_addr ;
+
+# check if it is a get the service name or numeric port and convert it
+# to a port number
+
+       if ( $port =~ /\D/ and not $port = getservbyname( $port, 'tcp' ) ) {
+
+               return "get_listen_sock: unknown port [$port]" ;
+       }
+
+# prepare the socket address
+
+       my $sock_addr = pack_sockaddr_in( $port, $inet_addr ) ;
+
+       my $listen_sock = IO::Socket::INET->new( 
+
+               Proto     => 'tcp',
+               LocalAddr => $host,
+               LocalPort => $port,
+               Listen    => $listen,
+               Reuse     => 1,
+       ) ;
+
+       return( "get_listen_sock: $host:$port $!" ) unless $listen_sock ;
+       return $listen_sock ;
+}
+
+1 ;
diff --git a/lib/Stem/Switch.pm b/lib/Stem/Switch.pm
new file mode 100644 (file)
index 0000000..30657f7
--- /dev/null
@@ -0,0 +1,243 @@
+#  File: Stem/Switch.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Switch ;
+
+use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
+use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
+
+use strict ;
+
+=head1 Stem::switch
+
+Stem::Switch has several functions:
+
+ new 
+ msg_in 
+ data_in 
+ map_cmd 
+ info_cmd 
+ status_cmd 
+
+=cut
+
+my $this_package = __PACKAGE__ ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'required'      => 1,
+               'help'          => <<HELP,
+Required field.
+This is a unique name used to register this instance of a Switch.
+HELP
+       },
+
+       {
+               'name'          => 'in_map',
+               'default'       => {},
+               'type'          => 'HoL',
+               'help'          => <<HELP,
+This field contains the incoming address map.
+Any message coming in to one of these addresses will be resent out
+to every address in out_map.
+HELP
+       },
+
+       {
+               'name'          => 'out_map',
+               'default'       => {},
+               'type'          => 'HoL',
+               'help'          => <<HELP,
+This contains the outgoing addresses for this Switch.
+HELP
+       },
+] ;
+
+=head2 new
+
+new creates a new Stem::Switch object, parsing $attr_spec and any arguments
+passed to it.
+
+It returns the new object.
+
+=cut
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+##########
+# to be replaced with Stem::Class supporting 'hash' attribute types
+##########
+
+       if ( ref $self->{'in_map'} eq 'ARRAY' ) {
+
+               $self->{'in_map'} = { @{$self->{'in_map'}} } ;
+       }
+
+       if ( ref $self->{'out_map'} eq 'ARRAY' ) {
+
+               $self->{'out_map'} = { @{$self->{'out_map'}} } ;
+       }
+
+       return( $self ) ;
+}
+
+use Data::Dumper ;
+
+
+sub msg_in {
+
+       my( $self, $msg ) = @_ ;
+
+       my $in_target = $msg->to_target() ;
+
+       my $in_map = $self->{'in_map'}{$in_target} ;
+
+       return unless $in_map ;
+
+       my @out_keys = ref $in_map ? @{$in_map} : ($in_map) ;
+
+# loop over all the output keys for this in_map entry
+
+       foreach my $out_key ( @out_keys ) {
+
+               my $out_addr = $self->{'out_map'}{$out_key} ;
+
+               next unless $out_addr ;
+
+               my @out_addrs = ref $out_addr ? @{$out_addr} : ($out_addr) ;
+
+# loop over all the output address for this out_map entry
+
+               foreach my $out_addr ( @out_addrs ) {
+
+# now we clone the message with the new address
+
+                       my $switched_msg = $msg->clone(
+
+                               'to'    =>      $out_addr,
+                       ) ;
+
+                       $switched_msg->dispatch() ;
+               }
+       }
+}
+
+
+sub map_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my @tokens = split( ' ', ${$msg->data()} ) ;
+
+       my $target = shift @tokens ;
+
+       $self->{'in_map'}{$target} = \@tokens ;
+
+       return ;
+}
+
+sub out_map_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my @tokens = split( ' ', ${$msg->data()} ) ;
+
+       my $key = shift @tokens ;
+
+       $self->{'out_map'}{$key} = \@tokens ;
+
+       return ;
+}
+       
+
+sub info_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       return <<INFO ;
+
+Info Response
+Class: $this_package
+Ref: $self
+
+This cell is a message multiplex or switch. Any message addressed to a
+target in the cell, can be resent to any subset of the output map
+addresses.
+
+INFO
+
+}
+
+
+sub status_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my( $status_text ) ;
+
+       $status_text = <<TEXT ;
+
+Status of switch: $self->{'reg_name'}
+
+In Map:
+
+TEXT
+
+       foreach my $target ( sort keys %{$self->{'in_map'}} ) {
+
+               my $targets_ref = $self->{'in_map'}{$target} ;
+               my @targets = ref $targets_ref ?
+                               @{$targets_ref} : ($targets_ref) ;
+
+               $status_text .= "\t$target -> @targets\n" ;
+       }
+
+       $status_text .= "\nOut Map:\n\n" ;
+
+       my $out_ref = $self->{'out_map'} ;
+
+       foreach my $key ( sort keys %{$out_ref} ) {
+
+               my $out_addr = $out_ref->{$key} ;
+
+               my @out_addrs = ref $out_addr ? @{$out_addr} : ($out_addr) ;
+
+               $status_text .= "\t$key -> @out_addrs\n" ;
+       }
+
+       return $status_text ;
+}
+
+1 ;
diff --git a/lib/Stem/Test/ConfTypes.pm b/lib/Stem/Test/ConfTypes.pm
new file mode 100644 (file)
index 0000000..e197ab3
--- /dev/null
@@ -0,0 +1,98 @@
+#  File: Stem/Test/ConfTypes.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Test::ConfTypes ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'bool_attr',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Boolean attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'list_attr',
+               'type'          => 'list',
+               'help'          => <<HELP,
+List attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'hash_attr',
+               'type'          => 'hash',
+               'help'          => <<HELP,
+Hash attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'lol_attr',
+               'type'          => 'LoL',
+               'help'          => <<HELP,
+List of Lists attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'loh_attr',
+               'type'          => 'LoH',
+               'help'          => <<HELP,
+List of Hashes attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'hol_attr',
+               'type'          => 'HoL',
+               'help'          => <<HELP,
+Hash of Lists attribute for testing Stem::Class
+HELP
+       },
+       {
+               'name'          => 'hoh_attr',
+               'type'          => 'HoH',
+               'help'          => <<HELP,
+Hash of Hashes attribute for testing Stem::Class
+HELP
+       },
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+#use YAML ;
+
+#      warn Dump $self ;
+
+       return( $self ) ;
+}
+
+1 ;
diff --git a/lib/Stem/Test/Echo.pm b/lib/Stem/Test/Echo.pm
new file mode 100644 (file)
index 0000000..b673c36
--- /dev/null
@@ -0,0 +1,164 @@
+#  File: Stem/Test/Echo.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+
+package Stem::Test::Echo ;
+
+use strict ;
+
+my $attr_spec = [ { } ];
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       return $self ;
+}
+
+# send this cell messages if you want test default delivery speed
+# this will handle all messages not covered by other methods
+
+sub msg_in {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $reply_msg = $msg->reply() ;
+
+       $reply_msg->dispatch() ;
+
+        return ;
+}
+
+# send this cell data messages if you want to just echo the data
+
+sub data_in {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'ECHO data_in' ) ;
+
+       my $reply_msg = $msg->reply(
+               type    => 'data',
+               data    => $msg->data(),
+       ) ;
+
+#print $reply_msg->dump( 'ECHO data reply' ) ;
+
+       $reply_msg->dispatch() ;
+
+        return ;
+}
+
+# send this cell 'echo' type messages if you want test plain reply speed
+
+sub echo_in {
+
+       my ( $self, $msg ) = @_ ;
+
+       $msg->reply()->dispatch() ;
+
+        return ;
+}
+
+# send this cell 'echo_data' type messages if you want test reply with data
+
+sub echo_data_in {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'ECHO_DATA' ) ;
+
+       my $data = $msg->data() ;
+
+       my $reply_msg = $msg->reply( data => { echo => $data } ) ;
+
+       $reply_msg->dispatch() ;
+
+        return ;
+}
+
+# send this cell 'echo' cmd messages if you want test plain command speed
+
+sub echo_cmd {
+
+       my ( $self ) = @_ ;
+
+        return '' ;
+}
+
+# send this cell 'echo_data' cmd messages if you want test command
+# speed with data.
+
+sub echo_data_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+       my $data = $msg->data() ;
+
+        return $data ;
+}
+
+1 ;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Stem::Test::Echo - This cell accepts messages and sends back reply
+messages or command data. It can be used to test message receipt,
+replies, and command returns and to benchmark message throughput.
+
+=head1 SYNOPSIS
+
+  [
+          'class'      =>      'Stem::Test::Echo',
+          'name'       =>      'test_echo',
+  ],
+
+=head1 USAGE
+
+This cell accepts various messages, all of which will echo some
+message back to the sender.
+
+       An echo type message will do a reply with no data.
+
+       An echo_data type message will do a reply with the sent data.
+
+       An echo command message will return a null string.
+
+       An echo_data command message will return the data.
+
+       Any other message type or command will do a plain reply like
+       an 'echo' type message.
+
+=cut
diff --git a/lib/Stem/Test/Flow.pm b/lib/Stem/Test/Flow.pm
new file mode 100644 (file)
index 0000000..9b22dc4
--- /dev/null
@@ -0,0 +1,339 @@
+#  File: Stem/Test/Flow.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Test::Flow ;
+
+use Test::More tests => 30 ;
+
+use base 'Stem::Cell' ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+This is the name under which this Cell was registered.
+HELP
+       },
+
+       {
+               'class'         => 'Stem::Cell',
+               'name'          => 'cell_attr',
+               'help'          => <<HELP,
+This value is the attributes for the included Stem::Cell which handles
+cloning, async I/O and pipes.
+HELP
+       },
+               
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $flow_text = <<FLOW ;
+
+               meth1 ;
+               meth2( 1, a ) ;
+               if if1 {
+                       meth3 ;
+               }
+
+               unless if1 {
+                       meth_bad ;
+               }
+
+               while while1 {
+                       meth4 ;
+               }
+
+               until until1 {
+
+                       unless unless1 {
+
+                               meth5 ;
+                       }
+                       else {
+
+                               meth6 ;
+                       }
+               }
+
+               while while2 {
+                       next ;
+               }
+
+               next_ok ;
+
+               LABEL1 :
+               while while3 {
+
+                       while while4 {
+
+                               next LABEL1 ;
+                       }
+               }
+
+               LABEL2 :
+               until until2 {
+
+                       if if2 {
+
+                               last LABEL2 ;
+                       }
+               }
+
+               last_ok ;
+
+               delay_time ;
+               delay 1 ;
+               delay_done( 1 ) ;
+
+               delay delay_set( 2 ) ;
+               delay_done( 2 ) ;
+
+               msg1 ;
+               get_msg1 ;
+
+
+               exit_meth ;
+FLOW
+
+       $self->cell_flow_init( 'test', $flow_text ) ;
+
+       $self->cell_flow_go_in() ;
+
+       return $self ;
+}
+
+sub meth1 {
+       ok(1, 'plain method') ;
+       return ;
+}
+
+sub meth2 {
+       my( $self, $arg1, $arg2 ) = @_ ;
+       ok( $arg1 ==1 && $arg2 eq 'a', 'methods with args' ) ;
+       return ;
+}
+
+sub if1 {
+       ok(1, 'if condition') ;
+       return 1 ;
+}
+
+sub meth3 {
+       ok(1, 'method in block' ) ;
+       return ;
+}
+
+sub meth_bad {
+       ok(0, 'then block was called' ) ;
+       return ;
+}
+
+my $w1 ;
+
+sub while1 {
+       ok(1, 'while condition') ;
+       return 1 if $w1++ < 2 ;
+
+       return ;
+}
+
+sub meth4 {
+       ok(1, 'method in while' ) ;
+       return ;
+}
+
+my $u1 ;
+my $u2 ;
+
+sub until1 {
+       ok(1, 'until condition') ;
+       return $u1 ;
+}
+
+sub unless1 {
+       ok(1, 'unless condition') ;
+       return $u2 ;
+}
+
+sub meth5 {
+       ok(1, 'method in unless' ) ;
+
+       $u2++ ;
+       return ;
+}
+
+sub meth6 {
+       ok(1, 'method in else' ) ;
+
+       $u1++ ;
+}
+
+my $w3 ;
+
+sub while3 {
+       ok(1, 'outer while condition') ;
+       return 1 if $w3++ < 1 ;
+
+       return ;
+}
+
+my $w4 ;
+
+sub while4 {
+       ok(1, 'inner while condition') ;
+       return 1 if $w4++ < 1 ;
+
+       return ;
+}
+
+sub next_ok {
+
+       ok( 1, 'next' ) ;
+
+       return ;
+}
+
+
+my $w2 ;
+
+sub while2 {
+       ok(1, 'while condition') ;
+       return 1 if $w1++ < 1 ;
+
+       return ;
+}
+
+sub until2 {
+       
+       return ;
+}
+
+sub if2 {
+       return 1 ;
+}
+
+sub last_ok {
+
+       ok( 1, 'last' ) ;
+
+       return ;
+}
+
+
+my $delay_time ;
+
+sub delay_time {
+
+       $delay_time = time ;
+
+       return ;
+}
+
+sub delay_done {
+
+       my( $self, $delta ) = @_ ;
+
+       my $time = time ;
+
+       $delta ||= 1 ;
+
+#print "$time $delay_time\n" ;
+
+       ok( $time - $delay_time >= $delta, 'delay done' ) ;
+
+       return ;
+}
+
+
+sub delay_set {
+
+       my( $self, $delay ) = @_ ;
+
+       ok( 1, 'delay set method' ) ;
+
+       return $delay || 1 ;
+}
+
+sub msg1 {
+
+       my ( $self ) = @_ ;
+
+       ok(1, 'message method' ) ;
+
+       my $msg = Stem::Msg->new( to => $self->{'reg_name'},
+                              from => $self->{'reg_name'},
+                              type => 'flow_msg'
+       ) ;
+
+#print $msg->dump( 'MSG1' ) ;
+
+       return $msg ;
+
+}
+
+sub flow_msg_in {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'FLOW' ) ;
+       ok(1, 'flow message in' ) ;
+
+       my $reply = $msg->reply() ;
+
+#print $reply->dump( 'reply' ) ;
+
+       $reply->dispatch() ;
+
+       return ;
+}
+
+sub get_msg1 {
+
+       my ( $self, $msg ) = @_ ;
+
+       ok(1, 'message received' ) ;
+
+#print $msg->dump( 'GET' ) ;
+
+       return ;
+}
+
+sub exit_meth {
+       ok(1, 'exit method' ) ;
+
+       exit ;
+}
+
+1 ;
diff --git a/lib/Stem/Test/PacketIO.pm b/lib/Stem/Test/PacketIO.pm
new file mode 100644 (file)
index 0000000..68bed1e
--- /dev/null
@@ -0,0 +1,265 @@
+#  File: Stem/Test/PacketIO.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Test::PacketIO ;
+
+use Test::More ;
+
+use Stem::Route qw( register_cell unregister_cell ) ;
+use Stem::SockMsg ;
+
+use base 'Stem::Cell' ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+This is the name under which this Cell was registered.
+HELP
+       },
+
+       {
+               'name'          => 'port',
+               'default'       => 8889,
+               'help'          => <<HELP,
+The port to use for the SockMsg cells.
+HELP
+       },
+       {
+               'name'          => 'write_addr',
+               'help'          => <<HELP,
+The Cell address of a sending port
+HELP
+       },
+       {
+               'name'          => 'cell_attr',
+               'class'         => 'Stem::Cell',
+               'help'          => <<HELP,
+Argument list passed to Stem::Cell for this Cell
+HELP
+       },
+] ;
+
+
+my @msg_data = (
+       "Packet scalar",
+       \"Packet ref",
+       { foo => 2 },
+       [ qw( a b c ) ],
+       bless( { abc => 1 }, 'PIO_class' ),
+       { bar => 'xyz', qwert => 3 },
+       {
+               list => [ 1 .. 4 ],
+               hash => { qwert => 3 },
+       }
+) ;
+
+my @codecs = qw( YAML Storable Data::Dumper SimpleHash ) ;
+#my @codecs = qw( SimpleHash ) ;
+@codecs = grep { eval "require Stem::Codec::$_" } @codecs ;
+
+plan tests => @msg_data * @codecs ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $flow_text = <<FLOW ;
+
+               WHILE codecs_left {
+
+                       create_sock_msg_pair ;
+                       DELAY 1 ;
+                       send_msg ;
+                       STOP ;
+               }
+               STOP ;
+FLOW
+
+       $self->cell_flow_init( 'test', $flow_text ) ;
+
+       $self->cell_flow_go_in() ;
+
+       return $self ;
+}
+
+sub send_msg {
+
+       my( $self ) = @_ ;
+
+       my $codec = $self->{'codec'} ;
+
+# we send to the client hence to the server, on to echo, back to the
+# server and through the client all the way to here
+
+       foreach my $data ( @msg_data ) {
+
+               if ( $codec eq 'SimpleHash' ) {
+
+                       if ( ref $data ne 'HASH' ) {
+
+                               ok( 1,
+                   'skip SimpleHash only allows hash refs for data') ;
+                               next ;
+                       }
+
+                       if ( grep ref $_, values %{$data} ) {
+
+                               ok( 1,
+           'skip SimpleHash only allows single level hashes for data') ;
+                               next ;
+                       }
+               }
+
+               my $msg = Stem::Msg->new(
+                       'to'    => "client_$codec",
+                       'from'  => $self->{'reg_name'},
+                       'type'  => 'data',
+                       'data'  => $data,
+               ) ;
+
+#print $msg->dump("MSG OUT") ;
+               $msg->dispatch() ;
+
+               push( @{$self->{'sent_data'}}, $data ) ;
+       }
+
+       return ;
+}
+
+sub data_in {
+
+       my( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'PACKET IN' ) ;
+
+       my $recv_data = $msg->data() ;
+
+       my $sent_data = shift @{$self->{'sent_data'}} ;
+
+#print "SENT [$sent_data]\nGOT[$recv_data]\n" ;
+
+       my $data_type = ref $sent_data || 'scalar' ;
+
+       is_deeply( $recv_data, $sent_data, "$self->{'codec'} - $data_type " ) ;
+
+       unless ( @{$self->{'sent_data'}} ) {
+
+               $self->destroy_sock_msg_pair() ;
+               $self->cell_flow_go_in() ;
+       }
+}
+
+sub test_done {
+
+       return 'FLOW_STOP' ;
+}
+
+sub codecs_left {
+
+       my( $self ) = @_ ;
+
+exit unless @codecs ;
+
+#die "CODECS END" 
+
+       return( $self->{codec} = shift @codecs ) ;
+}
+
+sub create_sock_msg_pair {
+
+       my( $self ) = @_ ;
+
+       my $codec = $self->{'codec'} ;
+
+#print "CREATE [$codec]\n" ;
+
+       my $server_name = "server_$codec" ;
+
+       my $server_sock = Stem::SockMsg->new( 
+               reg_name        => $server_name,
+               port            => ++$self->{port},
+               server          => 1,
+               cell_attr       => [
+                       'data_addr'     => 'echo',
+                       'codec'         => $codec,
+               ],
+       ) ;
+
+#print "SERVER [$server_sock]\n" ;
+       die $server_sock unless ref $server_sock ;
+       my $err = register_cell( $server_sock, $server_name ) ;
+       $err and die "register error: $err" ;
+
+       $self->{server_cell} = $server_sock ;
+       $self->{server_name} = $server_name ;
+
+       my $client_name = "client_$codec" ;
+
+       my $client_sock = Stem::SockMsg->new( 
+               reg_name        => $client_name,
+               port            => $self->{port},
+               connect_now     => 1,
+               sync            => 1,
+               cell_attr       => [
+                       'data_addr'     => 'packet_io',
+                       'codec'         => $codec,
+               ],
+       ) ;
+#print "CLIENT [$client_sock]\n" ;
+
+       die $client_sock unless ref $client_sock ;
+       register_cell( $client_sock, $client_name ) ;
+       $self->{client_cell} = $client_sock ;
+       $self->{client_name} = $client_name ;
+
+       return ;
+}
+
+sub destroy_sock_msg_pair {
+
+       my( $self ) = @_ ;
+
+       my $codec = $self->{'codec'} ;
+
+#print "DESTROY [$codec]\n" ;
+
+       foreach my $type ( qw( server client ) ) {
+
+               my $sock_msg = delete $self->{"${type}_cell"} ;
+#              my $sock_msg = delete $self->{"${type}_$codec"} ;
+               unregister_cell( $sock_msg ) ;
+               $sock_msg->shut_down() ;
+       }
+}
+
+1 ;
diff --git a/lib/Stem/Test/UDP.pm b/lib/Stem/Test/UDP.pm
new file mode 100644 (file)
index 0000000..52d53ca
--- /dev/null
@@ -0,0 +1,209 @@
+#  File: Stem/Test/UDP.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Test::UDP ;
+
+use Test::More tests => 7 ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+This is the name under which this Cell was registered.
+HELP
+       },
+
+       {
+               'name'          => 'send_addr',
+               'help'          => <<HELP,
+The Cell address of a sending port
+HELP
+       },
+       {
+               'name'          => 'send_host',
+               'help'          => <<HELP,
+The UDP packet is sent to this host if the send message has no host
+HELP
+       },
+       {
+               'name'          => 'send_port',
+               'help'          => <<HELP,
+The UDP packet is sent to this port if the send message has no port
+HELP
+       },
+               
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+
+       $self->{'udp_send_obj'} = Stem::UDPMsg->new() ;
+
+#print $self->{'udp_send_obj'}->status_cmd() ;
+
+# create a private udp server object and save it.
+
+       $self->{'udp_recv_obj'} = Stem::UDPMsg->new(
+                       object  => $self,
+                       bind_port       => 9998,
+                       bind_host       => '',
+                       server          => 1,
+                       timeout         => 1,
+       ) ;
+
+#print $self->{'udp_recv_obj'}->status_cmd() ;
+
+       my $err = $self->{'udp_send_obj'}->send( "LOCAL send",
+                                       send_host => 'local_host',
+                                       send_port => 9998,
+       ) ;
+
+       ok( $err, 'bad host lookup' ) ;
+
+       $err = $self->{'udp_send_obj'}->send( \"LOCAL send",
+                                       send_host => 'localhost',
+                                       send_port => 9998,
+       ) ;
+
+       ok( !$err, 'good host lookup' ) ;
+
+       return $self ;
+}
+
+sub udp_received {
+
+       my( $self, $udp_data, $from_port, $from_host ) = @_ ;
+
+       my $ok = ${$udp_data} =~ /LOCAL send/ ;
+
+       ok( $ok, 'udp received') ;
+
+#print "UDP [${$udp_data}]\n" ;
+
+# now send out a bad and a good message to the udp send cell
+
+       my $udp_msg = Stem::Msg->new(
+                       'to'    => $self->{'send_addr'},
+                       'from'  => $self->{'reg_name'},
+                       'cmd'   => 'send',
+                       'data'  => {
+                               'data'  => \"foo",
+                               'send_port' => $self->{'send_port'},
+                       }
+       ) ;
+
+       $udp_msg->dispatch() ;
+
+       $udp_msg = Stem::Msg->new(
+                       'to'    => $self->{'send_addr'},
+                       'from'  => $self->{'reg_name'},
+                       'cmd'   => 'send',
+                       'data'  => {
+                               'data'  => \"REMOTE foo",
+                               'send_port' => $self->{'send_port'},
+                               'send_host' => 'localhost',
+                       }
+       ) ;
+
+#print $udp_msg->dump( 'UDP msg' ) ;
+
+       $udp_msg->dispatch() ;
+}
+
+sub udp_timeout {
+
+       my( $self ) = @_ ;
+
+       ok(1, 'udp timeout') ;
+
+# kill the receiver object so we can exit eventually
+
+       $self->{'udp_recv_obj'}->shut_down() ;
+       delete $self->{'udp_recv_obj'} ;
+
+       return ;
+}
+
+sub udp_data_in {
+
+       my( $self, $msg ) = @_ ;
+
+       ok(1, 'udp data in called') ;
+
+       my $udp_data = $msg->data()->{data} ;
+
+       my $ok = ${$udp_data} =~ /REMOTE/ ;
+
+#print $msg->dump( 'UDP IN' ) ;
+
+       ok( $ok, 'udp data in') ;
+
+# send a shutdown message to the udp receiver cell. with no more
+# events it will cause the event loop to fall through and exit the
+# test script.
+
+       my $udp_msg = Stem::Msg->new(
+                       'to'    => $msg->from(),
+                       'from'  => $self->{'reg_name'},
+                       'cmd'   => 'shut_down',
+       ) ;
+
+       $udp_msg->dispatch() ;
+}
+
+sub udp_timeout_in {
+
+       my( $self, $msg ) = @_ ;
+
+       ok(1, 'udp timeout in') ;
+
+#print $msg->dump( 'UDP timeout IN' ) ;
+
+       return ;
+}
+
+sub response_in {
+
+       my( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'UDP DATA' ) ;
+
+       my $data = $msg->data() ;
+
+       my $ok = ${$data} =~ /Missing send_host/ ;
+
+       ok($ok, 'udp error response') ;
+}
+
+1 ;
diff --git a/lib/Stem/Trace.pm b/lib/Stem/Trace.pm
new file mode 100644 (file)
index 0000000..a79363e
--- /dev/null
@@ -0,0 +1,150 @@
+#  File: Stem/Trace.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Trace ;
+
+use strict;
+
+use Stem::Vars ;
+use Stem::Log::Entry ;
+
+sub import {
+
+       my( $class, %trace_args ) = @_ ;
+
+       $class = caller ;
+
+       my $sub              = $trace_args{ 'sub' }        || 'Trace' ;
+       my $type             = $trace_args{ 'type' }       || 'textlist' ;
+       my $def_level        = $trace_args{ 'level' }      || 5 ;
+       my $def_label        = $trace_args{ 'label' }      || 'trace' ;
+       my $def_log          = $trace_args{ 'log' }        || 'trace' ;
+       my $def_env          = $trace_args{ 'env' }        || "$class\::$sub" ;
+       my $def_env_level    = $trace_args{ 'env_level' }  || 0 ;
+       my $def_prefix       = $trace_args{ 'prefix' }     || '%P-%L - ' ;
+
+       no strict 'refs';
+
+       if ( $type eq 'args' ) {
+
+               *{ "${class}::$sub" } = sub {
+
+                       return if
+                               ( $Stem::Vars::Env{ $def_env } || 0 ) <
+                                                       $def_env_level ;
+
+                       my $prefix = $def_prefix ;
+                       my( $line_num ) = (caller)[2] ;
+
+                       $prefix =~ s/%P/$class/ ;
+                       $prefix =~ s/%L/$line_num/ ;
+
+# if only 1 arg, it is text.
+# if 2 args, it is level, text
+# if 3 args, it is label, level, text
+
+                       my $text = pop ;
+                       my $level = pop || $def_level ;
+                       my $label = pop || $def_label ;
+                       my $log = pop || $def_log ;
+
+                       Stem::Log::Entry->new (
+                              'logs'   => $log,
+                              'level'  => $level,
+                              'label'  => $label,
+                              'text'   => "$prefix$text\n"
+                       ) ;
+               } ;
+
+               return ;
+       }
+
+       if ( $type eq 'keyed' ) {
+
+               *{ "${class}::$sub" } = sub {
+
+                       my ( %args ) = @_;
+
+                       my $env       = $args{ 'env' }   || $def_env ;
+                       my $env_level = $args{ 'env_level' } || $def_env_level ;
+
+                       return if
+                               ( $Stem::Vars::Env{ $env } || 0 ) < $env_level ;
+
+                       my $text      = $args{ 'text' }   || '' ;
+                       my $log       = $args{ 'log' }    || $def_log ;
+                       my $level     = $args{ 'level' }  || $def_level ;
+                       my $label     = $args{ 'label' }  || $def_label ;
+                       my $prefix    = $args{ 'prefix' } || $def_prefix ;
+
+                       my( $line_num ) = (caller)[2] ;
+                       $prefix =~ s/%P/$class/ ;
+                       $prefix =~ s/%L/$line_num/ ;
+
+                       Stem::Log::Entry->new (
+                               'logs'  => $log,
+                               'level' => $level,
+                               'label' => $label,
+                               'text'  => "$prefix$text\n",
+                       ) ;
+               } ;
+
+               return ;
+       }
+
+       if ( $type eq 'textlist' ) {
+
+               *{ "${class}::$sub" } = sub {
+
+                       return if
+                               ( $Stem::Vars::Env{ $def_env } || 0 ) <
+                                                       $def_env_level ;
+
+                       my $text = join '', @_ ;
+
+                       my( $line_num ) = (caller)[2] ;
+
+                       my $prefix = $def_prefix ;
+
+                       $prefix =~ s/%P/$class/ ;
+                       $prefix =~ s/%L/$line_num/ ;
+
+
+                       Stem::Log::Entry->new (
+                              'logs'   => $def_log,
+                              'level'  => $def_level,
+                              'label'  => $def_label,
+                              'text'   => "$prefix$text\n",
+                       ) ;
+               } ;
+
+               return ;
+       }
+}
+
+1 ;
diff --git a/lib/Stem/TtySock.pm b/lib/Stem/TtySock.pm
new file mode 100644 (file)
index 0000000..7f48da0
--- /dev/null
@@ -0,0 +1,166 @@
+#  File: Stem/TtySock.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::TtySock ;
+
+use strict ;
+use Carp ;
+
+use Stem::AsyncIO ;
+#use Debug ;
+
+my $attr_spec = [
+       {
+               'name'          => 'port',
+               'default'       => 10_000,
+               'env'           => 'tty_port',
+               'help'          => <<HELP,
+HELP
+       },
+
+       {
+               'name'          => 'host',
+               'default'       => 'localhost',
+               'env'           => 'tty_host',
+               'help'          => <<HELP,
+HELP
+       },
+
+] ;
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $aio = Stem::AsyncIO->new(
+
+                       'object'        => $self,
+                       'read_fh'       => \*STDIN,
+                       'write_fh'      => \*STDOUT,
+                       'read_method'   => 'stdin_read',
+                       'closed_method' => 'stdin_closed',
+       ) ;
+
+       $self->{'aio'} = $aio ;
+
+       my $sock_obj = Stem::Socket->new( 
+                               'object'        => $self,
+                               'host'          => $self->{'host'},
+                               'port'          => $self->{'port'},
+                               'server'        => $self->{'server'},
+       ) ;
+
+       $self->{'sock_obj'} = $sock_obj ;
+
+#Debug "TTYSock new" ;
+
+       return( $self ) ;
+}
+
+
+sub connected {
+
+       my( $self, $connected_sock ) = @_ ;
+
+       my( $type, $sock_buf ) ;
+
+
+       $self->{'connected'} = 1 ;
+       $self->{'sock'} = $connected_sock ;
+
+       $type = $self->{'sock_obj'}->type() ;
+
+       if ( $type eq 'server' ) {
+
+               $self->{'sock_obj'}->stop_listening() ;
+       }
+
+       $sock_buf = Stem::AsyncIO->new(
+
+                       'object'        => $self,
+                       'fh'            => $connected_sock,
+                       'read_method'   => 'socket_read',
+                       'closed_method' => 'socket_closed',
+       ) ;
+
+       $self->{'sock_buf'} = $sock_buf ;
+}
+
+sub socket_read {
+
+       my( $self, $data_ref ) = @_ ;
+
+       $self->{'aio'}->write( $data_ref ) ;
+}
+
+sub socket_closed {
+
+       my( $self ) = @_ ;
+
+       $self->{'connected'} = 0 ;
+
+       $self->{'sock_buf'}->shut_down() ;
+
+       if ( $self->{'sock_obj'}->type() eq 'server' ) {
+
+               $self->{'sock_obj'}->start_listening() ;
+       }
+       else {
+
+               $self->{'sock_obj'}->connect_to() ;
+       }
+}
+
+sub stdin_read {
+
+       my( $self, $data_ref ) = @_ ;
+
+       unless ( $self->{'connected'} ) {
+
+               print "TTY::Sock not connected\n" ;
+               return ;
+       }
+
+       $self->{'sock_buf'}->write( $data_ref ) ;
+}
+
+sub stdin_closed {
+
+       my( $self ) = @_ ;
+
+
+#      print "stdin closed\n" ;
+
+       *STDIN->clearerr() ;
+}
+
+
+1 ;
diff --git a/lib/Stem/UDPMsg.pm b/lib/Stem/UDPMsg.pm
new file mode 100644 (file)
index 0000000..3e30b67
--- /dev/null
@@ -0,0 +1,416 @@
+#  File: Stem/UDPMsg.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::UDPMsg ;
+
+use strict ;
+
+use Data::Dumper ;
+use IO::Socket ;
+
+my $attr_spec = [
+
+       {
+               'name'          => 'reg_name',
+               'help'          => <<HELP,
+The registration name for this Cell
+HELP
+       },
+
+       {
+               'name'          => 'bind_host',
+               'help'          => <<HELP,
+The UDP socket is bound to this host for receiving or sending packets
+HELP
+       },
+
+       {
+               'name'          => 'bind_port',
+               'help'          => <<HELP,
+The UDP socket is bound to this port for receiving or sending packets
+HELP
+       },
+       {
+               'name'          => 'send_host',
+               'help'          => <<HELP,
+The UDP packet is sent to this host if the send message has no host
+HELP
+       },
+       {
+               'name'          => 'send_port',
+               'help'          => <<HELP,
+The UDP packet is sent to this port if the send message has no port
+HELP
+       },
+       {
+               'name'          => 'bind_port',
+               'help'          => <<HELP,
+The UDP socket is bound to this port for receiving or sending packets
+HELP
+       },
+       {
+               'name'          => 'server',
+               'type'          => 'boolean',
+               'help'          => <<HELP,
+Marks this socket as a server and it expect to receive UDP packets
+HELP
+       },
+       {
+               'name'          => 'max_recv_size',
+               'default'       => 4096,
+               'help'          => <<HELP,
+Maximum size of received UDP packets.
+
+HELP
+       },
+       {
+               'name'          => 'data_addr',
+               'help'          => <<HELP,
+Send received UDP packets as 'udp_data' type messages to this address
+HELP
+       },
+       {
+               'name'          => 'error_addr',
+               'help'          => <<HELP,
+Send received UDP errors as 'udp_error' type messages to this address
+HELP
+       },
+       {
+               'name'          => 'timeout_addr',
+               'help'          => <<HELP,
+Send UDP timeouts as 'udp_timeout' type messages to this address
+HELP
+       },
+       {
+               'name'          => 'object',
+               'help'          => <<HELP,
+This object will get the callbacks
+HELP
+       },
+       {
+               'name'          => 'timeout',
+               'help'          => <<HELP,
+This sets the timeout period to wait for UDP data. If no data has been
+received since the timer started, a timeout message or callback will
+be triggered.
+HELP
+       },
+       {
+               'name'          => 'recv_method',
+               'default'       => 'udp_received',
+               'help'          => <<HELP,
+This method will be called in the object when a UDP packet has been received 
+HELP
+       },
+       {
+               'name'          => 'error_method',
+               'default'       => 'udp_error',
+               'help'          => <<HELP,
+This method will be called in the object when a UDP had been detected
+HELP
+       },
+       {
+               'name'          => 'timeout_method',
+               'default'       => 'udp_timeout',
+               'help'          => <<HELP,
+This method will be called in the object when no UDP data has been received
+after the timeout period.
+HELP
+       },
+       {
+               'name'          => 'log_name',
+               'help'          => <<HELP,
+Log to send store sent and received messages
+HELP
+       },
+] ;
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       my $info_text = '' ;
+
+       my $socket = IO::Socket::INET->new( 'Proto' => 'udp' ) ;
+       $self->{'socket'} = $socket ;
+
+       if ( my $bind_port = $self->{'bind_port'} ) {
+
+               $info_text .= "Port:      $bind_port\n" ;
+
+               my $bind_ip ;
+               my $bind_host = $self->{'bind_host'} ;
+
+               if ( length $bind_host ) {
+
+                       $bind_ip = inet_aton( $bind_host ) ;
+                       $info_text .= "Host:       $bind_host\n" ;
+               }
+               else {
+
+                       $bind_ip = INADDR_ANY ;
+                       $info_text .= "Host:      INADDR_ANY\n" ;
+               }
+
+               $socket->bind( $bind_port, $bind_ip ) ;
+       }
+
+       my @timeout_args = ( $self->{'timeout'} ) ?
+                               ( 'timeout' => $self->{'timeout'} ) : () ;
+
+
+       if ( $self->{'server'} ) {
+
+               $self->{'read_event'} = Stem::Event::Read->new(
+                                       'object'        => $self,
+                                       'fh'            => $socket,
+                                       @timeout_args,
+               ) ;
+       }
+
+       my $reg_name = $self->{'reg_name'} || 'NONE' ;
+       my $sock_host = $socket->sockhost ;
+       my $sock_port = $socket->sockport ;
+
+       $self->{'info'} = <<INFO ;
+---------------------
+UDPMsg
+
+Cell name: $reg_name
+Port:      $sock_port
+---
+$info_text
+---------------------
+
+INFO
+
+       return $self ;
+}
+
+sub status_cmd {
+
+       my ( $self ) = @_ ;
+
+       return  $self->{'info'} ;
+}
+
+
+sub readable {
+
+       my( $self ) = @_ ;
+
+#print "UDP readable\n" ;
+
+       my $udp_data ;
+
+       my $udp_addr = $self->{'socket'}->recv( $udp_data,
+                                                $self->{'max_recv_size'} ) ;
+
+#print "UDP READ [$udp_data]\n" ;
+
+# handle errors
+
+       unless( defined( $udp_addr ) ) {
+
+               if ( my $error_addr = $self->{'error_addr'} ) {
+
+                       my $msg = Stem::Msg->new(
+                               'to'            => $error_addr,
+                               'from'          => $self->{'from_addr'},
+                               'type'          => 'udp_error',
+                               'data'          => \"$!",
+                       ) ;
+
+#print $msg->dump( 'UDP error' ) ;
+                       $msg->dispatch() ;
+                       return ;
+               }
+
+# send the data via a callback
+
+               if ( my $obj = $self->{'object'} ) {
+
+                       my $method = $self->{'error_method'} ;
+                       $obj->$method( \"$!" ) ;
+               }
+
+               return ;
+       }
+
+       my( $from_port, $from_host ) = unpack_sockaddr_in( $udp_addr ) ;
+
+       $from_host = inet_ntoa( $from_host ) ;
+
+# send out the data as a stem message
+
+#print "ADDR [$self->{'data_addr'}]\n" ;
+
+       if ( my $data_addr = $self->{'data_addr'} ) {
+
+               my $msg = Stem::Msg->new(
+                       'to'            => $data_addr,
+                       'from'          => $self->{'reg_name'},
+                       'type'          => 'udp_data',
+                       'data'          => {
+                               'data'          => \$udp_data,
+                               'from_port'     => $from_port,
+                               'from_host'     => $from_host,
+                       },
+               ) ;
+
+#print $msg->dump( 'UDP recv' ) ;
+               $msg->dispatch() ;
+               return ;
+       }
+
+# send the data via a callback
+
+       if ( my $obj = $self->{'object'} ) {
+
+               my $method = $self->{'recv_method'} ;
+               $obj->$method( \$udp_data, $from_port, $from_host ) ;
+       }
+
+       return ;
+}
+
+sub read_timeout {
+
+       my( $self ) = @_ ;
+
+#print "UDP timeout\n" ;
+
+# send out the timeout as a stem message
+
+       if ( my $timeout_addr = $self->{'timeout_addr'} ) {
+
+               my $msg = Stem::Msg->new(
+                       'to'            => $timeout_addr,
+                       'from'          => $self->{'reg_name'},
+                       'type'          => 'udp_timeout',
+               ) ;
+
+#print $msg->dump( 'UDP timeout' ) ;
+               $msg->dispatch() ;
+               return ;
+       }
+
+# send the timeout via a callback
+
+       if ( my $obj = $self->{'object'} ) {
+
+               my $method = $self->{'timeout_method'} ;
+               $obj->$method() ;
+       }
+
+       return ;
+}
+
+
+sub send_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'UDP send' ) ;
+       my $msg_data = $msg->data() ;
+
+       my $send_port = $msg_data->{'send_port'} || $self->{'send_port'} ;
+       my $send_host = $msg_data->{'send_host'} || $self->{'send_host'} ;
+
+       my $udp_data = $msg_data->{'data'} ;
+
+       return $self->_send( $udp_data, $send_port, $send_host ) ;
+}
+
+sub send {
+
+       my ( $self, $data, %args ) = @_ ;
+
+       my $send_port = $args{'send_port'} || $self->{'send_port'} ;
+       my $send_host = $args{'send_host'} || $self->{'send_host'} ;
+
+       return $self->_send( $data, $send_port, $send_host ) ;
+}
+
+sub _send {
+
+       my( $self, $data, $port, $host ) = @_ ;
+
+       $host or return "Missing send_host for UDP send" ;
+       $port or return "Missing send_port for UDP send" ;
+
+#print "P $port H $host\n" ;
+
+       my $host_ip = inet_aton( $host ) ;
+       $host_ip or return "Bad host '$host'" ;
+
+       my $send_addr = pack_sockaddr_in( $port, $host_ip ) ;
+
+       $data = $$data if ref $data ;
+
+       my $byte_cnt = $self->{'socket'}->send( $data, 0, $send_addr ) ;
+
+#print "BYTES [$byte_cnt]\n" ;
+
+       return "send error: $!" unless defined $byte_cnt ;
+       return ;
+}
+
+
+sub shut_down_cmd {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump( 'SHUT' ) ;
+
+       $self->shut_down() ;
+
+       return ;
+}
+
+sub shut_down {
+
+       my ( $self ) = @_ ;
+
+       if ( my $read_event = delete $self->{'read_event'} ) {
+
+               $read_event->cancel() ;
+       }
+
+       delete $self->{'object'} ;
+
+       my $socket = delete $self->{'socket'} ;
+
+       close $socket ;
+}
+
+1 ;
diff --git a/lib/Stem/Util.pm b/lib/Stem/Util.pm
new file mode 100644 (file)
index 0000000..cbca0f5
--- /dev/null
@@ -0,0 +1,130 @@
+#  File: Stem/Util.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Util ;
+
+use strict ;
+use Carp ;
+
+=head1 Stem::Util
+
+This file includes two subroutines: read_file and write_file.
+
+=cut
+
+=head2 read_file
+
+read_file is a utility sub to slurp in a file. 
+
+It returns a list of lines when called in list context.
+It returns one big string when called in scalar context. 
+
+=cut
+
+# utility sub to slurp in a file. list/scalar context determines either
+# list of lines or long single string
+
+sub read_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+       open( FH, $file_name ) || carp "can't open $file_name $!" ;
+
+       return <FH> if wantarray ;
+
+       my $buf ;
+
+       sysread( FH, $buf, -s FH ) ;
+       return $buf ;
+}
+
+=head2 load_file
+
+load_file is a utility sub to load a file of data.  It reads in a file
+and converts it to an internal form according to the first line of the
+file. The default file format is Perl data and eval is used to convert
+it. These other formats are also supported:
+
+       YAML
+
+=cut
+
+sub load_file {
+
+       my( $file_name ) = shift ;
+
+       my $text = read_file( $file_name ) ;
+
+       my @load_vals ;
+
+       if ( $text =~ /^.*#YAML/ ) {
+
+               require YAML ;
+
+               eval {
+                       @load_vals =  YAML::Load( $text ) ;
+               } ;
+
+               return "Load error in file '$file_name' with YAML: $@" if $@ ;
+
+# lose the outer anon array wrapper and return the values
+
+               return $load_vals[0] ;
+       }
+
+       @load_vals = eval "($text)" ;
+
+       return "Load error in file '$file_name' with eval: $@" if $@ ;
+       return \@load_vals ;
+}
+
+
+=head2 write_file
+
+write_sub is a utility sub to write a file. It takes a file
+name and a list of strings.  It opens the file and writes
+all data passed into the file.  This will overwrite any data
+in the file.
+
+=cut
+
+# utility sub to write a file. takes a file name and a list of strings
+
+sub write_file {
+
+       my( $file_name ) = shift ;
+
+       local( *FH ) ;
+
+       open( FH, ">$file_name" ) || carp "can't create $file_name $!" ;
+
+       print FH @_ ;
+}
+
+1 ;
diff --git a/lib/Stem/Vars.pm b/lib/Stem/Vars.pm
new file mode 100644 (file)
index 0000000..d3b1c53
--- /dev/null
@@ -0,0 +1,103 @@
+#  File: Stem/Vars.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::Vars ;
+
+use strict ;
+use Stem::Route ;
+use base 'Exporter' ;
+use vars qw( %Env @EXPORT ) ;
+
+@EXPORT = qw( %Env ) ;
+
+
+
+Stem::Route::register_class( __PACKAGE__, 'var', 'env' ) ;
+
+sub new {
+
+       my( $class, %env ) = @_ ;
+
+       delete $env{ 'reg_name' } ;
+
+       @Env{ keys %env } = values %env ;
+
+       return ;
+}
+
+sub set_env_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my( $data ) = $msg->data() ;
+
+       $data = ${$data} if ref $data ;
+
+       if ( my( $key, $val ) = $data =~ /^\s*(\w+)\s*=\s*(.+)$/ ) {
+
+               $val =~ s/\s+$// ;
+               $Env{ $key } = $val ;
+       }
+
+       return ;
+}
+
+sub get_env_cmd {
+
+       my( $self, $msg ) = @_ ;
+
+       my( $data ) = $msg->data() ;
+
+       $data = ${$data} if ref $data ;
+
+       return $Env{$data} ;
+}
+
+sub status_cmd {
+
+       my $text ;
+
+       $text = <<TEXT ;
+
+Status of Stem Environment
+
+TEXT
+
+       foreach my $key ( sort keys %Env ) {
+       
+               my $val = $Env{$key} ;
+
+               $text .= sprintf( "\t%-24s = '$val'\n", $key ) ;
+       }
+
+       $text .= "\n\n" ;
+
+       return $text ;
+}
+
+1 ;
diff --git a/lib/Stem/WorkQueue.pm b/lib/Stem/WorkQueue.pm
new file mode 100644 (file)
index 0000000..f4cfd0a
--- /dev/null
@@ -0,0 +1,133 @@
+#  File: Stem/WorkQueue.pm
+
+#  This file is part of Stem.
+#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
+
+#  Stem is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+
+#  Stem is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+
+#  You should have received a copy of the GNU General Public License
+#  along with Stem; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+#  For a license to use the Stem under conditions other than those
+#  described here, to purchase support for this software, or to purchase a
+#  commercial warranty contract, please contact Stem Systems at:
+
+#       Stem Systems, Inc.             781-643-7504
+#      79 Everett St.                  info@stemsystems.com
+#      Arlington, MA 02474
+#      USA
+
+package Stem::WorkQueue ;
+
+use strict ;
+
+my $attr_spec = [
+
+] ;
+
+
+###########
+# This POD section is autoegenerated. Any edits to it will be lost.
+
+=head2 Constructor Attributes for Class Stem::WorkQueue
+
+=over 4
+
+
+=back
+
+=cut
+
+# End of autogenerated POD
+###########
+
+
+
+sub new {
+
+       my( $class ) = shift ;
+
+       my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
+       return $self unless ref $self ;
+
+       $self->{ 'work_queue' } = [] ;
+       $self->{ 'worker_queue' } = [] ;
+
+       return $self ;
+}
+
+sub msg_in {
+
+       my ( $self, $msg ) = @_ ;
+
+       push( @{$self->{ 'work_queue' }}, $msg ) ;
+
+       $self->_check_for_work() ;
+
+       return ;
+}
+
+sub worker_in {
+
+       my ( $self, $msg ) = @_ ;
+
+#print $msg->dump('worker') ;
+
+       push( @{$self->{ 'worker_queue' }}, $msg ) ;
+
+       $self->_check_for_work() ;
+
+       return ;
+}
+
+sub _check_for_work {
+
+       my ( $self ) = @_ ;
+
+       my $work_q = $self->{ 'work_queue' } ;
+       my $worker_q = $self->{ 'worker_queue' } ;
+
+       while( 1 ) {
+
+# see if we have both workers and work to do
+
+               return unless @{$work_q} && @${worker_q} ;
+
+               my $work_msg = shift @{$work_q} ;
+               my $worker_msg = shift @{$worker_q} ;
+
+#print "WORK out [", Store( $worker_msg->from() ), "]\n" ;
+
+               $work_msg->to( scalar $worker_msg->from() ) ;
+
+#print $work_msg->dump( 'work' ) ;
+               $work_msg->dispatch() ;
+       }
+}
+
+sub status_cmd {
+
+       my ($self) = @_ ;
+
+       my $work_cnt = @{$self->{ 'work_queue' }} ;
+       my $worker_cnt = @{$self->{ 'worker_queue' }} ;
+
+       return <<STATUS ;
+
+Work Queue:    $work_cnt
+Worker Queue:  $worker_cnt
+
+STATUS
+
+}
+
+1;
diff --git a/sessions/backend.pl b/sessions/backend.pl
new file mode 100755 (executable)
index 0000000..1cf7fa3
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+use lib '../lib' ;
+
+use Stem::Event ;
+use Stem::Socket ;
+use Stem::AsyncIO ;
+
+use Time::HiRes qw( time ) ;
+use Getopt::Long ;
+
+my $opts_ok = GetOptions(
+       \my %opts,
+       'upper_port=s',
+       'reverse_port=s',
+       'v|verbose',
+       'help|h',
+) ;
+
+usage() unless $opts_ok ;
+usage() if $opts{help} ;
+
+
+my $time ;
+
+# this table defines the servers. each entry has the default port
+# number and the code to execute on the input data.
+
+my %servers = (
+
+       upper => {
+
+               port => 8888,
+               code => sub { uc $_[0] },
+       },
+
+       reverse => {
+
+               port => 8889,
+               code => sub { scalar( reverse $_[0] ) },
+       },
+) ;
+
+start_servers() ;
+
+Stem::Event::start_loop() ;
+
+exit ;
+
+sub start_servers {
+
+       while( my( $id, $server ) = each %servers ) {
+
+# make each server entry an object
+
+               bless $server, __PACKAGE__ ;
+
+# save its id in itself
+
+               $server->{id} = $id ;
+
+# get the port from the options or the default
+
+               my $port = $opts{"${id}_port"} || $server->{port} ;
+
+# get the listen socket and save it
+
+               my $listen = Stem::Socket->new(
+                       object  => $server,
+                       port    => $port,
+                       server  => 1,
+               ) ;
+
+               die "can't listen on $port: $listen" unless ref $listen ;
+
+               $server->{listen} = $listen ;
+       }
+}
+
+# this is called when a socket is connected
+
+sub connected {
+
+       my( $server, $socket ) = @_ ;
+
+# create a session object. blessed directly into this class because it
+# is simple and works nicely
+
+       my $self = bless {
+
+               socket  => $socket,
+               id      => $server->{id},
+       }, __PACKAGE__ ;
+
+# get an asyncio object and save it in the session object
+# this will buffer all input and send it only when the socket is closed
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               fh      => $socket,
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{async} = $async ;
+}
+
+# this is called when we have read data
+
+sub async_read_data {
+
+       my( $self, $data ) = @_ ;
+
+# print "READ [$$data]\n" ;
+
+# save (the ref to) the data 
+
+       $self->{'data'} = $data ;
+
+# get a random delay time
+
+#      my $delay = .5 ;
+       my $delay = rand( 1 ) + .5 ;
+       $delay = .01 ;
+
+#print "DELAY $delay\n" ;
+$time = time() ;
+
+# get and save a timer object with this delay
+
+       my $timer = Stem::Event::Timer->new(
+               object  => $self,
+               delay   => $delay,
+       ) ;
+       ref $timer or die "can't create Timer: $timer" ;
+       $self->{timer} = $timer ;
+
+       return ;
+}
+
+# timeout is over so this gets called
+
+sub timed_out {
+
+       my( $self ) = @_ ;
+
+# my $delta = time() - $time ;
+# printf "DELTA = %6f\n", $delta ;
+
+# get the real datat
+       my $data = ${$self->{data}};
+
+# find the server (we could have saved this in the session object but
+# we can do this quick lookup to get it)
+
+       my $server = $servers{ $self->{'id'} } ;
+
+# process the input data with the code in the server object
+
+       my $echo_data = $server->{code}->( $data ) ;
+
+# print "ECHO [$echo_data]\n" ;
+
+# write out the echo data to the socket and close it when done.
+
+       $self->{async}->final_write( $echo_data ) ;
+}
+
+sub usage {
+
+       my ( $error ) = @_ ;
+
+       $error ||= '' ;
+       die <<DIE ;
+$error
+usage: $0 [--help|h] [--upper_port <port>] [--reverse_port <port>]
+
+
+       upper_port <port>       Set the port for the upper case server
+                               (default is 8888)
+       reverse_port <port>     Set the port for the string reverse server
+                               (default is 8889)
+       help | h                Print this help text
+DIE
+
+}
+
+# sub async_closed {
+#      my( $self ) = @_  ;
+# print "CLOSED $self\n" ;
+# }
+
+# DESTROY {
+#      my( $self ) = @_  ;
+# print "DESTROY $self\n" ;
+# }
diff --git a/sessions/client.pl b/sessions/client.pl
new file mode 100755 (executable)
index 0000000..7da80f4
--- /dev/null
@@ -0,0 +1,200 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+use lib '../lib' ;
+
+BEGIN {
+#      $Stem::Vars::Env{event_loop} = 'perl' ;
+}
+
+use Stem ;
+use Stem::Socket ;
+use Stem::AsyncIO ;
+
+use Getopt::Long ;
+
+my $opts_ok = GetOptions(
+       \my %opts,
+       'port=s',
+       'max_clients=i',
+       'total_clients=i',
+       'string_min_len=i',
+       'string_max_len=i',
+       'verbose|v',
+       'help|h',
+) ;
+
+usage() unless $opts_ok ;
+usage() if $opts{help} ;
+
+# set defaults for various options
+
+$opts{max_clients} ||= 1 ;
+$opts{total_clients} ||= 1 ;
+$opts{port} ||= 8887 ;
+$opts{string_min_len} ||= 8 ;
+
+my $client_cnt = 0 ;
+
+my %clients ;
+
+make_clients() ;
+
+Stem::Event::start_loop() ;
+
+exit ;
+
+# this creates and saves the client sessions
+
+sub make_clients {
+
+# keep making new clients if we are under the total and the parallel counts
+
+       while( $client_cnt < $opts{total_clients} && 
+              keys %clients < $opts{max_clients} ) {
+
+# get a random token for our data
+
+               my $data = rand_string( $opts{string_min_len},
+                                       $opts{string_max_len},
+               ) ;
+
+               print "String [$data]\n" if $opts{verbose} ;
+
+# make the session object
+
+               my $self = bless { 
+                       data    => $data,
+               }, __PACKAGE__ ;
+
+# create the connection object and save it
+
+               my $connect = Stem::Socket->new(
+                       object  => $self,
+                       port    => $opts{port},
+               ) ;
+               ref $connect or die "can't create Socket: $connect" ;
+               $self->{connect} = $connect ;
+
+# save the session object so we can track all the active ones
+
+               $clients{ $self } = $self ;
+
+# print "cnt $client_cnt max $max_clients num ", keys %clients, "\n" ;
+
+               $client_cnt++ ;
+       }
+}
+
+# this is called when we have connected to the middle layer server
+
+sub connected {
+
+       my( $self, $socket ) = @_ ;
+
+# save the connected socket
+
+       $self->{'socket'} = $socket ;
+
+# we don't need the connection object anymore
+
+       my $connect = delete $self->{connect} ;
+       $connect->shut_down() ;
+
+# create and save an async i/o object to do i/o with the middle layer server
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               fh      => $socket,
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{async} = $async ;
+
+# write the data to the middle layer (and send no more data)
+
+       $async->final_write( \$self->{data} ) ;
+}
+
+# this is called when we have read all the data from the middle layer
+
+sub async_read_data {
+
+       my( $self, $data ) = @_ ;
+
+       print "Read [${$data}]\n" if $opts{verbose} ;
+
+# we don't need the async i/o object anymore
+
+       my $async = delete $self->{async} ;
+       $async->shut_down() ;
+
+# make the string that we expect back from the middle layer
+
+       my $expected = uc( $self->{data} ) . reverse( $self->{data} ) ;
+
+       print "Expected [$expected]\n" if $opts{verbose} ;
+
+# check and report the results
+       if ( ${$data} ne $expected ) {
+
+               print "ERROR\n"  if $opts{verbose} ;
+       }
+       else {
+               print "OK\n"  if $opts{verbose} ;
+       }
+
+# delete this client session as we are done
+
+       delete( $clients{ $self } ) ;
+
+# replace this session with a new one (if we haven't hit the max yet)
+
+       make_clients() ;
+}
+
+INIT {
+
+my @alpha = ( 'a' .. 'z', '0' .. '9' ) ;
+
+sub rand_string {
+
+       my( $min_len, $max_len ) = @_ ;
+
+       $min_len ||= 8 ;
+       $max_len ||= $min_len ;
+
+
+       my $length = $min_len + int rand( $max_len - $min_len + 1 ) ;
+
+       return join '', map $alpha[rand @alpha], 1 .. $length ;
+}
+
+}
+
+sub usage {
+
+       my ( $error ) = @_ ;
+
+       $error ||= '' ;
+       die <<DIE ;
+$error
+usage: $0 [--help|h] [--verbose|v] [--port <port>]
+       [--total_clients <count>] [--max_clients <count>]
+       [--string_min_len <len>] [--max_clients <count>]
+
+       port <port>             Set the port for the middle layer server
+                               (default is 8887)
+       max_clients <count>     Set the maximum number of parallel clients
+                               (default is 1)
+       total_clients <count>   Set the total number of clients to run
+                               (default is 1)
+       string_min_len <len>    Set the minimum length for the random strings
+                               (default is 8)
+       string_max_len <len>    Set the maximum length for the random strings
+                               (default is string_min_len which means a fixed
+                                length string)
+       help | h                Print this help text
+DIE
+
+}
diff --git a/sessions/mid_event.pl b/sessions/mid_event.pl
new file mode 100755 (executable)
index 0000000..9eb8564
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+use lib '../lib' ;
+
+BEGIN {
+       $Stem::Vars::Env{event_loop} = 'perl' ;
+}
+
+
+use Stem::Event ;
+use Stem::Socket ;
+use Stem::AsyncIO ;
+
+use Data::Dumper ;
+use Getopt::Long ;
+
+my $opts_ok = GetOptions(
+       \my %opts,
+       'server_port=s',
+       'upper_port=s',
+       'reverse_port=s',
+       'verbose|v',
+       'help|h',
+) ;
+
+usage() unless $opts_ok ;
+usage() if $opts{help} ;
+
+my %backend_ports = (
+
+       'reverse'       => $opts{reverse_port} || 8888,
+       'upper'         => $opts{upper_port} || 8889,
+) ;
+
+# this controls the order of requests to the backends.
+
+my @backend_ids = sort keys %backend_ports ;
+
+my $listen = init_server( $opts{server_port} || 8887 ) ;
+
+Stem::Event::start_loop() ;
+
+exit ;
+
+# create the listen socket for the server side of the middle layer.
+
+sub init_server {
+
+       my( $port ) = @_ ;
+
+# create the middle layer listen socket
+
+       my $listen = Stem::Socket->new(
+               object  => bless( {
+               }, __PACKAGE__),
+               method  => 'client_connected',
+               port    => $port,
+               server  => 1,
+       ) ;
+
+       die "can't listen on $port: $listen" unless ref $listen ;
+
+       return $listen ;
+}
+
+# this is called when the server has accepted a socket connection
+
+sub client_connected {
+
+       my( $obj, $socket ) = @_ ;
+
+# create the session object
+
+       my $self = bless {}, __PACKAGE__ ;
+
+# create and save the async io object for the client
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               fh      => $socket,
+               read_method     => 'client_read_data',
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{client_async} = $async ;
+
+# store a copy of the backend as we shift them out
+
+       $self->{backend_ids} = [ @backend_ids ] ;
+
+}
+
+# this is called when all the data from client has been read.
+
+sub client_read_data {
+
+       my( $self, $data ) = @_ ;
+
+       print "Client read [${$data}]\n"  if $opts{verbose} ;
+
+# store the client data (a ref is passed in)
+
+       $self->{'client_data'} = ${$data} ;
+
+# connect to the first backend server
+
+       my $backend_id = shift( @{$self->{backend_ids}} ) ;
+
+       $self->connect_to_backend( $backend_id ) ;
+}
+
+# this connects the session to one of the backends
+
+sub connect_to_backend {
+
+       my( $self, $id ) = @_ ;
+
+# connect to the backend with this id and its port and save the
+# connect object
+
+       my $connect = Stem::Socket->new(
+               object  => $self,
+               id      => $id,
+               port    => $backend_ports{ $id },
+               method  => 'backend_connected',
+       ) ;
+
+       ref $connect or die "can't create Socket: $connect" ;
+       $self->{connect}{$id} = $connect ;
+}
+
+# this is called when a backend end connection succeeds
+
+sub backend_connected {
+
+       my( $self, $socket, $id ) = @_ ;
+
+# delete and shutdown the connect object as we no longer need it
+
+       my $connect = delete $self->{connect}{$id} ;
+       $connect->shut_down() ;
+
+# create and save an async i/o object for this backend
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               id      => $id,
+               fh      => $socket,
+               read_method     => 'backend_read_data',
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{async}{$id} = $async ;
+
+# write the client data to the back end. no more data will follow.
+
+       $async->final_write( \$self->{client_data} ) ;
+}
+
+# this is called when we have read all the data from the backend
+
+sub backend_read_data {
+
+       my( $self, $data, $id ) = @_ ;
+
+       print "Backend $id READ [${$data}]\n" if $opts{verbose} ;
+
+# save the backend data (we are passed a ref)
+
+       $self->{backend_data}{$id} = ${$data} ;
+
+# delete and shutdown the async i/o for the backend since we don't
+# need it anymore
+
+       my $async = delete $self->{async}{$id} ;
+       $async->shut_down() ;
+
+# do the next backend in the list.  this is a simple way to handle
+# sequential backends we use the backend_ids array in the session
+# object to track which backends we have not used yet.
+
+       if ( my $backend_id = shift( @{$self->{backend_ids}} ) ) {
+
+# connect to the next backend server. 
+
+               $self->connect_to_backend( $backend_id ) ;
+               return ;
+       }
+
+# no more backends so we return the joined backend data to the client.
+
+# delete the async so we don't keep a ref to it around. this will
+# allow for self cleanup when it is done with the final write to the
+# client.
+
+       $async = delete $self->{client_async} ;
+       $async->final_write(
+               join( '',  @{$self->{backend_data}}{ @backend_ids } )
+       ) ;
+}
+
+sub usage {
+
+       my ( $error ) = @_ ;
+
+       $error ||= '' ;
+       die <<DIE ;
+$error
+usage: $0 [--help|h] [--upper_port <port>] [--reverse_port <port>]
+       [--server_port <port>] [--v|--verbose]
+
+       upper_port <port>       Set the port for the middleware server
+                               (default is 8888)
+       upper_port <port>       Set the port for the upper case server
+                               (default is 8888)
+       reverse_port <port>     Set the port for the string reverse server
+                               (default is 8889)
+       verbose                 Set verbose mode
+       help | h                Print this help text
+DIE
+
+}
+
+# this destroy can be uncommented to see the actual destruction of the
+# various obects in this script.
+
+# DESTROY {
+#      my( $self ) = @_ ;
+#      print "DEST [$self]\n" ;
+# }
diff --git a/sessions/mid_event_async.pl b/sessions/mid_event_async.pl
new file mode 100755 (executable)
index 0000000..0d67083
--- /dev/null
@@ -0,0 +1,238 @@
+#!/usr/local/bin/perl -w
+
+use strict ;
+
+use Stem::Event ;
+use Stem::Socket ;
+use Stem::AsyncIO ;
+use Stem::Gather ;
+
+use Data::Dumper ;
+use Getopt::Long ;
+
+my $opts_ok = GetOptions(
+       \my %opts,
+       'server_port=s',
+       'upper_port=s',
+       'reverse_port=s',
+       'verbose|v',
+       'help|h',
+) ;
+
+usage() unless $opts_ok ;
+usage() if $opts{help} ;
+
+my %backend_ports = (
+
+       'reverse'       => $opts{reverse_port} || 8888,
+       'upper'         => $opts{upper_port} || 8889,
+) ;
+
+# this controls the order of requests to the backends.
+
+my @backend_ids = sort keys %backend_ports ;
+
+my $listen = init_server( $opts{server_port} || 8887 ) ;
+
+Stem::Event::start_loop() ;
+
+exit ;
+
+# create the listen socket for the server side of the middle layer.
+
+sub init_server {
+
+       my( $port ) = @_ ;
+
+# create the middle layer listen socket
+
+       my $listen = Stem::Socket->new(
+               object  => bless( {
+               }, __PACKAGE__),
+               method  => 'client_connected',
+               port    => $port,
+               server  => 1,
+       ) ;
+
+       die "can't listen on $port: $listen" unless ref $listen ;
+
+       return $listen ;
+}
+
+# this is called when the server has accepted a socket connection
+
+sub client_connected {
+
+       my( $obj, $socket ) = @_ ;
+
+# create the session object
+
+       my $self = bless {}, __PACKAGE__ ;
+
+# create and save the async io object for the client
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               fh      => $socket,
+               read_method     => 'client_read_data',
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{client_async} = $async ;
+
+# create and save the gather object
+
+       my $gather = Stem::Gather->new(
+               object  => $self,
+               keys    => \@backend_ids,
+       ) ;
+
+       ref $gather or die "can't create Gather: $gather" ;
+       $self->{gather} = $gather ;
+}
+
+# this is called when all the data from client has been read.
+
+sub client_read_data {
+
+       my( $self, $data ) = @_ ;
+
+       print "Client read [${$data}]\n"  if $opts{verbose} ;
+
+# store the client data (a ref is passed in)
+
+       $self->{'client_data'} = ${$data} ;
+
+# connect to all of the backend servers
+
+       $self->connect_to_backends() ;
+}
+
+# this connects the session to all of the backends
+
+sub connect_to_backends {
+
+       my( $self ) = @_ ;
+
+# loop over all the backends
+
+       foreach my $id ( @backend_ids ) {
+
+# connect to the backend with this id and its port and save the
+# connect object
+
+               my $connect = Stem::Socket->new(
+                       object  => $self,
+                       id      => $id,
+                       port    => $backend_ports{ $id },
+                       method  => 'backend_connected',
+               ) ;
+
+               ref $connect or die "can't create Socket: $connect" ;
+               $self->{connect}{$id} = $connect ;
+       }
+}
+
+# this is called when a backend end connection succeeds
+
+sub backend_connected {
+
+       my( $self, $socket, $id ) = @_ ;
+
+# delete and shutdown the connect object as we no longer need it
+
+       my $connect = delete $self->{connect}{$id} ;
+       $connect->shut_down() ;
+
+# create and save an async i/o object for this backend
+
+       my $async = Stem::AsyncIO->new(
+               object  => $self,
+               id      => $id,
+               fh      => $socket,
+               read_method     => 'backend_read_data',
+               send_data_on_close => 1,
+       ) ;
+       ref $async or die "can't create Async: $async" ;
+       $self->{async}{$id} = $async ;
+
+# write the client data to the back end. no more data will follow.
+
+       $async->final_write( \$self->{client_data} ) ;
+}
+
+# this is called when we have read all the data from the backend
+
+sub backend_read_data {
+
+       my( $self, $data, $id ) = @_ ;
+
+       print "Backend $id READ [${$data}]\n" if $opts{verbose} ;
+
+# save the backend data (we are passed a ref)
+
+       $self->{backend_data}{$id} = ${$data} ;
+
+# delete and shutdown the async i/o for the backend since we don't
+# need it anymore
+
+       my $async = delete $self->{async}{$id} ;
+       $async->shut_down() ;
+
+# mark that this backend is done
+
+       $self->{'gather'}->gathered( $id ) ;
+}
+
+# this is called when all the backends are done.
+
+sub gather_done {
+
+       my( $self ) = @_ ;
+
+       my $gather = delete $self->{gather} ;
+       $gather->shut_down() ;
+
+# no more backends so we return the joined backend data to the client.
+
+# we don't need the gather object around anymore
+# allow for self cleanup when it is done with the final write to the
+# client.
+
+       my $async = delete $self->{client_async} ;
+       $async->final_write(
+               join( '',  @{$self->{backend_data}}{ @backend_ids } )
+       ) ;
+}
+
+
+
+sub usage {
+
+       my ( $error ) = @_ ;
+
+       $error ||= '' ;
+       die <<DIE ;
+$error
+usage: $0 [--help|h] [--upper_port <port>] [--reverse_port <port>]
+       [--server_port <port>] [--v|--verbose]
+
+       upper_port <port>       Set the port for the middleware server
+                               (default is 8888)
+       upper_port <port>       Set the port for the upper case server
+                               (default is 8888)
+       reverse_port <port>     Set the port for the string reverse server
+                               (default is 8889)
+       verbose                 Set verbose mode
+       help | h                Print this help text
+DIE
+
+}
+
+# this destroy can be uncommented to see the actual destruction of the
+# various obects in this script.
+
+# DESTROY {
+#      my( $self ) = @_ ;
+#      print "DEST [$self]\n" ;
+# }
diff --git a/t/cell/flow.t b/t/cell/flow.t
new file mode 100644 (file)
index 0000000..477c856
--- /dev/null
@@ -0,0 +1,8 @@
+
+unless ( eval { require Parse::RecDescent } ) {
+
+       print "1..0 # Skip Parse::RecDescent is not installed\n" ;
+       exit ;
+}
+
+exec qw( run_stem event_loop=perl test_flow) ;
diff --git a/t/event/event.t b/t/event/event.t
new file mode 100644 (file)
index 0000000..93986d8
--- /dev/null
@@ -0,0 +1,10 @@
+use lib 't/event' ;
+
+unless ( eval { require Event } ) {
+
+       print "1..0 # Skip Event.pm is not installed\n" ;
+       exit ;
+}
+
+@ARGV = 'event' ;
+require 'event_test.pl' ;
diff --git a/t/event/event_test.pl b/t/event/event_test.pl
new file mode 100644 (file)
index 0000000..11227d1
--- /dev/null
@@ -0,0 +1,337 @@
+#!/usr/local/bin/perl -w
+
+BEGIN {
+       $Stem::Vars::Env{ 'event_loop' } = shift ;
+
+       unless ( eval { require Time::HiRes } ) {
+
+               Time::HiRes->import( qw( time ) ) ;
+       }
+}
+
+use strict ;
+
+#use Test::More tests => 29 ;
+use Test::More tests => 24 ;
+
+use Symbol ;
+
+use Stem::Event ;
+use Stem::Class ;
+
+my $self = bless {} ;
+
+test_events() ;
+
+exit ;
+
+sub test_events {
+
+#      test_null_events() ;
+#      test_plain_events () ;
+#      test_signal_events () ;
+       test_hard_timer_events () ;
+       test_soft_timer_events () ;
+       test_io_events () ;
+}
+
+sub test_null_events {
+
+       local $SIG{__WARN__} = sub{} if
+                       $Stem::Vars::Env{ 'event_loop' } eq 'event' ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'null - event loop exit' ) ;
+}
+
+sub test_plain_events {
+
+       my $event = Stem::Event::Plain->new(
+               'object' => $self
+       ) ;
+
+       ok( ref $event, 'plain event created' ) ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'plain - event loop exit' ) ;
+}
+
+# callback method for plain
+
+sub triggered {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'plain event triggered' ) ;
+}
+
+sub test_signal_events {
+
+       SKIP: {
+               if ( $^O =~ /win32/i ) {
+
+                       skip( "signals not supported on windows", 3 ) ;
+                       return ;
+               }
+
+               my $event = Stem::Event::Signal->new(
+                       'object'        => $self,
+                       'signal'        => 'INT',
+               ) ;
+
+               ok( ref $event, 'signal event created' ) ;
+
+               $self->{'sig_event'} = $event ;
+
+               kill 'INT', $$ ;
+
+#print "kill INT\n" ;
+
+               Stem::Event::start_loop() ;
+
+               ok( 1, 'signal - event loop exit' ) ;
+       }
+}
+
+# callback method for signal
+
+sub sig_int_handler {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'signal event triggered' ) ;
+
+       $self->{'sig_event'}->cancel() ;
+       Stem::Event::stop_loop() ;
+}
+
+
+use constant INTERVAL  => 4 ;
+use constant SLEEP     => 2 ;
+use constant TIMER_CNT => 2 ;
+
+# hard timeouts are timed from the beginning of the callback. so accumulated
+# time in the callback doesn't affect the next callback.
+
+sub test_hard_timer_events {
+
+       my $event = Stem::Event::Timer->new(
+               'object'        => $self,
+               'method'        => 'hard_timeout',
+               'interval'      => INTERVAL,
+               'delay'         => INTERVAL,    # REMOVE - only for .10
+               'repeat'        => 1,
+               'hard'          => 1,
+       ) ;
+
+       ok( ref $event, 'hard timer event created' ) ;
+       print "$event\n" unless ref $event ;
+
+       $self->{'hard_timer_event'} = $event ;
+       $self->{'hard_timer_count'} = TIMER_CNT ;
+       $self->{'hard_timer_start_time'} = time ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'hard timer - event loop exit' ) ;
+}
+
+sub hard_timeout {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'hard timer event triggered' ) ;
+
+       if ( --$self->{'hard_timer_count'} > 0 ) {
+
+               my $time = time ;
+               my $delta = $time - $self->{'hard_timer_start_time'} ;
+               $self->{'hard_timer_start_time'} = $time ;
+
+               ok( $delta >= INTERVAL, 'hard delta' ) ;
+
+               hard_sleep( SLEEP ) ;
+
+               return ;
+       }
+
+       
+       my $time = time ;
+       my $delta = $time - $self->{'hard_timer_start_time'} ;
+
+#print "O $self->{'hard_timer_start_time'} T $time D $delta I ", INTERVAL, "\n" ;
+
+       ok( $delta >= INTERVAL, 'hard delta 2' ) ;
+       ok( $delta <= INTERVAL + SLEEP, 'hard delta sleep' ) ;
+
+       $self->{'hard_timer_event'}->cancel() ;
+
+       Stem::Event::stop_loop() ;
+}
+
+
+# Soft timeouts are timed from the end of the callback. so accumulated
+# time in the callback delays the next callback.
+
+sub test_soft_timer_events {
+
+       my $event = Stem::Event::Timer->new(
+               'object'        => $self,
+               'method'        => 'soft_timeout',
+               'interval'      => INTERVAL,
+               'delay'         => INTERVAL,    # REMOVE  - only for .10
+               'repeat'        => 1,
+       ) ;
+
+       ok( ref $event, 'soft timer event created' ) ;
+#      print "$event\n" unless ref $event ;
+
+       $self->{'soft_timer_event'} = $event ;
+       $self->{'soft_timer_count'} = TIMER_CNT ;
+       $self->{'soft_timer_start_time'} = time ;
+
+#print "OTIME $self->{'soft_timer_start_time'}\n" ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'soft timer - event loop exit' ) ;
+}
+
+sub soft_timeout {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'soft timer event triggered' ) ;
+
+       if ( --$self->{'soft_timer_count'} > 0 ) {
+
+               my $time = time ;
+               my $delta = $time - $self->{'soft_timer_start_time'} ;
+
+#print "T $time D $delta I ", INTERVAL, "\n" ;
+
+               ok( $delta >= INTERVAL, 'soft delta' ) ;
+
+               hard_sleep( SLEEP ) ;
+
+#my $curr_time = time() ;
+#print "DONE $curr_time\n" ;
+
+               return ;
+       }
+
+       my $time = time ;
+       my $delta = $time - $self->{'soft_timer_start_time'} ;
+
+#print "TIME2 $time OTIME $self->{'soft_timer_start_time'} DEL $delta INTERVAL ", INTERVAL, "\n" ;
+
+#      ok( $delta >= INTERVAL, 'soft delta 2' ) ;
+       ok( $delta >= INTERVAL + SLEEP, 'soft delta 3' ) ;
+
+       $self->{'soft_timer_event'}->cancel() ;
+
+       Stem::Event::stop_loop() ;
+}
+
+sub test_io_events {
+
+       Stem::Event::init_loop() ;
+
+       my $read_fh = gensym ;
+       my $write_fh = gensym ;
+
+# get a pipe to read/write through.
+
+       use Socket;
+       socketpair( $read_fh, $write_fh, AF_UNIX, SOCK_STREAM, PF_UNSPEC ) ;
+
+       $self->{read_fh} = $read_fh ;
+       $self->{write_fh} = $write_fh ;
+       $self->{message} = 'Stem Read/Write Event' ;
+
+       # create the read and write events
+
+       my $read_event = Stem::Event::Read->new(
+                               'object'        =>      $self,
+                               'fh'            =>      $read_fh,
+                               'timeout'       =>      3,
+       ) ;
+
+       ok( ref $read_event, 'read event created' ) ;
+       $self->{'read_event'} = $read_event ;
+
+       my $write_event = Stem::Event::Write->new(
+                               'object'        =>      $self,
+                               'fh'            =>      $write_fh,
+       ) ;
+
+       ok( ref $write_event, 'write event created' ) ;
+       $self->{'write_event'} = $write_event ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'io - event loop exit' ) ;
+}
+
+sub read_timeout {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'read event timed out' ) ;
+
+       $self->{'write_event'}->start() ;
+}
+
+
+sub writeable {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'write event triggered' ) ;
+
+       syswrite( $self->{'write_fh'}, $self->{'message'} ) ;
+
+       $self->{'write_event'}->cancel() ;
+}
+
+sub readable {
+
+       my( $self ) = @_ ;
+
+       ok(1, 'read event triggered' ) ;
+
+       my( $read_buf ) ;
+
+       my $bytes_read = sysread( $self->{'read_fh'}, $read_buf, 1000 ) ;
+
+       ok( $bytes_read, 'read byte count' ) ;
+
+       is( $read_buf, $self->{'message'}, 'read event compare' ) ;
+
+       $self->{'read_event'}->cancel() ;
+
+       Stem::Event::stop_loop() ;
+}
+
+# do a real hard sleep without alarm signal as that can screw up the tests
+# sleep time is in (float) seconds
+
+sub hard_sleep {
+
+       my( $sleep_time ) = @_ ;
+
+#print "BEFORE TIME $sleep_time\n" ;
+       while( $sleep_time > 0 ) {
+
+               my $curr_time = time() ;
+               select( undef, undef, undef, $sleep_time ) ;
+
+               $sleep_time -= time() - $curr_time ;
+
+#print "AFTER TIME $sleep_time\n" ;
+       }
+}
+
+1 ;
diff --git a/t/event/gtk.t b/t/event/gtk.t
new file mode 100644 (file)
index 0000000..916b68c
--- /dev/null
@@ -0,0 +1,13 @@
+use lib 't/event' ;
+
+unless ( eval { require Gtk } ) {
+
+       print "1..0 # Skip Gtk is not installed\n" ;
+       exit ;
+}
+
+print "1..0 # Skip Gtk is not supported yet\n" ;
+exit ;
+
+@ARGV = 'gtk' ;
+require 'event_test.pl' ;
diff --git a/t/event/perl.t b/t/event/perl.t
new file mode 100644 (file)
index 0000000..ae6700b
--- /dev/null
@@ -0,0 +1,4 @@
+use lib 't/event' ;
+
+@ARGV = 'perl' ;
+require 'event_test.pl' ;
diff --git a/t/event/poe.t b/t/event/poe.t
new file mode 100644 (file)
index 0000000..bc6a57c
--- /dev/null
@@ -0,0 +1,13 @@
+use lib 't/event' ;
+
+unless ( eval { require POE } ) {
+
+       print "1..0 # Skip POE is not installed\n" ;
+       exit ;
+}
+
+print "1..0 # Skip POE is not supported yet\n" ;
+exit ;
+
+@ARGV = 'poe' ;
+require 'event_test.pl' ;
diff --git a/t/event/qt.t b/t/event/qt.t
new file mode 100644 (file)
index 0000000..226ff5e
--- /dev/null
@@ -0,0 +1,13 @@
+use lib 't/event' ;
+
+unless ( eval { require Qt } ) {
+
+       print "1..0 # Skip Qt is not installed\n" ;
+       exit ;
+}
+
+print "1..0 # Skip Qt is not supported yet\n" ;
+exit ;
+
+@ARGV = 'qt' ;
+require 'event_test.pl' ;
diff --git a/t/event/tk.t b/t/event/tk.t
new file mode 100644 (file)
index 0000000..9296923
--- /dev/null
@@ -0,0 +1,11 @@
+use lib 't/event' ;
+
+unless ( eval { require Stem::Event::Tk } ) {
+
+       print "1..0 # Skip Tk is not installed\n" ;
+       exit ;
+}
+
+@ARGV = 'tk' ;
+eval{ require 'event_test.pl' } ;
+#print "ERR [$@]\n" if $@ ;
\ No newline at end of file
diff --git a/t/event/wx.t b/t/event/wx.t
new file mode 100644 (file)
index 0000000..d77b3c5
--- /dev/null
@@ -0,0 +1,10 @@
+use lib 't/event' ;
+
+unless ( eval { require Wx } ) {
+
+       print "1..0 # Skip WxWindows is not installed\n" ;
+       exit ;
+}
+
+@ARGV = 'wx' ;
+require 'event_test.pl' ;
diff --git a/t/io/packet.t b/t/io/packet.t
new file mode 100644 (file)
index 0000000..94f602f
--- /dev/null
@@ -0,0 +1,10 @@
+
+unless ( eval { require Parse::RecDescent } ) {
+
+       print "1..0 # Skip Parse::RecDescent is not installed\n" ;
+       exit ;
+}
+
+print "\n$_ = $ENV{ $_ }\n" for qw( PATH PERL5LIB STEM_CONF_PATH ) ;
+
+exec 'run_stem test_packet_io' ;
diff --git a/t/socket/SockFork.pm b/t/socket/SockFork.pm
new file mode 100644 (file)
index 0000000..23227e2
--- /dev/null
@@ -0,0 +1,150 @@
+# t/socket/SockFork.pm
+# common code for forked socket tests
+
+package SockFork ;
+
+use strict ;
+
+
+use Stem ;
+use Stem::Socket ;
+
+use Test::More tests => 7 ;
+
+my $self = bless {} ;
+my $pid ;
+my $port = 9000 ;
+my $data = "FOO\n" ;
+
+sub test {
+
+       my ( $ssl_client_args, $ssl_server_args ) = @_ ;
+
+       my ( @ssl_client_args, @ssl_server_args ) ;
+
+       if ( @{$ssl_client_args} ) {
+
+               @ssl_client_args = ( 'ssl_args' => $ssl_client_args ) ;
+               @ssl_server_args = ( 'ssl_args' => $ssl_server_args ) ;
+       }
+
+
+       if ( $pid = fork() ) {
+
+               sleep 1 ;
+
+               $self->{'pid'} = $pid ;
+
+               ok( 1, 'parent' ) ;
+
+#print "SSL @ssl_client_args\n" ;
+
+               my $connect_sock = Stem::Socket->new(
+                               'object'        => $self,
+                               'port'          => $port,
+                               @ssl_client_args,
+                               
+               ) ;
+
+               die $connect_sock unless ref $connect_sock ;
+
+               ok( 1, 'created connect event' ) ;
+               $self->{'connect_sock'} = $connect_sock ;
+       }
+       else {
+
+               my $listen_sock = Stem::Socket->new(
+                               'object'        => $self,
+                               'port'          => $port,
+                               'server'        => 1,
+                               'method'        => 'accepted',
+                               @ssl_server_args
+               ) ;
+
+               die $listen_sock unless ref $listen_sock ;
+               $self->{'listen_sock'} = $listen_sock ;
+       }
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'event loop exit' ) ;
+}
+
+sub connected {
+
+       my( $self, $client_sock ) = @_ ;
+
+#print "CLIENT [$client_sock]\n" ;
+
+       ok( 1, 'connected' ) ;
+
+       $self->{'client_sock'} = $client_sock ;
+
+       my $wcnt = $client_sock->syswrite( $data ) ;
+
+#print "WCNT [$wcnt] $!\n" ;
+
+       my $client_read_event = Stem::Event::Read->new(
+               'object'        => $self,
+               'fh'            => $client_sock,
+               'method'        => 'client_readable',
+       ) ;
+
+       ok( $client_read_event, 'created client read event' ) ;
+
+       $self->{'client_read_event'} = $client_read_event ;
+}
+
+sub client_readable {
+
+       my( $self ) = @_ ;
+
+       ok( 1, 'client readable' ) ;
+
+       my $buf ;
+
+       my $client_sock = $self->{'client_sock'} ;
+
+       my $cnt = $client_sock->sysread( $buf, 100 ) ;
+
+       is( $buf, "[$data]", 'client read data' ) ;
+       
+#print "CLIENT READ $cnt [$buf]\n" ;
+
+       $self->{'connect_sock'}->shut_down() ;
+       $self->{'client_read_event'}->cancel() ;
+}
+
+sub accepted {
+
+       my( $self, $accepted_sock ) = @_ ;
+
+       $self->{'accepted_sock'} = $accepted_sock ;
+
+       $self->{'listen_sock'}->shut_down() ;
+
+       my $server_read_event = Stem::Event::Read->new(
+               'object'        => $self,
+               'fh'            => $accepted_sock,
+               'method'        => 'server_readable',
+       ) ;
+
+       $self->{'server_read_event'} = $server_read_event ;
+}
+
+sub server_readable {
+
+       my( $self ) = @_ ;
+
+       my $accepted_sock = $self->{'accepted_sock'} ;
+
+       my $cnt = $accepted_sock->sysread( my $buf, 100 ) ;
+
+       $accepted_sock->syswrite( "[$buf]" ) ;
+
+# exit forked child
+
+       exit ;
+}
+
+1 ;
diff --git a/t/socket/plain.t b/t/socket/plain.t
new file mode 100644 (file)
index 0000000..58b442e
--- /dev/null
@@ -0,0 +1,95 @@
+# t/socket/plain.t
+
+use strict ;
+
+use Stem ;
+use Stem::Socket ;
+
+use Test::More tests => 9 ;
+
+my $data = "FOO\n" ;
+
+test_socket() ;
+
+exit 0 ;
+
+sub test_socket {
+
+       my( $self, $accept_event, $connect_event ) ;
+
+       $self = bless {} ;
+
+       $accept_event = Stem::Socket->new(
+               'object'        => $self,
+               'port'          => 10_000,
+               'server'        => 1,
+               'method'        => 'accepted',
+       ) ;
+
+       die $accept_event unless ref $accept_event ;
+       $self->{'accept_event'} = $accept_event ;
+       ok( 1, 'listen' ) ;
+
+       $connect_event = Stem::Socket->new(
+               'object' => $self,
+               'port' => 10_000,
+       ) ;
+
+       die $connect_event unless ref $connect_event ;
+       $self->{'connect_event'} = $connect_event ;
+       ok( 1, 'connect' ) ;
+
+       Stem::Event::start_loop() ;
+
+       ok( 1, 'event loop exit' ) ;
+}
+
+sub accepted {
+
+       my( $self, $accepted_sock ) = @_ ;
+
+       ok( 1, 'accepted' ) ;
+
+       $self->{'accepted_sock'} = $accepted_sock ;
+
+       $self->{'accept_event'}->shut_down() ;
+
+       ok( 1, 'accept canceled' ) ;
+
+       my $read_event = Stem::Event::Read->new(
+               'object'        =>      $self,
+               'fh'            =>      $accepted_sock,
+       ) ;
+
+       $self->{'read_event'} = $read_event ;
+}
+
+sub readable {
+
+       my( $self ) = @_ ;
+
+       ok(1, 'read event triggered' ) ;
+
+       my $bytes_read = sysread( $self->{'accepted_sock'},
+                                 my $read_buf, 1000 ) ;
+
+       ok( $bytes_read, 'read byte count' ) ;
+
+       is( $read_buf, $data, 'read event compare' ) ;
+
+       close( $self->{'accepted_sock'} ) ;
+
+       $self->{'read_event'}->cancel() ;
+}
+
+sub connected {
+
+       my( $self, $connected_sock ) = @_ ;
+
+       ok( 1, 'connected' ) ;
+
+       my $wcnt = $connected_sock->syswrite( $data ) ;
+#print "SYSWR C $wcnt\n" ;
+
+       $self->{'connect_event'}->shut_down() ;
+}
diff --git a/t/socket/plain_fork.t b/t/socket/plain_fork.t
new file mode 100644 (file)
index 0000000..d7ced4c
--- /dev/null
@@ -0,0 +1,14 @@
+# t/socket/plain.t
+
+use lib 't/socket' ;
+
+use strict ;
+
+use SockFork ;
+
+my @ssl_client_args ;
+my @ssl_server_args ;
+
+SockFork::test( \@ssl_client_args, \@ssl_server_args ) ;
+
+exit 0 ;
diff --git a/t/socket/ssl_fork.t b/t/socket/ssl_fork.t
new file mode 100644 (file)
index 0000000..a799f76
--- /dev/null
@@ -0,0 +1,25 @@
+# t/socket/ssl.t
+
+use strict ;
+use lib 't/socket' ;
+
+unless ( eval { require IO::Socket::SSL } ) {
+
+       print "1..0 # Skip IO::Socket::SSL is not installed\n" ;
+       exit ;
+}
+
+
+require SockFork ;
+
+my @ssl_client_args = (
+       SSL_use_cert    => 1,
+       SSL_verify_mode => 0x01,
+       SSL_passwd_cb   => sub {return "bluebell"}
+) ;
+
+my @ssl_server_args = () ;
+
+SockFork::test( \@ssl_client_args, \@ssl_server_args ) ;
+
+exit 0 ;
diff --git a/t/socket/udp.t b/t/socket/udp.t
new file mode 100644 (file)
index 0000000..638c9fd
--- /dev/null
@@ -0,0 +1,2 @@
+
+exec 'run_stem', 'test_udp' ;