Author: bdonlan
Date: 2005-07-18 00:42:57 -0400 (Mon, 18 Jul 2005)
New Revision: 870
Modified:
trunk/perl/client/lib/Haver/Client/POE.pm
Log:
Revert data changes from r869
Modified: trunk/perl/client/lib/Haver/Client/POE.pm
===================================================================
--- trunk/perl/client/lib/Haver/Client/POE.pm 2005-07-18 04:34:36 UTC (rev
869)
+++ trunk/perl/client/lib/Haver/Client/POE.pm 2005-07-18 04:42:57 UTC (rev
870)
@@ -17,37 +17,16 @@
# along with this module; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-# object fields:
-# self => {
-# [persistent data]
-# options => { key => value },
-# handlers => {
-# type => {
-# event name => {
-# reverse => {
-# stringified $subref => index
-# },
-# forward => [
-# $subref,
-# ...
-# ]
-# } # event name =>
-# } # type =>
-# }, # handlers =>
-# connection => {
-# [connection data]
-# UID => $uid,
-# authmethods => [ acceptable auth methods ],
-# state => STATE_NAME,
-# want => 'CURRENTLY_WANTED',
-# pending => [
-# ['WAITING', 'FOR', 'WANT'],
-# ],
-# POE => {
-# connwheel => POE::Wheel::ConnectionFactory,
-# sockwheel => POE::Wheel::ReadWrite
-# },
-# } # connection =>
+# heap fields:
+# heap => {
+# name => user name,
+# reg => {
+# event name => { session ID => 1 }
+# },
+# wheel => active wheel,
+# state => see below constants,
+# # for connect events sent while still shutting down
+# pending => { Host => host, Port => port, Name => name ] | nonexistent
# }
=head1 NAME
@@ -62,7 +41,7 @@
$conn->register(connected => \&on_connect);
$conn->connect( Host => 'example.com',
Port => 7070,
- UID => 'example');
+ Name => 'example');
=head1 DESCRIPTION
@@ -77,53 +56,72 @@
package Haver::Client::POE;
use strict;
use warnings;
+use Carp;
+use Data::Dumper;
+use Regexp::Shellish;
use constant {
- TRUE => 1,
- FALSE => 0,
- STATE_IDLE => 0, # not connected or connecting
- STATE_CONNECT => 1, # establishing connection
- STATE_AUTH => 2, # authenticating
- STATE_READY => 3, # ready for user control
- STATE_DISCON => 4, # disconnecting
+ S_IDLE => 0, # not connected, not connecting
+ S_CONN => 1, # establishing socket connection
+ S_INIT => 2, # C: HAVER sent
+ S_LOGIN => 3, # S: HAVER recieved, C: IDENT sent
+ S_ONLINE => 4, # S: HELLO received
+ S_DYING => 5, # C: BYE sent, socket still open
};
-use Carp;
-
use POE qw(Wheel::ReadWrite
Wheel::SocketFactory);
-use POE::Filter::Haver;
-use Haver::Preprocessor;
-use Haver::Formats::Error;
+use Haver::Protocol::Filter;
our $VERSION = 0.06;
+our %failures = (
+ 'invalid.name' => "The server rejected the name %1%.",
+ 'reserved.name' => "The name %1% is reserved for internal use by the
server.",
+ 'exists.user' => "The name %1% is in use.",
+ 'unknown.user' => "The user %1% is not online.",
+ 'unknown.channel' => "The channel %1% does not exist.",
+ 'unknown.namespace' => "The namespace %1% does not exist. This is probably
an application bug.",
+ 'invalid.type' => "The type of a message was invalid. This is almost
certainly an application error.",
+ 'already.joined' => "Tried to join %1% when already in it.",
+ 'already.parted' => "Tried to leave %1% when not in it.",
+);
+
+sub _format {
+ my @args = @_;
+ shift @args; # S: FAIL
+ my $code = $args[0];
+
+ if (!$failures{$code}) {
+ return "Unknown error: " . join(' ', @args);
+ }
+
+ my $msg = $failures{$code};
+ $msg = s{%(\d+)%}{$args[$1] || "MISSING ARGUMENT $1"}eg;
+ return $msg;
+}
+
sub _call {
return POE::Kernel->call(POE::Kernel->get_active_session(), @_);
}
+sub _dispatch {
+ _call('__dispatch', @_);
+}
+
sub _dprint {
my ($level, @text) = @_;
- return unless
POE::Kernel->get_active_session()->get_heap()->{object}{debug} >= $level;
+ return unless POE::Kernel->get_active_session()->get_heap()->{debug} >=
$level;
print STDERR @text;
}
sub _dprintf {
my ($level, $fmt, @text) = @_;
- return unless
POE::Kernel->get_active_session()->get_heap()->{object}{debug} >= $level;
+ return unless POE::Kernel->get_active_session()->get_heap()->{debug} >=
$level;
printf STDERR $fmt, @text;
}
-sub _object_states {
- my ($self, $ehash) = @_;
- $ehash = {qw{
- _start _start
- _default _default
- }, %$ehash};
- return $self->SUPER::_object_states($ehash);
-}
-
### SETUP
=head2 spawn($alias [, $Z<>resolver])
@@ -138,12 +136,10 @@
sub spawn {
my ($pkg, $alias, $resolver) = @_;
- my $self = {
- options => {
- version => "Haver::Client::POE/$VERSION",
- },
- handlers => {
- },
+ my $heap = {
+ reg => {},
+ state => S_IDLE,
+ alias => $alias,
resolver => $resolver
};
@@ -191,22 +187,16 @@
}
sub _start {
- die "STUB";
+ my ($kernel, $alias) = @_[KERNEL,ARG0];
+ $kernel->alias_set($alias);
}
-# Internal: _init_event_type($type)
-# Prepares to register events of type $type
+### SESSION MANAGEMENT
-sub _init_event_type {
- my $self = shift;
- $self->{handlers}{shift} = {
- hhash => {},
- byname => {},
- }
-}
+=head2 connect(Host => $Z<>host, Name => $Z<>name, [Port => $Z<>port])
-# Internal: _init_wants
-# Sets up default handlers for wants
+Connects to the haver server. The Host option is mandatory, all others are
optional.
+If it is already connected, it will disconnect, then connect with the new
parameters.
=cut
@@ -311,41 +301,19 @@
# XXX: timeout
}
-
-
-=head2 setoptions(option => value [, ...])
-
-Sets one or more options to the given value. The following options are
available:
-
-=head3 debug => level
-
-Sets debugging to the given level. 0 will disable debugging.
-
-=head3 autorespond => [ ... ]
-
-Enables autoresponding to certain types of messages. Currently only PING? and
TIME? are supported.
-
-=head3 version => "foobar/0.1"
-
-Sets the string sent in response to S: WANT VERSION
-
-=cut
-
-sub setoptions {
- die "STUB";
+sub _input {
+ my ($kernel, $heap, $arg) = @_[KERNEL,HEAP,ARG0];
+ return if (ref $arg ne 'ARRAY' || @$arg == 0);
+ print STDERR "S: ", join "\t", @$arg;
+ print STDERR "\n";
+ _dispatch('raw_in', @$arg);
+ my $cmd = $arg->[0];
+ $kernel->yield("_ev_$cmd", @$arg);
}
-### SESSION MANAGEMENT
-
-=head2 connect(Host => $Z<>host, [Port => $Z<>port])
-
-Connects to the haver server. The Host option is mandatory, all others are
optional.
-If it is already connected, it will disconnect, then connect with the new
parameters.
-
-=cut
-
-sub connect {
- die "STUB";
+sub _err {
+ _dispatch('disconnected', @_[ARG0..ARG2]);
+ _call('_cleanup');
}
=head2 disconnect(Z<>)
@@ -362,9 +330,27 @@
$poe_kernel->delay('_force_down', 5);
}
+sub _force_down {
+ my $heap = $_[HEAP];
+ $heap->{state} = S_IDLE;
+ _call('_cleanup');
+}
+
+sub _cleanup {
+ my $heap = $_[HEAP];
+ $poe_kernel->delay('_force_down');
+ if ($heap->{pending}) {
+ my @opts = %{delete $heap->{pending}};
+ $poe_kernel->yield('connect', @opts);
+ }
+ delete $heap->{wheel};
+ delete $heap->{name};
+ $heap->{state} = S_IDLE;
+}
+
=head2 send_raw(@args)
-Sends the arguments specified to the haver server. No checking is performed,
though escaping may occur.
+Sends the arguments specified to the haver server. No checking is performed,
though escaping will be done.
=cut
@@ -386,34 +372,12 @@
=cut
sub send {
- die "STUB"
+ my ($kernel, @args) = @_[KERNEL,ARG0..$#_];
+ _call('send_raw', @args);
}
### CLIENT EVENTS
-=head2 login(UID => $Z<>uid)
-
-Specifies the UID to use to log in. If authentication is already complete,
-this has no effect.
-
-=cut
-
-sub login {
- die "STUB";
-}
-
-=head2 authenticate($Z<>method, @Z<>args)
-
-Authenticates to the server. $method must be an object of type
-Haver::Client::POE::Auth capable of handling an authentication
-type listed in the auth_request() message.
-
-=cut
-
-sub authenticate {
- die "STUB";
-}
-
=head2 join($Z<>channel)
Attempts to join $channel
@@ -421,7 +385,8 @@
=cut
sub join {
- die "STUB";
+ my $channel = $_[ARG0];
+ _call('send', 'JOIN', $channel);
}
=head2 part($Z<>channel)
@@ -431,367 +396,205 @@
=cut
sub part {
- die "STUB";
+ my $channel = $_[ARG0];
+ _call('send', 'PART', $channel);
}
-=head2 make($Z<>channel)
+=head2 in($Z<>channel, $Z<>type, @Z<>args)
-Ask the server to create a channel $channel.
+Sends a message with specified type and arguments to $channel.
=cut
-sub make {
- die "STUB";
+sub public {
+ my ($kernel, $heap, $c, $t, @a) = @_[KERNEL,HEAP,ARG0..$#_];
+ _call('send', 'IN', $c, $t, @a);
}
-=head2 msg($Z<>channel, $Z<>type, $Z<>text)
+=head2 to($Z<>uid, $Z<>type, @Z<>args)
-Sends a message with specified type and text to $channel.
+Sends a private message with specified type and arguments to $uid.
=cut
-sub msg {
- die "STUB";
+sub private {
+ my ($kernel, $heap, $d, $t, @a) = @_[KERNEL,HEAP,ARG0..$#_];
+ _call('send', 'TO', $d, $t, @a);
}
-=head2 pmsg($Z<>uid, $Z<>type, $Z<>text)
+=head2 list($Z<>channel [, $Z<>type])
-Sends a private message with specified type and text to $uid.
+Ask the server which entities of $type are in $channel. If $type is not
+given, it defaults to 'user'.
=cut
-sub pmsg {
- die "STUB";
+sub list {
+ my ($chan, $type) = @_[ARG0, ARG1];
+ $type = defined $type || 'user';
+ _call('send', 'LIST', $chan, $type);
}
-=head2 users($Z<>channel)
+=head2 destroy(Z<>)
-Ask the server which users are on $channel.
+Disconnects from the Haver server, and destroys the session.
=cut
-sub users {
- die "STUB";
+sub destroy {
+ my ($kernel, $heap) = @_[KERNEL,HEAP];
+ _dispatch('destroyed');
+ delete $heap->{pending};
+ my $reg = $heap->{reg};
+ foreach my $ehash (values %$reg) {
+ foreach my $id (keys %$ehash) {
+ $poe_kernel->refcount_decrement($id, $ehash->{$id});
+ }
+ }
+ $heap->{reg} = {};
+ _call('disconnect');
+ $kernel->alias_remove($heap->{alias});
}
-=head2 chans(Z<>)
+## server-response stuff
-Ask the server for a list of all channels
+sub _ev_HAVER {
+ my ($kernel, $heap) = @_[KERNEL,HEAP];
+ return if ($heap->{state} != S_INIT); # should never happen, unless the
+ # server is non-compliant
+ $kernel->yield('send_raw', 'IDENT', $heap->{name});
+ $heap->{state} = S_LOGIN;
+}
-=cut
+sub _ev_HELLO {
+ my $heap = $_[HEAP];
+ $heap->{state} = S_ONLINE;
+ _dispatch('ready');
+}
-sub chans {
- die "STUB";
+sub _ev_JOIN {
+ my ($heap, $chan, $name) = @_[HEAP,ARG1,ARG2];
+ if ($name eq $heap->{name}) {
+ _dispatch('ijoined', $chan);
+ } else {
+ _dispatch('join', $chan, $name);
+ }
}
-=head2 destroy(Z<>)
+sub _ev_PART {
+ my ($heap, $chan, $name) = @_[HEAP,ARG1,ARG2];
+ if ($name eq $heap->{name}) {
+ _dispatch('iparted', $chan);
+ } else {
+ _dispatch('part', $chan, $name);
+ }
+}
-Disconnects from the Haver server, and destroys the session. This event may
not complete
-immediately, so you should not attempt to create another session with the same
alias
-until it finishes.
+sub _ev_LIST {
+ my ($heap, $chan, $ns, @things) = @_[HEAP,ARG1..$#_];
+ return unless defined $ns;
+ _dispatch('list', $chan, $ns, @things);
+}
-=cut
+sub _ev_IN {
+ _dispatch('public', @_[ARG1..$#_]);
+}
-sub destroy {
- die "STUB";
+sub _ev_FROM {
+ _dispatch('private', @_[ARG1..$#_]);
}
-sub _default {
- my ( $kernel, $state, $event, $args, $self ) = @_[ KERNEL, STATE, ARG0,
ARG1, OBJECT ];
- $args ||= []; # Prevents uninitialized-value warnings.
- # DEBUG: "default: $state = $event. Args:\n", Dumper $args;
- return 0;
+sub _ev_PING {
+ _call('send_raw', 'PONG', @_[ARG1..$#_]);
}
-=head1 HANDLERS
-
-Handlers are subroutines called to handle some event from the server. Any
-number of handlers may be registered to an event, and they will be called
-in reverse order of registration until one indicates that it has handled the
-event.
-
-The handler prototype is as follows:
-
- sub handler {
- my ($handled, $type, $name, $args) = @_;
- # ...
- $handled = 1; # To handle the event
- }
-
-$type is the type of event, $name is the event name, and $args is an array ref
-of arguments for the event.
-
-If $$handled is 0 when the handler returns, the next handler in the chain will
-be called. Any changes to the contents of $args will be passed on.
-
-If no handler in a chain handles the event, an event with the same type and
-a name of '_default' will be generated, with the old event name prepended
-to @args. If no handler in the _default chain handles the event, it will be
-discarded.
-
-=head2 METHODS FOR HANDLER MANIPULATION
-
-=head3 register_handler($Z<>type, $Z<>event, $Z<>handler)
-
-Register a handler $handler for an event of type $type, name $event. If such
-a handler already exists, does nothing.
-
-=cut
-
-sub register_handler {
- my ($self, $type, $event, $handler) = @_;
- my $htype = $self->{handlers}{$type}
- or croak "Unknown event type $type";
- my $hevent = $htype->{$event};
- if (!$hevent) {
- $hevent = $htype->{$event} = {
- forward => {},
- reverse => {},
- };
- }
- return if (exists $hevent->{reverse}{$handler});
- my $index = @{$hevent->{forward}};
- push @{$hevent->{forward}}, $handler;
- $hevent->{reverse}{$handler} = $index;
+sub _ev_BYE {
+ my ($type, $detail) = @_[ARG2,ARG3];
+ _dispatch('bye', $detail);
+ _call('_cleanup');
}
-=head3 unregister_handler($Z<>type, $Z<>event, $Z<>handler)
-
-Unregisters a handler previously registered by passing the same arguments
-to register_handler. If such a handler does not exist, does nothing.
-
-=cut
-
-sub unregister_handler {
- my ($self, $type, $event, $handler) = @_;
- my $htype = $self->{handlers}{$type}
- or croak "Unknown event type $type";
- my $hevent = $self->{handlers}{$type}{$event} or return;
- my $index = $hevent->{reverse}{$handler} or return;
- # Remove the item
- splice @{$hevent->{forward}}, $index, 1;
- # and renumber the ones afterward
- my $len = @{$hevent->{forward}};
- for (; $index < $len; $index++) {
- my $handler = $hevent->{forward}[$index];
- $hevent->{reverse}{$handler} = $index;
- }
- # and delete the reverse mapping
- delete $hevent->{reverse}{$handler};
+sub _ev_FAIL {
+ my $msg = _format(@_[ARG0..$#_]);
+ _dispatch('fail', $msg, @_[ARG0..$#_]);
+ my $code = $_[ARG1];
+ $code =~ tr/./_/;
+ _dispatch("fail_$code", $msg, @_[ARG0..$#_]);
}
-# Internal: _event_dispatch($type, $event, @args)
-# dispatches event $event
-
-sub _event_dispatch {
- my ($self, $type, $event, @args) = @_;
- my $etype = $self->{handlers}{$type}
- or croak "Unknown event type $etype";
- my $ev = $etype->{$event};
- if (!$ev) {
- if ($event eq '_default') {
- return; # Nobody seems to be interested.
- }
- $self->_event_dispatch($type, '_default', $event, @args);
- }
- for my $handler (@{$ev->{forward}}) {
- my $handled = 0;
- $handler->(\$handled, $type, $event, [EMAIL PROTECTED]);
- return if $handled;
- }
- if ($event ne '_default') {
- $self->_event_dispatch($type, '_default', $event, @args);
- }
+sub _default {
+ my ( $kernel, $state, $event, $args, $self ) = @_[ KERNEL, STATE, ARG0,
ARG1, OBJECT ];
+ $args ||= []; # Prevents uninitialized-value warnings.
+ # DEBUG: "default: $state = $event. Args:\n", Dumper $args;
+ return 0;
}
-
-
-1;
-__END__
-
-=head2 TYPES OF HANDLERS
-
-=head3 want
-
-Handlers of type 'want' are invoked when the server sends a S: WANT message.
The
-first argument of the S: WANT is the event name, the rest are used as
-arguments for the first handler.
-
-The default action, should all handlers pass the event, is to send a
-C: CANT $event to the server.
-
-=head3 smsg
-
-Handlers of type 'smsg' are invoked whenever a command is received from the
server.
-The command name is used for the event name, and the arguments are passed for
-the first handler's @args.
-
-Note that adding a handler with $type of smsg, and $event of 'WANT' may
-prevent handlers of $type want from being called.
-
=head1 EVENTS
-Events are sent to inform multiple handlers of some happening on the server.
-Any number of event callbacks may be registered to an event, and they will all
-be invoked in an undefined order when the event is dispatched.
+docme
=cut
-# implementation notes:
-# events are implemented with handlers, with type 'event'
-# register and unregister create an anonymous wrapper closure to hide the
-# details. These subs are stored in %{$self->{evwrap}}
-# TODO: expire evwrap entries
-
=head2 EVENT MANIPULATION FUNCTIONS
-=head3 register(event => handler)
+=head3 register(@Z<>events)
-Registers to receive a specified event with the specified handler function.
-The special event 'all' may be used to receive all events.
-
=cut
sub register {
- my ($self, @events) = @_;
- while (@events) {
- my ($ev, $handler) = splice @events, 0, 2;
- my $wrap = $self->{evwrap}{$handler} || sub {
- my ($handled, $type, $name, $args) = @_[1..3];
- if ($type ne 'event') {
- die "Impossible: event handler on non-event
type!";
- }
- $handler->($name, @$args);
- };
- $self->{evwrap}{$handler} = $wrap;
- $self->register_handler('event', $ev, $wrap);
- }
+ my ($kernel, $heap, $session, @events) =
@_[KERNEL,HEAP,SENDER,ARG0..$#_];
+ my $reg = $heap->{reg};
+ my $id = $session->ID;
+ foreach my $event (@events) {
+ $event = uc $event;
+ next if exists $reg->{$event}{$id};
+ my $tag = '1' . $reg->{$event} . '\0' . $id . '\0' . rand;
+ $reg->{$event}{$id} = $tag;
+ $kernel->refcount_increment( $id, $tag );
+ }
}
-=head3 unregister(event => handler)
+=head3 unregister(@Z<>events)
-Unregisters the given handler for the specified event.
=cut
sub unregister {
- my ($self, @events) = @_;
- while (@events) {
- my ($ev, $handler) = splice @events, 0, 2;
- my $wrap = $self->{evwrap}{$handler} or next;
- $self->unregister_handler('event', $ev, $wrap);
- }
+ my ($kernel, $heap, $session, @events) =
@_[KERNEL,HEAP,SENDER,ARG0..$#_];
+ my $reg = $heap->{reg};
+ my $id = $session->ID;
+ foreach my $event (@events) {
+ $event = uc $event;
+ my $tag;
+ next unless $tag = delete $reg->{$event}{$id};
+ $kernel->refcount_decrement( $id, $tag );
+ }
}
-=head2 TYPES OF EVENTS
+sub __dispatch {
+ my ($kernel, $heap, $evname, @args) = @_[KERNEL,HEAP,ARG0..$#_];
+ $evname = uc $evname;
+ my $reg = $heap->{reg};
+ $reg->{$evname} ||= {};
+ $reg->{ALL} ||= {};
+ my %targ = (%{$reg->{$evname}}, %{$reg->{ALL}});
+ my @ids = keys %targ;
-Client events have the following prototype:
+ unshift @args, [$heap->{alias}];
foreach my $id (@ids) {
$kernel->post($id, "haver_$evname", @args);
}
}
-The contents of @args vary depending on the event name, and are documented
-below.
+=head2 TYPES OF EVENTS
-=head4 connected(Z<>)
+docme
-This event is sent when a connection is established (but before it is logged
in)
-
-=head4 connect_fail($Z<>enum, $Z<>estr)
-
-The connection could not be established. An error code is in $enum, and the
human-readable
-version is in $estr
-
-=head4 disconnected($Z<>enum, $Z<>estr)
-
-The connection has been lost. If the server closed the connection, $enum will
be 0 and $estr will
-be meaningless. If the user closed the connection, and the server failed to
respond, $enum will be -1.
-Otherwise, $enum will contain an error code, and $estr the human-readable
version.
-
-=head4 raw_in(@data)
-
-The client has received @data from the Haver server. Mostly useful for
debugging.
-
-=head4 raw_out(@data)
-
-The client has sent @data to the Haver server. Mostly useful for debugging.
-
-=head4 login_request(Z<>)
-
-The server is asking for a UID, and one was not provided in connect().
-The authentication will not proceed until login() is invoked with the UID.
-
-=head4 auth_request(@Z<>methods)
-
-The server requests authentication using one of the listed methods. The
-authentication will not proceed until authenticate() is invoked with a
-suitable authentication handler.
-
-=head4 login(Z<>)
-
-The client has successfully logged in.
-
-=head4 close($Z<>etyp, $Z<>estr)
-Z<XXX: Describe args>
-
-Server is closing connection, and sent $etyp and $estr
-
-=head4 join(Z<>)
-
-$uid has joined $cid
-
-=head4 joined(Z<>)
-
-The client has joined $cid.
-
-=head4 part(Z<>)
-
-$uid has left $cid.
-
-=head4 parted(Z<>)
-
-The client has left $Z<>cid.
-
-=head4 msg($Z<>type, @Z<>msg)
-
-A public message with type $type and contents @msg was sent on channel $cid by
user $uid.
-
-=head4 pmsg($Z<>type, @Z<>text)
-
-A private message with type $type and contents @msg was sent to you by user
$uid.
-
-=head4 users(@Z<>who)
-
-Channel $cid has the users listed in @who in it. Sent in response to message
users().
-
-=head4 bye($Z<>why)
-
-The server is disconnecting you due to the reason in $why
-
-=head4 chans(@Z<>channels)
-
-The server has the channels listed in @channels. Sent in response to message
chans()
-
-=head4 warn($Z<>err, $Z<>short, $Z<>long, @Z<>args)
-
-The server has sent a non-fatal error message with code $err and arguments
@args. $short and $long have the
-short and long human-readable forms, respectively.
-
-=head4 die($Z<>err, $Z<>short, $Z<>long, @Z<>args)
-
-The server has sent a fatal error message with code $err and arguments @args.
$short and $long have the
-short and long human-readable forms, respectively. The connection will be
closed shortly.
-
-=head4 quit($Z<>why)
-
-UID $uid has disconnected due to the reason in $why.
-
=head1 SEE ALSO
-L<http://wiki.chani3.com/wiki/ProjectHaver/>
+L<http://haverdev.org>
=head1 AUTHOR
@@ -818,3 +621,5 @@
=cut
+
+1;