]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/AddedContent/OpenLibrary.pm
Working TOC HTML and cover images from OpenLibrary Read API
[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 URL is always the same for OpenLibrary, so there's no advantage to
32 # pulling from opensrf.xml
33
34 my $read_api = 'http://openlibrary.org/api/volumes/brief/json/';
35
36 sub new {
37     my( $class, $args ) = @_;
38     $class = ref $class || $class;
39     return bless($args, $class);
40 }
41
42 # --------------------------------------------------------------------------
43 sub jacket_small {
44     my( $self, $key ) = @_;
45     return $self->send_img(
46         $self->fetch_cover_response('small', $key));
47 }
48
49 sub jacket_medium {
50     my( $self, $key ) = @_;
51     return $self->send_img(
52         $self->fetch_cover_response('medium', $key));
53
54 }
55 sub jacket_large {
56     my( $self, $key ) = @_;
57     return $self->send_img(
58         $self->fetch_cover_response('large', $key));
59 }
60
61 # --------------------------------------------------------------------------
62
63 sub ebooks_html {
64     my( $self, $key ) = @_;
65     my $book_data_json = $self->fetch_response($key);
66
67     $logger->debug("$key: " . $book_data_json);
68
69     my $ebook_html;
70     
71     my $book_data = OpenSRF::Utils::JSON->JSON2perl($book_data_json);
72     my $book_key = (keys %$book_data)[0];
73
74     # We didn't find a matching book; short-circuit our response
75     if (!$book_key) {
76         $logger->debug("$key: no found book");
77         return 0;
78     }
79
80     my $ebooks_json = $book_data->{$book_key}->{ebooks};
81
82     # No ebooks are available for this book; short-circuit
83     if (!$ebooks_json or !scalar(@$ebooks_json)) {
84         $logger->debug("$key: no ebooks");
85         return 0;
86     }
87
88     # Check the availability of the ebooks
89     my $available = $ebooks_json->[0]->{'availability'} || '';
90     if (!$available) {
91         $logger->debug("$key: no available ebooks");
92         return 0;
93     }
94
95     # Build a basic list of available ebook types and their URLs
96     # ebooks appears to be an array containing one element - a hash
97
98     # First element of the hash is 'read_url' which is a URL to
99     # Internet Archive online reader
100     my $stream_url = $ebooks_json->[0]->{'read_url'} || '';
101     if ($stream_url) {
102         $ebook_html .= "<li class='ebook_stream'><a href='$stream_url'>Read online</a></li>\n";
103         $logger->debug("$key: stream URL = $stream_url");
104     }
105
106     my $ebook_formats = $ebooks_json->[0]->{'formats'} || '';
107     # Next elements are various ebook formats that are available
108     foreach my $ebook (keys %{$ebook_formats}) {
109         if ($ebook_formats->{$ebook} eq 'read_url') {
110             next;
111         }
112         $ebook_html .= "<li class='ebook_$ebook'><a href='" . 
113             $ebook_formats->{$ebook}->{'url'} . "'>" . uc($ebook) . "</a></li>\n";
114     }
115
116     $logger->debug("$key: $ebook_html");
117     $self->send_html("<ul class='ebooks'>$ebook_html</ul>");
118 }
119
120 sub excerpt_html {
121     my( $self, $key ) = @_;
122
123     my $excerpt_html;
124
125     my $content = $self->fetch_details_response($key)->content();
126
127     my $first_sentence = $content->{first_sentence};
128     if ($first_sentence) {
129         $excerpt_html .= "<div class='sentence1'>$first_sentence</div>\n";
130     }
131
132     my $excerpts_json = $content->{excerpts};
133     if ($excerpts_json && scalar(@$excerpts_json)) {
134         # Load up excerpt text with comments in tooltip
135         foreach my $excerpt (@$excerpts_json) {
136             my $text = $excerpt->{text};
137             my $cmnt = $excerpt->{comment};
138             $excerpt_html .= "<div class='ac_excerpt' title='$text'>$cmnt</div>\n";
139         }
140     }
141
142     if (!$excerpt_html) {
143         return 0;
144     }
145
146     $logger->debug("$key: $excerpt_html");
147     $self->send_html("<div class='ac_excerpts'>$excerpt_html</div>");
148 }
149
150 =head1
151
152 OpenLibrary returns a JSON hash of zero or more book responses matching our
153 request. Each response may contain a table of contents within the details
154 section of the response.
155
156 For now, we check only the first response in the hash for a table of
157 contents, and if we find a table of contents, we transform it to a simple
158 HTML table.
159
160 =cut
161
162 sub toc_html {
163     my( $self, $key ) = @_;
164
165     my $toc_html;
166     
167     my $book_data = $self->fetch_data_response($key) || return 0;
168
169     my $toc_json = $book_data->{table_of_contents};
170
171     # No table of contents is available for this book; short-circuit
172     if (!$toc_json or !scalar(@$toc_json)) {
173         $logger->debug("$key: no TOC");
174         return 0;
175     }
176
177     # Build a basic HTML table containing the section number, section title,
178     # and page number. Some rows may not contain section numbers, we should
179     # protect against empty page numbers too.
180     foreach my $chapter (@$toc_json) {
181         my $label = $chapter->{label};
182         if ($label) {
183             $label .= '. ';
184         }
185         my $title = $chapter->{title} || '';
186         my $page_number = $chapter->{pagenum} || '';
187  
188         $toc_html .= '<tr>' .
189             "<td class='toc_label'>$label</td>" .
190             "<td class='toc_title'>$title</td>" .
191             "<td class='toc_page'>$page_number</td>" .
192             "</tr>\n";
193     }
194
195     $logger->debug("$key: $toc_html");
196     $self->send_html("<table>$toc_html</table>");
197 }
198
199 sub toc_json {
200     my( $self, $key ) = @_;
201     my $toc = $self->send_json(
202         $self->fetch_response($key)
203     );
204 }
205
206 sub send_img {
207     my($self, $response) = @_;
208     return { 
209         content_type => $response->header('Content-type'),
210         content => $response->content, 
211         binary => 1 
212     };
213 }
214
215 sub send_json {
216     my( $self, $content ) = @_;
217     return 0 unless $content;
218
219     return { content_type => 'text/plain', content => $content };
220 }
221
222 sub send_html {
223     my( $self, $content ) = @_;
224     return 0 unless $content;
225
226     # Hide anything that might contain a link since it will be broken
227     my $HTML = <<"    HTML";
228         <div>
229             <style type='text/css'>
230                 div.ac input, div.ac a[href],div.ac img, div.ac button { display: none; visibility: hidden }
231             </style>
232             <div class='ac'>
233                 $content
234             </div>
235         </div>
236     HTML
237
238     return { content_type => 'text/html', content => $HTML };
239 }
240
241 # returns the HTTP response object from the URL fetch
242 sub fetch_response {
243     my( $self, $key ) = @_;
244
245     # TODO: OpenLibrary can also accept lccn, oclc, olid...
246     # Hardcoded to only accept ISBNs for now.
247     $key = "isbn:$key";
248
249     my $url = $read_api . $key;
250     my $response = $AC->get_url($url)->content();
251
252     $logger->debug("$key: response was $response");
253
254     my $book_results = OpenSRF::Utils::JSON->JSON2perl($response);
255     my $record = $book_results->{$key};
256
257     # We didn't find a matching book; short-circuit our response
258     if (!$record) {
259         $logger->debug("$key: no found record");
260         return 0;
261     }
262
263     return $record;
264 }
265
266 sub fetch_data_response {
267     my ($self, $key) = @_;
268
269     my $book_results = $self->fetch_response($key);
270
271     my $book_key = (keys %{$book_results->{records}})[0];
272
273     $logger->debug("$key: using record key $book_key");
274
275     # We didn't find a matching book; short-circuit our response
276     if (!$book_key || !$book_results->{records}->{$book_key}->{data}) {
277         $logger->debug("$key: no found book");
278         return 0;
279     }
280
281     return $book_results->{records}->{$book_key}->{data};
282 }
283
284
285 sub fetch_details_response {
286     my ($self, $key) = @_;
287
288     my $book_results = $self->fetch_response($key);
289
290     my $book_key = (keys %{$book_results->{records}})[0];
291
292     $logger->debug("$key: using record key $book_key");
293
294     # We didn't find a matching book; short-circuit our response
295     if (!$book_key) {
296         $logger->debug("$key: no found book");
297         return 0;
298     }
299
300     return $book_results->{$book_key}->{details};
301 }
302
303 sub fetch_items_response {
304     my ($self, $key) = @_;
305
306     my $book_results = $self->fetch_response($key);
307
308     my $items = $book_results->{items};
309
310     # We didn't find a matching book; short-circuit our response
311     if (!$items || scalar(@$items) == 0) {
312         $logger->debug("$key: no found items");
313         return 0;
314     }
315
316     return $book_results->{items};
317 }
318
319
320 # returns a cover image from the list of associated items
321 # TODO: Look for the best match in the array of items
322 sub fetch_cover_response {
323     my( $self, $size, $key ) = @_;
324
325     my $items = $self->fetch_items_response($key);
326
327     $logger->debug("$key: items request got " . scalar(@$items) . " items back");
328
329     foreach my $item (@$items) {
330         if ($item->{cover}) {
331             return $AC->get_url($item->{cover}->{$size}) || 0;
332         }
333     }
334
335     $logger->debug("$key: no covers for this book");
336     return 0;
337 }
338
339 1;