fix and moan if mycall and myalias are lowercase
[spider.git] / perl / DXUser.pm
1 #
2 # DX cluster user routines
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 #
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 use File::Copy;
20 use Data::Structure::Util qw(unbless);
21 use Time::HiRes qw(gettimeofday tv_interval);
22 use IO::File;
23 use DXChannel;
24 use DXJSON;
25
26 use strict;
27
28 use vars qw(%u $dbm $filename %valid $lastoperinterval $lasttime $lru $lrusize $tooold $veryold $v3);
29
30 %u = ();
31 $dbm = undef;
32 $filename = undef;
33 $lastoperinterval = 60*24*60*60;
34 $lasttime = 0;
35 $lrusize = 5000;
36 $tooold = 86400 * 365 * 2;              # this marks an old user who hasn't given enough info to be useful
37 $veryold = $tooold * 6;         # Ancient default 12 years
38 $v3 = 0;
39 our $maxconnlist = 3;                   # remember this many connection time (duration) [start, end] pairs
40
41 my $json;
42
43 # hash of valid elements and a simple prompt
44 %valid = (
45                   'sort' => '0,Type of User', # A - ak1a, U - User, S - spider cluster, B - BBS
46                   addr => '0,Full Address',
47                   alias => '0,Real Callsign',
48                   annok => '9,Accept Announces?,yesno', # accept his announces?
49                   bbs => '0,Home BBS',
50                   believe => '1,Believable nodes,parray',
51                   buddies => '0,Buddies,parray',
52                   build => '1,Build',
53                   call => '0,Callsign',
54                   clientoutput => '0,User OUT Format',
55                   clientinput => '0,User IN Format',
56                   connlist => '1,Connections,parraydifft',
57                   dxok => '9,Accept DX Spots?,yesno', # accept his dx spots?
58                   email => '0,E-mail Address,parray',
59                   group => '0,Group,parray',    # used to create a group of users/nodes for some purpose or other
60                   hmsgno => '0,Highest Msgno',
61                   homenode => '0,Home Node',
62                   isolate => '9,Isolate network,yesno',
63                   K => '9,Seen on PC92 K,yesno',
64                   lang => '0,Language',
65                   lastin => '0,Last Time in,cldatetime',
66                   lastoper => '9,Last for/oper,cldatetime',
67                   lastping => '1,Last Ping at,ptimelist',
68                   lastseen => '0,Last Seen,cldatetime',
69                   lat => '0,Latitude,slat',
70                   lockout => '9,Locked out?,yesno',     # won't let them in at all
71                   long => '0,Longitude,slong',
72                   maxconnect => '1,Max Connections',
73                   name => '0,Name',
74                   node => '0,Last Node',
75                   nopings => '9,Ping Obs Count',
76                   nothere => '0,Not Here Text',
77                   pagelth => '0,Current Pagelth',
78                   passphrase => '9,Pass Phrase,yesno',
79                   passwd => '9,Password,yesno',
80                   pingint => '9,Node Ping interval',
81                   priv => '9,Privilege Level',
82                   prompt => '0,Required Prompt',
83                   qra => '0,Locator',
84                   qth => '0,Home QTH',
85                   rbnseeme => '0,RBN See Me,yesno',
86                   registered => '9,Registered?,yesno',
87                   startt => '0,Start Time,cldatetime',
88                   user_interval => '0,Prompt IdleTime',
89                   version => '1,Version',
90                   wantann => '0,Req Announce,yesno',
91                   wantann_talk => '0,Talklike Anns,yesno',
92                   wantbeacon => '0,Want RBN Beacon,yesno',
93                   wantbeep => '0,Req Beep,yesno',
94                   wantcw => '0,Want RBN CW,yesno',
95                   wantdx => '0,Req DX Spots,yesno',
96                   wantdxcq => '0,Show CQ Zone,yesno',
97                   wantdxitu => '0,Show ITU Zone,yesno',
98                   wantecho => '0,Req Echo,yesno',
99                   wantemail => '0,Req Msgs as Email,yesno',
100                   wantft => '0,Want RBN FT4/8,yesno',
101                   wantgtk => '0,Want GTK interface,yesno',
102                   wantlogininfo => '0,Login Info Req,yesno',
103                   wantpc16 => '9,Want Users from node,yesno',
104                   wantpc9x => '0,Want PC9X interface,yesno',
105                   wantpsk => '0,Want RBN PSK,yesno',
106                   wantrbn => '0,Want RBN spots,yesno',
107                   wantroutepc19 => '9,Route PC19,yesno',
108                   wantrtty => '0,Want RBN RTTY,yesno',
109                   wantsendpc16 => '9,Send PC16,yesno',
110                   wanttalk => '0,Req Talk,yesno',
111                   wantusstate => '0,Show US State,yesno',
112                   wantwcy => '0,Req WCY,yesno',
113                   wantwwv => '0,Req WWV,yesno',
114                   wantwx => '0,Req WX,yesno',
115                   width => '0,Preferred Width',
116                   xpert => '0,Expert Status,yesno',
117           wantgrid => '0,Show DX Grid,yesno',
118                  );
119
120 #no strict;
121 sub AUTOLOAD
122 {
123         no strict;
124         my $name = $AUTOLOAD;
125   
126         return if $name =~ /::DESTROY$/;
127         $name =~ s/^.*:://o;
128   
129         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
130         # this clever line of code creates a subroutine which takes over from autoload
131         # from OO Perl - Conway
132         *$AUTOLOAD = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
133        goto &$AUTOLOAD;
134 }
135
136 my $readonly;
137
138 #use strict;
139
140 #
141 # initialise the system
142 #
143 sub init
144 {
145         my $mode = shift;
146   
147         $json = DXJSON->new->canonical(1);
148         my $fn = "users";
149         $filename = localdata("$fn.v3j");
150         unless (-e $filename || $mode == 2 ) {
151                 if (-e localdata("$fn.v3") || -e localdata("$fn.v2")) {
152                         LogDbg('DXUser', "New User File version $filename does not exist, running conversion from users.v3 or v2, please wait");
153                         system('/spider/perl/convert-users-v3-to-v3j.pl');
154                         init(1);
155                         export();
156                         return;
157                 }
158         }
159         if (-e $filename || $mode) {
160                 $lru = LRU->newbase("DXUser", $lrusize);
161                 if ($mode) {
162                         $dbm = tie (%u, 'DB_File', $filename, O_CREAT|O_RDWR, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_json?]";
163                 } else {
164                         $dbm = tie (%u, 'DB_File', $filename, O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!) [rebuild it from user_json?]";
165                 }
166         }
167         $readonly = !$mode;
168         
169         die "Cannot open $filename ($!)\n" unless $dbm || $mode == 2;
170         return;
171 }
172
173 # delete files with extreme prejudice
174 sub del_file
175 {
176         # with extreme prejudice
177         unlink "$main::data/users.v3j";
178         unlink "$main::local_data/users.v3j";
179 }
180
181 #
182 # periodic processing
183 #
184 sub process
185 {
186         if ($main::systime > $lasttime + 15) {
187                 $dbm->sync if $dbm;
188                 $lasttime = $main::systime;
189         }
190 }
191
192 #
193 # close the system
194 #
195
196 sub finish
197 {
198         dbg('DXUser finished') unless $readonly;
199         $dbm->sync;
200         undef $dbm;
201         untie %u;
202 }
203
204 #
205 # new - create a new user
206 #
207
208 sub alloc
209 {
210         my $pkg = shift;
211         my $call = uc shift;
212         my $self = bless {call => $call, 'sort'=>'U'}, $pkg;
213         return $self;
214 }
215
216 sub new
217 {
218         my $pkg = shift;
219         my $call = shift;
220         #  $call =~ s/-\d+$//o;
221   
222 #       confess "can't create existing call $call in User\n!" if $u{$call};
223
224         my $self = $pkg->alloc($call);
225         $self->put;
226         return $self;
227 }
228
229 #
230 # get - get an existing user - this seems to return a different reference everytime it is
231 #       called - see below
232 #
233
234 sub get
235 {
236         my $call = uc shift;
237         my $data;
238         
239         # is it in the LRU cache?
240         my $ref = $lru->get($call);
241         if ($ref && ref $ref eq 'DXUser') {
242                 return $ref;
243         }
244         
245         # search for it
246         unless ($dbm->get($call, $data)) {
247                 eval { $ref = decode($data); };
248                 if ($ref) {
249                         if (!UNIVERSAL::isa($ref, 'DXUser')) {
250                                 dbg("DXUser::get: got strange answer from decode of $call". ref $ref. " ignoring");
251                                 return undef;
252                         }
253                         # we have a reference and it *is* a DXUser
254                 } else {
255                         if ($@) {
256                                 LogDbg('err', "DXUser::get decode error on $call '$@'");
257                         } else {
258                                 dbg("DXUser::get: no reference returned from decode of $call $!");
259                         }
260                         return undef;
261                 }
262                 $lru->put($call, $ref);
263                 return $ref;
264         }
265         return undef;
266 }
267
268 #
269 # get an existing either from the channel (if there is one) or from the database
270 #
271 # It is important to note that if you have done a get (for the channel say) and you
272 # want access or modify that you must use this call (and you must NOT use get's all
273 # over the place willy nilly!)
274 #
275
276 sub get_current
277 {
278         my $call = uc shift;
279   
280         my $dxchan = DXChannel::get($call);
281         if ($dxchan) {
282                 my $ref = $dxchan->user;
283                 return $ref if $ref && UNIVERSAL::isa($ref, 'DXUser');
284
285                 dbg("DXUser::get_current: got invalid user ref for $call from dxchan $dxchan->{call} ". ref $ref. " ignoring");
286         }
287         return get($call);
288 }
289
290 #
291 # get all callsigns in the database 
292 #
293
294 sub get_all_calls
295 {
296         return (sort keys %u);
297 }
298
299 #
300 # put - put a user
301 #
302
303 sub put
304 {
305         my $self = shift;
306         confess "Trying to put nothing!" unless $self && ref $self;
307         my $call = $self->{call};
308
309         $dbm->del($call);
310         delete $self->{annok};
311         delete $self->{dxok};
312         $self->{lastseen} = $main::systime;
313         $lru->put($call, $self);
314         my $ref = $self->encode;
315         $dbm->put($call, $ref);
316         DXChannel::refresh_user($call, $ref);
317         return $ref;
318 }
319
320
321 # thaw the user
322 sub decode
323 {
324         return $json->decode(shift, __PACKAGE__);
325 }
326
327 # freeze the user
328 sub encode
329 {
330         return $json->encode(shift);
331 }
332
333
334 #
335 # del - delete a user
336 #
337
338 sub del
339 {
340         my $self = shift;
341         my $call = $self->{call};
342         $lru->remove($call);
343         $dbm->del($call);
344 }
345
346 #
347 # close - close down a user
348 #
349
350 sub close
351 {
352         my $self = shift;
353         my $startt = shift;
354         my $ip = shift;
355         # add a record to the connect list
356         $self->{lastin} = $main::systime;
357         my $ref = [$self->{startt} || $startt, $main::systime];
358         push @$ref, $ip if $ip;
359         push @{$self->{connlist}}, $ref;
360         shift @{$self->{connlist}} if @{$self->{connlist}} > $maxconnlist;
361         $self->put();
362 }
363
364 #
365 # sync the database
366 #
367
368 sub sync
369 {
370         $dbm->sync;
371 }
372
373 #
374 # return a list of valid elements 
375
376
377 sub fields
378 {
379         return keys(%valid);
380 }
381
382
383 #
384 # group handling
385 #
386
387 # add one or more groups
388 sub add_group
389 {
390         my $self = shift;
391         my $ref = $self->{group} || [ 'local' ];
392         $self->{group} = $ref if !$self->{group};
393         push @$ref, @_ if @_;
394 }
395
396 # remove one or more groups
397 sub del_group
398 {
399         my $self = shift;
400         my $ref = $self->{group} || [ 'local' ];
401         my @in = @_;
402         
403         $self->{group} = $ref if !$self->{group};
404         
405         @$ref = map { my $a = $_; return (grep { $_ eq $a } @in) ? () : $a } @$ref;
406 }
407
408 # does this thing contain all the groups listed?
409 sub union
410 {
411         my $self = shift;
412         my $ref = $self->{group};
413         my $n;
414         
415         return 0 if !$ref || @_ == 0;
416         return 1 if @$ref == 0 && @_ == 0;
417         for ($n = 0; $n < @_; ) {
418                 for (@$ref) {
419                         my $a = $_;
420                         $n++ if grep $_ eq $a, @_; 
421                 }
422         }
423         return $n >= @_;
424 }
425
426 # simplified group test just for one group
427 sub in_group
428 {
429         my $self = shift;
430         my $s = shift;
431         my $ref = $self->{group};
432         
433         return 0 if !$ref;
434         return grep $_ eq $s, $ref;
435 }
436
437 # set up a default group (only happens for them's that connect direct)
438 sub new_group
439 {
440         my $self = shift;
441         $self->{group} = [ 'local' ];
442 }
443
444 # set up empty buddies (only happens for them's that connect direct)
445 sub new_buddies
446 {
447         my $self = shift;
448         $self->{buddies} = [  ];
449 }
450
451 #
452 # return a prompt for a field
453 #
454
455 sub field_prompt
456
457         my ($self, $ele) = @_;
458         return $valid{$ele};
459 }
460
461 # some variable accessors
462 sub sort
463 {
464         my $self = shift;
465         @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
466 }
467
468 # some accessors
469
470 # want is default = 1
471 sub _want
472 {
473         my $n = shift;
474         my $self = shift;
475         my $val = shift;
476         my $s = "want$n";
477         $self->{$s} = $val if defined $val;
478         return exists $self->{$s} ? $self->{$s} : 1;
479 }
480
481 # wantnot is default = 0
482 sub _wantnot
483 {
484         my $n = shift;
485         my $self = shift;
486         my $val = shift;
487         my $s = "want$n";
488         $self->{$s} = $val if defined $val;
489         return exists $self->{$s} ? $self->{$s} : 0;
490 }
491
492 sub wantbeep
493 {
494         return _want('beep', @_);
495 }
496
497 sub wantann
498 {
499         return _want('ann', @_);
500 }
501
502 sub wantwwv
503 {
504         return _want('wwv', @_);
505 }
506
507 sub wantwcy
508 {
509         return _want('wcy', @_);
510 }
511
512 sub wantecho
513 {
514         return _want('echo', @_);
515 }
516
517 sub wantwx
518 {
519         return _want('wx', @_);
520 }
521
522 sub wantdx
523 {
524         return _want('dx', @_);
525 }
526
527 sub wanttalk
528 {
529         return _want('talk', @_);
530 }
531
532 sub wantgrid
533 {
534         return _wantnot('grid', @_);
535 }
536
537 sub wantemail
538 {
539         return _want('email', @_);
540 }
541
542 sub wantann_talk
543 {
544         return _want('ann_talk', @_);
545 }
546
547 sub wantpc16
548 {
549         return _want('pc16', @_);
550 }
551
552 sub wantsendpc16
553 {
554         return _want('sendpc16', @_);
555 }
556
557 sub wantroutepc16
558 {
559         return _want('routepc16', @_);
560 }
561
562 sub wantusstate
563 {
564         return _want('usstate', @_);
565 }
566
567 sub wantdxcq
568 {
569         return _wantnot('dxcq', @_);
570 }
571
572 sub wantdxitu
573 {
574         return _wantnot('dxitu', @_);
575 }
576
577 sub wantgtk
578 {
579         return _want('gtk', @_);
580 }
581
582 sub wantpc9x
583 {
584         return _want('pc9x', @_);
585 }
586
587 sub wantlogininfo
588 {
589         my $self = shift;
590         my $val = shift;
591         $self->{wantlogininfo} = $val if defined $val;
592         return $self->{wantlogininfo};
593 }
594
595 sub is_node
596 {
597         my $self = shift;
598         return $self->{sort} =~ /^[ACRSX]$/;
599 }
600
601 sub is_local_node
602 {
603         my $self = shift;
604         return grep $_ eq 'local_node', @{$self->{group}};
605 }
606
607 sub is_user
608 {
609         my $self = shift;
610         return $self->{sort} =~ /^[UW]$/;
611 }
612
613 sub is_web
614 {
615         my $self = shift;
616         return $self->{sort} eq 'W';
617 }
618
619 sub is_bbs
620 {
621         my $self = shift;
622         return $self->{sort} eq 'B';
623 }
624
625 sub is_spider
626 {
627         my $self = shift;
628         return $self->{sort} eq 'S';
629 }
630
631 sub is_clx
632 {
633         my $self = shift;
634         return $self->{sort} eq 'C';
635 }
636
637 sub is_dxnet
638 {
639         my $self = shift;
640         return $self->{sort} eq 'X';
641 }
642
643 sub is_arcluster
644 {
645         my $self = shift;
646         return $self->{sort} eq 'R';
647 }
648
649 sub is_ak1a
650 {
651         my $self = shift;
652         return $self->{sort} eq 'A';
653 }
654
655 sub is_rbn
656 {
657         my $self = shift;
658         return $self->{sort} eq 'N'
659 }
660
661 sub unset_passwd
662 {
663         my $self = shift;
664         delete $self->{passwd};
665 }
666
667 sub unset_passphrase
668 {
669         my $self = shift;
670         delete $self->{passphrase};
671 }
672
673 sub set_believe
674 {
675         my $self = shift;
676         my $call = uc shift;
677         $self->{believe} ||= [];
678         push @{$self->{believe}}, $call unless grep $_ eq $call, @{$self->{believe}};
679 }
680
681 sub unset_believe
682 {
683         my $self = shift;
684         my $call = uc shift;
685         if (exists $self->{believe}) {
686                 $self->{believe} = [grep {$_ ne $call} @{$self->{believe}}];
687                 delete $self->{believe} unless @{$self->{believe}};
688         }
689 }
690
691 sub believe
692 {
693         my $self = shift;
694         return exists $self->{believe} ? @{$self->{believe}} : ();
695 }
696
697 sub lastping
698 {
699         my $self = shift;
700         my $call = shift;
701         $self->{lastping} ||= {};
702         $self->{lastping} = {} unless ref $self->{lastping};
703         my $b = $self->{lastping};
704         $b->{$call} = shift if @_;
705         return $b->{$call};     
706 }
707
708
709 #
710 # export the database to an ascii file
711 #
712
713 sub export
714 {
715         my $name = shift || 'user_json';
716
717         my $fn = $name ne 'user_json' ? $name : "$main::local_data/$name";                       # force use of local
718         
719         # save old ones
720         copy $fn, "$fn.keep" unless -e "$fn.keep";
721         copy "$fn.ooooo", "$fn.backstop" unless -e "$fn,backstop";
722
723         move "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
724         move "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
725         move "$fn.oo", "$fn.ooo" if -e "$fn.oo";
726         move "$fn.o", "$fn.oo" if -e "$fn.o";
727         move "$fn", "$fn.o" if -e "$fn";
728
729         
730         my $ta = [gettimeofday];
731         my $count = 0;
732         my $err = 0;
733         my $del = 0;
734         my $spurious = 0;
735         my $unlocked = 0;
736         my $old =  0;
737         my $ancient =  0;
738         my $nodes = 0;
739         my $renamed = 0;
740
741         my %del;
742         
743         my $fh = new IO::File ">$fn" or return "cannot open $fn ($!)";
744         if ($fh) {
745                 my $key = 0;
746                 my $val = undef;
747                 my $action;
748                 my $t = scalar localtime;
749                 print $fh export_preamble();
750                 
751
752         for ($action = R_FIRST; !$dbm->seq($key, $val, $action); $action = R_NEXT) {
753                         if (!is_callsign($key) || $key =~ /^0/) {
754                                 my $eval = $val;
755                                 my $ekey = $key;
756                                 $eval =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg; 
757                                 $ekey =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg;
758                                 LogDbg('DXCommand', "Export Error1: invalid call '$key' => '$val'");
759
760                                 $del{$key} = $val;
761                                 ++$err;
762                                 next;
763                         }
764                         my $ref;
765                         eval {$ref = decode($val); };
766                         if ($ref) {
767                                 my $t = $ref->{lastseen} if exists $ref->{lastseen};
768                                 $t ||= $ref->{lastin} if exists $ref->{lastin};
769                                 $t ||= $ref->{lastoper} if exists $ref->{lastoper};
770                                 $t //= 0;
771                                 
772                                 if ($ref->is_user) {
773                                         if (!$ref->{priv} && $main::systime > $t + $tooold) {
774                                                 unless (($ref->{lat} && $ref->{long}) || $ref->{qth} || $ref->{name} || $ref->{qra}) {
775                                                         LogDbg('DXCommand', sprintf("$ref->{call} deleted, empty and too Old at %s", difft($t, ' ')));
776                                                         ++$del;
777                                                         ++$old;
778                                                         $del{$key} = $val;
779                                                         next;
780                                                 }
781                                         }
782                                         if ($main::systime > $t + $veryold) {
783                                                 LogDbg('DXCommand', sprintf("$ref->{call} deleted, POSITIVELY ANCIENT at %s", difft($t, ' ')));
784                                                 ++$del;
785                                                 ++$ancient;
786                                                 $del{$key} = $val;
787                                                 next;
788                                         }
789                                         if (exists $ref->{lockout} && $ref->{lockout} == 1 && exists $ref->{priv} && $ref->{priv} == 1) {
790                                                 LogDbg('DXCommand', "$ref->{call} depriv'd and unlocked");
791                                                 $ref->{lockout} = $ref->{priv} = 0;
792                                                 $ref->put;
793                                                 ++$unlocked;
794                                         }
795                                         if ($ref->is_node && $main::systime > $t + $veryold) {
796                                                 LogDbg('DXCommand', sprintf("NODE $ref->{call} deleted (%s) old", difft($t, ' ')));
797                                                 ++$del;
798                                                 ++$nodes;
799                                                 $del{$key} = $val;
800                                                 next;
801                                         }
802                                         
803                                         my $normcall = normalise_call($key);
804                                         if ($normcall ne $key) {
805                                                 # if the normalised call does not exist, create it from the duff call.
806                                                 my $nref = DXUser::get_current($normcall);
807                                                 unless ($nref) {
808                                                         $ref->{call} = $normcall;
809                                                         $ref->put;
810                                                         LogDbg('DXCommand', "DXProt: spurious call $key normalises to $normcall renaming $key -> $normcall");
811                                                         ++$renamed;
812                                                 } 
813                                                 LogDbg('DXCommand', "DXProt: spurious call $key (should be $normcall), removing");
814                                                 $del{$key} = $val;
815                                                 ++$spurious;
816                                                 ++$del;
817                                                 next;
818                                         }
819                                 }
820                         } else {
821                                 LogDbg('DXCommand', "Export Error3: '$key'\t" . carp($val) ."\n$@");
822                                 $del{$key} = $val;
823                                 ++$err;
824                                 next;
825                         }
826                         
827                         # only store users that are reasonably active or have useful information
828                         print $fh "$key\t" . encode($ref) . "\n";
829                         ++$count;
830                 }
831         } 
832         $fh->close;
833         
834         while (my ($k, $v) = each %del) {
835                 eval {$dbm->del($k)};
836                 LogDbg('DXCommand', "Error deleting key: $k value: $v error: $@") if $@;
837         }
838
839         my $diff = _diffms($ta);
840         my $s = qq{Exported users to $fn - $count Users,  $del Deleted ($old empty \& too old, $ancient ancient, $nodes nodes, $spurious spurious), $renamed renamed, $unlocked Unlocked, $err Errors in $diff mS ('sh/log Export' for details)};
841         LogDbg('command', $s);
842         return ($s);
843 }
844
845 sub export_preamble
846 {
847         return q{#!/usr/bin/perl
848 #
849 # The exported userfile for a DXSpider System
850 #
851 # Input file: $filename
852 #       Time: $t
853 #
854                         
855 package main;
856                         
857 # search local then perl directories
858 BEGIN {
859         umask 002;
860                                 
861         # root of directory tree for this system
862         $root = "/spider"; 
863         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
864         
865         unshift @INC, "$root/perl";     # this IS the right way round!
866         unshift @INC, "$root/local";
867         
868         # try to detect a lockfile (this isn't atomic but 
869         # should do for now
870         $lockfn = "$root/local_data/cluster.lck";       # lock file name
871         if (-e $lockfn) {
872                 open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!";
873                 my $pid = <CLLOCK>;
874                 chomp $pid;
875                 die "Lockfile ($lockfn) and process $pid exists - cluster must be stopped first\n" if kill 0, $pid;
876                 close CLLOCK;
877         }
878 }
879
880 use SysVar;
881 use DXUtil;
882 use DXUser;
883 use DXChannel;
884 use JSON;
885 use Time::HiRes qw(gettimeofday tv_interval);
886 package DXUser;
887
888 our $json = JSON->new->canonical(1);
889
890 my $ta = [gettimeofday];
891 our $filename = "$main::local_data/users.v3j";
892 my $exists = -e $filename ? "OVERWRITING" : "CREATING"; 
893 print "perl user_json $exists $filename\n";
894
895 del_file();
896 init(2);
897 %u = ();
898 my $count = 0;
899 my $err = 0;
900
901 while (<DATA>) {
902         chomp;
903         my @f = split /\t/;
904         my $ref = decode($f[1]);
905         if ($ref) {
906                 $ref->put();
907                 $count++;
908         } else {
909                 print "# Error: $f[0]\t$f[1]\n";
910                 $err++
911         }
912 }
913 DXUser::sync(); DXUser::finish();
914 my $diff = _diffms($ta);
915 print "There are $count user records and $err errors in $diff mS\n";
916
917 exit $err ? -1 : 0;
918
919 __DATA__
920 };
921
922 }
923
924 sub recover
925 {
926         my $name = shift || 'recover_json';
927
928         my $fn = $name ne 'recover_json' ? $name : "$main::local_data/$name";                       # force use of local
929         
930         # save old ones
931         move "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
932         move "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
933         move "$fn.oo", "$fn.ooo" if -e "$fn.oo";
934         move "$fn.o", "$fn.oo" if -e "$fn.o";
935         move "$fn", "$fn.o" if -e "$fn";
936
937         my $ta = [gettimeofday];
938         my $count = 0;
939         my $errs = 0;
940         my $total = 0;
941                 
942         my $strings = "strings $filename";
943         my $ifh = new IO::File "$strings |" or return "cannot open input $filename ($!)";
944         my $fh = new IO::File ">$fn" or return "cannot open output $fn ($!)";
945         if ($ifh && $fh) {
946                 my $key = 0;
947                 my $val = undef;
948                 my $action;
949                 my $t = scalar localtime;
950                 print $fh export_preamble();
951
952                 my $call;
953                 my $l;
954
955                 my $last = '';
956                 while (defined ($l = $ifh->getline)) {
957                         next unless  $l =~ /^{"call":"[-\d\w\/]+"/;
958                         dbg("recover: $l");
959                         $l =~ s/[^}]+$//;
960                         my $data = $l;
961                         if ($data) {
962                                 my $v;
963                                 
964                                 eval{ $v = decode($data); };
965                                 if ($@) {
966                                         ++$errs;
967                                         ++$total;
968                                 } else {
969                                         next if $data eq $last;
970                                         print $fh  "$v->{call}\t$l\n";
971                                         ++$count;
972                                         ++$total;
973                                         $last = $l;
974                                 }
975                         }
976                 }
977         }
978         $fh->close;
979         $ifh->close;
980
981         my $diff = _diffms($ta);
982         my $s = qq{Recovered users to $fn - $count Users, $errs errors $total possible records read in $diff mS ('sh/log recover' for details)};
983         LogDbg('command', $s);
984         return ($s);
985 }
986
987 sub END
988 {
989         if ($dbm) {
990                 print "DXUser Ended\n" unless $readonly;
991                 finish();
992         }
993 }
994
995 1;
996 __END__
997
998
999
1000
1001