1. tidy up AUTOLOAD
[spider.git] / perl / DXUser.pm
1 #
2 # DX cluster user routines
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 # $Id$
7 #
8
9 package DXUser;
10
11 use DXLog;
12 use DB_File;
13 use Data::Dumper;
14 use Fcntl;
15 use IO::File;
16 use DXDebug;
17 use DXUtil;
18 use LRU;
19
20 use strict;
21
22 use vars qw($VERSION $BRANCH);
23 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
24 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/  || (0,0));
25 $main::build += $VERSION;
26 $main::branch += $BRANCH;
27
28 use vars qw(%u $dbm $filename %valid $lastoperinterval $lasttime $lru $lrusize);
29
30 %u = ();
31 $dbm = undef;
32 $filename = undef;
33 $lastoperinterval = 60*24*60*60;
34 $lasttime = 0;
35 $lrusize = 2000;
36
37 # hash of valid elements and a simple prompt
38 %valid = (
39                   call => '0,Callsign',
40                   alias => '0,Real Callsign',
41                   name => '0,Name',
42                   qth => '0,Home QTH',
43                   lat => '0,Latitude,slat',
44                   long => '0,Longitude,slong',
45                   qra => '0,Locator',
46                   email => '0,E-mail Address,parray',
47                   priv => '9,Privilege Level',
48                   lastin => '0,Last Time in,cldatetime',
49                   passwd => '9,Password,yesno',
50                   passphrase => '9,Pass Phrase,yesno',
51                   addr => '0,Full Address',
52                   'sort' => '0,Type of User', # A - ak1a, U - User, S - spider cluster, B - BBS
53                   xpert => '0,Expert Status,yesno',
54                   bbs => '0,Home BBS',
55                   node => '0,Last Node',
56                   homenode => '0,Home Node',
57                   lockout => '9,Locked out?,yesno',     # won't let them in at all
58                   dxok => '9,Accept DX Spots?,yesno', # accept his dx spots?
59                   annok => '9,Accept Announces?,yesno', # accept his announces?
60                   lang => '0,Language',
61                   hmsgno => '0,Highest Msgno',
62                   group => '0,Access Group,parray',     # used to create a group of users/nodes for some purpose or other
63                   isolate => '9,Isolate network,yesno',
64                   wantbeep => '0,Req Beep,yesno',
65                   wantann => '0,Req Announce,yesno',
66                   wantwwv => '0,Req WWV,yesno',
67                   wantwcy => '0,Req WCY,yesno',
68                   wantecho => '0,Req Echo,yesno',
69                   wanttalk => '0,Req Talk,yesno',
70                   wantwx => '0,Req WX,yesno',
71                   wantdx => '0,Req DX Spots,yesno',
72                   wantemail => '0,Req Msgs as Email,yesno',
73                   pagelth => '0,Current Pagelth',
74                   pingint => '9,Node Ping interval',
75                   nopings => '9,Ping Obs Count',
76                   wantlogininfo => '9,Login info req,yesno',
77           wantgrid => '0,DX Grid Info,yesno',
78                   wantann_talk => '0,Talklike Anns,yesno',
79                   wantpc90 => '1,Req PC90,yesno',
80                   wantnp => '1,Req New Protocol,yesno',
81                   wantpc16 => '9,Want Users from node,yesno',
82                   wantsendpc16 => '9,Send users to node,yesno',
83                   lastoper => '9,Last for/oper,cldatetime',
84                   nothere => '0,Not Here Text',
85                   registered => '9,Registered?,yesno',
86                   prompt => '0,Required Prompt',
87                   version => '1,Version',
88                   build => '1,Build',
89                  );
90
91 #no strict;
92 sub AUTOLOAD
93 {
94         no strict;
95         my $name = $AUTOLOAD;
96   
97         return if $name =~ /::DESTROY$/;
98         $name =~ s/^.*:://o;
99   
100         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
101         # this clever line of code creates a subroutine which takes over from autoload
102         # from OO Perl - Conway
103         *$AUTOLOAD = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
104        goto &$AUTOLOAD;
105 }
106
107 #use strict;
108
109 #
110 # initialise the system
111 #
112 sub init
113 {
114         my ($pkg, $fn, $mode) = @_;
115   
116         confess "need a filename in User" if !$fn;
117         $fn .= ".v2";
118         if ($mode) {
119                 $dbm = tie (%u, 'DB_File', $fn, O_CREAT|O_RDWR, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_asc?]";
120         } else {
121                 $dbm = tie (%u, 'DB_File', $fn, O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_asc?]";
122         }
123         
124         $filename = $fn;
125         $lru = LRU->newbase("DXUser", $lrusize);
126 }
127
128 sub del_file
129 {
130         my ($pkg, $fn) = @_;
131   
132         confess "need a filename in User" if !$fn;
133         $fn .= ".v2";
134         unlink $fn;
135 }
136
137 #
138 # periodic processing
139 #
140 sub process
141 {
142         if ($main::systime > $lasttime + 15) {
143                 $dbm->sync;
144                 $lasttime = $main::systime;
145         }
146 }
147
148 #
149 # close the system
150 #
151
152 sub finish
153 {
154         undef $dbm;
155         untie %u;
156 }
157
158 #
159 # new - create a new user
160 #
161
162 sub new
163 {
164         my $pkg = shift;
165         my $call = uc shift;
166         #  $call =~ s/-\d+$//o;
167   
168 #       confess "can't create existing call $call in User\n!" if $u{$call};
169
170         my $self = bless {}, $pkg;
171         $self->{call} = $call;
172         $self->{'sort'} = 'U';
173         $self->put;
174         return $self;
175 }
176
177 #
178 # get - get an existing user - this seems to return a different reference everytime it is
179 #       called - see below
180 #
181
182 sub get
183 {
184         my $pkg = shift;
185         my $call = uc shift;
186         my $data;
187         
188         # is it in the LRU cache?
189         my $ref = $lru->get($call);
190         return $ref if $ref;
191         
192         # search for it
193         unless ($dbm->get($call, $data)) {
194                 $ref = decode($data);
195                 $lru->put($call, $ref);
196                 return $ref;
197         }
198         return undef;
199 }
200
201 #
202 # get an existing either from the channel (if there is one) or from the database
203 #
204 # It is important to note that if you have done a get (for the channel say) and you
205 # want access or modify that you must use this call (and you must NOT use get's all
206 # over the place willy nilly!)
207 #
208
209 sub get_current
210 {
211         my $pkg = shift;
212         my $call = uc shift;
213   
214         my $dxchan = DXChannel->get($call);
215         return $dxchan->user if $dxchan;
216         my $rref = Route::get($call);
217         return $rref->user if $rref && exists $rref->{user};
218         return $pkg->get($call);
219 }
220
221 #
222 # get all callsigns in the database 
223 #
224
225 sub get_all_calls
226 {
227         return (sort keys %u);
228 }
229
230 #
231 # put - put a user
232 #
233
234 sub put
235 {
236         my $self = shift;
237         confess "Trying to put nothing!" unless $self && ref $self;
238         my $call = $self->{call};
239         # delete all instances of this 
240 #       for ($dbm->get_dup($call)) {
241 #               $dbm->del_dup($call, $_);
242 #       }
243         $dbm->del($call);
244         delete $self->{annok} if $self->{annok};
245         delete $self->{dxok} if $self->{dxok};
246         $lru->put($call, $self);
247         my $ref = $self->encode;
248         $dbm->put($call, $ref);
249 }
250
251
252 # create a string from a user reference
253 #
254 sub encode
255 {
256         my $self = shift;
257         my $dd = new Data::Dumper([$self]);
258         $dd->Indent(0);
259         $dd->Terse(1);
260     $dd->Quotekeys($] < 5.005 ? 1 : 0);
261         return $dd->Dumpxs;
262 }
263
264 #
265 # create a hash from a string
266 #
267 sub decode
268 {
269         my $s = shift;
270         my $ref;
271         eval '$ref = ' . $s;
272         if ($@) {
273                 dbg($@);
274                 Log('err', $@);
275                 $ref = undef;
276         }
277         return $ref;
278 }
279
280 #
281 # del - delete a user
282 #
283
284 sub del
285 {
286         my $self = shift;
287         my $call = $self->{call};
288         # delete all instances of this 
289 #       for ($dbm->get_dup($call)) {
290 #               $dbm->del_dup($call, $_);
291 #       }
292         $lru->remove($call);
293         $dbm->del($call);
294 }
295
296 #
297 # close - close down a user
298 #
299
300 sub close
301 {
302         my $self = shift;
303         $self->{lastin} = time;
304         $self->put();
305 }
306
307 #
308 # sync the database
309 #
310
311 sub sync
312 {
313         $dbm->sync;
314 }
315
316 #
317 # return a list of valid elements 
318
319
320 sub fields
321 {
322         return keys(%valid);
323 }
324
325
326 #
327 # export the database to an ascii file
328 #
329
330 sub export
331 {
332         my $fn = shift;
333         
334         # save old ones
335         rename "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
336         rename "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
337         rename "$fn.oo", "$fn.ooo" if -e "$fn.oo";
338         rename "$fn.o", "$fn.oo" if -e "$fn.o";
339         rename "$fn", "$fn.o" if -e "$fn";
340
341         my $count = 0;
342         my $err = 0;
343         my $fh = new IO::File ">$fn" or return "cannot open $fn ($!)";
344         if ($fh) {
345                 my $key = 0;
346                 my $val = undef;
347                 my $action;
348                 my $t = scalar localtime;
349                 print $fh q{#!/usr/bin/perl
350 #
351 # The exported userfile for a DXSpider System
352 #
353 # Input file: $filename
354 #       Time: $t
355 #
356                         
357 package main;
358                         
359 # search local then perl directories
360 BEGIN {
361         umask 002;
362                                 
363         # root of directory tree for this system
364         $root = "/spider"; 
365         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
366         
367         unshift @INC, "$root/perl";     # this IS the right way round!
368         unshift @INC, "$root/local";
369         
370         # try to detect a lockfile (this isn't atomic but 
371         # should do for now
372         $lockfn = "$root/perl/cluster.lck";       # lock file name
373         if (-e $lockfn) {
374                 open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!";
375                 my $pid = <CLLOCK>;
376                 chomp $pid;
377                 die "Lockfile ($lockfn) and process $pid exists - cluster must be stopped first\n" if kill 0, $pid;
378                 close CLLOCK;
379         }
380 }
381
382 package DXUser;
383
384 use DXVars;
385 use DXUser;
386
387 if (@ARGV) {
388         $main::userfn = shift @ARGV;
389         print "user filename now $userfn\n";
390 }
391
392 DXUser->del_file($main::userfn);
393 DXUser->init($main::userfn, 1);
394 %u = ();
395 my $count = 0;
396 my $err = 0;
397 while (<DATA>) {
398         chomp;
399         my @f = split /\t/;
400         my $ref = decode($f[1]);
401         if ($ref) {
402                 $ref->put();
403                 $count++;
404         } else {
405                 print "# Error: $f[0]\t$f[1]\n";
406                 $err++
407         }
408 }
409 DXUser->sync; DXUser->finish;
410 print "There are $count user records and $err errors\n";
411 };
412                 print $fh "__DATA__\n";
413
414         for ($action = R_FIRST; !$dbm->seq($key, $val, $action); $action = R_NEXT) {
415                         if (!is_callsign($key) || $key =~ /^0/) {
416                                 Log('DXCommand', "Export Error1: $key\t$val");
417                                 eval {$dbm->del($key)};
418                                 dbg(carp("Export Error1: $key\t$val\n$@")) if $@;
419                                 ++$err;
420                                 next;
421                         }
422                         my $ref = decode($val);
423                         if ($ref) {
424                                 print $fh "$key\t" . $ref->encode . "\n";
425                                 ++$count;
426                         } else {
427                                 Log('DXCommand', "Export Error2: $key\t$val");
428                                 eval {$dbm->del($key)};
429                                 dbg(carp("Export Error2: $key\t$val\n$@")) if $@;
430                                 ++$err;
431                         }
432                 } 
433         $fh->close;
434     } 
435         return "$count Users $err Errors ('sh/log Export' for details)";
436 }
437
438 #
439 # group handling
440 #
441
442 # add one or more groups
443 sub add_group
444 {
445         my $self = shift;
446         my $ref = $self->{group} || [ 'local' ];
447         $self->{group} = $ref if !$self->{group};
448         push @$ref, @_ if @_;
449 }
450
451 # remove one or more groups
452 sub del_group
453 {
454         my $self = shift;
455         my $ref = $self->{group} || [ 'local' ];
456         my @in = @_;
457         
458         $self->{group} = $ref if !$self->{group};
459         
460         @$ref = map { my $a = $_; return (grep { $_ eq $a } @in) ? () : $a } @$ref;
461 }
462
463 # does this thing contain all the groups listed?
464 sub union
465 {
466         my $self = shift;
467         my $ref = $self->{group};
468         my $n;
469         
470         return 0 if !$ref || @_ == 0;
471         return 1 if @$ref == 0 && @_ == 0;
472         for ($n = 0; $n < @_; ) {
473                 for (@$ref) {
474                         my $a = $_;
475                         $n++ if grep $_ eq $a, @_; 
476                 }
477         }
478         return $n >= @_;
479 }
480
481 # simplified group test just for one group
482 sub in_group
483 {
484         my $self = shift;
485         my $s = shift;
486         my $ref = $self->{group};
487         
488         return 0 if !$ref;
489         return grep $_ eq $s, $ref;
490 }
491
492 # set up a default group (only happens for them's that connect direct)
493 sub new_group
494 {
495         my $self = shift;
496         $self->{group} = [ 'local' ];
497 }
498
499 #
500 # return a prompt for a field
501 #
502
503 sub field_prompt
504
505         my ($self, $ele) = @_;
506         return $valid{$ele};
507 }
508
509 # some variable accessors
510 sub sort
511 {
512         my $self = shift;
513         @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
514 }
515
516 # some accessors
517 sub _want
518 {
519         my $n = shift;
520         my $self = shift;
521         my $val = shift;
522         my $s = "want$n";
523         $self->{$s} = $val if defined $val;
524         return exists $self->{$s} ? $self->{$s} : 1;
525 }
526
527 sub wantbeep
528 {
529         return _want('beep', @_);
530 }
531
532 sub wantann
533 {
534         return _want('ann', @_);
535 }
536
537 sub wantwwv
538 {
539         return _want('wwv', @_);
540 }
541
542 sub wantwcy
543 {
544         return _want('wcy', @_);
545 }
546
547 sub wantecho
548 {
549         return _want('echo', @_);
550 }
551
552 sub wantwx
553 {
554         return _want('wx', @_);
555 }
556
557 sub wantdx
558 {
559         return _want('dx', @_);
560 }
561
562 sub wanttalk
563 {
564         return _want('talk', @_);
565 }
566
567 sub wantgrid
568 {
569         return _want('grid', @_);
570 }
571
572 sub wantemail
573 {
574         return _want('email', @_);
575 }
576
577 sub wantann_talk
578 {
579         return _want('ann_talk', @_);
580 }
581
582 sub wantpc16
583 {
584         return _want('pc16', @_);
585 }
586
587 sub wantsendpc16
588 {
589         return _want('sendpc16', @_);
590 }
591
592 sub wantlogininfo
593 {
594         my $self = shift;
595         my $val = shift;
596         $self->{wantlogininfo} = $val if defined $val;
597         return $self->{wantlogininfo};
598 }
599
600 sub is_node
601 {
602         my $self = shift;
603         return $self->{sort} =~ /[ACRSX]/;
604 }
605
606 sub is_user
607 {
608         my $self = shift;
609         return $self->{sort} eq 'U';
610 }
611
612 sub is_bbs
613 {
614         my $self = shift;
615         return $self->{sort} eq 'B';
616 }
617
618 sub is_spider
619 {
620         my $self = shift;
621         return $self->{sort} eq 'S';
622 }
623
624 sub is_clx
625 {
626         my $self = shift;
627         return $self->{sort} eq 'C';
628 }
629
630 sub is_dxnet
631 {
632         my $self = shift;
633         return $self->{sort} eq 'X';
634 }
635
636 sub is_arcluster
637 {
638         my $self = shift;
639         return $self->{sort} eq 'R';
640 }
641
642 sub is_ak1a
643 {
644         my $self = shift;
645         return $self->{sort} eq 'A';
646 }
647
648 sub unset_passwd
649 {
650         my $self = shift;
651         delete $self->{passwd};
652 }
653
654 sub unset_passphrase
655 {
656         my $self = shift;
657         delete $self->{passphrase};
658 }
659 1;
660 __END__
661
662
663
664
665