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