Esempio n. 1
0
def get_loan(identifier, user_key=None):
    """Returns the loan object for given identifier, if a loan exists.

    If user_key is specified, it returns the loan only if that user is
    borrowed that book.
    """
    _loan = None
    account = None
    if user_key:
        if user_key.startswith('@'):
            account = OpenLibraryAccount.get(link=user_key)
        else:
            account = OpenLibraryAccount.get(key=user_key)

    d = web.ctx.site.store.get("loan-" + identifier)
    if d and (user_key is None or (d['user'] == account.username) or \
              (d['user'] == account.itemname)):
        loan = Loan(d)
        if loan.is_expired():
            return loan.delete()
    try:
        _loan = _get_ia_loan(identifier, account
                             and userkey2userid(account.username))
    except Exception as e:
        pass

    try:
        _loan = _get_ia_loan(identifier, account and account.itemname)
    except Exception as e:
        pass

    return _loan
Esempio n. 2
0
def get_loan(identifier, user_key=None):
    """Returns the loan object for given identifier, if a loan exists.

    If user_key is specified, it returns the loan only if that user is
    borrowed that book.
    """
    _loan = None
    account = None
    if user_key:
        if user_key.startswith('@'):
            account = OpenLibraryAccount.get(link=user_key)
        else:
            account = OpenLibraryAccount.get(key=user_key)

    d = web.ctx.site.store.get("loan-" + identifier)
    if d and (user_key is None or (d['user'] == account.username) or \
              (d['user'] == account.itemname)):
        loan = Loan(d)
        if loan.is_expired():
            return loan.delete()
    try:
        _loan = _get_ia_loan(identifier, account and userkey2userid(account.username))
    except Exception as e:
        pass

    try:
        _loan = _get_ia_loan(identifier, account and account.itemname)
    except Exception as e:
        pass

    return _loan
Esempio n. 3
0
 def new(cls, **kw):
     user_key = kw['user_key']
     itemname = kw.get('itemname', '')
     if not itemname:
         account = OpenLibraryAccount.get(key=user_key)
         itemname = account.itemname
     _wl_api.join_waitinglist(kw['identifier'], itemname)
     return cls.find(user_key, kw['identifier'], itemname=itemname)
Esempio n. 4
0
 def new(cls, **kw):
     user_key = kw['user_key']
     itemname = kw.get('itemname', '')
     if not itemname:
         account = OpenLibraryAccount.get(key=user_key)
         itemname = account.itemname
     _wl_api.join_waitinglist(kw['identifier'], itemname)
     return cls.find(user_key, kw['identifier'], itemname=itemname)
Esempio n. 5
0
def get_waitinglist_for_user(user_key):
    """Returns the list of records for all the books that a user is waiting for."""
    waitlist = []
    account = OpenLibraryAccount.get(key=user_key)
    if account.itemname:
        waitlist.extend(WaitingLoan.query(userid=account.itemname))
    waitlist.extend(WaitingLoan.query(userid=lending.userkey2userid(user_key)))
    return waitlist
Esempio n. 6
0
def get_loans_of_user(user_key):
    """TODO: Remove inclusion of local data; should only come from IA"""
    account = OpenLibraryAccount.get(username=user_key.split('/')[-1])

    loandata = web.ctx.site.store.values(type='/type/loan', name='user', value=user_key)
    loans = [Loan(d) for d in loandata] + (_get_ia_loans_of_user(account.itemname) +
                                           _get_ia_loans_of_user(userkey2userid(user_key)))
    return loans
Esempio n. 7
0
def get_loans_of_user(user_key):
    """TODO: Remove inclusion of local data; should only come from IA"""
    account = OpenLibraryAccount.get(username=user_key.split('/')[-1])

    loandata = web.ctx.site.store.values(type='/type/loan', name='user', value=user_key)
    loans = [Loan(d) for d in loandata] + (_get_ia_loans_of_user(account.itemname) +
                                           _get_ia_loans_of_user(userkey2userid(user_key)))
    return loans
Esempio n. 8
0
def get_waitinglist_for_user(user_key):
    """Returns the list of records for all the books that a user is waiting for.
    """
    waitlist = []
    account = OpenLibraryAccount.get(key=user_key)
    if account.itemname:
        waitlist.extend(WaitingLoan.query(userid=account.itemname))
    waitlist.extend(WaitingLoan.query(userid=lending.userkey2userid(user_key)))
    return waitlist
Esempio n. 9
0
    def POST(self):
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            s3_keys = web.ctx.site.store.get(account._key).get('s3_keys')

            if s3_keys:
                response = post_observation(web.data(), s3_keys)
                return delegate.RawText(response)
Esempio n. 10
0
    def find(cls, user_key, identifier, itemname=None):
        """Returns the waitingloan for given book_key and user_key.

        Returns None if there is no such waiting loan.
        """
        if not itemname:
            account = OpenLibraryAccount.get(key=user_key)
            itemname = account.itemname
        result = cls.query(userid=itemname, identifier=identifier)
        if result:
            return result[0]
Esempio n. 11
0
    def find(cls, user_key, identifier, itemname=None):
        """Returns the waitingloan for given book_key and user_key.

        Returns None if there is no such waiting loan.
        """
        if not itemname:
            account = OpenLibraryAccount.get(key=user_key)
            itemname = account.itemname
        result = (cls.query(userid=itemname, identifier=identifier) or
                  cls.query(userid=lending.userkey2userid(user_key), identifier=identifier))
        if result:
            return result[0]
Esempio n. 12
0
    def get_user_key(self):
        user_key = self.get("user_key")
        if user_key:
            return user_key

        userid = self.get("userid")
        username = ""
        if userid.startswith('@'):
            account = OpenLibraryAccount.get(link=userid)
            username = account.username
        elif userid.startswith('ol:'):
            username = userid[len("ol:"):]
        return "/people/%s" % username
Esempio n. 13
0
    def get_user_key(self):
        user_key = self.get("user_key")
        if user_key:
            return user_key

        userid = self.get("userid")
        username = ""
        if userid.startswith('@'):
            account = OpenLibraryAccount.get(link=userid)
            username = account.username
        elif userid.startswith('ol:'):
            username = userid[len("ol:"):]
        return "/people/%s" % username
Esempio n. 14
0
    def delete(self):
        loan = dict(self, returned_at=time.time())
        user_key = self['user']
        account = OpenLibraryAccount.get(key=user_key)
        if self.get("stored_at") == 'ia':
            ia_lending_api.delete_loan(self['ocaid'], userkey2userid(user_key))
            if account.itemname:
                ia_lending_api.delete_loan(self['ocaid'], account.itemname)
        else:
            web.ctx.site.store.delete(self['_key'])

        sync_loan(self['ocaid'])
        # Inform listers that a loan is completed
        msgbroker.send_message("loan-completed", loan)
Esempio n. 15
0
    def delete(self):
        loan = dict(self, returned_at=time.time())
        user_key = self['user']
        account = OpenLibraryAccount.get(key=user_key)
        if self.get("stored_at") == 'ia':
            ia_lending_api.delete_loan(self['ocaid'], userkey2userid(user_key))
            if account.itemname:
                ia_lending_api.delete_loan(self['ocaid'], account.itemname)
        else:
            web.ctx.site.store.delete(self['_key'])

        sync_loan(self['ocaid'])
        # Inform listers that a loan is completed
        msgbroker.send_message("loan-completed", loan)
Esempio n. 16
0
    def GET(self):
        user = accounts.get_current_user()
        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
        if not user or not ia_itemname:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            raise web.seeother("/account/login?redirect=/sponsorship/join")
        try:
            with accounts.RunAs('archive_support'):
                models.UserGroup.from_key('sponsors-waitlist').add_user(user.key)
        except KeyError as e:
            add_flash_message('error', 'Unable to join waitlist: %s' % e.message)

        raise web.seeother('/sponsorship')
Esempio n. 17
0
    def from_ia_loan(data):
        if data['userid'].startswith('ol:'):
            user_key = '/people/' + data['userid'][len('ol:'):]
        elif data['userid'].startswith('@'):
            account = OpenLibraryAccount.get_by_link(data['userid'])
            user_key = '/people/' + account.username
        else:
            user_key = None

        if data['ol_key']:
            book_key = data['ol_key']
        else:
            book_key = resolve_identifier(data['identifier'])

        created = h.parse_datetime(data['created'])

        # For historic reasons, OL considers expiry == None as un-fulfilled
        # loan.
        if data['fulfilled']:
            expiry = data['until']
        else:
            expiry = None

        d = {
            '_key': "loan-{0}".format(data['identifier']),
            '_rev': 1,
            'type': '/type/loan',
            'userid': data['userid'],
            'user': user_key,
            'book': book_key,
            'ocaid': data['identifier'],
            'expiry': expiry,
            'fulfilled': data['fulfilled'],
            'uuid': 'loan-{0}'.format(data['id']),
            'loaned_at': time.mktime(created.timetuple()),
            'resource_type': data['format'],
            'resource_id': data['resource_id'],
            'loan_link': data['loan_link'],
            'stored_at': 'ia'
        }
        return Loan(d)
Esempio n. 18
0
    def from_ia_loan(data):
        if data['userid'].startswith('ol:'):
            user_key = '/people/' + data['userid'][len('ol:'):]
        elif data['userid'].startswith('@'):
            account = OpenLibraryAccount.get_by_link(data['userid'])
            user_key = '/people/' + account.username
        else:
            user_key = None

        if data['ol_key']:
            book_key = data['ol_key']
        else:
            book_key = resolve_identifier(data['identifier'])

        created = h.parse_datetime(data['created'])

        # For historic reasons, OL considers expiry == None as un-fulfilled
        # loan.

        expiry = data.get('until')

        d = {
            '_key': "loan-{0}".format(data['identifier']),
            '_rev': 1,
            'type': '/type/loan',
            'userid': data['userid'],
            'user': user_key,
            'book': book_key,
            'ocaid': data['identifier'],
            'expiry': expiry,
            'fulfilled': data['fulfilled'],
            'uuid': 'loan-{0}'.format(data['id']),
            'loaned_at': time.mktime(created.timetuple()),
            'resource_type': data['format'],
            'resource_id': data['resource_id'],
            'loan_link': data['loan_link'],
            'stored_at': 'ia'
        }
        return Loan(d)
Esempio n. 19
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:
            ol_host = 'openlibrary.org'

        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother('https://archive.org/stream/' + edition.ocaid +
                               '?ref=ol')

        error_redirect = ('https://archive.org/stream/' + edition.ocaid +
                          '?ref=ol')
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
        if not user or not ia_itemname:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            raise web.seeother("/account/login?redirect=%s/borrow?action=%s" %
                               (edition.url(), i.action))

        action = i.action

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        if action == 'borrow' and user.has_borrowed(edition):
            action = 'read'

        if action == 'borrow':
            resource_type = i.format or 'bookreader'

            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            user_meets_borrow_criteria = user_can_borrow_edition(
                user, edition, resource_type)

            if user_meets_borrow_criteria:
                # This must be called before the loan is initiated,
                # otherwise the user's waitlist status will be cleared
                # upon loan creation
                track_loan = False if is_users_turn_to_borrow(
                    user, edition) else True

                loan = lending.create_loan(identifier=edition.ocaid,
                                           resource_type=resource_type,
                                           user_key=ia_itemname,
                                           book_key=key)

                if loan:
                    loan_link = loan['loan_link']
                    if resource_type == 'bookreader':
                        if track_loan:
                            # As of 2017-12-14, Petabox will be
                            # responsible for tracking borrows which
                            # are the result of waitlist redemptions,
                            # so we don't want to track them here to
                            # avoid double accounting. When a reader
                            # is at the head of a waitlist and goes to
                            # claim their loan, Petabox now checks
                            # whether the waitlist was initiated from
                            # OL, and if it was, petabox tracks
                            # ol.loans.bookreader accordingly via
                            # lending.create_loan.
                            stats.increment('ol.loans.bookreader')

                        raise web.seeother(
                            make_bookreader_auth_link(
                                loan.get_key(), edition.ocaid,
                                '/stream/' + edition.ocaid, ol_host))
                    elif resource_type == 'pdf':
                        stats.increment('ol.loans.pdf')
                        raise web.seeother(loan_link)
                    elif resource_type == 'epub':
                        stats.increment('ol.loans.epub')
                        raise web.seeother(loan_link)
                else:
                    raise web.seeother(error_redirect)
            else:
                raise web.seeother(error_redirect)

        elif action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key
                            or loan['ocaid'] == edition.ocaid)
                if has_loan:
                    user_loan = loan
                    break

            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)

            user_loan.return_loan()

            # Show the page with "you've returned this". Use a dummy slug.
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url())

        elif action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(
                        make_bookreader_auth_link(loan['_key'], edition.ocaid,
                                                  '/stream/' + edition.ocaid,
                                                  ol_host))
        elif action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)
Esempio n. 20
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None, _autoReadAloud=None, q="")

        ol_host = i.ol_host or 'openlibrary.org'
        action = i.action
        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        archive_url = get_bookreader_stream_url(edition.ocaid) + '?ref=ol'
        if i._autoReadAloud is not None:
            archive_url += '&_autoReadAloud=show'

        if i.q:
            _q = urllib.parse.quote(i.q, safe='')
            archive_url += "#page/-/mode/2up/search/%s" % _q

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother(archive_url)

        error_redirect = (archive_url)
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
            s3_keys = web.ctx.site.store.get(account._key).get('s3_keys')
        if not user or not ia_itemname or not s3_keys:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            redirect_url = "/account/login?redirect=%s/borrow?action=%s" % (
                edition.url(), action)
            if i._autoReadAloud is not None:
                redirect_url += '&_autoReadAloud=' + i._autoReadAloud
            raise web.seeother(redirect_url)

        if action == 'return':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='return_loan')
            stats.increment('ol.loans.return')
            raise web.seeother(edition.url())
        elif action == 'join-waitinglist':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='join_waitlist')
            stats.increment('ol.loans.joinWaitlist')
            raise web.redirect(edition.url())
        elif action == 'leave-waitinglist':
            lending.s3_loan_api(edition.ocaid, s3_keys, action='leave_waitlist')
            stats.increment('ol.loans.leaveWaitlist')
            raise web.redirect(edition.url())

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        elif user.has_borrowed(edition):
            action = 'read'

        elif action in ('borrow', 'browse'):
            borrow_access = user_can_borrow_edition(user, edition)

            if not (s3_keys or borrow_access):
                raise web.seeother(error_redirect)

            lending.s3_loan_api(edition.ocaid, s3_keys, action='%s_book' % borrow_access)
            stats.increment('ol.loans.bookreader')
            stats.increment('ol.loans.%s' % borrow_access)
            action = 'read'

        if action == 'read':
            bookPath = '/stream/' + edition.ocaid
            if i._autoReadAloud is not None:
                bookPath += '?_autoReadAloud=show'

            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(
                        loan['_key'], edition.ocaid, bookPath,
                        ol_host, ia_userid=ia_itemname
                    ))

        # Action not recognized
        raise web.seeother(error_redirect)
Esempio n. 21
0
    def POST(self, key):
        """Called when the user wants to borrow the edition"""

        i = web.input(action='borrow', format=None, ol_host=None)

        if i.ol_host:
            ol_host = i.ol_host
        else:
            ol_host = 'openlibrary.org'

        edition = web.ctx.site.get(key)
        if not edition:
            raise web.notfound()

        # Make a call to availability v2 update the subjects according
        # to result if `open`, redirect to bookreader
        response = lending.get_availability_of_ocaid(edition.ocaid)
        availability = response[edition.ocaid] if response else {}
        if availability and availability['status'] == 'open':
            raise web.seeother('https://archive.org/stream/' + edition.ocaid + '?ref=ol')

        error_redirect = ('https://archive.org/stream/' + edition.ocaid + '?ref=ol')
        user = accounts.get_current_user()

        if user:
            account = OpenLibraryAccount.get_by_email(user.email)
            ia_itemname = account.itemname if account else None
        if not user or not ia_itemname:
            web.setcookie(config.login_cookie_name, "", expires=-1)
            raise web.seeother("/account/login?redirect=%s/borrow?action=%s" % (
                edition.url(), i.action))

        action = i.action

        # Intercept a 'borrow' action if the user has already
        # borrowed the book and convert to a 'read' action.
        # Added so that direct bookreader links being routed through
        # here can use a single action of 'borrow', regardless of
        # whether the book has been checked out or not.
        if action == 'borrow' and user.has_borrowed(edition):
            action = 'read'

        if action == 'borrow':
            resource_type = i.format or 'bookreader'

            if resource_type not in ['epub', 'pdf', 'bookreader']:
                raise web.seeother(error_redirect)

            user_meets_borrow_criteria = user_can_borrow_edition(user, edition, resource_type)

            if user_meets_borrow_criteria:
                # This must be called before the loan is initiated,
                # otherwise the user's waitlist status will be cleared
                # upon loan creation
                track_loan = False if is_users_turn_to_borrow(user, edition) else True

                loan = lending.create_loan(
                    identifier=edition.ocaid,
                    resource_type=resource_type,
                    user_key=ia_itemname,
                    book_key=key)

                if loan:
                    loan_link = loan['loan_link']
                    if resource_type == 'bookreader':
                        if track_loan:
                            # As of 2017-12-14, Petabox will be
                            # responsible for tracking borrows which
                            # are the result of waitlist redemptions,
                            # so we don't want to track them here to
                            # avoid double accounting. When a reader
                            # is at the head of a waitlist and goes to
                            # claim their loan, Petabox now checks
                            # whether the waitlist was initiated from
                            # OL, and if it was, petabox tracks
                            # ol.loans.bookreader accordingly via
                            # lending.create_loan.
                            stats.increment('ol.loans.bookreader')

                        raise web.seeother(make_bookreader_auth_link(
                            loan.get_key(), edition.ocaid,
                            '/stream/' + edition.ocaid, ol_host,
                            ia_userid=ia_itemname))
                    elif resource_type == 'pdf':
                        stats.increment('ol.loans.pdf')
                        raise web.seeother(loan_link)
                    elif resource_type == 'epub':
                        stats.increment('ol.loans.epub')
                        raise web.seeother(loan_link)
                else:
                    raise web.seeother(error_redirect)
            else:
                raise web.seeother(error_redirect)

        elif action == 'return':
            # Check that this user has the loan
            user.update_loan_status()
            loans = get_loans(user)

            # We pick the first loan that the user has for this book that is returnable.
            # Assumes a user can't borrow multiple formats (resource_type) of the same book.
            user_loan = None
            for loan in loans:
                # Handle the case of multiple edition records for the same
                # ocaid and the user borrowed from one and returning from another
                has_loan = (loan['book'] == edition.key or loan['ocaid'] == edition.ocaid)
                if has_loan:
                    user_loan = loan
                    break

            if not user_loan:
                # $$$ add error message
                raise web.seeother(error_redirect)

            user_loan.return_loan()

            # Show the page with "you've returned this". Use a dummy slug.
            # $$$ this would do better in a session variable that can be cleared
            #     after the message is shown once
            raise web.seeother(edition.url())

        elif action == 'read':
            # Look for loans for this book
            user.update_loan_status()
            loans = get_loans(user)
            for loan in loans:
                if loan['book'] == edition.key:
                    raise web.seeother(make_bookreader_auth_link(
                        loan['_key'], edition.ocaid, '/stream/' + edition.ocaid,
                        ol_host, ia_userid=ia_itemname
                    ))
        elif action == 'join-waitinglist':
            return self.POST_join_waitinglist(edition, user)
        elif action == 'leave-waitinglist':
            return self.POST_leave_waitinglist(edition, user, i)

        # Action not recognized
        raise web.seeother(error_redirect)