+ my $self = shift;
+
+ $self->{group} = undef; # belt and braces
+ delete $channels{$self->{call}};
+}
+
+# is it a bbs
+sub is_bbs
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'B';
+}
+
+sub is_node
+{
+ my $self = shift;
+ return $self->{'sort'} =~ /[ACRSX]/;
+}
+# is it an ak1a node ?
+sub is_ak1a
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'A';
+}
+
+# is it a user?
+sub is_user
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'U';
+}
+
+# is it a clx node
+sub is_clx
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'C';
+}
+
+# is it a spider node
+sub is_spider
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'S';
+}
+
+# is it a DXNet node
+sub is_dxnet
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'X';
+}
+
+# is it a ar-cluster node
+sub is_arcluster
+{
+ my $self = shift;
+ return $self->{'sort'} eq 'R';
+}
+
+# for perl 5.004's benefit
+sub sort
+{
+ my $self = shift;
+ return @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
+}
+
+# handle out going messages, immediately without waiting for the select to drop
+# this could, in theory, block
+sub send_now
+{
+ my $self = shift;
+ my $conn = $self->{conn};
+ return unless $conn;
+ my $sort = shift;
+ my $call = $self->{call};
+
+ for (@_) {
+ chomp;
+ my @lines = split /\n/;
+ for (@lines) {
+ $conn->send_now("$sort$call|$_");
+ dbg('chan', "-> $sort $call $_");
+ }
+ }
+ $self->{t} = time;
+}
+
+#
+# the normal output routine
+#
+sub send # this is always later and always data
+{
+ my $self = shift;
+ my $conn = $self->{conn};
+ return unless $conn;
+ my $call = $self->{call};
+
+ for (@_) {
+ chomp;
+ my @lines = split /\n/;
+ for (@lines) {
+ $conn->send_later("D$call|$_");
+ dbg('chan', "-> D $call $_");
+ }
+ }
+ $self->{t} = time;
+}
+
+# send a file (always later)
+sub send_file
+{
+ my ($self, $fn) = @_;
+ my $call = $self->{call};
+ my $conn = $self->{conn};
+ my @buf;
+
+ open(F, $fn) or die "can't open $fn for sending file ($!)";
+ @buf = <F>;
+ close(F);
+ $self->send(@buf);
+}
+
+# this will implement language independence (in time)
+sub msg
+{
+ my $self = shift;
+ return DXM::msg($self->{lang}, @_);
+}
+
+# stick a broadcast on the delayed queue (but only up to 20 items)
+sub delay
+{
+ my $self = shift;
+ my $s = shift;
+
+ $self->{delayed} = [] unless $self->{delayed};
+ push @{$self->{delayed}}, $s;
+ if (@{$self->{delayed}} >= 20) {
+ shift @{$self->{delayed}}; # lose oldest one
+ }
+}
+
+# change the state of the channel - lots of scope for debugging here :-)
+sub state
+{
+ my $self = shift;
+ if (@_) {
+ $self->{oldstate} = $self->{state};
+ $self->{state} = shift;
+ $self->{func} = '' unless defined $self->{func};
+ dbg('state', "$self->{call} channel func $self->{func} state $self->{oldstate} -> $self->{state}\n");
+
+ # if there is any queued up broadcasts then splurge them out here
+ if ($self->{delayed} && ($self->{state} eq 'prompt' || $self->{state} eq 'convers')) {
+ $self->send (@{$self->{delayed}});
+ delete $self->{delayed};
+ }
+ }
+ return $self->{state};
+}
+
+# disconnect this channel
+sub disconnect
+{
+ my $self = shift;
+ my $user = $self->{user};
+ my $conn = $self->{conn};
+ my $call = $self->{call};
+
+ $self->finish($conn);
+ $user->close() if defined $user;
+ $conn->disconnect() if $conn;
+ $self->del();
+}
+
+#
+# just close all the socket connections down without any fiddling about, cleaning, being
+# nice to other processes and otherwise telling them what is going on.
+#
+# This is for the benefit of forked processes to prepare for starting new programs, they
+# don't want or need all this baggage.
+#
+
+sub closeall
+{
+ my $ref;
+ foreach $ref (values %channels) {
+ $ref->{conn}->disconnect() if $ref->{conn};
+ }
+}
+
+#
+# Tell all the users that we have come in or out (if they want to know)
+#
+sub tell_login
+{
+ my ($self, $m) = @_;
+
+ # send info to all logged in thingies
+ my @dxchan = get_all_users();
+ my $dxchan;
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $self;
+ $dxchan->send($dxchan->msg($m, $self->{call})) if $dxchan->{logininfo};
+ }
+}
+
+# various access routines
+
+#
+# return a list of valid elements
+#
+
+sub fields
+{
+ return keys(%valid);
+}
+
+#
+# return a prompt for a field
+#
+
+sub field_prompt
+{
+ my ($self, $ele) = @_;
+ return $valid{$ele};
+}
+
+# take a standard input message and decode it into its standard parts
+sub decode_input
+{
+ my $dxchan = shift;
+ my $data = shift;
+ my ($sort, $call, $line) = $data =~ /^([A-Z])([A-Z0-9\-]{3,9})\|(.*)$/;
+
+ my $chcall = (ref $dxchan) ? $dxchan->call : "UN.KNOWN";
+
+ # the above regexp must work
+ if (!defined $sort || !defined $call || !defined $line ||
+ (ref $dxchan && $call ne $chcall)) {
+ $data =~ s/([\x00-\x1f\x7f-\xff])/uc sprintf("%%%02x",ord($1))/eg;
+ dbg('chan', "DUFF Line from $chcall: $data");
+ return ();
+ }
+
+ return ($sort, $call, $line);
+}
+
+no strict;
+sub AUTOLOAD
+{
+ my $self = shift;
+ my $name = $AUTOLOAD;
+ return if $name =~ /::DESTROY$/;
+ $name =~ s/.*:://o;
+
+ confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
+ @_ ? $self->{$name} = shift : $self->{$name} ;