2 use strict; use warnings;
5 use OpenSRF::Utils::Logger qw/$logger/;
6 use OpenILS::Application::AppUtils;
8 use OpenSRF::EX qw/:try/;
11 use OpenILS::Utils::Fieldmapper;
12 use Digest::MD5 qw/md5_hex/;
13 use OpenSRF::Utils qw/:daemon/;
14 use OpenILS::Utils::OfflineStore;
20 #Class::DBI->autoupdate(1);
21 #local $OpenILS::Utils::OfflineStore::NO_TRIGGERS = 1;
23 my $U = "OpenILS::Application::AppUtils";
24 my $DB = "OpenILS::Utils::OfflineStore";
25 my $SES = "${DB}::Session";
26 my $SCRIPT = "OpenILS::Utils::OfflineStore::Script";
31 # --------------------------------------------------------------------
33 # --------------------------------------------------------------------
34 #do '##CONFIG##/upload-server.pl';
35 do 'offline-config.pl';
39 my $basedir = $config{base_dir};
40 my $wsname = $cgi->param('ws');
41 my $org = $cgi->param('org');
42 my $authtoken = $cgi->param('ses') || "";
43 my $seskey = $cgi->param('seskey');
44 my $action = $cgi->param('action'); # - create, load, execute, status
56 # --------------------------------------------------------------------
58 # This function should behave as a child_init might behave in case
59 # this is moved to mod_perl
60 # --------------------------------------------------------------------
63 $DB->DBFile($config{db});
64 OpenSRF::System->bootstrap_client(config_file => $config{bootstrap} );
68 sub _ol_debug_params {
70 my @params = $cgi->param;
71 @params = sort { $a cmp $b } @params;
72 $s .= "$_=" . $cgi->param($_) . "\n" for @params;
74 warn '-'x60 ."\n$s\n";
78 # --------------------------------------------------------------------
79 # Finds the requestor and other info specific to this request
80 # --------------------------------------------------------------------
83 # fetch the requestor object
84 ($requestor, $evt) = $U->checkses($authtoken);
85 ol_handle_result($evt) if $evt;
88 # try the param, the workstation, and finally the user's ws org
90 $wsobj = ol_fetch_workstation($wsname);
91 $org = $wsobj->owning_lib if $wsobj;
92 $org = $requestor->ws_ou unless $org;
93 ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org;
98 # --------------------------------------------------------------------
99 # Runs the requested action
100 # --------------------------------------------------------------------
105 if( $action eq 'create' ) {
107 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
108 ol_handle_result($evt) if $evt;
109 $payload = ol_create_session();
111 } elsif( $action eq 'load' ) {
113 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD');
114 ol_handle_result($evt) if $evt;
115 $payload = ol_load();
117 } elsif( $action eq 'execute' ) {
119 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE');
120 ol_handle_result($evt) if $evt;
121 $payload = ol_execute();
123 } elsif( $action eq 'status' ) {
125 $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW');
126 ol_handle_result($evt) if $evt;
127 $payload = ol_status();
130 ol_handle_event('SUCCESS', payload => $payload );
134 # --------------------------------------------------------------------
135 # Creates a new session
136 # --------------------------------------------------------------------
137 sub ol_create_session {
139 my $desc = $cgi->param('desc') || "";
140 $seskey = time . "_${$}_" . int(rand() * 1000);
142 $logger->debug("offline: user ".$requestor->id.
143 " creating new session with key $seskey and description $desc");
149 description => $desc,
150 creator => $requestor->id,
151 create_time => CORE::time(),
160 # --------------------------------------------------------------------
161 # Holds the meta-info for a script file
162 # --------------------------------------------------------------------
163 sub ol_create_script {
166 my $session = ol_find_session($seskey);
167 my $delta = $cgi->param('delta') || 0;
169 my $script = $session->add_to_scripts(
171 requestor => $requestor->id,
172 create_time => CORE::time,
173 workstation => $wsname,
174 logfile => "$basedir/pending/$seskey/$wsname.log",
175 time_delta => $delta,
181 # --------------------------------------------------------------------
182 # Finds the current session in the db
183 # --------------------------------------------------------------------
184 sub ol_find_session {
185 my $ses = $SES->retrieve($seskey);
186 ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses;
190 # --------------------------------------------------------------------
191 # Finds a script object in the DB based on workstation and seskey
192 # --------------------------------------------------------------------
194 my $ws = shift || $wsname;
195 my $sk = shift || $seskey;
196 my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws );
200 # --------------------------------------------------------------------
201 # Creates a new script in the database and loads the new script file
202 # --------------------------------------------------------------------
204 my $session = ol_find_session;
205 my $handle = $cgi->upload('file');
206 my $outdir = "$basedir/pending/$seskey";
207 my $outfile = "$outdir/$wsname.log";
209 ol_handl_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script();
210 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
211 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
213 qx/mkdir -p $outdir/;
215 open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR');
216 while( <$handle> ) { print FILE; $x++;}
219 ol_create_script($x);
225 # --------------------------------------------------------------------
226 sub ol_handle_result {
228 my $json = JSON->perl2JSON($obj);
230 if( $cgi->param('html')) {
231 my $html = "<html><body onload='xulG.handle_event($json)></body></html>";
232 print "content-type: text/html\n\n";
237 print "content-type: text/plain\n\n";
244 # --------------------------------------------------------------------
245 sub ol_handle_event {
246 my( $evt, @args ) = @_;
247 ol_handle_result(OpenILS::Event->new($evt, @args));
251 # --------------------------------------------------------------------
252 sub ol_flesh_session {
256 map { $data{$_} = $session->$_ } $session->columns;
259 for my $script ($session->scripts) {
261 map { $sdata{$_} = $script->$_ } $script->columns;
263 # the client doesn't need this info
264 delete $sdata{session};
266 delete $sdata{logfile};
268 push( @{$data{scripts}}, \%sdata );
275 # --------------------------------------------------------------------
276 # Returns various information on the sessions and scripts
277 # --------------------------------------------------------------------
280 my $type = $cgi->param('status_type') || "scripts";
282 # --------------------------------------------------------------------
283 # Returns info on every script upload attached to the current session
284 # --------------------------------------------------------------------
285 if( $type eq 'scripts' ) {
286 my $session = ol_find_session();
287 ol_handle_result(ol_flesh_session($session));
290 # --------------------------------------------------------------------
291 # Returns all scripts and sessions for the given org
292 # --------------------------------------------------------------------
293 } elsif( $type eq 'sessions' ) {
294 my @sessions = $SES->search( org => $org );
296 # can I do this in the DB without raw SQL?
297 @sessions = sort { $a->create_time <=> $b->create_time } @sessions;
299 push( @data, ol_flesh_session($_) ) for @sessions;
300 ol_handle_result(\@data);
303 # --------------------------------------------------------------------
304 # Returns total commands and completed commands counts
305 # --------------------------------------------------------------------
306 } elsif( $type eq 'summary' ) {
307 my $session = ol_find_session();
309 $logger->debug("offline: retrieving summary info ".
310 "for session ".$session->key." with completed=".$session->num_complete);
313 $count += $_->count for ($session->scripts);
315 { total => $count, num_complete => $session->num_complete });
319 # --------------------------------------------------------------------
320 # Returns the list of non-SUCCESS events that have occurred so far for
321 # this set of commands
322 # --------------------------------------------------------------------
323 } elsif( $type eq 'exceptions' ) {
325 my $session = ol_find_session();
326 my $resfile = "$basedir/pending/$seskey/results";
327 if( $session->end_time ) {
328 $resfile = "$basedir/archive/$org/$seskey/results";
330 my $data = ol_file_to_perl($resfile);
331 $data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ];
332 ol_handle_result($data);
337 sub ol_fetch_workstation {
339 $logger->debug("offline: Fetching workstation $name");
340 my $ws = $U->storagereq(
341 'open-ils.storage.direct.actor.workstation.search.name', $name);
342 ol_handle_result(OpenILS::Event->new('WORKSTATION_NOT_FOUND')) unless $ws;
349 # --------------------------------------------------------------------
350 # Sorts the script commands then forks a child to executes them.
351 # --------------------------------------------------------------------
354 my $session = ol_find_session();
355 ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process;
356 ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time;
358 my $commands = ol_collect_commands();
360 # --------------------------------------------------------------------
361 # Note that we must disconnect from opensrf before forking or the
362 # connection will be borked...
363 # --------------------------------------------------------------------
364 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
370 # --------------------------------------------------------------------
371 # Tell the client all is well
372 # --------------------------------------------------------------------
373 ol_handle_event('SUCCESS'); # - this exits
378 # --------------------------------------------------------------------
379 # close stdout/stderr or apache will wait on the child to finish
380 # --------------------------------------------------------------------
384 $logger->debug("offline: child $$ processing data...");
386 # --------------------------------------------------------------------
387 # The child re-connects to the opensrf network and processes
388 # the script requests
389 # --------------------------------------------------------------------
390 OpenSRF::System->bootstrap_client(config_file => $config{bootstrap});
395 #Class::DBI->autoupdate(1);
399 my $sesion = ol_find_session();
400 $session->in_process(1);
401 ol_process_commands($session, $commands);
406 $logger->error("offline: child process error $e");
411 sub ol_file_to_perl {
413 open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR');
416 push(@p, JSON->JSON2perl($_)) for @d;
421 # collects the commands and sorts them on timestamp+delta
422 sub ol_collect_commands {
423 my $ses = ol_find_session();
426 # cycle through every script loaded to this session
427 for my $script ($ses->scripts) {
428 my $coms = ol_file_to_perl($script->logfile);
430 # cycle through all of the commands for this script
431 for my $com (@$coms) {
432 $$com{_worksation} = $script->workstation;
433 $$com{_realtime} = $script->time_delta + $com->{timestamp};
434 push( @commands, $com );
438 # make sure thera are no blank commands
439 @commands = grep { ($_ and $_->{type}) } @commands;
442 @commands = sort { $a->{_realtime} <=> $b->{_realtime} } @commands;
444 # push user registrations to the front
445 my @regs = grep { $_->{type} eq 'register' } @commands;
446 my @others = grep { $_->{type} ne 'register' } @commands;
448 return [ @regs, @others ];
452 my $time = shift || CORE::time;
453 my (undef,undef, undef, $mday,$mon,$year) = localtime($time);
454 $mon++; $year += 1900;
455 $mday = "0$mday" unless $mday =~ /\d{2}/o;
456 $mon = "0$mon" unless $mon =~ /\d{2}/o;
457 return ($year, $mon, $mday);
461 # --------------------------------------------------------------------
462 # Moves all files from the pending directory to the archive directory
463 # and removes the pending directory
464 # --------------------------------------------------------------------
465 sub ol_archive_files {
466 my ($y, $m, $d) = ol_date();
468 my $dir = "$basedir/pending/$seskey";
469 my $archdir = "$basedir/archive/$org/$seskey";
470 $logger->debug("offline: archiving files to $archdir");
472 qx/mkdir -p $archdir/;
473 qx/mv $_ $archdir/ for <$dir/*>;
478 # --------------------------------------------------------------------
479 # Appends results to the results file.
480 # --------------------------------------------------------------------
482 sub ol_append_result {
487 $obj = JSON->perl2JSON($obj);
490 open($rhandle, ">>$basedir/pending/$seskey/results")
491 or ol_handle_event('OFFLINE_FILE_ERROR');
494 print $rhandle "$obj\n";
495 close($rhandle) if $last;
500 # --------------------------------------------------------------------
501 # Runs the commands and returns the list of errors
502 # --------------------------------------------------------------------
503 sub ol_process_commands {
506 my $commands = shift;
509 $session->start_time(CORE::time);
511 for my $d ( @$commands ) {
514 my $last = ($x++ == scalar(@$commands) - 1) ? 1 : 0;
515 my $res = { command => $d };
517 $res->{event} = ol_handle_checkin($d) if $t eq 'checkin';
518 $res->{event} = ol_handle_inhouse($d) if $t eq 'in_house_use';
519 $res->{event} = ol_handle_checkout($d) if $t eq 'checkout';
520 $res->{event} = ol_handle_renew($d) if $t eq 'renew';
521 $res->{event} = ol_handle_register($d) if $t eq 'register';
524 ol_append_result($res, $last);
525 $session->num_complete( $session->num_complete + 1 );
527 $logger->debug("offline: set session [".$session->key."] num_complete to ".$session->num_complete);
530 $session->end_time(CORE::time);
531 $session->in_process(0);
535 # --------------------------------------------------------------------
536 # Runs an in_house_use action
537 # --------------------------------------------------------------------
538 sub ol_handle_inhouse {
541 my $realtime = $command->{_realtime};
542 my $ws = $command->{_workstation};
543 my $barcode = $command->{barcode};
544 my $count = $command->{count} || 1;
545 my $use_time = $command->{use_time} || "";
547 $logger->activity("offline: in_house_use : requestor=". $requestor->id.", realtime=$realtime, ".
548 "workstation=$ws, barcode=$barcode, count=$count, use_time=$use_time");
550 my $ids = $U->simplereq(
552 'open-ils.circ.in_house_use.create', $authtoken,
553 { barcode => $barcode, count => $count, location => &offline_org, use_time => $use_time } );
555 return OpenILS::Event->new('SUCCESS', payload => $ids) if( ref($ids) eq 'ARRAY' );
561 # --------------------------------------------------------------------
562 # Pulls the relevant circ args from the command, fetches data where
564 # --------------------------------------------------------------------
565 sub ol_circ_args_from_command {
568 my $type = $command->{type};
569 my $realtime = $command->{_realtime};
570 my $ws = $command->{_workstation};
571 my $barcode = $command->{barcode} || "";
572 my $cotime = $command->{checkout_time} || "";
573 my $pbc = $command->{patron_barcode};
574 my $due_date = $command->{due_date} || "";
575 my $noncat = ($command->{noncat}) ? "yes" : "no"; # for logging
577 $logger->activity("offline: $type : requestor=". $requestor->id.
578 ", realtime=$realtime, workstation=$ws, checkout_time=$cotime, ".
579 "patron=$pbc, due_date=$due_date, noncat=$noncat");
582 permit_override => 1,
584 checkout_time => $cotime,
585 patron_barcode => $pbc,
586 due_date => $due_date };
588 if( $command->{noncat} ) {
590 $args->{noncat_type} = $command->{noncat_type};
591 $args->{noncat_count} = $command->{noncat_count};
599 # --------------------------------------------------------------------
600 # Performs a checkout action
601 # --------------------------------------------------------------------
602 sub ol_handle_checkout {
604 my $args = ol_circ_args_from_command($command);
605 return $U->simplereq(
606 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args );
610 # --------------------------------------------------------------------
611 # Performs the renewal action
612 # --------------------------------------------------------------------
613 sub ol_handle_renew {
615 my $args = ol_circ_args_from_command($command);
617 return $U->simplereq(
618 'open-ils.circ', 'open-ils.circ.renew', $authtoken, $args );
622 # --------------------------------------------------------------------
623 # Runs a checkin action
624 # --------------------------------------------------------------------
625 sub ol_handle_checkin {
628 my $realtime = $command->{_realtime};
629 my $ws = $command->{_workstation};
630 my $barcode = $command->{barcode};
631 my $backdate = $command->{backdate} || "";
633 $logger->activity("offline: checkin : requestor=". $requestor->id.
634 ", realtime=$realtime, ". "workstation=$ws, barcode=$barcode, backdate=$backdate");
636 return $U->simplereq(
638 'open-ils.circ.checkin', $authtoken,
639 { barcode => $barcode, backdate => $backdate } );
644 # --------------------------------------------------------------------
645 # Registers a new patron
646 # --------------------------------------------------------------------
647 sub ol_handle_register {
650 my $barcode = $command->{user}->{card}->{barcode};
651 delete $command->{user}->{card};
653 $logger->info("offline: creating new user with barcode $barcode");
655 # now, create the user
656 my $actor = Fieldmapper::actor::user->new;
657 my $card = Fieldmapper::actor::card->new;
660 # username defaults to the barcode
661 $actor->usrname( ($actor->usrname) ? $actor->usrname : $barcode );
663 # Set up all of the virtual IDs, isnew, etc.
667 $actor->cards([$card]);
672 $card->barcode($barcode);
678 for my $resp (@{$command->{user}->{survey_responses}}) {
679 my $sr = Fieldmapper::action::survey_response->new;
680 $sr->$_( $resp->{$_} ) for keys %$resp;
684 $logger->debug("offline: created new survey response for survey ".$sr->survey);
686 delete $command->{user}->{survey_responses};
687 $actor->survey_responses(\@sresp) if @sresp;
689 # extract the billing address
690 if( my $addr = $command->{user}->{billing_address} ) {
691 $billing_address = Fieldmapper::actor::user_address->new;
692 $billing_address->$_($addr->{$_}) for keys %$addr;
693 $billing_address->isnew(1);
694 $billing_address->id(-1);
695 $billing_address->usr(-1);
696 delete $command->{user}->{billing_address};
697 $logger->debug("offline: read billing address ".$billing_address->street1);
700 # extract the mailing address
701 if( my $addr = $command->{user}->{mailing_address} ) {
702 $mailing_address = Fieldmapper::actor::user_address->new;
703 $mailing_address->$_($addr->{$_}) for keys %$addr;
704 $mailing_address->isnew(1);
705 $mailing_address->id(-2);
706 $mailing_address->usr(-1);
707 delete $command->{user}->{mailing_address};
708 $logger->debug("offline: read mailing address ".$mailing_address->street1);
711 # make sure we have values for both
712 $billing_address ||= $mailing_address;
713 $mailing_address ||= $billing_address;
715 $actor->billing_address($billing_address->id);
716 $actor->mailing_address($mailing_address->id);
717 $actor->addresses([$mailing_address]);
719 push( @{$actor->addresses}, $billing_address )
720 unless $billing_address->id eq $mailing_address->id;
722 # pull all of the rest of the data from the command blob
723 $actor->$_( $command->{user}->{$_} ) for keys %{$command->{user}};
725 $logger->debug("offline: creating user object...");
726 $actor = $U->simplereq(
728 'open-ils.actor.patron.update', $authtoken, $actor);
730 return $actor if(ref($actor) eq 'HASH'); # an event occurred
732 return OpenILS::Event->new('SUCCESS', payload => $actor);