# - barcode: patron barcode
#
sub do_xact {
- my ($self, $conn, $auth, $session_id, $title_id, $barcode, $email) = @_;
+ my ($self, $conn, $auth, $session_id, $title_id, $barcode, $param) = @_;
my $action;
if ($self->api_name =~ /checkout/) {
# handler method constructs and submits request (and handles any external authentication)
my $res;
- # place_hold has email as optional additional param
- if ($action eq 'place_hold') {
- $res = $handler->place_hold($title_id, $user_token, $email);
+ if ($action eq 'checkout') {
+ # checkout has format as optional additional param
+ $res = $handler->checkout($title_id, $user_token, $param);
+ } elsif ($action eq 'place_hold') {
+ # place_hold has email as optional additional param
+ $res = $handler->place_hold($title_id, $user_token, $param);
} else {
$res = $handler->$action($title_id, $user_token);
}
}
);
+sub get_download_link {
+ my ($self, $conn, $auth, $session_id, $request_link) = @_;
+ my $handler = new_handler($session_id);
+ return $handler->do_get_download_link($request_link);
+}
+__PACKAGE__->register_method(
+ method => 'get_download_link',
+ api_name => 'open-ils.ebook_api.title.get_download_link',
+ api_level => 1,
+ argc => 3,
+ signature => {
+ desc => "Get download link for an OverDrive title that has been checked out",
+ params => [
+ {
+ name => 'authtoken',
+ desc => 'Authentication token',
+ type => 'string'
+ },
+ {
+ name => 'session_id',
+ desc => 'The session ID (provided by open-ils.ebook_api.start_session)',
+ type => 'string'
+ },
+ {
+ name => 'request_link',
+ desc => 'The URL used to request a download link',
+ type => 'string'
+ }
+ ],
+ return => {
+ desc => 'Success: { url => "http://example.com/download-link" } / Failure: { error_msg => "Download link request failed." }',
+ type => 'hashref'
+ }
+ }
+);
+
1;
};
if (my $res = $self->handle_http_request($req, $self->{session_id})) {
if ($res->{content}->{title}) {
- return {
+ my $info = {
title => $res->{content}->{title},
author => $res->{content}->{creators}[0]{name}
};
+ # Append format information (useful for checkouts).
+ $info->{formats} = $self->get_formats();
+ return $info;
} else {
$logger->error("EbookAPI: OverDrive metadata lookup failed for $title_id");
}
}
# request available formats
+ $holdings->{formats} = $self->get_formats();
+
+ return $holdings;
+}
+
+# Returns a list of available formats for a given title.
+sub get_formats {
+ my ($self, $title_id) = @_;
+ $self->do_client_auth() if (!$self->{bearer_token});
+ $self->get_library_info() if (!$self->{collection_token});
+ my $collection_token = $self->{collection_token};
+
+ my $formats = [];
+
my $format_req = {
method => 'GET',
uri => $self->{discovery_base_uri} . "/collections/$collection_token/products/$title_id/metadata"
if (my $format_res = $self->handle_http_request($format_req, $self->{session_id})) {
if ($format_res->{content}->{formats}) {
foreach my $f (@{$format_res->{content}->{formats}}) {
- push @{$holdings->{formats}}, $f->{name};
+ push @$formats, $f->{name};
}
} else {
$logger->info("EbookAPI: OverDrive holdings format request for title $title_id contained no format information");
$logger->error("EbookAPI: failed to retrieve OverDrive holdings formats for title $title_id");
}
- return $holdings;
+ return $formats;
}
# POST https://patron.api.overdrive.com/v1/patrons/me/checkouts
# ],
# ...
# }
+#
+# Our return value looks like this:
+# {
+# due_date => "10/14/2013 10:56:00 AM",
+# formats => [
+# "ebook-overdrive" => "https://patron.api.overdrive.com/v1/patrons/me/checkouts/76C1B7D0-17F4-4C05-8397-C66C17411584/formats/ebook-overdrive/downloadlink?errorpageurl={errorpageurl}&odreadauthurl={odreadauthurl}",
+# ...
+# ]
+# }
sub checkout {
- my ($self, $title_id, $patron_token) = @_;
+ my ($self, $title_id, $patron_token, $format) = @_;
my $request_content = {
fields => [
{
}
]
};
+ if ($format) {
+ push @{$request_content->{fields}}, { name => 'formatType', value => $format };
+ }
my $req = {
method => 'POST',
uri => $self->{circulation_base_uri} . "/patrons/me/checkouts",
};
if (my $res = $self->handle_http_request($req, $self->{session_id})) {
if ($res->{content}->{expires}) {
- return { due_date => $res->{content}->{expires} };
+ my $checkout = { due_date => $res->{content}->{expires} };
+ if (defined $res->{content}->{formats}) {
+ my $formats = {};
+ foreach my $f (@{$res->{content}->{formats}}) {
+ my $ftype = $f->{formatType};
+ $formats->{$ftype} = $f->{linkTemplates}->{downloadLink}->{href};
+ }
+ $checkout->{formats} = $formats;
+ }
+ return $checkout;
}
$logger->error("EbookAPI: checkout failed for OverDrive title $title_id");
return { error_msg => ( (defined $res->{content}) ? $res->{content} : 'Unknown checkout error' ) };
foreach my $checkout (@{$res->{content}->{checkouts}}) {
my $title_id = $checkout->{reserveId};
my $title_info = $self->get_title_info($title_id);
- # TODO get download URL - need to "lock in" a format first, see OD Checkouts API docs
+ my $formats = {};
+ foreach my $f (@{$checkout->{formats}}) {
+ my $ftype = $f->{formatType};
+ $formats->{$ftype} = $f->{linkTemplates}->{downloadLink}->{href};
+ };
push @$checkouts, {
title_id => $title_id,
due_date => $checkout->{expires},
title => $title_info->{title},
- author => $title_info->{author}
+ author => $title_info->{author},
+ formats => $formats
}
};
$self->{checkouts} = $checkouts;
return $self->handle_http_request($req, $self->{session_id});
}
+# get download URL for checked-out title
+sub do_get_download_link {
+ my ($self, $request_link) = @_;
+ my $req = {
+ method => 'GET',
+ uri => $request_link
+ };
+ if (my $res = $self->handle_http_request($req, $self->{session_id})) {
+ if ($res->{content}->{links}->{contentLink}->{href}) {
+ return { url => $res->{content}->{links}->{contentLink}->{href} };
+ }
+ return { error_msg => ( (defined $res->{content}) ? $res->{content} : 'Could not get content link' ) };
+ }
+ $logger->error("EbookAPI: no response received from OverDrive server");
+ return;
+}
+
1;
# Patron ID or patron auth token, as returned by do_patron_auth().
my $user_token = shift;
+ # Ebook format to be checked out (optional, not used here).
+ my $format = shift;
+
# If checkout succeeds, the response is a hashref with the following fields:
# - due_date
# - xact_id (optional)
return $self->{holds};
}
+sub do_get_download_link {
+ my $self = shift;
+ my $request_link = shift;
+
+ # For some vendors (e.g. OverDrive), the workflow is as follows:
+ #
+ # 1. Perform a checkout.
+ # 2. Checkout response contains a URL which we use to request a
+ # format-specific download link for the checked-out title.
+ # 3. Submit a request to the request link.
+ # 4. Response contains a (temporary/dynamic) URL which the user
+ # clicks on to download the ebook in the desired format.
+ #
+ # For other vendors, the download link for a title is static and not
+ # format-dependent. In that case, we just return the original request link
+ # (but ideally the UI will skip the download link request altogether, since
+ # it's superfluous in that case).
+
+ return $request_link;
+}
#!perl
use strict; use warnings;
-use Test::More tests => 23; # XXX
+use Test::More tests => 24; # XXX
use OpenILS::Utils::TestUtils;
diag("Tests Ebook API");
my $checkout = $checkout_req->recv->content;
ok(exists $checkout->{due_date}, 'Ebook checked out');
+# open-ils.ebook_api.title.get_download_link
+my $request_link = 'http://example.com/ebookapi/t/003';
+my $download_link_req = $ebook_api->request(
+ 'open-ils.ebook_api.title.get_download_link', $authtoken, $session_id, $request_link);
+my $download_link = $download_link_req->recv->content;
+# Test module just returns the original request_link as the response.
+ok($download_link eq $request_link, 'Received download link for ebook');
+
# open-ils.ebook_api.renew
my $renew_req = $ebook_api->request(
'open-ils.ebook_api.renew', $authtoken, $session_id, '001', EBOOK_API_PATRON_USERNAME);
this.avail; // availability info for this title
this.holdings = {}; // holdings info
this.conns = {}; // references to Dojo event connection for performing actions with this ebook
-
}
Ebook.prototype.getDetails = function(callback) {
console.log('title details response: ' + resp.content());
ebook.title = resp.content().title;
ebook.author = resp.content().author;
+ if (typeof resp.content().formats !== 'undefined')
+ ebook.formats = resp.content().formats;
return callback(ebook);
}
}
Ebook.prototype.checkout = function(authtoken, patron_id, callback) {
var ses = dojo.cookie(this.vendor);
var ebook = this;
+ // get selected checkout format (optional, used by OverDrive)
+ var checkout_format;
+ var format_selector = dojo.byId('checkout-format');
+ if (format_selector) {
+ checkout_format = format_selector.value;
+ }
+ // perform checkout
new OpenSRF.ClientSession('open-ils.ebook_api').request({
method: 'open-ils.ebook_api.checkout',
- params: [ authtoken, ses, ebook.id, patron_id ],
+ params: [ authtoken, ses, ebook.id, patron_id, checkout_format ],
async: true,
oncomplete: function(r) {
var resp = r.recv();
}).send();
}
+Ebook.prototype.download = function(authtoken) {
+ var ses = dojo.cookie(this.vendor);
+ var ebook = this;
+ var request_link;
+ var format_selector = dojo.byId('download-format');
+ if (!format_selector) {
+ console.log('could not find a specified format for download');
+ return;
+ } else {
+ request_link = format_selector.value;
+ }
+ new OpenSRF.ClientSession('open-ils.ebook_api').request({
+ method: 'open-ils.ebook_api.title.get_download_link',
+ params: [ authtoken, ses, request_link ],
+ async: true,
+ oncomplete: function(r) {
+ var resp = r.recv();
+ if (resp) {
+ if (resp.content().error_msg) {
+ console.log('download link request failed: ' + resp.content().error_msg);
+ } else if (resp.content().url) {
+ console.log('download link received: ' + resp.content().url);
+ //window.location = resp.url;
+ } else {
+ console.log('unknown error requesting download link');
+ }
+ }
+ }
+ }).send();
+}
+
} else {
dojo.empty('ebook_circs_main_table_body');
dojo.forEach(xacts.checkouts, function(x) {
- var dl_link = '<a href="' + x.download_url + '">' + l_strings.download + '</a>';
+ x.ebook = new Ebook(x.vendor, x.title_id);
var tr = dojo.create("tr", null, dojo.byId('ebook_circs_main_table_body'));
dojo.create("td", { innerHTML: x.title }, tr);
dojo.create("td", { innerHTML: x.author }, tr);
dojo.create("td", { innerHTML: x.due_date }, tr);
- dojo.create("td", { innerHTML: dl_link}, tr);
+ var dl_td = dojo.create("td", null, tr);
+ if (x.download_url) {
+ dl_td.innerHTML = '<a href="' + x.download_url + '">' + l_strings.download + '</a>';
+ }
+ if (x.formats) {
+ var select = dojo.create("select", { id: "download-format" }, dl_td);
+ for (f in x.formats) {
+ dojo.create("option", { value: x.formats[f], innerHTML: f }, select);
+ }
+ var button = dojo.create("input", { id: "download-button", type: "button", value: l_strings.download }, dl_td);
+ x.ebook.conns.download = dojo.connect(button, 'onclick', x.ebook, "download");
+ }
// TODO: more actions (renew, checkin)
});
dojo.addClass('no_ebook_circs', "hidden");
dojo.create("td", { innerHTML: ebook.author }, tr);
dojo.create("td", null, tr);
dojo.create("td", { id: "checkout-button-td" }, tr);
+ if (typeof active_ebook.formats !== 'undefined') {
+ var select = dojo.create("select", { id: "checkout-format" }, dojo.byId('checkout-button-td'));
+ dojo.forEach(active_ebook.formats, function(f) {
+ dojo.create("option", { value: f, innerHTML: f }, select);
+ });
+ }
var button = dojo.create("input", { id: "checkout-button", type: "button", value: l_strings.checkout }, dojo.byId('checkout-button-td'));
ebook.conns.checkout = dojo.connect(button, 'onclick', "doCheckout");
dojo.removeClass('ebook_circs_main', "hidden");
if (resp.due_date) {
console.log('Checkout succeeded!');
dojo.destroy('checkout-button');
+ dojo.destroy('checkout-format'); // remove optional format selector
dojo.removeClass('ebook_checkout_succeeded', "hidden");
// add our successful checkout to top of transaction cache
var new_xact = {
title_id: active_ebook.id,
title: active_ebook.title,
author: active_ebook.author,
- due_date: resp.due_date,
- download_url: '' // TODO - for OverDrive, user must "lock in" a format first!
+ due_date: resp.due_date
};
+ if (resp.download_url) {
+ new_xact.download_url = resp.download_url;
+ }
+ if (typeof resp.formats !== 'undefined') {
+ new_xact.ebook = new Ebook(active_ebook.vendor, active_ebook.title_id);
+ var select = dojo.create("select", { id: "download-format" }, dojo.byId('checkout-button-td'));
+ for (f in resp.formats) {
+ dojo.create("option", { value: resp.formats[f], innerHTML: f }, select);
+ }
+ var button = dojo.create("input", { id: "download-button", type: "button", value: l_strings.download }, dojo.byId('checkout-button-td'));
+ new_xact.ebook.conns.download = dojo.connect(button, 'onclick', new_xact.ebook, "download");
+ }
xacts.checkouts.unshift(new_xact);
cleanupAfterAction();
} else {