]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/AddedContent/OpenLibrary.pm
Merge branch 'master' of git://git.evergreen-ils.org/Evergreen into ttopac
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / AddedContent / OpenLibrary.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2009 David Christensen <david.a.christensen@gmail.com>
3 # Copyright (C) 2009-2011 Dan Scott <dscott@laurentian.ca>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16 package OpenILS::WWW::AddedContent::OpenLibrary;
17 use strict; use warnings;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils::SettingsParser;
20 use OpenILS::WWW::AddedContent;
21 use OpenSRF::Utils::JSON;
22 use OpenSRF::EX qw/:try/;
23 use Data::Dumper;
24
25 # Edit the <added_content> section of /openils/conf/opensrf.xml
26 # Change <module> to:
27 #   <module>OpenILS::WWW::AddedContent::OpenLibrary</module>
28
29 my $AC = 'OpenILS::WWW::AddedContent';
30
31 # This should work for most setups
32 my $blank_img = 'http://localhost/opac/images/blank.png';
33
34 # This URL is always the same for OpenLibrary, so there's no advantage to
35 # pulling from opensrf.xml
36
37 my $read_api = 'http://openlibrary.org/api/volumes/brief/json/';
38
39 sub new {
40     my( $class, $args ) = @_;
41     $class = ref $class || $class;
42     return bless($args, $class);
43 }
44
45 # --------------------------------------------------------------------------
46 sub jacket_small {
47     my( $self, $key ) = @_;
48     return $self->send_img(
49         $self->fetch_cover_response('small', $key));
50 }
51
52 sub jacket_medium {
53     my( $self, $key ) = @_;
54     return $self->send_img(
55         $self->fetch_cover_response('medium', $key));
56
57 }
58 sub jacket_large {
59     my( $self, $key ) = @_;
60     return $self->send_img(
61         $self->fetch_cover_response('large', $key));
62 }
63
64 # --------------------------------------------------------------------------
65
66 sub ebooks_html {
67     my( $self, $key ) = @_;
68     my $book_data_json = $self->fetch_response($key);
69
70     $logger->debug("$key: " . $book_data_json);
71
72     my $ebook_html;
73     
74     my $book_data = OpenSRF::Utils::JSON->JSON2perl($book_data_json);
75     my $book_key = (keys %$book_data)[0];
76
77     # We didn't find a matching book; short-circuit our response
78     if (!$book_key) {
79         $logger->debug("$key: no found book");
80         return 0;
81     }
82
83     my $ebooks_json = $book_data->{$book_key}->{ebooks};
84
85     # No ebooks are available for this book; short-circuit
86     if (!$ebooks_json or !scalar(@$ebooks_json)) {
87         $logger->debug("$key: no ebooks");
88         return 0;
89     }
90
91     # Check the availability of the ebooks
92     my $available = $ebooks_json->[0]->{'availability'} || '';
93     if (!$available) {
94         $logger->debug("$key: no available ebooks");
95         return 0;
96     }
97
98     # Build a basic list of available ebook types and their URLs
99     # ebooks appears to be an array containing one element - a hash
100
101     # First element of the hash is 'read_url' which is a URL to
102     # Internet Archive online reader
103     my $stream_url = $ebooks_json->[0]->{'read_url'} || '';
104     if ($stream_url) {
105         $ebook_html .= "<li class='ebook_stream'><a href='$stream_url'>Read online</a></li>\n";
106         $logger->debug("$key: stream URL = $stream_url");
107     }
108
109     my $ebook_formats = $ebooks_json->[0]->{'formats'} || '';
110     # Next elements are various ebook formats that are available
111     foreach my $ebook (keys %{$ebook_formats}) {
112         if ($ebook_formats->{$ebook} eq 'read_url') {
113             next;
114         }
115         $ebook_html .= "<li class='ebook_$ebook'><a href='" . 
116             $ebook_formats->{$ebook}->{'url'} . "'>" . uc($ebook) . "</a></li>\n";
117     }
118
119     $logger->debug("$key: $ebook_html");
120     $self->send_html("<ul class='ebooks'>$ebook_html</ul>");
121 }
122
123 sub excerpt_html {
124     my( $self, $key ) = @_;
125
126     my $excerpt_html;
127
128     my $content = $self->fetch_details_response($key)->content();
129
130     my $first_sentence = $content->{first_sentence};
131     if ($first_sentence) {
132         $excerpt_html .= "<div class='sentence1'>$first_sentence</div>\n";
133     }
134
135     my $excerpts_json = $content->{excerpts};
136     if ($excerpts_json && scalar(@$excerpts_json)) {
137         # Load up excerpt text with comments in tooltip
138         foreach my $excerpt (@$excerpts_json) {
139             my $text = $excerpt->{text};
140             my $cmnt = $excerpt->{comment};
141             $excerpt_html .= "<div class='ac_excerpt' title='$text'>$cmnt</div>\n";
142         }
143     }
144
145     if (!$excerpt_html) {
146         return 0;
147     }
148
149     $logger->debug("$key: $excerpt_html");
150     $self->send_html("<div class='ac_excerpts'>$excerpt_html</div>");
151 }
152
153 =head1
154
155 OpenLibrary returns a JSON hash of zero or more book responses matching our
156 request. Each response may contain a table of contents within the details
157 section of the response.
158
159 For now, we check only the first response in the hash for a table of
160 contents, and if we find a table of contents, we transform it to a simple
161 HTML table.
162
163 =cut
164
165 sub toc_html {
166     my( $self, $key ) = @_;
167
168     my $toc_html;
169     
170     my $book_data = $self->fetch_data_response($key) || return 0;
171
172     my $toc_json = $book_data->{table_of_contents};
173
174     # No table of contents is available for this book; short-circuit
175     if (!$toc_json or !scalar(@$toc_json)) {
176         $logger->debug("$key: no TOC");
177         return 0;
178     }
179
180     # Build a basic HTML table containing the section number, section title,
181     # and page number. Some rows may not contain section numbers, we should
182     # protect against empty page numbers too.
183     foreach my $chapter (@$toc_json) {
184         my $label = $chapter->{label};
185         if ($label) {
186             $label .= '. ';
187         }
188         my $title = $chapter->{title} || '';
189         my $page_number = $chapter->{pagenum} || '';
190  
191         $toc_html .= '<tr>' .
192             "<td class='toc_label'>$label</td>" .
193             "<td class='toc_title'>$title</td>" .
194             "<td class='toc_page'>$page_number</td>" .
195             "</tr>\n";
196     }
197
198     $logger->debug("$key: $toc_html");
199     $self->send_html("<table>$toc_html</table>");
200 }
201
202 sub toc_json {
203     my( $self, $key ) = @_;
204     my $toc = $self->send_json(
205         $self->fetch_response($key)
206     );
207 }
208
209 sub send_img {
210     my($self, $response) = @_;
211     return { 
212         content_type => $response->header('Content-type'),
213         content => $response->content, 
214         binary => 1 
215     };
216 }
217
218 sub send_json {
219     my( $self, $content ) = @_;
220     return 0 unless $content;
221
222     return { content_type => 'text/plain', content => $content };
223 }
224
225 sub send_html {
226     my( $self, $content ) = @_;
227     return 0 unless $content;
228
229     # Hide anything that might contain a link since it will be broken
230     my $HTML = <<"    HTML";
231         <div>
232             <style type='text/css'>
233                 div.ac input, div.ac a[href],div.ac img, div.ac button { display: none; visibility: hidden }
234             </style>
235             <div class='ac'>
236                 $content
237             </div>
238         </div>
239     HTML
240
241     return { content_type => 'text/html', content => $HTML };
242 }
243
244 # proxy OpenLibrary requests so that the IP address of the library
245 # can be used to determine access rights to materials
246 sub proxy_json {
247     my( $self, $key ) = @_;
248
249     my $url = $read_api . $key;
250     $logger->debug("proxy_json with key '$key', url $url");
251
252     $self->send_json($AC->get_url($url)->content());
253 }
254
255
256 # returns the HTTP response object from the URL fetch
257 sub fetch_response {
258     my( $self, $key ) = @_;
259
260     # TODO: OpenLibrary can also accept lccn, oclc, olid...
261     # Hardcoded to only accept ISBNs for now.
262     $key = "isbn:$key";
263
264     my $url = $read_api . $key;
265     my $response = $AC->get_url($url)->content();
266
267     $logger->debug("$key: response was $response");
268
269     my $book_results = OpenSRF::Utils::JSON->JSON2perl($response);
270     my $record = $book_results->{$key};
271
272     # We didn't find a matching book; short-circuit our response
273     if (!$record) {
274         $logger->debug("$key: no found record");
275         return 0;
276     }
277
278     return $record;
279 }
280
281 sub fetch_data_response {
282     my ($self, $key) = @_;
283
284     my $book_results = $self->fetch_response($key);
285
286     my $book_key = (keys %{$book_results->{records}})[0];
287
288     $logger->debug("$key: using record key $book_key");
289
290     # We didn't find a matching book; short-circuit our response
291     if (!$book_key || !$book_results->{records}->{$book_key}->{data}) {
292         $logger->debug("$key: no found book");
293         return 0;
294     }
295
296     return $book_results->{records}->{$book_key}->{data};
297 }
298
299
300 sub fetch_details_response {
301     my ($self, $key) = @_;
302
303     my $book_results = $self->fetch_response($key);
304
305     my $book_key = (keys %{$book_results->{records}})[0];
306
307     $logger->debug("$key: using record key $book_key");
308
309     # We didn't find a matching book; short-circuit our response
310     if (!$book_key) {
311         $logger->debug("$key: no found book");
312         return 0;
313     }
314
315     return $book_results->{$book_key}->{details};
316 }
317
318 sub fetch_items_response {
319     my ($self, $key) = @_;
320
321     my $book_results = $self->fetch_response($key) || return 0;
322
323     my $items = $book_results->{items};
324
325     # We didn't find a matching book; short-circuit our response
326     if (!$items || scalar(@$items) == 0) {
327         $logger->debug("$key: no found items");
328         return 0;
329     }
330
331     return $book_results->{items};
332 }
333
334 # returns a cover image from the list of associated items
335 # TODO: Look for the best match in the array of items
336 sub fetch_cover_response {
337     my( $self, $size, $key ) = @_;
338
339     my $cover;
340
341     my $response = $self->fetch_response($key);
342
343     # Short-circuit if we get an empty response, or a response
344     #with no matching records
345     if (!$response or scalar(keys %$response) == 0) {
346         return $AC->get_url($blank_img);
347     }
348
349     # Try to return a cover image from the record->data metadata
350     foreach my $rec_key (keys %{$response->{records}}) {
351         my $record = $response->{records}->{$rec_key};
352         if (exists $record->{data}->{cover}->{$size}) {
353             $cover = $record->{data}->{cover}->{$size};
354         }
355         if ($cover) {
356             return $AC->get_url($cover);
357         }
358     }
359
360     # If we didn't find a cover in the record metadata, look in the items
361     # Seems unlikely, but might as well try.
362     my $items = $response->{items};
363
364     $logger->debug("$key: items request got " . scalar(@$items) . " items back");
365
366     foreach my $item (@$items) {
367         if (exists $item->{cover}->{$size}) {
368             return $AC->get_url($item->{cover}->{$size}) || 0;
369         }
370     }
371
372     $logger->debug("$key: no covers for this book");
373
374     # Return a blank image
375     return $AC->get_url($blank_img);
376 }
377
378 1;