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