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