bug 2608: let offline circ processing work in background
authorGalen Charlton <galen.charlton@liblime.com>
Tue, 16 Sep 2008 01:38:22 +0000 (20:38 -0500)
committerGalen Charlton <galen.charlton@liblime.com>
Wed, 17 Sep 2008 03:40:22 +0000 (22:40 -0500)
If an offline circulation file is large, process_koc.pl
could time out before processing all of the transactions.

To prevent this, the processing can now be run in the
background, with an AJAX loop to check the job's
status.

As a consequence of the patch, the UI for uploading offline circ files has
changed slightly.  One must select a file, click the "upload file" button, then
click the "process offline circulation file" button.

Also removed the now-superfluous upload_koc.pl, added the warnings pragma,
and fixed a typo in the template.

Signed-off-by: Galen Charlton <galen.charlton@liblime.com>

koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tmpl
koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/process_koc.tmpl
koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/upload_koc.tmpl [deleted file]
offline_circ/process_koc.pl
offline_circ/upload_koc.pl [deleted file]

index 0adbdba..e692fe7 100644 (file)
@@ -47,7 +47,7 @@
        <div class="yui-u">
                <h5>Offline Circulation</h5>
                <ul>
-                       <li><a href="/cgi-bin/koha/offline_circ/upload_koc.pl">Offline Circulation File (.koc) Uploader</a></li>
+                       <li><a href="/cgi-bin/koha/offline_circ/process_koc.pl">Offline Circulation File (.koc) Uploader</a></li>
                </ul>
        </div>
 </div>
index aa7c063..0b233af 100644 (file)
@@ -1,19 +1,56 @@
 <!-- TMPL_INCLUDE NAME="doc-head-open.inc" -->
-<title>Koha &rsaquo; Circulation &rsaquo; Offline Circulation File Processing</title>
+<title>Koha &rsaquo; Circulation &rsaquo; Offline Circulation File Upload</title>
 <!-- TMPL_INCLUDE NAME="doc-head-close.inc" -->
+<!-- TMPL_INCLUDE NAME="file-upload.inc" -->
+<!-- TMPL_INCLUDE NAME="background-job.inc" -->
+<script type="text/javascript">
+//<![CDATA[
+
+function CheckForm(f) {
+    if (f.uploadedfileid.value == '') {
+        alert('Please upload a file first.');
+    } else {
+        return submitBackgroundJob(f);
+    }
+    return false;
+}
+
+//]]>
+</script>
 </head>
 <body>
 <!-- TMPL_INCLUDE NAME="header.inc" -->
 <!-- TMPL_INCLUDE NAME="circ-search.inc" -->
 
-<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/circ/circulation-home.pl">Circulation</a> &rsaquo; Offline Circulation File Uplaod</div>
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/circ/circulation-home.pl">Circulation</a> &rsaquo; Offline Circulation File Upload</div>
 
-<h2>Koha Offline Circulation</h2>
-<p>Your data was processed. Here are the results.</p>
+<!-- TMPL_IF NAME="transactions_loaded" -->
+  <h2>Koha Offline Circulation</h2>
+  <p>Your data was processed. Here are the results.</p>
+  <!-- TMPL_LOOP NAME="messages" -->
+    <p><!-- TMPL_VAR NAME="message" --></p>
+  <!-- /TMPL_LOOP -->
+<!-- TMPL_ELSE -->
+  <h2>Upload Offline Circulation Data</h2>
+   <div id="fileuploadform">
+     <form method="post" action="<!-- TMPL_VAR name="SCRIPT_NAME" -->" enctype="multipart/form-data">
+       <label for="fileToUpload">Choose .koc File: </label>
+       <input type="file" id="fileToUpload" name="fileToUpload" /><br />
+       <button class="input" onclick="return ajaxFileUpload();">Upload file</button>
+     </form>
+     <div id="fileuploadstatus" style="display:none">Upload progress: <span id="fileuploadprogress">0</span>%</div>
+     <div id="fileuploadfailed" style="display:none"></div>
+   </div>
+   <form action="process_koc.pl" method="post" enctype="multipart/form-data">
+     <input type="hidden" name="uploadedfileid" id="uploadedfileid" value="" />
+     <input type="hidden" name="runinbackground" id="runinbackground" value="" />
+     <input type="hidden" name="completedJobID" id="completedJobID" value="" />
+     <input type="submit" value="Process offline circulation file" onclick="return CheckForm(this.form);" id="mainformsubmit" />
+     <div id="jobstatus" style="display:none">Job progress: <span id="jobprogress">0</span>%</div>
+     <div id="jobfailed" style="display:none"></div>
+   </form>
+<!-- /TMPL_IF -->
 
-<!-- TMPL_LOOP NAME="messages" -->
-  <p><!-- TMPL_VAR NAME="message" --></p>
-<!-- /TMPL_LOOP -->
 
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/upload_koc.tmpl b/koha-tmpl/intranet-tmpl/prog/en/modules/offline_circ/upload_koc.tmpl
deleted file mode 100644 (file)
index f1d9d16..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- TMPL_INCLUDE NAME="doc-head-open.inc" -->
-<title>Koha &rsaquo; Circulation &rsaquo; Offline Circulation File Upload</title>
-<!-- TMPL_INCLUDE NAME="doc-head-close.inc" -->
-</head>
-<body>
-<!-- TMPL_INCLUDE NAME="header.inc" -->
-<!-- TMPL_INCLUDE NAME="circ-search.inc" -->
-
-<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/circ/circulation-home.pl">Circulation</a> &rsaquo; Offline Circulation File Uplaod</div>
-
-<h2>Upload Offline Circulation Data</h2>
-<form action="process_koc.pl" method="post" enctype="multipart/form-data">
-  <label for="kocfile">Choose .koc File</label>
-  <input type="file" id="kocfile" name="kocfile" />
-
-  <br />
-
-  <input type="submit" value="Upload File"/>
-</form>
-
-</body>
-</html>
index d8624f5..5b3d749 100755 (executable)
@@ -19,7 +19,7 @@
 #
 
 use strict;
-require Exporter;
+use warnings;
 
 use CGI;
 use C4::Output;
@@ -31,6 +31,8 @@ use C4::Accounts;
 use C4::Circulation;
 use C4::Members;
 use C4::Stats;
+use C4::UploadedFile;
+use C4::BackgroundJob;
 
 use Date::Calc qw( Add_Delta_Days Date_to_Days );
 
@@ -49,49 +51,107 @@ my ($template, $loggedinuser, $cookie)
                                debug => 1,
                                });
 
+
+my $fileID=$query->param('uploadedfileid');
+my $runinbackground = $query->param('runinbackground');
+my $completedJobID = $query->param('completedJobID');
+my %cookies = parse CGI::Cookie($cookie);
+my $sessionID = $cookies{'CGISESSID'}->value;
 ## 'Local' globals.
 our $dbh = C4::Context->dbh();
-our @output; ## For storing messages to be displayed to the user
-
-$query::POST_MAX = 1024 * 10000;
-my $file = $query->param("kocfile");
-$file=~m/^.*(\\|\/)(.*)/; # strip the remote path and keep the filename 
-
-my $header_line = <$file>;
-my $file_info   = parse_header_line($header_line);
-if ($file_info->{'Version'} ne $FILE_VERSION) {
-    push( @output, { message => "Warning: This file is version '$file_info->{'Version'}', but I only know how to import version '$FILE_VERSION'. I'll try my best." } );
-}
-
-
-while ( my $line = <$file> ) {
-
-    # my ( $date, $time, $command, @arguments ) = parse_command_line( $line );
-    my $command_line = parse_command_line($line);
-
-    # map command names in the file to subroutine names
-    my %dispatch_table = (
-        issue   => \&kocIssueItem,
-        return  => \&kocReturnItem,
-        payment => \&kocMakePayment,
-    );
+our @output = (); ## For storing messages to be displayed to the user
+
+
+if ($completedJobID) {
+    my $job = C4::BackgroundJob->fetch($sessionID, $completedJobID);
+    my $results = $job->results();
+    $template->param(transactions_loaded => 1);
+    $template->param(messages => $results->{results});
+} elsif ($fileID) {
+    my $uploaded_file = C4::UploadedFile->fetch($sessionID, $fileID);
+    my $fh = $uploaded_file->fh();
+    my @input_lines = <$fh>;
+  
+    my $filename = $uploaded_file->name(); 
+    my $job = undef;
+
+    if ($runinbackground) {
+        my $job_size = scalar(@input_lines);
+        $job = C4::BackgroundJob->new($sessionID, $filename, $ENV{'SCRIPT_NAME'}, $job_size);
+        my $jobID = $job->id();
+
+        # fork off
+        if (my $pid = fork) {
+            # parent
+            # return job ID as JSON
+
+            # prevent parent exiting from
+            # destroying the kid's database handle
+            # FIXME: according to DBI doc, this may not work for Oracle
+            $dbh->{InactiveDestroy}  = 1;
+
+            my $reply = CGI->new("");
+            print $reply->header(-type => 'text/html');
+            print "{ jobID: '$jobID' }";
+            exit 0;
+        } elsif (defined $pid) {
+            # child
+            # close STDOUT to signal to Apache that
+            # we're now running in the background
+            close STDOUT;
+            close STDERR;
+        } else {
+            # fork failed, so exit immediately
+            # fork failed, so exit immediately
+            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
+            exit 0;
+        }
+
+        # if we get here, we're a child that has detached
+        # itself from Apache
+
+    }     
+
+    my $header_line = shift @input_lines;
+    my $file_info   = parse_header_line($header_line);
+    if ($file_info->{'Version'} ne $FILE_VERSION) {
+        push( @output, { message => "Warning: This file is version '$file_info->{'Version'}', but I only know how to import version '$FILE_VERSION'. I'll try my best." } );
+    }
+    
+    
+    my $i = 0;
+    foreach  my $line (@input_lines)  {
+    
+        $i++;
+        my $command_line = parse_command_line($line);
+        
+        # map command names in the file to subroutine names
+        my %dispatch_table = (
+            issue     => \&kocIssueItem,
+            'return'  => \&kocReturnItem,
+            payment   => \&kocMakePayment,
+        );
+
+        # call the right sub name, passing the hashref of command_line to it.
+        if ( exists $dispatch_table{ $command_line->{'command'} } ) {
+            $dispatch_table{ $command_line->{'command'} }->($command_line);
+        } else {
+            warn "unknown command: '$command_line->{command}' not processed";
+        }
+
+        if ($runinbackground) {
+            $job->progress($i);
+        }
+    }
 
-    # call the right sub name, passing the hashref of command_line to it.
-    if ( exists $dispatch_table{ $command_line->{'command'} } ) {
-        $dispatch_table{ $command_line->{'command'} }->($command_line);
+    if ($runinbackground) {
+        $job->finish({ results => \@output }) if defined($job);
     } else {
-        warn "unknown command: '$command_line->{command}' not processed";
+        $template->param(transactions_loaded => 1);
+        $template->param(messages => \@output);
     }
-
 }
 
-$template->param(
-               intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
-               intranetstylesheet => C4::Context->preference("intranetstylesheet"),
-               IntranetNav => C4::Context->preference("IntranetNav"),
-
-                messages => \@output,
-       );
 output_html_with_http_headers $query, $cookie, $template->output;
 
 =head3 parse_header_line
diff --git a/offline_circ/upload_koc.pl b/offline_circ/upload_koc.pl
deleted file mode 100755 (executable)
index 700d7cd..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/perl
-
-# This file is part of Koha.
-#
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
-#
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
-#
-
-use strict;
-require Exporter;
-
-use C4::Output;
-use C4::Auth;
-use C4::Koha;
-
-use CGI;
-
-my $query = new CGI;
-
-my ($template, $loggedinuser, $cookie)
-= get_template_and_user({template_name => "offline_circ/upload_koc.tmpl",
-                               query => $query,
-                               type => "intranet",
-                               authnotrequired => 1,
-                               debug => 1,
-                               });
-
-$template->param(
-               intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"),
-               intranetstylesheet => C4::Context->preference("intranetstylesheet"),
-               IntranetNav => C4::Context->preference("IntranetNav"),
-       );
-output_html_with_http_headers $query, $cookie, $template->output;