#!/usr/bin/perl use strict; use warnings; use CGI; use OpenSRF::Utils::JSON; use OpenSRF::System; use OpenSRF::Utils::Logger qw/$logger/; use OpenILS::Application::AppUtils; use OpenILS::Event; use OpenSRF::EX qw/:try/; use Data::Dumper; use OpenILS::Utils::Fieldmapper; use Digest::MD5 qw/md5_hex/; use OpenSRF::Utils qw/:daemon/; use OpenILS::Utils::OfflineStore; use OpenSRF::Utils::SettingsClient; use OpenSRF::Utils; use DateTime; use DBI; $DBI::trace = 1; my $U = "OpenILS::Application::AppUtils"; my $DB = "OpenILS::Utils::OfflineStore"; my $SES = "${DB}::Session"; my $SCRIPT = "OpenILS::Utils::OfflineStore::Script"; my $user_groups; # -------------------------------------------------------------------- # Load the config # -------------------------------------------------------------------- our %config; do '##CONFIG##/offline-config.pl'; my $cgi = new CGI; my $basedir = $config{base_dir} || die "Offline config error: no base_dir defined\n"; my $bootstrap = $config{bootstrap} || die "Offline config error: no bootstrap defined\n"; my $wsname = $cgi->param('ws'); my $org = $cgi->param('org'); my $authtoken = $cgi->param('ses') || ""; my $seskey = $cgi->param('seskey'); my $action = $cgi->param('action'); # - create, load, execute, status my $requestor; my $wsobj; my $orgobj; my $evt; &ol_init; &ol_runtime_init; &ol_do_action; # -------------------------------------------------------------------- # Set it all up # This function should behave as a child_init might behave in case # this is moved to mod_perl # -------------------------------------------------------------------- sub ol_init { $DB->DBFile($config{dsn}, $config{usr}, $config{pw}); ol_connect(); } sub ol_connect { OpenSRF::System->bootstrap_client(config_file => $bootstrap ); Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL")); } sub _ol_debug_params { my $s = ""; my @params = $cgi->param; @params = sort { $a cmp $b } @params; $s .= "$_=" . $cgi->param($_) . "\n" for @params; $s =~ s/\n$//o; warn '-'x60 ."\n$s\n"; } # -------------------------------------------------------------------- # Finds the requestor and other info specific to this request # -------------------------------------------------------------------- sub ol_runtime_init { # fetch the requestor object ($requestor, $evt) = $U->checkses($authtoken); ol_handle_result($evt) if $evt; # try the param, the workstation, and finally the user's ws org if(!$org) { $wsobj = ol_fetch_workstation($wsname); $org = $wsobj->owning_lib if $wsobj; $org = $requestor->ws_ou unless $org; ol_handle_result(OpenILS::Event->new('OFFLINE_NO_ORG')) unless $org; } $user_groups = $U->simplereq( 'open-ils.actor', 'open-ils.actor.groups.retrieve'); } # -------------------------------------------------------------------- # Runs the requested action # -------------------------------------------------------------------- sub ol_do_action { my $payload; if( $action eq 'create' ) { $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD'); ol_handle_result($evt) if $evt; $payload = ol_create_session(); } elsif( $action eq 'load' ) { $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_UPLOAD'); ol_handle_result($evt) if $evt; $payload = ol_load(); } elsif( $action eq 'execute' ) { $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_EXECUTE'); ol_handle_result($evt) if $evt; $payload = ol_execute(); } elsif( $action eq 'status' ) { $evt = $U->check_perms($requestor->id, $org, 'OFFLINE_VIEW'); ol_handle_result($evt) if $evt; $payload = ol_status(); } ol_handle_event('SUCCESS', payload => $payload ); } # -------------------------------------------------------------------- # Creates a new session # -------------------------------------------------------------------- sub ol_create_session { my $desc = $cgi->param('desc') || ""; $seskey = time . "_${$}_" . int(rand() * 1000); $logger->debug("offline: user ".$requestor->id. " creating new session with key $seskey and description $desc"); $SES->create( { key => $seskey, org => $org, description => $desc, creator => $requestor->id, create_time => CORE::time(), num_complete => 0, } ); return $seskey; } # -------------------------------------------------------------------- # Holds the meta-info for a script file # -------------------------------------------------------------------- sub ol_create_script { my $count = shift; my $session = ol_find_session($seskey); my $delta = $cgi->param('delta') || 0; my $script = $session->add_to_scripts( { requestor => $requestor->id, create_time => CORE::time, workstation => $wsname, logfile => "$basedir/pending/$org/$seskey/$wsname.log", time_delta => $delta, count => $count, } ); } # -------------------------------------------------------------------- # Finds the current session in the db # -------------------------------------------------------------------- sub ol_find_session { my $ses = $SES->retrieve($seskey); ol_handle_event('OFFLINE_INVALID_SESSION', payload => $seskey) unless $ses; return $ses; } # -------------------------------------------------------------------- # Finds a script object in the DB based on workstation and seskey # -------------------------------------------------------------------- sub ol_find_script { my $ws = shift || $wsname; my $sk = shift || $seskey; my ($script) = $SCRIPT->search( session => $seskey, workstation => $ws ); return $script; } # -------------------------------------------------------------------- # Creates a new script in the database and loads the new script file # -------------------------------------------------------------------- sub ol_load { my $session = ol_find_session; my $handle = $cgi->upload('file'); my $outdir = "$basedir/pending/$org/$seskey"; my $outfile = "$outdir/$wsname.log"; ol_handle_event('OFFLINE_SESSION_FILE_EXISTS') if ol_find_script(); ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process; ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time; qx/mkdir -p $outdir/; my $x = 0; open(FILE, ">>$outfile") or ol_handle_event('OFFLINE_FILE_ERROR'); while( <$handle> ) { print FILE; $x++;} close(FILE); ol_create_script($x); return undef; } # -------------------------------------------------------------------- sub ol_handle_result { my $obj = shift; my $json = OpenSRF::Utils::JSON->perl2JSON($obj); # Clear this so it's not remembered $evt = undef; if( $cgi->param('html')) { my $html = "
"; print "content-type: text/html\n\n"; print "$html\n"; } else { print "content-type: text/plain\n\n"; print "$json\n"; } exit(0); } # -------------------------------------------------------------------- sub ol_handle_event { my( $evt, @args ) = @_; ol_handle_result(OpenILS::Event->new($evt, @args)); } # -------------------------------------------------------------------- sub ol_flesh_session { my $session = shift; my %data; map { $data{$_} = $session->$_ } $session->columns; $data{scripts} = []; for my $script ($session->scripts) { my %sdata; map { $sdata{$_} = $script->$_ } $script->columns; # the client doesn't need this info delete $sdata{session}; delete $sdata{id}; delete $sdata{logfile}; push( @{$data{scripts}}, \%sdata ); } return \%data; } # -------------------------------------------------------------------- # Returns various information on the sessions and scripts # -------------------------------------------------------------------- sub ol_status { my $type = $cgi->param('status_type') || "scripts"; # -------------------------------------------------------------------- # Returns info on every script upload attached to the current session # -------------------------------------------------------------------- if( $type eq 'scripts' ) { my $session = ol_find_session(); ol_handle_result(ol_flesh_session($session)); # -------------------------------------------------------------------- # Returns all scripts and sessions for the given org # -------------------------------------------------------------------- } elsif( $type eq 'sessions' ) { my @sessions = $SES->search( org => $org ); # can I do this in the DB without raw SQL? @sessions = sort { $a->create_time <=> $b->create_time } @sessions; my @data; push( @data, ol_flesh_session($_) ) for @sessions; ol_handle_result(\@data); # -------------------------------------------------------------------- # Returns total commands and completed commands counts # -------------------------------------------------------------------- } elsif( $type eq 'summary' ) { my $session = ol_find_session(); $logger->debug("offline: retrieving summary info ". "for session ".$session->key." with completed=".$session->num_complete); my $count = 0; $count += $_->count for ($session->scripts); ol_handle_result( { total => $count, num_complete => $session->num_complete }); # -------------------------------------------------------------------- # Returns the list of non-SUCCESS events that have occurred so far for # this set of commands # -------------------------------------------------------------------- } elsif( $type eq 'exceptions' ) { my $session = ol_find_session(); my $resfile = "$basedir/pending/$org/$seskey/results"; if( $session->end_time ) { $resfile = "$basedir/archive/$org/$seskey/results"; } my $data = ol_file_to_perl($resfile); $data = [ grep { $_->{event}->{ilsevent} ne '0' } @$data ]; ol_handle_result($data); } } sub ol_fetch_workstation { my $name = shift; $logger->debug("offline: Fetching workstation $name"); my $ws = $U->storagereq( 'open-ils.storage.direct.actor.workstation.search.name', $name); ol_handle_result(OpenILS::Event->new('ACTOR_WORKSTATION_NOT_FOUND')) unless $ws; return $ws; } # -------------------------------------------------------------------- # Sorts the script commands then forks a child to executes them. # -------------------------------------------------------------------- sub ol_execute { my $session = ol_find_session(); ol_handle_event('OFFLINE_SESSION_ACTIVE') if $session->in_process; ol_handle_event('OFFLINE_SESSION_COMPLETE') if $session->end_time; my $commands = ol_collect_commands(); # -------------------------------------------------------------------- # Note that we must disconnect from opensrf before forking or the # connection will be borked... # -------------------------------------------------------------------- OpenSRF::Transport::PeerHandle->retrieve->disconnect; $DB->disconnect; if( safe_fork() ) { # -------------------------------------------------------------------- # Tell the client all is well # -------------------------------------------------------------------- ol_handle_event('SUCCESS'); # - this exits } else { # -------------------------------------------------------------------- # close stdout/stderr or apache will wait on the child to finish # -------------------------------------------------------------------- close(STDOUT); close(STDERR); $logger->debug("offline: child $$ processing data..."); # -------------------------------------------------------------------- # The child re-connects to the opensrf network and processes # the script requests # -------------------------------------------------------------------- OpenSRF::System->bootstrap_client(config_file => $bootstrap); try { #use Class::DBI #Class::DBI->autoupdate(1); $DB->autoupdate(1); my $sesion = ol_find_session(); $session->in_process(1); ol_process_commands($session, $commands); ol_archive_files($session); } catch Error with { my $e = shift; $logger->error("offline: child process error $e"); }; } } sub ol_file_to_perl { my $fname = shift; open(F, "$fname") or ol_handle_event('OFFLINE_FILE_ERROR'); my @d =