def fulfill(self, patron, pin, licensepool, internal_format):
        # Download the book from the appropriate acquisition link and return its content.
        # TODO: Implement https://github.com/NYPL-Simplified/Simplified/wiki/BearerTokenPropagation#advertising-bearer-token-propagation
        # instead.

        links = licensepool.identifier.links
        # Find the acquisition link with the right media type.
        for link in links:
            media_type = link.resource.representation.media_type
            if link.rel == Hyperlink.GENERIC_OPDS_ACQUISITION and media_type == internal_format:
                url = link.resource.representation.url

                # Obtain a bearer token.
                _db = Session.object_session(patron)
                token = self._get_token(_db)

                headers = dict()
                auth_header = "Bearer %s" % token
                headers['Authorization'] = auth_header
                response = self._request_with_timeout('GET', url, headers=headers)

                return FulfillmentInfo(
                    licensepool.collection,
                    licensepool.data_source.name,
                    licensepool.identifier.type,
                    licensepool.identifier.identifier,
                    content_link=None,
                    content_type=internal_format,
                    content=response.content,
                    content_expires=None,
                )

        # We couldn't find an acquisition link for this book.
        raise CannotFulfill()
Ejemplo n.º 2
0
    def fulfill(self, patron, pin, licensepool, internal_format):
        _db = Session.object_session(patron)

        loan = _db.query(Loan).filter(Loan.patron == patron).filter(
            Loan.license_pool_id == licensepool.id)
        loan = loan.one()

        doc = self.get_license_status_document(loan)
        status = doc.get("status")

        if status not in [self.READY_STATUS, self.ACTIVE_STATUS]:
            # This loan isn't available for some reason. It's possible
            # the distributor revoked it or the patron already returned it
            # through the DRM system, and we didn't get a notification
            # from the distributor yet.
            self.update_loan(loan, doc)
            raise CannotFulfill()

        expires = doc.get("potential_rights", {}).get("end")
        expires = datetime.datetime.strptime(expires, self.TIME_FORMAT)
        content_link = doc.get("links", {}).get("license", {}).get("href")
        content_type = doc.get("links", {}).get("license", {}).get("type")
        return FulfillmentInfo(
            licensepool.collection,
            licensepool.data_source.name,
            licensepool.identifier.type,
            licensepool.identifier.identifier,
            content_link,
            content_type,
            None,
            expires,
        )
Ejemplo n.º 3
0
    def fulfill(self, patron, pin, licensepool, internal_format):
        book_id = licensepool.identifier.identifier
        response = self.loan_request(patron.authorization_identifier, pin,
                                     book_id)
        if response.status_code != 200:
            raise CannotFulfill(response.status_code)
        result = json.loads(response.content)['result']
        if not result['success']:
            message = result['message']
            if "There are no available copies" in message:
                self.log.error("There are no copies of book %s available." %
                               book_id)
                raise NoAvailableCopies()
            elif "Login unsuccessful" in message:
                self.log.error(
                    "User validation against Enki server with %s / %s was unsuccessful."
                    % (patron.authorization_identifier, pin))
                raise AuthorizationFailedException()
        drm_type = self.get_enki_drm_type(book_id)
        url, item_type, expires = self.parse_fulfill_result(result)

        if not drm_type and item_type == 'epub':
            drm_type = self.no_drm

        return FulfillmentInfo(licensepool.collection,
                               licensepool.data_source.name,
                               licensepool.identifier.type,
                               licensepool.identifier.identifier,
                               content_link=url,
                               content_type=drm_type,
                               content=None,
                               content_expires=expires)
Ejemplo n.º 4
0
    def process_one(self, e, namespaces):
        """Either turn the given document into a LoanInfo
        object, or raise an appropriate exception.
        """
        self.raise_exception_on_error(e, namespaces)

        # If we get to this point it's because the checkout succeeded.
        expiration_date = self._xpath1(e, '//axis:expirationDate', namespaces)
        fulfillment_url = self._xpath1(e, '//axis:url', namespaces)
        if fulfillment_url is not None:
            fulfillment_url = fulfillment_url.text

        if expiration_date is not None:
            expiration_date = expiration_date.text
            expiration_date = datetime.strptime(expiration_date,
                                                self.FULL_DATE_FORMAT)

        # WTH??? Why is identifier None? Is this ever used?
        fulfillment = FulfillmentInfo(collection=self.collection,
                                      data_source_name=DataSource.AXIS_360,
                                      identifier_type=self.id_type,
                                      identifier=None,
                                      content_link=fulfillment_url,
                                      content_type=None,
                                      content=None,
                                      content_expires=None)
        loan_start = datetime.utcnow()
        loan = LoanInfo(collection=self.collection,
                        data_source_name=DataSource.AXIS_360,
                        identifier_type=self.id_type,
                        identifier=None,
                        start_date=loan_start,
                        end_date=expiration_date,
                        fulfillment_info=fulfillment)
        return loan
Ejemplo n.º 5
0
 def fulfill(self, patron, password, pool, delivery_mechanism):
     response = self.get_fulfillment_file(patron.authorization_identifier,
                                          pool.identifier.identifier)
     return FulfillmentInfo(
         pool.identifier.type,
         pool.identifier.identifier,
         content_link=None,
         content_type=response.headers.get('Content-Type'),
         content=response.content,
         content_expires=None,
     )
Ejemplo n.º 6
0
    def process_one(self, e, ns):

        # Figure out which book we're talking about.
        axis_identifier = self.text_of_subtag(e, "axis:titleId", ns)
        availability = self._xpath1(e, 'axis:availability', ns)
        if availability is None:
            return None
        reserved = self._xpath1_boolean(availability, 'axis:isReserved', ns)
        checked_out = self._xpath1_boolean(availability, 'axis:isCheckedout',
                                           ns)
        on_hold = self._xpath1_boolean(availability, 'axis:isInHoldQueue', ns)

        info = None
        if checked_out:
            start_date = self._xpath1_date(availability,
                                           'axis:checkoutStartDate', ns)
            end_date = self._xpath1_date(availability, 'axis:checkoutEndDate',
                                         ns)
            download_url = self.text_of_optional_subtag(
                availability, 'axis:downloadUrl', ns)
            if download_url:
                fulfillment = FulfillmentInfo(identifier_type=self.id_type,
                                              identifier=axis_identifier,
                                              content_link=download_url,
                                              content_type=None,
                                              content=None,
                                              content_expires=None)
            else:
                fulfillment = None
            info = LoanInfo(identifier_type=self.id_type,
                            identifier=axis_identifier,
                            start_date=start_date,
                            end_date=end_date,
                            fulfillment_info=fulfillment)

        elif reserved:
            end_date = self._xpath1_date(availability, 'axis:reservedEndDate',
                                         ns)
            info = HoldInfo(identifier_type=self.id_type,
                            identifier=axis_identifier,
                            start_date=None,
                            end_date=end_date,
                            hold_position=0)
        elif on_hold:
            position = self.int_of_optional_subtag(availability,
                                                   'axis:holdsQueuePosition',
                                                   ns)
            info = HoldInfo(identifier_type=self.id_type,
                            identifier=axis_identifier,
                            start_date=None,
                            end_date=None,
                            hold_position=position)
        return info
Ejemplo n.º 7
0
    def fulfill(self, patron, pin, licensepool, internal_format, **kwargs):
        """Get the actual resource file to the patron.

        :param kwargs: A container for arguments to fulfill()
           which are not relevant to this vendor.

        :return: a FulfillmentInfo object.
        """
        book_id = licensepool.identifier.identifier
        enki_library_id = self.enki_library_id(patron.library)
        response = self.loan_request(
            patron.authorization_identifier, pin, book_id, enki_library_id
        )
        if response.status_code != 200:
            raise CannotFulfill(response.status_code)
        result = json.loads(response.content)['result']
        if not result['success']:
            message = result['message']
            if "There are no available copies" in message:
                self.log.error("There are no copies of book %s available." % book_id)
                raise NoAvailableCopies()
            elif "Login unsuccessful" in message:
                self.log.error("User validation against Enki server with %s / %s was unsuccessful."
                    % (patron.authorization_identifier, pin))
                raise AuthorizationFailedException()

        url, item_type, expires = self.parse_fulfill_result(result)
        # We don't know for sure which DRM scheme is in use, (that is,
        # whether the content link points to the actual book or an
        # ACSM file) but since Enki titles only have a single delivery
        # mechanism, it's easy to make a guess.
        drm_type = self.no_drm
        for lpdm in licensepool.delivery_mechanisms:
            delivery_mechanism = lpdm.delivery_mechanism
            if delivery_mechanism:
                drm_type = delivery_mechanism.drm_scheme
                break

        return FulfillmentInfo(
            licensepool.collection,
            licensepool.data_source.name,
            licensepool.identifier.type,
            licensepool.identifier.identifier,
            content_link=url,
            content_type=drm_type,
            content=None,
            content_expires=expires
        )
Ejemplo n.º 8
0
    def fulfill(self, patron, pin, licensepool, internal_format):
        record_id = licensepool.identifier.identifier
        content_link, content, content_type = self.get_fulfillment_link(patron, pin, record_id, internal_format)

        if not content_link and not content:
            self.log.info("Odilo record_id %s was not available as %s" % (record_id, internal_format))
        else:
            return FulfillmentInfo(
                licensepool.collection,
                DataSource.ODILO,
                Identifier.ODILO_ID,
                record_id,
                content_link=content_link,
                content=content,
                content_type=content_type,
                content_expires=None
            )
    def fulfill(self, patron, pin, licensepool, internal_format, **kwargs):
        """Retrieve a bearer token that can be used to download the book.

        :param kwargs: A container for arguments to fulfill()
           which are not relevant to this vendor.

        :return: a FulfillmentInfo object.
        """

        links = licensepool.identifier.links
        # Find the acquisition link with the right media type.
        for link in links:
            media_type = link.resource.representation.media_type
            if link.rel == Hyperlink.GENERIC_OPDS_ACQUISITION and media_type == internal_format:
                url = link.resource.representation.url

                # Obtain a Credential with the information from our
                # bearer token.
                _db = Session.object_session(licensepool)
                credential = self._get_token(_db)

                # Build a application/vnd.librarysimplified.bearer-token
                # document using information from the credential.
                now = datetime.datetime.utcnow()
                expiration = int((credential.expires - now).total_seconds())
                token_document = dict(
                    token_type="Bearer",
                    access_token=credential.credential,
                    expires_in=expiration,
                    location=url,
                )

                return FulfillmentInfo(
                    licensepool.collection,
                    licensepool.data_source.name,
                    licensepool.identifier.type,
                    licensepool.identifier.identifier,
                    content_link=None,
                    content_type=DeliveryMechanism.BEARER_TOKEN,
                    content=json.dumps(token_document),
                    content_expires=credential.expires,
                )

        # We couldn't find an acquisition link for this book.
        raise CannotFulfill()
    def fulfill(self, patron, pin, licensepool, internal_format):
        # Download the book from the appropriate acquisition link and return its content.
        # TODO: Implement https://github.com/NYPL-Simplified/Simplified/wiki/BearerTokenPropagation#advertising-bearer-token-propagation
        # instead.

        links = licensepool.identifier.links
        # Find the acquisition link with the right media type.
        for link in links:
            media_type = link.resource.representation.media_type
            if link.rel == Hyperlink.GENERIC_OPDS_ACQUISITION and media_type == internal_format:
                url = link.resource.representation.url

                # Obtain a Credential with the information from our
                # bearer token.
                _db = Session.object_session(patron)
                credential = self._get_token(_db)

                # Build a application/vnd.librarysimplified.bearer-token
                # document using information from the credential.
                now = datetime.datetime.utcnow()
                expiration = int((credential.expires - now).total_seconds())
                token_document = dict(
                    token_type="Bearer",
                    access_token=credential.credential,
                    expires_in=expiration,
                    location=url,
                )

                return FulfillmentInfo(
                    licensepool.collection,
                    licensepool.data_source.name,
                    licensepool.identifier.type,
                    licensepool.identifier.identifier,
                    content_link=None,
                    content_type=DeliveryMechanism.BEARER_TOKEN,
                    content=json.dumps(token_document),
                    content_expires=credential.expires,
                )

        # We couldn't find an acquisition link for this book.
        raise CannotFulfill()
Ejemplo n.º 11
0
    def fulfill(self, patron, pin, licensepool, internal_format, **kwargs):
        """Get the actual resource file to the patron.

        :param kwargs: A container for arguments to fulfill()
           which are not relevant to this vendor.

        :return: a FulfillmentInfo object.
        """
        record_id = licensepool.identifier.identifier
        content_link, content, content_type = self.get_fulfillment_link(
            patron, pin, record_id, internal_format)

        if not content_link and not content:
            self.log.info("Odilo record_id %s was not available as %s" %
                          (record_id, internal_format))
        else:
            return FulfillmentInfo(licensepool.collection,
                                   DataSource.ODILO,
                                   Identifier.ODILO_ID,
                                   record_id,
                                   content_link=content_link,
                                   content=content,
                                   content_type=content_type,
                                   content_expires=None)
Ejemplo n.º 12
0
        response = fulfill_method(patron.authorization_identifier,
                                  pool.identifier.identifier)
        content = response.content
        content_type = None
        if content_transformation:
            try:
                content_type, content = (content_transformation(pool, content))
            except Exception, e:
                self.log.error("Error transforming fulfillment document: %s",
                               response.content,
                               exc_info=e)
        return FulfillmentInfo(
            pool.collection,
            DataSource.BIBLIOTHECA,
            pool.identifier.type,
            pool.identifier.identifier,
            content_link=None,
            content_type=content_type or response.headers.get('Content-Type'),
            content=content,
            content_expires=None,
        )

    def get_fulfillment_file(self, patron_id, bibliotheca_id):
        args = dict(request_type='ACSMRequest',
                    item_id=bibliotheca_id,
                    patron_id=patron_id)
        body = self.TEMPLATE % args
        return self.request('GetItemACSM', body, method="PUT")

    def get_audio_fulfillment_file(self, patron_id, bibliotheca_id):
        args = dict(request_type='AudioFulfillmentRequest',
                    item_id=bibliotheca_id,
Ejemplo n.º 13
0
                else:
                    # slightly risky assumption here
                    file_format = Representation.MP3_MEDIA_TYPE

                # Note: download urls expire 15 minutes after being handed out
                # in the checkouts call
                download_url = file.get('downloadUrl', None)
                # is included in the downloadUrl, actually
                acs_resource_id = file.get('acsResourceId', None)

            # TODO: For audio books, the downloads are done by parts, and there are
            # multiple download urls.  Need to have a mechanism for putting lists of
            # parts into fulfillment objects.
            fulfillment_info = FulfillmentInfo(Identifier.ONECLICK_ID,
                                               identifier,
                                               content_link=download_url,
                                               content_type=file_format,
                                               content=None,
                                               content_expires=None)

            loan = LoanInfo(
                Identifier.ONECLICK_ID,
                isbn,
                start_date=None,
                end_date=expires,
                fulfillment_info=fulfillment_info,
            )

            loans.append(loan)

        return loans
Ejemplo n.º 14
0
            # have an inaccurate delivery mechanism. Try to update the formats, but
            # reraise the error regardless.
            self.log.info("Overdrive id %s was not available as %s, getting updated formats" % (licensepool.identifier.identifier, internal_format))

            try:
                self.update_formats(licensepool)
            except Exception, e2:
                self.log.error("Could not update formats for Overdrive ID %s" % licensepool.identifier.identifier)

            raise e

        return FulfillmentInfo(
            licensepool.collection,
            licensepool.data_source.name,
            licensepool.identifier.type,
            licensepool.identifier.identifier,
            content_link=url,
            content_type=media_type,
            content=None,
            content_expires=None
        )


    def get_fulfillment_link(self, patron, pin, overdrive_id, format_type):
        """Get the link to the ACSM file corresponding to an existing loan.
        """
        loan = self.get_loan(patron, pin, overdrive_id)
        if not loan:
            raise NoActiveLoan("Could not find active loan for %s" % overdrive_id)
        download_link = None
        if not loan['isFormatLockedIn'] and format_type not in self.STREAMING_FORMATS:
            # The format is not locked in. Lock it in.
Ejemplo n.º 15
0
            # We need to use self._make_request instead of self.request
            # because it's a different server that will reject the credentials
            # we use for the API.
            access_document = self._make_request(download_url, 'GET', {})
            data = json.loads(access_document.content)
            content_link = data['url']
            content_type = data['type']
            if content_type == 'application/vnd.adobe':
                # The manifest spells the media type wrong. Fix it.
                content_type = DeliveryMechanism.ADOBE_DRM
            fulfillment_info = FulfillmentInfo(
                self.collection,
                DataSource.RB_DIGITAL,
                Identifier.RB_DIGITAL_ID, 
                identifier, 
                content_link = content_link, 
                content_type = content_type, 
                content = None, 
                content_expires = None
            )

            loan = LoanInfo(
                self.collection,
                DataSource.RB_DIGITAL,
                Identifier.RB_DIGITAL_ID,
                isbn,
                start_date=None,
                end_date=expires,
                fulfillment_info=fulfillment_info,
            )
Ejemplo n.º 16
0
            # reraise the error regardless.
            self.log.info(
                "Overdrive id %s was not available as %s, getting updated formats"
                % (licensepool.identifier.identifier, internal_format))

            try:
                self.update_formats(licensepool)
            except Exception, e2:
                self.log.error("Could not update formats for Overdrive ID %s" %
                               licensepool.identifier.identifier)

            raise e

        return FulfillmentInfo(licensepool.identifier.type,
                               licensepool.identifier.identifier,
                               content_link=url,
                               content_type=media_type,
                               content=None,
                               content_expires=None)

    def get_fulfillment_link(self, patron, pin, overdrive_id, format_type):
        """Get the link to the ACSM file corresponding to an existing loan.
        """
        loan = self.get_loan(patron, pin, overdrive_id)
        if not loan:
            raise NoActiveLoan("Could not find active loan for %s" %
                               overdrive_id)
        download_link = None
        if not loan[
                'isFormatLockedIn'] and format_type not in self.STREAMING_FORMATS:
            # The format is not locked in. Lock it in.
            # This will happen the first time someone tries to fulfill
Ejemplo n.º 17
0
                    file_format = Representation.MP3_MEDIA_TYPE

                # Note: download urls expire 15 minutes after being handed out
                # in the checkouts call
                download_url = file.get('downloadUrl', None)
                # is included in the downloadUrl, actually
                acs_resource_id = file.get('acsResourceId', None)

            # TODO: For audio books, the downloads are done by parts, and there are 
            # multiple download urls.  Need to have a mechanism for putting lists of 
            # parts into fulfillment objects.
            fulfillment_info = FulfillmentInfo(
                self.collection,
                DataSource.ONECLICK,
                Identifier.ONECLICK_ID, 
                identifier, 
                content_link = download_url, 
                content_type = file_format, 
                content = None, 
                content_expires = None
            )

            loan = LoanInfo(
                self.collection,
                DataSource.ONECLICK,
                Identifier.ONECLICK_ID,
                isbn,
                start_date=None,
                end_date=expires,
                fulfillment_info=fulfillment_info,
            )