]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/offline/offline.pl
now installing offline cgi and config
[working/Evergreen.git] / Open-ILS / src / offline / offline.pl
1 #!/usr/bin/perl
2 use strict; use warnings;
3 use CGI;
4 use OpenSRF::System;
5 use OpenSRF::Utils::Logger qw/$logger/;
6 use OpenILS::Application::AppUtils;
7 use OpenILS::Event;
8 use OpenSRF::EX qw/:try/;
9 use JSON;
10 use Data::Dumper;
11 use OpenILS::Utils::Fieldmapper;
12 use Digest::MD5 qw/md5_hex/;
13 use OpenSRF::Utils qw/:daemon/;
14 use OpenILS::Utils::OfflineStore;
15
16 use DBI;
17 $DBI::trace = 1;
18
19 #use Class::DBI;
20 #Class::DBI->autoupdate(1);
21 #local $OpenILS::Utils::OfflineStore::NO_TRIGGERS = 1;
22
23 my $U = "OpenILS::Application::AppUtils";
24 my $DB = "OpenILS::Utils::OfflineStore";
25 my $SES = "${DB}::Session";
26 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
27 #$DB->autoupdate(1);
28
29 our %config;
30
31 # --------------------------------------------------------------------
32 # Load the config
33 # --------------------------------------------------------------------
34 do '##CONFIG##/offline-config.pl';
35
36
37 my $cgi                 = new CGI;
38 my $basedir             = $config{base_dir};
39 my $wsname              = $cgi->param('ws');
40 my $org                 = $cgi->param('org');
41 my $authtoken   = $cgi->param('ses') || "";
42 my $seskey              = $cgi->param('seskey');
43 my $action              = $cgi->param('action'); # - create, load, execute, status
44 my $requestor; 
45 my $wsobj;
46 my $orgobj;
47 my $evt;
48
49
50 &ol_init;
51 &ol_runtime_init;
52 &ol_do_action;
53
54
55 # --------------------------------------------------------------------
56 # Set it all up
57 # This function should behave as a child_init might behave in case 
58 # this is moved to mod_perl
59 # --------------------------------------------------------------------
60 sub ol_init {
61         _ol_debug_params();
62         $DB->DBFile($config{db});
63         OpenSRF::System->bootstrap_client(config_file => $config{bootstrap} ); 
64 }
65
66
67 sub _ol_debug_params {
68         my $s = "";
69         my @params = $cgi->param;
70         @params = sort { $a cmp $b } @params;
71         $s .= "$_=" . $cgi->param($_) . "\n" for @params;
72         $s =~ s/\n$//o;
73         warn '-'x60 ."\n$s\n";
74 }
75
76
77 # --------------------------------------------------------------------
78 # Finds the requestor and other info specific to this request
79 # --------------------------------------------------------------------
80 sub ol_runtime_init {
81
82         # fetch the requestor object
83         ($requestor, $evt) = $U->checkses($authtoken);
84         ol_handle_result($evt) if $evt;
85
86         # try the param, the workstation, and finally the user's ws org
87         if(!$org) { 
88                 $wsobj = ol_fetch_workstation($wsname);
89                 $org = $wsobj->owning_lib if $wsobj;
90                 $org = $requestor->ws_ou unless $org;
91                 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
92         }
93 }
94
95
96 # --------------------------------------------------------------------
97 # Runs the requested action
98 # --------------------------------------------------------------------
99 sub ol_do_action {
100
101         my $payload;
102
103         if( $action eq 'create' ) {
104                 
105                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
106                 ol_handle_result($evt) if $evt;
107                 $payload = ol_create_session();
108
109         } elsif( $action eq 'load' ) {
110
111                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
112                 ol_handle_result($evt) if $evt;
113                 $payload = ol_load();
114
115         } elsif( $action eq 'execute' ) {
116
117                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
118                 ol_handle_result($evt) if $evt;
119                 $payload = ol_execute();
120
121         } elsif( $action eq 'status' ) {
122
123                 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
124                 ol_handle_result($evt) if $evt;
125                 $payload = ol_status();
126         }
127
128         ol_handle_event('SUCCESS', payload => $payload );
129 }
130
131
132 # --------------------------------------------------------------------
133 # Creates a new session
134 # --------------------------------------------------------------------
135 sub ol_create_session {
136
137         my $desc = $cgi->param('desc') || "";
138         $seskey = time . "_${$}_" . int(rand() * 1000);
139
140         $logger->debug("offline: user ".$requestor->id.
141                 " creating new session with key $seskey and description $desc");
142
143         $SES->create(
144                 {       
145                         key                             => $seskey,
146                         org                             => $org,
147                         description             => $desc,
148                         creator                 => $requestor->id,
149                         create_time             => CORE::time(), 
150                         num_complete    => 0,
151                 } 
152         );
153
154         return $seskey;
155 }
156
157
158 # --------------------------------------------------------------------
159 # Holds the meta-info for a script file
160 # --------------------------------------------------------------------
161 sub ol_create_script {
162         my $count = shift;
163
164         my $session = ol_find_session($seskey);
165         my $delta = $cgi->param('delta') || 0;
166
167         my $script = $session->add_to_scripts( 
168                 {
169                         requestor       => $requestor->id,
170                         create_time     => CORE::time,
171                         workstation     => $wsname,
172                         logfile         => "$basedir/pending/$org/$seskey/$wsname.log",
173                         time_delta      => $delta,
174                         count                   => $count,
175                 }
176         );
177 }
178
179 # --------------------------------------------------------------------
180 # Finds the current session in the db
181 # --------------------------------------------------------------------
182 sub ol_find_session {
183         my $ses = $SES->retrieve($seskey);
184         ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
185         return $ses;
186 }
187
188 # --------------------------------------------------------------------
189 # Finds a script object in the DB based on workstation and seskey
190 # --------------------------------------------------------------------
191 sub ol_find_script {
192         my $ws = shift || $wsname;
193         my $sk = shift || $seskey;
194         my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
195         return $script;
196 }
197
198 # --------------------------------------------------------------------
199 # Creates a new script in the database and loads the new script file
200 # --------------------------------------------------------------------
201 sub ol_load {
202
203         my $session = ol_find_session;
204         my $handle      = $cgi->upload('file');
205         my $outdir      = "$basedir/pending/$org/$seskey";
206         my $outfile = "$outdir/$wsname.log";
207
208         ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
209         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
210         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
211
212         qx/mkdir -p $outdir/;
213         my $x = 0;
214         open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
215         while( <$handle> ) { print FILE; $x++;}
216         close(FILE);
217
218         ol_create_script($x);
219
220         return undef;
221 }
222
223
224 # --------------------------------------------------------------------
225 sub ol_handle_result {
226         my $obj = shift;
227         my $json = JSON->perl2JSON($obj);
228
229         if( $cgi->param('html')) {
230                 my $html = "<html><body onload='xulG.handle_event($json)'></body></html>";
231                 print "content-type: text/html\n\n";
232                 print "$html\n";
233
234         } else {
235
236                 print "content-type: text/plain\n\n";
237                 print "$json\n";
238         }
239
240         exit(0);
241 }
242
243 # --------------------------------------------------------------------
244 sub ol_handle_event {
245         my( $evt, @args ) = @_;
246         ol_handle_result(OpenILS::Event->new($evt, @args));
247 }
248
249
250 # --------------------------------------------------------------------
251 sub ol_flesh_session {
252         my $session = shift;
253         my %data;
254
255         map { $data{$_} = $session->$_ } $session->columns;
256         $data{scripts} = [];
257
258         for my $script ($session->scripts) {
259                 my %sdata;
260                 map { $sdata{$_} = $script->$_ } $script->columns;
261
262                 # the client doesn't need this info
263                 delete $sdata{session};
264                 delete $sdata{id};
265                 delete $sdata{logfile};
266
267                 push( @{$data{scripts}}, \%sdata );
268         }
269
270         return \%data;
271 }
272
273
274 # --------------------------------------------------------------------
275 # Returns various information on the sessions and scripts
276 # --------------------------------------------------------------------
277 sub ol_status {
278
279         my $type = $cgi->param('status_type') || "scripts";
280
281         # --------------------------------------------------------------------
282         # Returns info on every script upload attached to the current session
283         # --------------------------------------------------------------------
284         if( $type eq 'scripts' ) {
285                 my $session = ol_find_session();
286                 ol_handle_result(ol_flesh_session($session));
287
288
289         # --------------------------------------------------------------------
290         # Returns all scripts and sessions for the given org
291         # --------------------------------------------------------------------
292         } elsif( $type eq 'sessions' ) {
293                 my @sessions = $SES->search( org => $org );
294
295                 # can I do this in the DB without raw SQL?
296                 @sessions = sort { $a->create_time <=> $b->create_time } @sessions; 
297                 my @data;
298                 push( @data, ol_flesh_session($_) ) for @sessions;
299                 ol_handle_result(\@data);
300
301
302         # --------------------------------------------------------------------
303         # Returns total commands and completed commands counts
304         # --------------------------------------------------------------------
305         } elsif( $type eq 'summary' ) {
306                 my $session = ol_find_session();
307
308                 $logger->debug("offline: retrieving summary info ".
309                         "for session ".$session->key." with completed=".$session->num_complete);
310
311                 my $count = 0;
312                 $count += $_->count for ($session->scripts);
313                 ol_handle_result(
314                         { total => $count, num_complete => $session->num_complete });
315
316
317
318         # --------------------------------------------------------------------
319         # Returns the list of non-SUCCESS events that have occurred so far for 
320         # this set of commands
321         # --------------------------------------------------------------------
322         } elsif( $type eq 'exceptions' ) {
323
324                 my $session = ol_find_session();
325                 my $resfile = "$basedir/pending/$org/$seskey/results";
326                 if( $session->end_time ) {
327                         $resfile = "$basedir/archive/$org/$seskey/results";
328                 }
329                 my $data = ol_file_to_perl($resfile);
330                 $data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
331                 ol_handle_result($data);
332         }
333 }
334
335
336 sub ol_fetch_workstation {
337         my $name = shift;
338         $logger->debug("offline: Fetching workstation $name");
339         my $ws = $U->storagereq(
340                 'open-ils.storage.direct.actor.workstation.search.name', $name);
341         ol_handle_result(OpenILS::Event->new('WORKSTATION_NOT_FOUND')) unless $ws;
342         return $ws;
343 }
344
345
346
347
348 # --------------------------------------------------------------------
349 # Sorts the script commands then forks a child to executes them.
350 # --------------------------------------------------------------------
351 sub ol_execute {
352
353         my $session = ol_find_session();
354         ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
355         ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
356
357         my $commands = ol_collect_commands();
358
359         # --------------------------------------------------------------------
360         # Note that we must disconnect from opensrf before forking or the 
361         # connection will be borked...
362         # --------------------------------------------------------------------
363         OpenSRF::Transport::PeerHandle->retrieve->disconnect;
364         $DB->disconnect;
365
366
367         if( safe_fork() ) {
368
369                 # --------------------------------------------------------------------
370                 # Tell the client all is well
371                 # --------------------------------------------------------------------
372                 ol_handle_event('SUCCESS'); # - this exits
373
374         } else {
375
376
377                 # --------------------------------------------------------------------
378                 # close stdout/stderr or apache will wait on the child to finish
379                 # --------------------------------------------------------------------
380                 close(STDOUT);
381                 close(STDERR);
382
383                 $logger->debug("offline: child $$ processing data...");
384
385                 # --------------------------------------------------------------------
386                 # The child re-connects to the opensrf network and processes
387                 # the script requests 
388                 # --------------------------------------------------------------------
389                 OpenSRF::System->bootstrap_client(config_file => $config{bootstrap});
390         
391                 try {
392
393                         #use Class::DBI
394                         #Class::DBI->autoupdate(1);
395
396                         $DB->autoupdate(1);
397
398                         my $sesion = ol_find_session();
399                         $session->in_process(1);
400                         ol_process_commands($session, $commands);
401                         ol_archive_files($session);
402
403                 } catch Error with {
404                         my $e = shift;
405                         $logger->error("offline: child process error $e");
406                 };
407         }
408 }
409
410 sub ol_file_to_perl {
411         my $fname = shift;
412         open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
413         my @d = <F>;
414         my @p;
415         push(@p, JSON->JSON2perl($_)) for @d;
416         close(F);
417         return \@p;
418 }
419
420 # collects the commands and sorts them on timestamp+delta
421 sub ol_collect_commands {
422         my $ses = ol_find_session();
423         my @commands;
424
425         # cycle through every script loaded to this session
426         for my $script ($ses->scripts) {
427                 my $coms = ol_file_to_perl($script->logfile);
428
429                 # cycle through all of the commands for this script
430                 for my $com (@$coms) {
431                         $$com{_workstation} = $script->workstation;
432                         $$com{_realtime} = $script->time_delta + $com->{timestamp};
433                         push( @commands, $com );
434                 }
435         }
436
437         # make sure thera are no blank commands
438         @commands = grep { ($_ and $_->{type}) } @commands;
439
440         # sort on realtime
441         @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
442
443         # push user registrations to the front
444         my @regs                = grep { $_->{type} eq 'register' } @commands;
445         my @others      = grep { $_->{type} ne 'register' } @commands;
446
447         return [ @regs, @others ];
448 }
449
450 sub ol_date {
451         my $time = shift || CORE::time;
452         my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
453         $mon++; $year   += 1900;
454         $mday   = "0$mday" unless $mday =~ /\d{2}/o;
455         $mon    = "0$mon" unless $mon   =~ /\d{2}/o;
456         return ($year, $mon, $mday);
457 }
458
459
460 # --------------------------------------------------------------------
461 # Moves all files from the pending directory to the archive directory
462 # and removes the pending directory
463 # --------------------------------------------------------------------
464 sub ol_archive_files {
465         my $session = shift;
466         my ($y, $m, $d) = ol_date();
467
468         my $dir = "$basedir/pending/$org/$seskey";
469         my $archdir = "$basedir/archive/$org/$seskey";
470         $logger->debug("offline: archiving files to $archdir");
471
472         # Tell the db the files are moving
473         $_->logfile($archdir.'/'.$_->workstation.'.log') for ($session->scripts);
474
475         qx/mkdir -p $archdir/;
476         qx/mv $_ $archdir/ for <$dir/*>;
477         qx/rmdir $dir/;
478 }
479
480
481 # --------------------------------------------------------------------
482 # Appends results to the results file.
483 # --------------------------------------------------------------------
484 my $rhandle;
485 sub ol_append_result {
486
487         my $obj = shift;
488         my $last = shift;
489
490         $obj = JSON->perl2JSON($obj);
491
492         if(!$rhandle) {
493                 open($rhandle, ">>$basedir/pending/$org/$seskey/results") 
494                         or ol_handle_event('OFFLINE_FILE_ERROR');
495         }
496
497         print $rhandle "$obj\n";
498         close($rhandle) if $last;
499 }
500
501
502
503 # --------------------------------------------------------------------
504 # Runs the commands and returns the list of errors
505 # --------------------------------------------------------------------
506 sub ol_process_commands {
507
508         my $session      = shift;
509         my $commands = shift;
510         my $x        = 0;
511
512         $session->start_time(CORE::time);
513
514         for my $d ( @$commands ) {
515
516                 my $t           = $d->{type};
517                 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
518                 my $res = { command => $d };
519
520                 $res->{event} = ol_handle_checkin($d)   if $t eq 'checkin';
521                 $res->{event} = ol_handle_inhouse($d)   if $t eq 'in_house_use';
522                 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
523                 $res->{event} = ol_handle_renew($d)             if $t eq 'renew';
524                 $res->{event} = ol_handle_register($d) if $t eq 'register';
525
526
527                 ol_append_result($res, $last);
528                 $session->num_complete( $session->num_complete + 1 );
529
530                 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
531         }
532
533         $session->end_time(CORE::time);
534         $session->in_process(0);
535 }
536
537
538 # --------------------------------------------------------------------
539 # Runs an in_house_use action
540 # --------------------------------------------------------------------
541 sub ol_handle_inhouse {
542
543         my $command             = shift;
544         my $realtime    = $command->{_realtime};
545         my $ws                  = $command->{_workstation};
546         my $barcode             = $command->{barcode};
547         my $count               = $command->{count} || 1;
548         my $use_time    = $command->{use_time} || "";
549
550         $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".  
551                 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
552
553         my $ids = $U->simplereq(
554                 'open-ils.circ', 
555                 'open-ils.circ.in_house_use.create', $authtoken, 
556                 { barcode => $barcode, count => $count, location => $org, use_time => $use_time } );
557         
558         return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
559         return $ids;
560 }
561
562
563
564 # --------------------------------------------------------------------
565 # Pulls the relevant circ args from the command, fetches data where 
566 # necessary
567 # --------------------------------------------------------------------
568 sub ol_circ_args_from_command {
569         my $command = shift;
570
571         my $type                        = $command->{type};
572         my $realtime    = $command->{_realtime};
573         my $ws                  = $command->{_workstation};
574         my $barcode             = $command->{barcode} || "";
575         my $cotime              = $command->{checkout_time} || "";
576         my $pbc                 = $command->{patron_barcode};
577         my $due_date    = $command->{due_date} || "";
578         my $noncat              = ($command->{noncat}) ? "yes" : "no"; # for logging
579
580         $logger->activity("offline: $type : requestor=". $requestor->id.
581                 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
582                 "patron=$pbc, due_date=$due_date, noncat=$noncat");
583
584         my $args = { 
585                 permit_override => 1, 
586                 barcode                         => $barcode,            
587                 checkout_time           => $cotime, 
588                 patron_barcode          => $pbc,
589                 due_date                                => $due_date };
590
591         if( $command->{noncat} ) {
592                 $args->{noncat} = 1;
593                 $args->{noncat_type} = $command->{noncat_type};
594                 $args->{noncat_count} = $command->{noncat_count};
595         }
596
597         return $args;
598 }
599
600
601
602 # --------------------------------------------------------------------
603 # Performs a checkout action
604 # --------------------------------------------------------------------
605 sub ol_handle_checkout {
606         my $command     = shift;
607         my $args = ol_circ_args_from_command($command);
608         return $U->simplereq(
609                 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
610 }
611
612
613 # --------------------------------------------------------------------
614 # Performs the renewal action
615 # --------------------------------------------------------------------
616 sub ol_handle_renew {
617         my $command = shift;
618         my $args = ol_circ_args_from_command($command);
619         my $t = time;
620         return $U->simplereq(
621                 'open-ils.circ', 'open-ils.circ.renew', $authtoken, $args );
622 }
623
624
625 # --------------------------------------------------------------------
626 # Runs a checkin action
627 # --------------------------------------------------------------------
628 sub ol_handle_checkin {
629
630         my $command             = shift;
631         my $realtime    = $command->{_realtime};
632         my $ws                  = $command->{_workstation};
633         my $barcode             = $command->{barcode};
634         my $backdate    = $command->{backdate} || "";
635
636         $logger->activity("offline: checkin : requestor=". $requestor->id.
637                 ", realtime=$realtime, ".  "workstation=$ws, barcode=$barcode, backdate=$backdate");
638
639         return $U->simplereq(
640                 'open-ils.circ', 
641                 'open-ils.circ.checkin', $authtoken,
642                 { barcode => $barcode, backdate => $backdate } );
643 }
644
645
646
647 # --------------------------------------------------------------------
648 # Registers a new patron
649 # --------------------------------------------------------------------
650 sub ol_handle_register {
651         my $command = shift;
652
653         my $barcode = $command->{user}->{card}->{barcode};
654         delete $command->{user}->{card}; 
655
656         $logger->info("offline: creating new user with barcode $barcode");
657
658         # now, create the user
659         my $actor       = Fieldmapper::actor::user->new;
660         my $card                = Fieldmapper::actor::card->new;
661
662
663         # username defaults to the barcode
664         $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
665
666         # Set up all of the virtual IDs, isnew, etc.
667         $actor->isnew(1);
668         $actor->id(-1);
669         $actor->card(-1);
670         $actor->cards([$card]);
671
672         $card->isnew(1);
673         $card->id(-1);
674         $card->usr(-1);
675         $card->barcode($barcode);
676
677         my $billing_address;
678         my $mailing_address;
679
680         my @sresp;
681         for my $resp (@{$command->{user}->{survey_responses}}) {
682                 my $sr = Fieldmapper::action::survey_response->new;
683                 $sr->$_( $resp->{$_} ) for keys %$resp;
684                 $sr->isnew(1);
685                 $sr->usr(-1);
686                 push(@sresp, $sr);
687                 $logger->debug("offline: created new survey response for survey ".$sr->survey);
688         }
689         delete $command->{user}->{survey_responses};
690         $actor->survey_responses(\@sresp) if @sresp;
691
692         # extract the billing address
693         if( my $addr = $command->{user}->{billing_address} ) {
694                 $billing_address = Fieldmapper::actor::user_address->new;
695                 $billing_address->$_($addr->{$_}) for keys %$addr;
696                 $billing_address->isnew(1);
697                 $billing_address->id(-1);
698                 $billing_address->usr(-1);
699                 delete $command->{user}->{billing_address};
700                 $logger->debug("offline: read billing address ".$billing_address->street1);
701         }
702
703         # extract the mailing address
704         if( my $addr = $command->{user}->{mailing_address} ) {
705                 $mailing_address = Fieldmapper::actor::user_address->new;
706                 $mailing_address->$_($addr->{$_}) for keys %$addr;
707                 $mailing_address->isnew(1);
708                 $mailing_address->id(-2);
709                 $mailing_address->usr(-1);
710                 delete $command->{user}->{mailing_address};
711                 $logger->debug("offline: read mailing address ".$mailing_address->street1);
712         }
713
714         # make sure we have values for both
715         $billing_address ||= $mailing_address;
716         $mailing_address ||= $billing_address;
717
718         $actor->billing_address($billing_address->id);
719         $actor->mailing_address($mailing_address->id);
720         $actor->addresses([$mailing_address]);
721
722         push( @{$actor->addresses}, $billing_address ) 
723                 unless $billing_address->id eq $mailing_address->id;
724         
725         # pull all of the rest of the data from the command blob
726         $actor->$_( $command->{user}->{$_} ) for keys %{$command->{user}};
727
728         $logger->debug("offline: creating user object...");
729         $actor = $U->simplereq(
730                 'open-ils.actor', 
731                 'open-ils.actor.patron.update', $authtoken, $actor);
732
733         return $actor if(ref($actor) eq 'HASH'); # an event occurred
734
735         return OpenILS::Event->new('SUCCESS', payload => $actor);
736 }
737
738
739
740
741
742
743