1 # ---------------------------------------------------------------
2 # Copyright (C) 2009 David Christensen <david.a.christensen@gmail.com>
3 # Copyright (C) 2009-2011 Dan Scott <dscott@laurentian.ca>
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.
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 # ---------------------------------------------------------------
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/;
25 # Edit the <added_content> section of /openils/conf/opensrf.xml
27 # <module>OpenILS::WWW::AddedContent::OpenLibrary</module>
29 my $AC = 'OpenILS::WWW::AddedContent';
31 # This should work for most setups
32 my $blank_img = 'http://localhost/opac/images/blank.png';
34 # This URL is always the same for OpenLibrary, so there's no advantage to
35 # pulling from opensrf.xml
37 my $read_api = 'http://openlibrary.org/api/volumes/brief/json/';
40 my( $class, $args ) = @_;
41 $class = ref $class || $class;
42 return bless($args, $class);
45 # --------------------------------------------------------------------------
47 my( $self, $key ) = @_;
48 return $self->send_img(
49 $self->fetch_cover_response('small', $key));
53 my( $self, $key ) = @_;
54 return $self->send_img(
55 $self->fetch_cover_response('medium', $key));
59 my( $self, $key ) = @_;
60 return $self->send_img(
61 $self->fetch_cover_response('large', $key));
64 # --------------------------------------------------------------------------
67 my( $self, $key ) = @_;
68 my $book_data_json = $self->fetch_response($key);
70 $logger->debug("$key: " . $book_data_json);
74 my $book_data = OpenSRF::Utils::JSON->JSON2perl($book_data_json);
75 my $book_key = (keys %$book_data)[0];
77 # We didn't find a matching book; short-circuit our response
79 $logger->debug("$key: no found book");
83 my $ebooks_json = $book_data->{$book_key}->{ebooks};
85 # No ebooks are available for this book; short-circuit
86 if (!$ebooks_json or !scalar(@$ebooks_json)) {
87 $logger->debug("$key: no ebooks");
91 # Check the availability of the ebooks
92 my $available = $ebooks_json->[0]->{'availability'} || '';
94 $logger->debug("$key: no available ebooks");
98 # Build a basic list of available ebook types and their URLs
99 # ebooks appears to be an array containing one element - a hash
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'} || '';
105 $ebook_html .= "<li class='ebook_stream'><a href='$stream_url'>Read online</a></li>\n";
106 $logger->debug("$key: stream URL = $stream_url");
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') {
115 $ebook_html .= "<li class='ebook_$ebook'><a href='" .
116 $ebook_formats->{$ebook}->{'url'} . "'>" . uc($ebook) . "</a></li>\n";
119 $logger->debug("$key: $ebook_html");
120 $self->send_html("<ul class='ebooks'>$ebook_html</ul>");
124 my( $self, $key ) = @_;
128 my $content = $self->fetch_details_response($key)->content();
130 my $first_sentence = $content->{first_sentence};
131 if ($first_sentence) {
132 $excerpt_html .= "<div class='sentence1'>$first_sentence</div>\n";
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";
145 if (!$excerpt_html) {
149 $logger->debug("$key: $excerpt_html");
150 $self->send_html("<div class='ac_excerpts'>$excerpt_html</div>");
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.
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
166 my( $self, $key ) = @_;
170 my $book_data = $self->fetch_data_response($key) || return 0;
172 my $toc_json = $book_data->{table_of_contents};
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");
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};
188 my $title = $chapter->{title} || '';
189 my $page_number = $chapter->{pagenum} || '';
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>" .
198 $logger->debug("$key: $toc_html");
199 $self->send_html("<table>$toc_html</table>");
203 my( $self, $key ) = @_;
204 my $toc = $self->send_json(
205 $self->fetch_response($key)
210 my($self, $response) = @_;
212 content_type => $response->header('Content-type'),
213 content => $response->content,
219 my( $self, $content ) = @_;
220 return 0 unless $content;
222 return { content_type => 'text/plain', content => $content };
226 my( $self, $content ) = @_;
227 return 0 unless $content;
229 # Hide anything that might contain a link since it will be broken
230 my $HTML = <<" HTML";
232 <style type='text/css'>
233 div.ac input, div.ac a[href],div.ac img, div.ac button { display: none; visibility: hidden }
241 return { content_type => 'text/html', content => $HTML };
244 # proxy OpenLibrary requests so that the IP address of the library
245 # can be used to determine access rights to materials
247 my( $self, $key ) = @_;
249 my $url = $read_api . $key;
250 $logger->debug("proxy_json with key '$key', url $url");
252 $self->send_json($AC->get_url($url)->content());
256 # returns the HTTP response object from the URL fetch
258 my( $self, $key ) = @_;
260 # TODO: OpenLibrary can also accept lccn, oclc, olid...
261 # Hardcoded to only accept ISBNs for now.
264 my $url = $read_api . $key;
265 my $response = $AC->get_url($url)->content();
267 $logger->debug("$key: response was $response");
269 my $book_results = OpenSRF::Utils::JSON->JSON2perl($response);
270 my $record = $book_results->{$key};
272 # We didn't find a matching book; short-circuit our response
274 $logger->debug("$key: no found record");
281 sub fetch_data_response {
282 my ($self, $key) = @_;
284 my $book_results = $self->fetch_response($key);
286 my $book_key = (keys %{$book_results->{records}})[0];
288 $logger->debug("$key: using record key $book_key");
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");
296 return $book_results->{records}->{$book_key}->{data};
300 sub fetch_details_response {
301 my ($self, $key) = @_;
303 my $book_results = $self->fetch_response($key);
305 my $book_key = (keys %{$book_results->{records}})[0];
307 $logger->debug("$key: using record key $book_key");
309 # We didn't find a matching book; short-circuit our response
311 $logger->debug("$key: no found book");
315 return $book_results->{$book_key}->{details};
318 sub fetch_items_response {
319 my ($self, $key) = @_;
321 my $book_results = $self->fetch_response($key) || return 0;
323 my $items = $book_results->{items};
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");
331 return $book_results->{items};
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 ) = @_;
341 my $response = $self->fetch_response($key);
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);
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};
356 return $AC->get_url($cover);
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};
364 $logger->debug("$key: items request got " . scalar(@$items) . " items back");
366 foreach my $item (@$items) {
367 if (exists $item->{cover}->{$size}) {
368 return $AC->get_url($item->{cover}->{$size}) || 0;
372 $logger->debug("$key: no covers for this book");
374 # Return a blank image
375 return $AC->get_url($blank_img);