1 package OpenILS::WWW::Vandelay;
7 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);
8 use APR::Const -compile => qw(:error SUCCESS);
11 use Apache2::RequestRec ();
12 use Apache2::RequestIO ();
13 use Apache2::RequestUtil;
19 use OpenSRF::EX qw(:try);
20 use OpenSRF::Utils::Cache;
22 use OpenSRF::AppSession;
25 use OpenILS::Utils::Fieldmapper;
26 use OpenSRF::Utils::Logger qw/$logger/;
29 use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
32 use Digest::MD5 qw/md5_hex/;
33 use OpenSRF::Utils::SettingsClient;
35 use UNIVERSAL::require;
37 our @formats = qw/USMARC UNIMARC XML BRE/;
38 my $MAX_FILE_SIZE = 10737418240; #10G
39 my $MAX_JACKET_SIZE = 10737418240; #10G
40 my $FILE_READ_SIZE = 4096;
42 # set the bootstrap config and template include directory when
43 # this module is loaded
53 OpenSRF::System->bootstrap_client( config_file => $bootstrap );
54 return Apache2::Const::OK;
61 my $auth = $cgi->param('ses') || $cgi->cookie('ses') || $cgi->cookie('eg.auth.token');
62 if ($auth =~ /^"(.+)"$/) {
66 unless(verify_login($auth)) {
67 $logger->error("authentication failed on vandelay record import: $auth");
68 return Apache2::Const::FORBIDDEN;
71 my $data_fingerprint = '';
72 my $purpose = $cgi->param('purpose') || '';
73 my $infile = $cgi->param('marc_upload') || '';
74 my $bib_source = $cgi->param('bib_source') || '';
76 $logger->debug("purpose = $purpose, infile = $infile, bib_source = $bib_source");
78 my $conf = OpenSRF::Utils::SettingsClient->new;
79 my $dir = $conf->config_value(
80 apps => 'open-ils.vandelay' => app_settings => databases => 'importer');
83 $logger->error("We need some place to store our MARC files");
84 return Apache2::Const::FORBIDDEN;
87 if($infile and -e $infile) {
88 my ($total_bytes, $buf, $bytes) = (0);
89 $data_fingerprint = md5_hex(time."$$".rand());
90 my $outfile = "$dir/$data_fingerprint.mrc";
92 unless(open(OUTFILE, ">$outfile")) {
93 $logger->error("unable to open MARC file [$outfile] for writing: $@");
94 return Apache2::Const::FORBIDDEN;
97 while($bytes = sysread($infile, $buf, $FILE_READ_SIZE)) {
98 $total_bytes += $bytes;
99 if($total_bytes >= $MAX_FILE_SIZE) {
102 $logger->error("import exceeded upload size: $MAX_FILE_SIZE");
103 return Apache2::Const::FORBIDDEN;
110 OpenSRF::Utils::Cache->new->put_cache(
111 'vandelay_import_spool_' . $data_fingerprint,
112 { purpose => $purpose,
114 bib_source => $bib_source,
119 $logger->info("uploaded MARC batch with key $data_fingerprint");
120 $r->content_type('text/plain; charset=utf-8');
121 print "$data_fingerprint";
122 return Apache2::Const::OK;
129 my $auth = $cgi->param('ses') || $cgi->cookie('ses') || $cgi->cookie('eg.auth.token');
130 if ($auth =~ /^"(.+)"$/) {
133 my $user = verify_login($auth);
134 my $perm_check = verify_permission($auth, $user, $user->ws_ou, ['UPLOAD_COVER_IMAGE']);
137 $logger->error("spool_jacket: authentication failed on jacket image import: $auth");
138 print '"session not found"';
139 return Apache2::Const::OK;
141 unless($perm_check) {
142 $logger->error("spool_jacket: authorization failed on jacket image import: $auth");
143 print '"permission denied"';
144 return Apache2::Const::OK;
147 my $ses = OpenSRF::AppSession->create('open-ils.cstore');
148 my $compression_flag = $ses->request( 'open-ils.cstore.direct.config.global_flag.retrieve', 'opac.cover_upload_compression' )->gather(1);
149 my $compression_level = ($compression_flag && OpenILS::Application::AppUtils->is_true($compression_flag->enabled)) ? $compression_flag->value : -1;
150 if ($compression_level < -1 || $compression_level > 9) {
151 $r->content_type('text/plain; charset=utf-8');
152 print '"invalid compression level"';
153 return Apache2::Const::OK;
155 $logger->debug("spool_jacket: PNG compression set to $compression_level");
157 my $max_jacket_size = OpenILS::Application::AppUtils->ou_ancestor_setting_value($user->ws_ou, 'opac.cover_upload_max_file_size') || $MAX_JACKET_SIZE;
159 my $infile = $cgi->param('jacket_upload') || '';
160 my $bib_record = $cgi->param('bib_record') || '';
161 unless ($bib_record =~ /^-?\d+$/) {
162 $logger->error("spool_jacket: passed bib_record = $bib_record");
163 $r->content_type('text/plain; charset=utf-8');
164 print '"bib not found"';
165 return Apache2::Const::OK;
168 $logger->debug("infile = $infile, bib_record = $bib_record");
170 my $conf = OpenSRF::Utils::SettingsClient->new;
171 my $dir = $conf->config_value(
172 apps => 'open-ils.vandelay' => app_settings => databases => 'jackets');
174 unless(-w $dir) { # FIXME: good or bad idea to fallback to /openils/var/web/opac/extracs/ac if opensrf.xml is not updated?
175 $logger->error("spool_jacket: We need some place to store our jacket files");
176 print '"jacket location not configured"';
177 return Apache2::Const::OK;
180 if($infile and -e $infile) {
181 my $memcache = OpenSRF::Utils::Cache->new('global');
183 my ($total_bytes, $buf, $bytes) = (0);
184 my $outfile_large = "$dir/jacket/large/r/$bib_record";
185 my $outfile_medium = "$dir/jacket/medium/r/$bib_record";
186 my $outfile_small = "$dir/jacket/small/r/$bib_record";
188 unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
189 $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
190 return Apache2::Const::FORBIDDEN;
191 print '"unable to open file for writing"';
192 return Apache2::Const::OK;
195 while($bytes = sysread($infile, $buf, $FILE_READ_SIZE)) {
196 $total_bytes += $bytes;
197 if($total_bytes >= $max_jacket_size) {
198 close(OUTFILE_LARGE);
199 unlink $outfile_large . ".temp";
200 $logger->error("spool_jacket: import exceeded upload size: $max_jacket_size");
201 print '"file too large"';
202 return Apache2::Const::OK;
204 print OUTFILE_LARGE $buf;
207 close(OUTFILE_LARGE);
210 eval { $image = GD::Image->newFromPng("$outfile_large.temp"); };
211 eval { $image = $image || GD::Image->newFromJpeg("$outfile_large.temp"); };
212 eval { $image = $image || GD::Image->newFromGif("$outfile_large.temp"); };
213 eval { $image = $image || GD::Image->newFromXpm("$outfile_large.temp"); };
214 eval { $image = $image || GD::Image->newFromWBMP("$outfile_large.temp"); };
215 eval { $image = $image || GD::Image->newFromXbm("$outfile_large.temp"); };
216 eval { $image = $image || GD::Image->newFromGd("$outfile_large.temp"); };
217 eval { $image = $image || GD::Image->newFromGd2("$outfile_large.temp"); };
219 unlink $outfile_large . ".temp";
220 $logger->error("spool_jacket: unable to parse $outfile_large.temp");
221 $r->content_type('text/plain; charset=utf-8');
222 print '"parse error"';
223 return Apache2::Const::OK;
226 my ($image_width, $image_height) = $image->getBounds();
228 #### resizing for small
230 my $target_width = 55; # FIXME: get these from settings, but for now, using customer desired width and aspect ratio observed from OpenLibrary
231 my $target_height = 91;
233 my $width_ratio = $target_width / $image_width;
234 my $height_ratio = $target_height / $image_height;
236 my $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
238 my ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
240 my $new_image = $image->copyScaleInterpolated($new_width, $new_height);
242 unless(open(OUTFILE_SMALL, ">$outfile_small.temp")) {
243 $logger->error("spool_jacket: unable to open jacket file [$outfile_small.temp] for writing: $@");
244 print '"unable to open file for writing"';
245 return Apache2::Const::OK;
247 print OUTFILE_SMALL $new_image->png($compression_level);
248 close(OUTFILE_SMALL);
250 #### resizing for medium
253 $target_height = 200;
255 $width_ratio = $target_width / $image_width;
256 $height_ratio = $target_height / $image_height;
258 $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
260 ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
262 $new_image = $image->copyScaleInterpolated($new_width, $new_height);
264 unless(open(OUTFILE_MEDIUM, ">$outfile_medium.temp")) {
265 $logger->error("spool_jacket: unable to open jacket file [$outfile_medium.temp] for writing: $@");
266 print '"unable to open file for writing"';
267 return Apache2::Const::OK;
269 print OUTFILE_MEDIUM $new_image->png($compression_level);
270 close(OUTFILE_MEDIUM);
272 #### resizing for large
275 $target_height = 787;
277 $width_ratio = $target_width / $image_width;
278 $height_ratio = $target_height / $image_height;
280 $best_ratio = $width_ratio < $height_ratio ? $width_ratio : $height_ratio;
282 ($new_width, $new_height) = ($image_width * $best_ratio, $image_height * $best_ratio);
284 $new_image = $image->copyScaleInterpolated($new_width, $new_height);
286 unless(open(OUTFILE_LARGE, ">$outfile_large.temp")) {
287 $logger->error("spool_jacket: unable to open jacket file [$outfile_large.temp] for writing: $@");
288 print "'unable to open file for writing'\n";
289 return Apache2::Const::OK;
291 print OUTFILE_LARGE $new_image->png($compression_level);
292 close(OUTFILE_LARGE);
294 #### renaming temp files to final images
296 rename "$outfile_small.temp", $outfile_small;
297 rename "$outfile_medium.temp", $outfile_medium;
298 rename "$outfile_large.temp", $outfile_large;
300 #### clearing memcache
302 my @jacket_sizes = ('large','medium','small');
303 foreach my $size (@jacket_sizes) {
304 my $key = "ac.jacket.$size.record_$bib_record";
305 $memcache->delete_cache($key);
309 $logger->error("spool_jacket: image not uploaded? check form action and encoding");
310 print '"upload error"';
311 return Apache2::Const::OK;
314 $logger->info("spool_jacket: uploaded jacket file for record $bib_record");
315 $r->content_type('text/plain; charset=utf-8');
317 return Apache2::Const::OK;
321 my $auth_token = shift;
322 return undef unless $auth_token;
324 my $user = OpenSRF::AppSession
325 ->create("open-ils.auth")
326 ->request( "open-ils.auth.session.retrieve", $auth_token )
329 if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
333 return $user if ref($user);
337 sub verify_permission { # FIXME: could refactor these verify_ subs in WWW/
338 my ($token, $user, $org_unit, $permissions) = @_;
340 my $failures = OpenSRF::AppSession
341 ->create('open-ils.actor')
342 ->request('open-ils.actor.user.perm.check', $token, $user->id, $org_unit, $permissions)
345 return !scalar(@$failures);