get all the debugging finally into the debug files when things go wrong
[spider.git] / perl / DXChannel.pm
1 #
2 # module to manage channel lists & data
3 #
4 # This is the base class for all channel operations, which is everything to do 
5 # with input and output really.
6 #
7 # The instance variable in the outside world will be generally be called $dxchann
8 #
9 # This class is 'inherited' (if that is the goobledegook for what I am doing)
10 # by various other modules. The point to understand is that the 'instance variable'
11 # is in fact what normal people would call the state vector and all useful info
12 # about a connection goes in there.
13 #
14 # Another point to note is that a vector may contain a list of other vectors. 
15 # I have simply added another variable to the vector for 'simplicity' (or laziness
16 # as it is more commonly called)
17 #
18 # PLEASE NOTE - I am a C programmer using this as a method of learning perl
19 # firstly and OO about ninthly (if you don't like the design and you can't 
20 # improve it with better OO by make it smaller and more efficient, then tough). 
21 #
22 # Copyright (c) 1998 - Dirk Koopman G1TLH
23 #
24 # $Id$
25 #
26 package DXChannel;
27
28 use Msg;
29 use DXM;
30 use DXUtil;
31 use DXDebug;
32 use Filter;
33
34 use strict;
35 use vars qw(%channels %valid);
36
37 %channels = ();
38
39 %valid = (
40                   call => '0,Callsign',
41                   conn => '9,Msg Conn ref',
42                   user => '9,DXUser ref',
43                   startt => '0,Start Time,atime',
44                   t => '9,Time,atime',
45                   pc50_t => '5,Last PC50 Time,atime',
46                   priv => '9,Privilege',
47                   state => '0,Current State',
48                   oldstate => '5,Last State',
49                   list => '9,Dep Chan List',
50                   name => '0,User Name',
51                   consort => '5,Connection Type',
52                   'sort' => '5,Type of Channel',
53                   wwv => '0,Want WWV,yesno',
54                   wx => '0,Want WX,yesno',
55                   talk => '0,Want Talk,yesno',
56                   ann => '0,Want Announce,yesno',
57                   here => '0,Here?,yesno',
58                   confmode => '0,In Conference?,yesno',
59                   dx => '0,DX Spots,yesno',
60                   redirect => '0,Redirect messages to',
61                   lang => '0,Language',
62                   func => '5,Function',
63                   loc => '9,Local Vars', # used by func to store local variables in
64                   beep => '0,Want Beeps,yesno',
65                   lastread => '5,Last Msg Read',
66                   outbound => '5,outbound?,yesno',
67                   remotecmd => '9,doing rcmd,yesno',
68                   pagelth => '0,Page Length',
69                   pagedata => '9,Page Data Store',
70                   group => '0,Access Group,parray',     # used to create a group of users/nodes for some purpose or other
71                   isolate => '5,Isolate network,yesno',
72                   delayed => '5,Delayed messages,parray',
73                   annfilter => '5,Announce Filter',
74                   wwvfilter => '5,WWV Filter',
75                   spotfilter => '5,Spot Filter',
76                   inannfilter => '5,Input Ann Filter',
77                   inwwvfilter => '5,Input WWV Filter',
78                   inspotfilter => '5,Input Spot Filter',
79                   passwd => '9,Passwd List,parray',
80                   pingint => '5,Ping Interval ',
81                   nopings => '5,Ping Obs Count',
82                   lastping => '5,Ping last sent,atime',
83                   pingtime => '5,Ping totaltime,parray',
84                   pingave => '0,Ping ave time',
85                   logininfo => '9,Login info req,yesno',
86                  );
87
88 # object destruction
89 sub DESTROY
90 {
91         my $self = shift;
92         undef $self->{user};
93         undef $self->{conn};
94         undef $self->{loc};
95         undef $self->{pagedata};
96         undef $self->{group};
97         undef $self->{delayed};
98         undef $self->{annfilter};
99         undef $self->{wwvfilter};
100         undef $self->{spotfilter};
101         undef $self->{inannfilter};
102         undef $self->{inwwvfilter};
103         undef $self->{inspotfilter};
104         undef $self->{passwd};
105 }
106
107 # create a new channel object [$obj = DXChannel->new($call, $msg_conn_obj, $user_obj)]
108 sub alloc
109 {
110         my ($pkg, $call, $conn, $user) = @_;
111         my $self = {};
112   
113         die "trying to create a duplicate channel for $call" if $channels{$call};
114         $self->{call} = $call;
115         $self->{priv} = 0;
116         $self->{conn} = $conn if defined $conn; # if this isn't defined then it must be a list
117         if (defined $user) {
118                 $self->{user} = $user;
119                 $self->{lang} = $user->lang;
120                 $user->new_group() if !$user->group;
121                 $self->{group} = $user->group;
122         }
123         $self->{startt} = $self->{t} = time;
124         $self->{state} = 0;
125         $self->{oldstate} = 0;
126         $self->{lang} = $main::lang if !$self->{lang};
127         $self->{func} = "";
128
129         # get the filters
130         $self->{spotfilter} = Filter::read_in('spots', $call, 0);
131         $self->{wwvfilter} = Filter::read_in('wwv', $call, 0);
132         $self->{annfilter} = Filter::read_in('ann', $call, 0);
133
134         bless $self, $pkg; 
135         return $channels{$call} = $self;
136 }
137
138 # obtain a channel object by callsign [$obj = DXChannel->get($call)]
139 sub get
140 {
141         my ($pkg, $call) = @_;
142         return $channels{$call};
143 }
144
145 # obtain all the channel objects
146 sub get_all
147 {
148         my ($pkg) = @_;
149         return values(%channels);
150 }
151
152 #
153 # gimme all the ak1a nodes
154 #
155 sub get_all_ak1a
156 {
157         my @list = DXChannel->get_all();
158         my $ref;
159         my @out;
160         foreach $ref (@list) {
161                 push @out, $ref if $ref->is_ak1a;
162         }
163         return @out;
164 }
165
166 # return a list of all users
167 sub get_all_users
168 {
169         my @list = DXChannel->get_all();
170         my $ref;
171         my @out;
172         foreach $ref (@list) {
173                 push @out, $ref if $ref->is_user;
174         }
175         return @out;
176 }
177
178 # return a list of all user callsigns
179 sub get_all_user_calls
180 {
181         my @list = DXChannel->get_all();
182         my $ref;
183         my @out;
184         foreach $ref (@list) {
185                 push @out, $ref->call if $ref->is_user;
186         }
187         return @out;
188 }
189
190 # obtain a channel object by searching for its connection reference
191 sub get_by_cnum
192 {
193         my ($pkg, $conn) = @_;
194         my $self;
195   
196         foreach $self (values(%channels)) {
197                 return $self if ($self->{conn} == $conn);
198         }
199         return undef;
200 }
201
202 # get rid of a channel object [$obj->del()]
203 sub del
204 {
205         my $self = shift;
206
207         $self->{group} = undef;         # belt and braces
208         delete $channels{$self->{call}};
209 }
210
211 # is it a bbs
212 sub is_bbs
213 {
214         my $self = shift;
215         return $self->{'sort'} eq 'B';
216 }
217
218 # is it an ak1a cluster ?
219 sub is_ak1a
220 {
221         my $self = shift;
222         return $self->{'sort'} eq 'A';
223 }
224
225 # is it a user?
226 sub is_user
227 {
228         my $self = shift;
229         return $self->{'sort'} eq 'U';
230 }
231
232 # is it a connect type
233 sub is_connect
234 {
235         my $self = shift;
236         return $self->{'sort'} eq 'C';
237 }
238
239 # for perl 5.004's benefit
240 sub sort
241 {
242         my $self = shift;
243         return @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
244 }
245
246 # handle out going messages, immediately without waiting for the select to drop
247 # this could, in theory, block
248 sub send_now
249 {
250         my $self = shift;
251         my $conn = $self->{conn};
252         return unless $conn;
253         my $sort = shift;
254         my $call = $self->{call};
255         
256         for (@_) {
257                 chomp;
258         my @lines = split /\n/;
259                 for (@lines) {
260                         $conn->send_now("$sort$call|$_");
261                         dbg('chan', "-> $sort $call $_");
262                 }
263         }
264         $self->{t} = time;
265 }
266
267 #
268 # the normal output routine
269 #
270 sub send                                                # this is always later and always data
271 {
272         my $self = shift;
273         my $conn = $self->{conn};
274         return unless $conn;
275         my $call = $self->{call};
276
277         for (@_) {
278                 chomp;
279         my @lines = split /\n/;
280                 for (@lines) {
281                         $conn->send_later("D$call|$_");
282                         dbg('chan', "-> D $call $_");
283                 }
284         }
285         $self->{t} = time;
286 }
287
288 # send a file (always later)
289 sub send_file
290 {
291         my ($self, $fn) = @_;
292         my $call = $self->{call};
293         my $conn = $self->{conn};
294         my @buf;
295   
296         open(F, $fn) or die "can't open $fn for sending file ($!)";
297         @buf = <F>;
298         close(F);
299         $self->send(@buf);
300 }
301
302 # this will implement language independence (in time)
303 sub msg
304 {
305         my $self = shift;
306         return DXM::msg($self->{lang}, @_);
307 }
308
309 # stick a broadcast on the delayed queue (but only up to 20 items)
310 sub delay
311 {
312         my $self = shift;
313         my $s = shift;
314         
315         $self->{delayed} = [] unless $self->{delayed};
316         push @{$self->{delayed}}, $s;
317         if (@{$self->{delayed}} >= 20) {
318                 shift @{$self->{delayed}};   # lose oldest one
319         }
320 }
321
322 # change the state of the channel - lots of scope for debugging here :-)
323 sub state
324 {
325         my $self = shift;
326         if (@_) {
327                 $self->{oldstate} = $self->{state};
328                 $self->{state} = shift;
329                 $self->{func} = '' unless defined $self->{func};
330                 dbg('state', "$self->{call} channel func $self->{func} state $self->{oldstate} -> $self->{state}\n");
331
332                 # if there is any queued up broadcasts then splurge them out here
333                 if ($self->{delayed} && ($self->{state} eq 'prompt' || $self->{state} eq 'convers')) {
334                         $self->send (@{$self->{delayed}});
335                         delete $self->{delayed};
336                 }
337         }
338         return $self->{state};
339 }
340
341 # disconnect this channel
342 sub disconnect
343 {
344         my $self = shift;
345         my $user = $self->{user};
346         my $conn = $self->{conn};
347         my $call = $self->{call};
348     my $nopc39 = shift || 0;
349         
350         $self->finish($nopc39);
351         $conn->send_now("Z$call|bye") if $conn; # this will cause 'client' to disconnect
352         $user->close() if defined $user;
353         $conn->disconnect() if $conn;
354         $self->del();
355 }
356
357 #
358 # just close all the socket connections down without any fiddling about, cleaning, being
359 # nice to other processes and otherwise telling them what is going on.
360 #
361 # This is for the benefit of forked processes to prepare for starting new programs, they
362 # don't want or need all this baggage.
363 #
364
365 sub closeall
366 {
367         my $ref;
368         foreach $ref (values %channels) {
369                 $ref->{conn}->disconnect() if $ref->{conn};
370         }
371 }
372
373 #
374 # Tell all the users that we have come in or out (if they want to know)
375 #
376 sub tell_login
377 {
378         my ($self, $m) = @_;
379         
380         # send info to all logged in thingies
381         my @dxchan = get_all_users();
382         my $dxchan;
383         foreach $dxchan (@dxchan) {
384                 next if $dxchan == $self;
385                 $dxchan->send($dxchan->msg($m, $self->{call})) if $dxchan->{logininfo};
386         }
387 }
388
389 # various access routines
390
391 #
392 # return a list of valid elements 
393
394
395 sub fields
396 {
397         return keys(%valid);
398 }
399
400 #
401 # return a prompt for a field
402 #
403
404 sub field_prompt
405
406         my ($self, $ele) = @_;
407         return $valid{$ele};
408 }
409
410 no strict;
411 sub AUTOLOAD
412 {
413         my $self = shift;
414         my $name = $AUTOLOAD;
415         return if $name =~ /::DESTROY$/;
416         $name =~ s/.*:://o;
417   
418         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
419         @_ ? $self->{$name} = shift : $self->{$name} ;
420 }
421
422 1;
423 __END__;