lp1787968 jacket_upload: server-side
authorJason Etheridge <jason@EquinoxOLI.org>
Fri, 13 Aug 2021 16:23:49 +0000 (12:23 -0400)
committerMichele Morgan <mmorgan@noblenet.org>
Thu, 24 Mar 2022 15:53:01 +0000 (11:53 -0400)
Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>

Open-ILS/examples/apache_24/eg_vhost.conf.in
Open-ILS/examples/opensrf.xml.example
Open-ILS/src/perlmods/lib/OpenILS/WWW/Vandelay.pm

index a6d07e8..376aca9 100644 (file)
@@ -618,6 +618,12 @@ RewriteRule ^/conify/([a-z]{2}-[A-Z]{2})/global/(.*)$ /conify/global/$2 [E=local
     Options +ExecCGI
     Require all granted 
 </Location>
+<Location /jacket-upload>
+    SetHandler perl-script
+    PerlHandler OpenILS::WWW::Vandelay::spool_jacket
+    Options +ExecCGI
+    Require all granted 
+</Location>
 
 # OpenURL 0.1 searching based on OpenSearch
 RewriteMap openurl prg:@bindir@/openurl_map.pl
index c713fa3..24e9005 100644 (file)
@@ -1226,6 +1226,8 @@ vim:et:ts=4:sw=4:
                             *note:  in a multi-brick environment, this will need to
                             be on a write-able NFS share.  -->
                         <importer>/tmp</importer>
+                        <!-- permanent location for locally added content -->
+                        <jackets>/openils/var/web/opac/extras/ac</jackets>
                     </databases>
                 </app_settings>
             </open-ils.vandelay>
index c46b380..85a5884 100644 (file)
@@ -4,7 +4,7 @@ use warnings;
 use bytes;
 
 use Apache2::Log;
-use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND FORBIDDEN :log);
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND AUTH_REQUIRED FORBIDDEN HTTP_UNAUTHORIZED HTTP_REQUEST_ENTITY_TOO_LARGE HTTP_INTERNAL_SERVER_ERROR :log);
 use APR::Const    -compile => qw(:error SUCCESS);
 use APR::Table;
 
@@ -14,6 +14,7 @@ use Apache2::RequestUtil;
 use CGI;
 use Data::Dumper;
 use Text::CSV;
+use GD;
 
 use OpenSRF::EX qw(:try);
 use OpenSRF::Utils::Cache;
@@ -35,6 +36,7 @@ use UNIVERSAL::require;
 
 our @formats = qw/USMARC UNIMARC XML BRE/;
 my $MAX_FILE_SIZE = 10737418240; #10G
+my $MAX_JACKET_SIZE = 10737418240; #10G
 my $FILE_READ_SIZE = 4096;
 
 # set the bootstrap config and template include directory when
@@ -117,6 +119,198 @@ sub spool_marc {
     return Apache2::Const::OK;
 }
 
+sub spool_jacket {
+    my $r = shift;
+    my $cgi = new CGI;
+
+    my $auth = $cgi->param('ses') || $cgi->cookie('ses');
+    my $user = verify_login($auth);
+    my $perm_check = verify_permission($auth, $user, $user->ws_ou, ['UPLOAD_COVER_IMAGE']);
+
+    unless($user) {
+        $logger->error("spool_jacket: authentication failed on jacket image import: $auth");
+        print '"session not found"';
+        return Apache2::Const::OK;
+    }
+    unless($perm_check) {
+        $logger->error("spool_jacket: authorization failed on jacket image import: $auth");
+        print '"permission denied"';
+        return Apache2::Const::OK;
+    }
+
+    my $ses = OpenSRF::AppSession->create('open-ils.cstore');
+    my $compression_flag = $ses->request( 'open-ils.cstore.direct.config.global_flag.retrieve', 'opac.cover_upload_compression' )->gather(1);
+    my $compression_level = ($compression_flag && OpenILS::Application::AppUtils->is_true($compression_flag->enabled)) ? $compression_flag->value : -1;
+    if ($compression_level < -1 || $compression_level > 9) {
+        $r->content_type('text/plain; charset=utf-8');
+        print '"invalid compression level"';
+        return Apache2::Const::OK;
+    }
+    $logger->debug("spool_jacket: PNG compression set to $compression_level");
+
+    my $max_jacket_size = OpenILS::Application::AppUtils->ou_ancestor_setting_value($user->ws_ou, 'opac.cover_upload_max_file_size') || $MAX_JACKET_SIZE;
+
+    my $infile = $cgi->param('jacket_upload') || '';
+    my $bib_record = $cgi->param('bib_record') || '';
+    unless ($bib_record =~ /^-?\d+$/) {
+        $logger->error("spool_jacket: passed bib_record = $bib_record");
+        $r->content_type('text/plain; charset=utf-8');
+        print '"bib not found"';
+        return Apache2::Const::OK;
+    }
+
+    $logger->debug("infile = $infile, bib_record = $bib_record");
+
+    my $conf = OpenSRF::Utils::SettingsClient->new;
+    my $dir = $conf->config_value(
+        apps => 'open-ils.vandelay' => app_settings => databases => 'jackets');
+
+    unless(-w $dir) { # FIXME: good or bad idea to fallback to /openils/var/web/opac/extracs/ac if opensrf.xml is not updated?
+        $logger->error("spool_jacket: We need some place to store our jacket files");
+        print '"jacket location not configured"';
+        return Apache2::Const::OK;
+    }
+
+    if($infile and -e $infile) {
+        my $memcache = OpenSRF::Utils::Cache->new('global');
+
+        my ($total_bytes, $buf, $bytes) = (0);
+        my $outfile_large = "$dir/jacket/large/r/$bib_record";
+        my $outfile_medium = "$dir/jacket/medium/r/$bib_record";
+        my $outfile_small = "$dir/jacket/small/r/$bib_record";
+
+        unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
+            $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
+            return Apache2::Const::FORBIDDEN;
+            print '"unable to open file for writing"';
+            return Apache2::Const::OK;
+        }
+
+        while($bytes = sysread($infile, $buf, $FILE_READ_SIZE)) {
+            $total_bytes += $bytes;
+            if($total_bytes >= $max_jacket_size) {
+                close(OUTFILE_LARGE);
+                unlink $outfile_large . ".temp";
+                $logger->error("spool_jacket: import exceeded upload size: $max_jacket_size");
+                print '"file too large"';
+                return Apache2::Const::OK;
+            }
+            print OUTFILE_LARGE $buf;
+        }
+
+        close(OUTFILE_LARGE);
+
+        my $image;
+        eval { $image = GD::Image->newFromPng("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromJpeg("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromGif("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromXpm("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromWBMP("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromXbm("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromGd("$outfile_large.temp"); };
+        eval { $image = $image || GD::Image->newFromGd2("$outfile_large.temp"); };
+        unless ($image) {
+            unlink $outfile_large . ".temp";
+            $logger->error("spool_jacket: unable to parse $outfile_large.temp");
+            $r->content_type('text/plain; charset=utf-8');
+            print '"parse error"';
+            return Apache2::Const::OK;
+        }
+
+        my ($image_width, $image_height) = $image->getBounds();
+
+        #### resizing for small
+
+        my $target_width = 55; # FIXME: get these from settings, but for now, using customer desired width and aspect ratio observed from OpenLibrary
+        my $target_height = 91;
+
+        my $width_ratio = $target_width / $image_width;
+        my $height_ratio = $target_height / $image_height;
+
+        my $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
+
+        my ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
+
+        my $new_image = $image->copyScaleInterpolated($new_width, $new_height);
+
+        unless(open(OUTFILE_SMALL, ">$outfile_small.temp")) {
+            $logger->error("spool_jacket: unable to open jacket file [$outfile_small.temp] for writing: $@");
+            print '"unable to open file for writing"';
+            return Apache2::Const::OK;
+        }
+        print OUTFILE_SMALL $new_image->png($compression_level);
+        close(OUTFILE_SMALL);
+
+        #### resizing for medium
+
+        $target_width = 120;
+        $target_height = 200;
+
+        $width_ratio = $target_width / $image_width;
+        $height_ratio = $target_height / $image_height;
+
+        $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
+
+        ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
+
+        $new_image = $image->copyScaleInterpolated($new_width, $new_height);
+
+        unless(open(OUTFILE_MEDIUM, ">$outfile_medium.temp")) {
+            $logger->error("spool_jacket: unable to open jacket file [$outfile_medium.temp] for writing: $@");
+            print '"unable to open file for writing"';
+            return Apache2::Const::OK;
+        }
+        print OUTFILE_MEDIUM $new_image->png($compression_level);
+        close(OUTFILE_MEDIUM);
+
+        #### resizing for large
+
+        $target_width = 475;
+        $target_height = 787;
+
+        $width_ratio = $target_width / $image_width;
+        $height_ratio = $target_height / $image_height;
+
+        $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
+
+        ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
+
+        $new_image = $image->copyScaleInterpolated($new_width, $new_height);
+
+        unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
+            $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
+            print "'unable to open file for writing'\n";
+            return Apache2::Const::OK;
+        }
+        print OUTFILE_LARGE $new_image->png($compression_level);
+        close(OUTFILE_LARGE);
+
+        #### renaming temp files to final images
+
+        rename "$outfile_small.temp", $outfile_small;
+        rename "$outfile_medium.temp", $outfile_medium;
+        rename "$outfile_large.temp", $outfile_large;
+
+        #### clearing memcache
+
+        my @jacket_sizes = ('large','medium','small');
+        foreach my $size (@jacket_sizes) {
+            my $key = "ac.jacket.$size.record_$bib_record";
+            $memcache->delete_cache($key);
+        }
+
+    } else {
+        $logger->error("spool_jacket: image not uploaded? check form action and encoding");
+        print '"upload error"';
+        return Apache2::Const::OK;
+    }
+
+    $logger->info("spool_jacket: uploaded jacket file for record $bib_record");
+    $r->content_type('text/plain; charset=utf-8');
+    print "1";
+    return Apache2::Const::OK;
+}
+
 sub verify_login {
         my $auth_token = shift;
         return undef unless $auth_token;
@@ -134,4 +328,15 @@ sub verify_login {
         return undef;
 }
 
+sub verify_permission { # FIXME: could refactor these verify_ subs in WWW/
+    my ($token, $user, $org_unit, $permissions) = @_;
+
+    my $failures = OpenSRF::AppSession
+        ->create('open-ils.actor')
+        ->request('open-ils.actor.user.perm.check', $token, $user->id, $org_unit, $permissions)
+        ->gather(1);
+
+    return !scalar(@$failures);
+}
+
 1;