Пример #1
0
def interface_data(ctx, rd):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=date.desc&library_id=<default library>
    '''
    ans = {'username':rd.username}
    ans['library_map'], ans['default_library'] = ctx.library_map
    ud = {}
    if rd.username:
        # Override session data with stored values for the authenticated user,
        # if any
        ud = ctx.user_manager.get_session_data(rd.username)
        lid = ud.get('library_id')
        if lid and lid in ans['library_map']:
            rd.query.set('library_id', lid)
        usort = ud.get('sort')
        if usort:
            rd.query.set('sort', usort)
    ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd.query)
    ans['user_session_data'] = ud
    try:
        num = int(rd.query.get('num', 50))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    with db.safe_read_lock:
        ans['search_result'] = _search(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        # ans['categories'] = ctx.get_categories(rd, db)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            mdata[book_id] = data

    return ans
Пример #2
0
def get_library_init_data(ctx, rd, db, num, sorts, orders):
    ans = {}
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders))
        except ParseException:
            ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop('ondevice', None)
        ans['sortable_fields'] = sorted(((
            sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
                                        key=lambda (field, name):sort_key(name))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        mdata = ans['metadata'] = {}
        try:
            extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(','))
        except Exception:
            extra_books = ()
        for coll in (ans['search_result']['book_ids'], extra_books):
            for book_id in coll:
                if book_id not in mdata:
                    data = book_as_json(db, book_id)
                    if data is not None:
                        mdata[book_id] = data
    return ans
Пример #3
0
def interface_data(ctx, rd):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=timestamp.desc&library_id=<default library>
              &search=''&extra_books=''
    '''
    ans = {
        'username':rd.username,
        'output_format':prefs['output_format'].upper(),
        'input_formats':{x.upper():True for x in available_input_formats()},
        'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'],
        'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'],
        'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'],
        'use_roman_numerals_for_series_number': get_use_roman(),
    }
    ans['library_map'], ans['default_library'] = ctx.library_info(rd)
    ud = {}
    if rd.username:
        # Override session data with stored values for the authenticated user,
        # if any
        ud = ctx.user_manager.get_session_data(rd.username)
        lid = ud.get('library_id')
        if lid and lid in ans['library_map']:
            rd.query.set('library_id', lid)
        usort = ud.get('sort')
        if usort:
            rd.query.set('sort', usort)
    ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd)
    ans['user_session_data'] = ud
    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders))
        except ParseException:
            ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop('ondevice', None)
        ans['sortable_fields'] = sorted(((
            sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
                                        key=lambda (field, name):sort_key(name))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        ans['icon_map'] = icon_map()
        ans['icon_path'] = ctx.url_for('/icon', which='')
        mdata = ans['metadata'] = {}
        try:
            extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(','))
        except Exception:
            extra_books = ()
        for coll in (ans['search_result']['book_ids'], extra_books):
            for book_id in coll:
                if book_id not in mdata:
                    data = book_as_json(db, book_id)
                    if data is not None:
                        mdata[book_id] = data

    return ans
Пример #4
0
def more_books(ctx, rd):
    """
    Get more results from the specified search-query, which must
    be specified as JSON in the request body.

    Optional: ?num=50&library_id=<default library>
    """
    db, library_id = get_library_data(ctx, rd.query)[:2]

    try:
        num = int(rd.query.get("num", DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound("Invalid number of books: %r" % rd.query.get("num"))
    try:
        search_query = load_json_file(rd.request_body_file)
        query, offset, sorts, orders = (
            search_query["query"],
            search_query["offset"],
            search_query["sort"],
            search_query["sort_order"],
        )
    except KeyError as err:
        raise HTTPBadRequest("Search query missing key: %s" % as_unicode(err))
    except Exception as err:
        raise HTTPBadRequest("Invalid query: %s" % as_unicode(err))
    ans = {}
    with db.safe_read_lock:
        ans["search_result"] = search_result(ctx, rd, db, query, num, offset, sorts, orders)
        mdata = ans["metadata"] = {}
        for book_id in ans["search_result"]["book_ids"]:
            data = book_as_json(db, book_id)
            if data is not None:
                mdata[book_id] = data

    return ans
Пример #5
0
def get_books(ctx, rd):
    '''
    Get books for the specified query

    Optional: ?library_id=<default library>&num=50&sort=timestamp.desc&search=''
    '''
    library_id, db, sorts, orders = get_basic_query_data(ctx, rd.query)
    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    searchq = rd.query.get('search', '')
    db = get_library_data(ctx, rd.query)[0]
    ans = {}
    mdata = ans['metadata'] = {}
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db, searchq, num, 0, ','.join(sorts), ','.join(orders))
        except ParseException as err:
            # This must not be translated as it is used by the front end to
            # detect invalid search expressions
            raise HTTPBadRequest('Invalid search expression: %s' % as_unicode(err))
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            if data is not None:
                mdata[book_id] = data
    return ans
Пример #6
0
def more_books(ctx, rd):
    '''
    Get more results from the specified search-query, which must
    be specified as JSON in the request body.

    Optional: ?num=50&library_id=<default library>
    '''
    db, library_id = get_library_data(ctx, rd.query)[:2]

    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    try:
        search_query = load_json_file(rd.request_body_file)
        query, offset, sorts, orders = search_query['query'], search_query['offset'], search_query['sort'], search_query['sort_order']
    except KeyError as err:
        raise HTTPBadRequest('Search query missing key: %s' % as_unicode(err))
    except Exception as err:
        raise HTTPBadRequest('Invalid query: %s' % as_unicode(err))
    ans = {}
    with db.safe_read_lock:
        ans['search_result'] = search_result(ctx, rd, db, query, num, offset, sorts, orders)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            if data is not None:
                mdata[book_id] = data

    return ans
Пример #7
0
def cdb_set_fields(ctx, rd, book_id, library_id):
    db = get_db(ctx, rd, library_id)
    if ctx.restriction_for(rd, db):
        raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions')
    data = load_payload_data(rd)
    try:
        changes, loaded_book_ids = data['changes'], frozenset(map(int, data.get('loaded_book_ids', ())))
        all_dirtied = bool(data.get('all_dirtied'))
        if not isinstance(changes, dict):
            raise TypeError('changes must be a dict')
    except Exception:
        raise HTTPBadRequest(
        '''Data must be of the form {'changes': {'title': 'New Title', ...}, 'loaded_book_ids':[book_id1, book_id2, ...]'}''')
    dirtied = set()
    cdata = changes.pop('cover', False)
    if cdata is not False:
        if cdata is not None:
            try:
                cdata = standard_b64decode(cdata.split(',', 1)[-1].encode('ascii'))
            except Exception:
                raise HTTPBadRequest('Cover data is not valid base64 encoded data')
            try:
                fmt = what(None, cdata)
            except Exception:
                fmt = None
            if fmt not in ('jpeg', 'png'):
                raise HTTPBadRequest('Cover data must be either JPEG or PNG')
        dirtied |= db.set_cover({book_id: cdata})

    for field, value in iteritems(changes):
        dirtied |= db.set_field(field, {book_id: value})
    ctx.notify_changes(db.backend.library_path, metadata(dirtied))
    all_ids = dirtied if all_dirtied else (dirtied & loaded_book_ids)
    all_ids |= {book_id}
    return {bid: book_as_json(db, bid) for bid in all_ids}
Пример #8
0
def get_library_init_data(ctx, rd, db, num, sorts, orders, vl):
    ans = {}
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db,
                                                 rd.query.get('search', ''),
                                                 num, 0, ','.join(sorts),
                                                 ','.join(orders), vl)
        except ParseException:
            ans['search_result'] = search_result(ctx, rd, db, '', num, 0,
                                                 ','.join(sorts),
                                                 ','.join(orders), vl)
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop('ondevice', None)
        ans['sortable_fields'] = sorted(
            ((sanitize_sort_field_name(db.field_metadata, k), v)
             for k, v in sf.iteritems()),
            key=lambda field_name: sort_key(field_name[1]))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        ans['virtual_libraries'] = db._pref('virtual_libraries', {})
        ans['book_display_fields'] = get_field_list(db)
        mdata = ans['metadata'] = {}
        try:
            extra_books = set(
                int(x) for x in rd.query.get('extra_books', '').split(','))
        except Exception:
            extra_books = ()
        for coll in (ans['search_result']['book_ids'], extra_books):
            for book_id in coll:
                if book_id not in mdata:
                    data = book_as_json(db, book_id)
                    if data is not None:
                        mdata[book_id] = data
    return ans
Пример #9
0
def book_manifest(ctx, rd, book_id, fmt):
    db, library_id = get_library_data(ctx, rd)[:2]
    if plugin_for_input_format(fmt) is None:
        raise HTTPNotFound('The format %s cannot be viewed' % fmt.upper())
    if book_id not in ctx.allowed_book_ids(rd, db):
        raise HTTPNotFound('No book with id: %s in library: %s' % (book_id, library_id))
    with db.safe_read_lock:
        fm = db.format_metadata(book_id, fmt)
        if not fm:
            raise HTTPNotFound('No %s format for the book (id:%s) in the library: %s' % (fmt, book_id, library_id))
        size, mtime = map(int, (fm['size'], time.mktime(fm['mtime'].utctimetuple())*10))
        bhash = book_hash(db.library_id, book_id, fmt, size, mtime)
        with cache_lock:
            mpath = abspath(os.path.join(books_cache_dir(), 'f', bhash, 'calibre-book-manifest.json'))
            try:
                os.utime(mpath, None)
                with lopen(mpath, 'rb') as f:
                    ans = jsonlib.load(f)
                ans['metadata'] = book_as_json(db, book_id)
                return ans
            except EnvironmentError as e:
                if e.errno != errno.ENOENT:
                    raise
            x = failed_jobs.pop(bhash, None)
            if x is not None:
                return {'aborted':x[0], 'traceback':x[1], 'job_status':'finished'}
            job_id = queued_jobs.get(bhash)
            if job_id is None:
                job_id = queue_job(ctx, partial(db.copy_format_to, book_id, fmt), bhash, fmt, book_id, size, mtime)
    status, result, tb, aborted = ctx.job_status(job_id)
    return {'aborted': aborted, 'traceback':tb, 'job_status':status, 'job_id':job_id}
Пример #10
0
def book_manifest(ctx, rd, book_id, fmt):
    db, library_id = get_library_data(ctx, rd)[:2]
    force_reload = rd.query.get('force_reload') == '1'
    if plugin_for_input_format(fmt) is None:
        raise HTTPNotFound('The format %s cannot be viewed' % fmt.upper())
    if book_id not in ctx.allowed_book_ids(rd, db):
        raise HTTPNotFound('No book with id: %s in library: %s' % (book_id, library_id))
    with db.safe_read_lock:
        fm = db.format_metadata(book_id, fmt)
        if not fm:
            raise HTTPNotFound('No %s format for the book (id:%s) in the library: %s' % (fmt, book_id, library_id))
        size, mtime = map(int, (fm['size'], time.mktime(fm['mtime'].utctimetuple())*10))
        bhash = book_hash(db.library_id, book_id, fmt, size, mtime)
        with cache_lock:
            mpath = abspath(os.path.join(books_cache_dir(), 'f', bhash, 'calibre-book-manifest.json'))
            if force_reload:
                safe_remove(mpath, True)
            try:
                os.utime(mpath, None)
                with lopen(mpath, 'rb') as f:
                    ans = jsonlib.load(f)
                ans['metadata'] = book_as_json(db, book_id)
                return ans
            except EnvironmentError as e:
                if e.errno != errno.ENOENT:
                    raise
            x = failed_jobs.pop(bhash, None)
            if x is not None:
                return {'aborted':x[0], 'traceback':x[1], 'job_status':'finished'}
            job_id = queued_jobs.get(bhash)
            if job_id is None:
                job_id = queue_job(ctx, partial(db.copy_format_to, book_id, fmt), bhash, fmt, book_id, size, mtime)
    status, result, tb, aborted = ctx.job_status(job_id)
    return {'aborted': aborted, 'traceback':tb, 'job_status':status, 'job_id':job_id}
Пример #11
0
def interface_data(ctx, rd):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=timestamp.desc&library_id=<default library>
              &search=''&extra_books=''
    '''
    ans = {
        'username':rd.username,
        'output_format':prefs['output_format'].upper(),
        'input_formats':{x.upper():True for x in available_input_formats()},
        'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'],
        'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'],
        'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'],
        'use_roman_numerals_for_series_number': get_use_roman(),
    }
    ans['library_map'], ans['default_library'] = ctx.library_map
    ud = {}
    if rd.username:
        # Override session data with stored values for the authenticated user,
        # if any
        ud = ctx.user_manager.get_session_data(rd.username)
        lid = ud.get('library_id')
        if lid and lid in ans['library_map']:
            rd.query.set('library_id', lid)
        usort = ud.get('sort')
        if usort:
            rd.query.set('sort', usort)
    ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd.query)
    ans['user_session_data'] = ud
    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders))
        except ParseException:
            ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop('ondevice', None)
        ans['sortable_fields'] = sorted(((
            sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
                                        key=lambda (field, name):sort_key(name))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        ans['icon_map'] = icon_map()
        ans['icon_path'] = ctx.url_for('/icon', which='')
        mdata = ans['metadata'] = {}
        try:
            extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(','))
        except Exception:
            extra_books = ()
        for coll in (ans['search_result']['book_ids'], extra_books):
            for book_id in coll:
                if book_id not in mdata:
                    data = book_as_json(db, book_id)
                    if data is not None:
                        mdata[book_id] = data

    return ans
Пример #12
0
def cdb_set_fields(ctx, rd, book_id, library_id):
    db = get_db(ctx, rd, library_id)
    if ctx.restriction_for(rd, db):
        raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions')
    data = load_payload_data(rd)
    try:
        changes, loaded_book_ids = data['changes'], frozenset(map(int, data.get('loaded_book_ids', ())))
        all_dirtied = bool(data.get('all_dirtied'))
        if not isinstance(changes, dict):
            raise TypeError('changes must be a dict')
    except Exception:
        raise HTTPBadRequest(
        '''Data must be of the form {'changes': {'title': 'New Title', ...}, 'loaded_book_ids':[book_id1, book_id2, ...]'}''')
    dirtied = set()
    cdata = changes.pop('cover', False)
    if cdata is not False:
        if cdata is not None:
            try:
                cdata = from_base64_bytes(cdata.split(',', 1)[-1])
            except Exception:
                raise HTTPBadRequest('Cover data is not valid base64 encoded data')
            try:
                fmt = what(None, cdata)
            except Exception:
                fmt = None
            if fmt not in ('jpeg', 'png'):
                raise HTTPBadRequest('Cover data must be either JPEG or PNG')
        dirtied |= db.set_cover({book_id: cdata})

    for field, value in iteritems(changes):
        dirtied |= db.set_field(field, {book_id: value})
    ctx.notify_changes(db.backend.library_path, metadata(dirtied))
    all_ids = dirtied if all_dirtied else (dirtied & loaded_book_ids)
    all_ids |= {book_id}
    return {bid: book_as_json(db, bid) for bid in all_ids}
Пример #13
0
def interface_data(ctx, rd, library_id):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50
    '''
    session = rd.session
    ans = {'session_data': {k:session[k] for k in defaults.iterkeys()}}
    ans['library_map'], ans['default_library'] = ctx.library_map
    ans['library_id'] = library_id or ans['default_library']
    sorts, orders = [], []
    for x in ans['session_data']['sort'].split(','):
        s, o = x.partition(':')[::2]
        sorts.append(s.strip()), orders.append(o.strip())
    try:
        num = int(rd.query.get('num', 50))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    db = get_db(ctx, library_id)
    with db.safe_read_lock:
        ans['search_result'] = _search(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        # ans['categories'] = ctx.get_categories(rd, db)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            mdata[book_id] = data

    return ans
Пример #14
0
def get_books(ctx, rd):
    '''
    Get books for the specified query

    Optional: ?library_id=<default library>&num=50&sort=timestamp.desc&search=''
    '''
    library_id, db, sorts, orders = get_basic_query_data(ctx, rd.query)
    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    searchq = rd.query.get('search', '')
    db = get_library_data(ctx, rd.query)[0]
    ans = {}
    mdata = ans['metadata'] = {}
    with db.safe_read_lock:
        try:
            ans['search_result'] = search_result(ctx, rd, db, searchq, num, 0, ','.join(sorts), ','.join(orders))
        except ParseException as err:
            # This must not be translated as it is used by the front end to
            # detect invalid search expressions
            raise HTTPBadRequest('Invalid search expression: %s' % as_unicode(err))
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            if data is not None:
                mdata[book_id] = data
    return ans
Пример #15
0
def more_books(ctx, rd):
    '''
    Get more results from the specified search-query, which must
    be specified as JSON in the request body.

    Optional: ?num=50&library_id=<default library>
    '''
    db, library_id = get_library_data(ctx, rd.query)[:2]

    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    try:
        search_query = load_json_file(rd.request_body_file)
        query, offset, sorts, orders = search_query['query'], search_query['offset'], search_query['sort'], search_query['sort_order']
    except KeyError as err:
        raise HTTPBadRequest('Search query missing key: %s' % as_unicode(err))
    except Exception as err:
        raise HTTPBadRequest('Invalid query: %s' % as_unicode(err))
    ans = {}
    with db.safe_read_lock:
        ans['search_result'] = search_result(ctx, rd, db, query, num, offset, sorts, orders)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            if data is not None:
                mdata[book_id] = data

    return ans
Пример #16
0
def interface_data(ctx, rd, library_id):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50
    '''
    session = rd.session
    ans = {'session_data': {k: session[k] for k in defaults.iterkeys()}}
    ans['library_map'], ans['default_library'] = ctx.library_map
    ans['library_id'] = library_id or ans['default_library']
    sorts, orders = [], []
    for x in ans['session_data']['sort'].split(','):
        s, o = x.partition(':')[::2]
        sorts.append(s.strip()), orders.append(o.strip())
    try:
        num = int(rd.query.get('num', 50))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    db = get_db(ctx, library_id)
    with db.safe_read_lock:
        ans['search_result'] = _search(ctx, rd, db, '', num, 0,
                                       ','.join(sorts), ','.join(orders))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        # ans['categories'] = ctx.get_categories(rd, db)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            mdata[book_id] = data

    return ans
Пример #17
0
def book_manifest(ctx, rd, book_id, fmt):
    db, library_id = get_library_data(ctx, rd)[:2]
    force_reload = rd.query.get('force_reload') == '1'
    if plugin_for_input_format(fmt) is None:
        raise HTTPNotFound('The format %s cannot be viewed' % fmt.upper())
    if not ctx.has_id(rd, db, book_id):
        raise BookNotFound(book_id, db)
    with db.safe_read_lock:
        fm = db.format_metadata(book_id, fmt, allow_cache=False)
        if not fm:
            raise HTTPNotFound(
                'No %s format for the book (id:%s) in the library: %s' %
                (fmt, book_id, library_id))
        size, mtime = map(
            int, (fm['size'], time.mktime(fm['mtime'].utctimetuple()) * 10))
        bhash = book_hash(db.library_id, book_id, fmt, size, mtime)
        with cache_lock:
            mpath = abspath(
                os.path.join(books_cache_dir(), 'f', bhash,
                             'calibre-book-manifest.json'))
            if force_reload:
                safe_remove(mpath, True)
            try:
                os.utime(mpath, None)
                with lopen(mpath, 'rb') as f:
                    ans = jsonlib.load(f)
                ans['metadata'] = book_as_json(db, book_id)
                user = rd.username or None
                ans['last_read_positions'] = db.get_last_read_positions(
                    book_id, fmt, user) if user else []
                ans['annotations_map'] = db.annotations_map_for_book(
                    book_id, fmt, user_type='web', user=user or '*')
                return ans
            except OSError as e:
                if e.errno != errno.ENOENT:
                    raise
            x = failed_jobs.pop(bhash, None)
            if x is not None:
                return {
                    'aborted': x[0],
                    'traceback': x[1],
                    'job_status': 'finished'
                }
            job_id = queued_jobs.get(bhash)
            if job_id is None:
                job_id = queue_job(ctx, partial(db.copy_format_to, book_id,
                                                fmt), bhash, fmt, book_id,
                                   size, mtime)
    status, result, tb, aborted = ctx.job_status(job_id)
    return {
        'aborted': aborted,
        'traceback': tb,
        'job_status': status,
        'job_id': job_id
    }
Пример #18
0
def cdb_set_fields(ctx, rd, book_id, library_id):
    db = get_db(ctx, rd, library_id)
    if ctx.restriction_for(rd, db):
        raise HTTPForbidden(
            'Cannot use the set fields interface with a user who has per library restrictions'
        )
    raw = rd.read()
    ct = rd.inheaders.get('Content-Type', all=True)
    ct = {x.lower().partition(';')[0] for x in ct}
    try:
        if MSGPACK_MIME in ct:
            data = msgpack_loads(raw)
        elif 'application/json' in ct:
            data = json_loads(raw)
        else:
            raise HTTPBadRequest('Only JSON or msgpack requests are supported')
    except Exception:
        raise HTTPBadRequest('Invalid encoded data')
    try:
        changes, loaded_book_ids = data['changes'], frozenset(
            map(int, data.get('loaded_book_ids', ())))
        all_dirtied = bool(data.get('all_dirtied'))
        if not isinstance(changes, dict):
            raise TypeError('changes must be a dict')
    except Exception:
        raise HTTPBadRequest(
            '''Data must be of the form {'changes': {'title': 'New Title', ...}, 'loaded_book_ids':[book_id1, book_id2, ...]'}'''
        )
    dirtied = set()
    cdata = changes.pop('cover', False)
    if cdata is not False:
        if cdata is not None:
            try:
                cdata = standard_b64decode(
                    cdata.split(',', 1)[-1].encode('ascii'))
            except Exception:
                raise HTTPBadRequest(
                    'Cover data is not valid base64 encoded data')
            try:
                fmt = what(None, cdata)
            except Exception:
                fmt = None
            if fmt not in ('jpeg', 'png'):
                raise HTTPBadRequest('Cover data must be either JPEG or PNG')
        dirtied |= db.set_cover({book_id: cdata})

    for field, value in changes.iteritems():
        dirtied |= db.set_field(field, {book_id: value})
    ctx.notify_changes(db.backend.library_path, metadata(dirtied))
    all_ids = dirtied if all_dirtied else (dirtied & loaded_book_ids)
    all_ids |= {book_id}
    return {bid: book_as_json(db, book_id) for bid in all_ids}
Пример #19
0
def book_metadata(ctx, rd, book_id):
    '''
    Get metadata for the specified book. If no book_id is specified, return metadata for a random book.

    Optional: ?library_id=<default library>&vl=<virtual library>
    '''
    library_id, db, sorts, orders, vl = get_basic_query_data(ctx, rd)

    if not book_id:
        all_ids = ctx.allowed_book_ids(rd, db)
        book_id = random.choice(tuple(all_ids))
    elif not ctx.has_id(rd, db, book_id):
        raise BookNotFound(book_id, db)
    data = book_as_json(db, book_id)
    if data is None:
        raise BookNotFound(book_id, db)
    data['id'] = book_id  # needed for random book view (when book_id=0)
    return data
Пример #20
0
def book_metadata(ctx, rd, book_id):
    '''
    Get metadata for the specified book. If no book_id is specified, return metadata for a random book.

    Optional: ?library_id=<default library>&vl=<virtual library>
    '''
    library_id, db, sorts, orders, vl = get_basic_query_data(ctx, rd)

    if not book_id:
        all_ids = ctx.allowed_book_ids(rd, db)
        book_id = random.choice(tuple(all_ids))
    elif not ctx.has_id(rd, db, book_id):
        raise BookNotFound(book_id, db)
    data = book_as_json(db, book_id)
    if data is None:
        raise BookNotFound(book_id, db)
    data['id'] = book_id  # needed for random book view (when book_id=0)
    return data
Пример #21
0
def interface_data(ctx, rd):
    '''
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=timestamp.desc&library_id=<default library>
              &sort_tags_by=name&partition_method=first letter&collapse_at=25&
              &dont_collapse=
    '''
    ans = {'username':rd.username}
    ans['library_map'], ans['default_library'] = ctx.library_map
    ud = {}
    if rd.username:
        # Override session data with stored values for the authenticated user,
        # if any
        ud = ctx.user_manager.get_session_data(rd.username)
        lid = ud.get('library_id')
        if lid and lid in ans['library_map']:
            rd.query.set('library_id', lid)
        usort = ud.get('sort')
        if usort:
            rd.query.set('sort', usort)
    ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd.query)
    ans['user_session_data'] = ud
    try:
        num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
    with db.safe_read_lock:
        ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop('ondevice', None)
        ans['sortable_fields'] = sorted(((
            sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
                                        key=lambda (field, name):sort_key(name))
        ans['field_metadata'] = db.field_metadata.all_metadata()
        ans['categories'] = categories_as_json(ctx, rd, db)
        mdata = ans['metadata'] = {}
        for book_id in ans['search_result']['book_ids']:
            data = book_as_json(db, book_id)
            mdata[book_id] = data

    return ans
Пример #22
0
def book_metadata(ctx, rd, book_id):
    '''
    Get metadata for the specified book. If no book_id is specified, return metadata for a random book.

    Optional: ?library_id=<default library>
    '''
    library_id, db = get_basic_query_data(ctx, rd.query)[:2]
    book_ids = ctx.allowed_book_ids(rd, db)
    def notfound():
        raise HTTPNotFound(_('No book with id: %d in library') % book_id)
    if not book_ids:
        notfound()
    if not book_id:
        book_id = random.choice(tuple(book_ids))
    elif book_id not in book_ids:
        notfound()
    data = book_as_json(db, book_id)
    if data is None:
        notfound()
    return data
Пример #23
0
def book_metadata(ctx, rd, book_id):
    '''
    Get metadata for the specified book. If no book_id is specified, return metadata for a random book.

    Optional: ?library_id=<default library>
    '''
    library_id, db = get_basic_query_data(ctx, rd.query)[:2]
    book_ids = ctx.allowed_book_ids(rd, db)
    def notfound():
        raise HTTPNotFound(_('No book with id: %d in library') % book_id)
    if not book_ids:
        notfound()
    if not book_id:
        book_id = random.choice(tuple(book_ids))
    elif book_id not in book_ids:
        notfound()
    data = book_as_json(db, book_id)
    if data is None:
        notfound()
    return data
Пример #24
0
def cdb_set_fields(ctx, rd, book_id, library_id):
    db = get_db(ctx, rd, library_id)
    if ctx.restriction_for(rd, db):
        raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions')
    raw = rd.read()
    ct = rd.inheaders.get('Content-Type', all=True)
    ct = {x.lower().partition(';')[0] for x in ct}
    try:
        if MSGPACK_MIME in ct:
            data = msgpack_loads(raw)
        elif 'application/json' in ct:
            data = json_loads(raw)
        else:
            raise HTTPBadRequest('Only JSON or msgpack requests are supported')
        changes, loaded_book_ids = data['changes'], frozenset(map(int, data['loaded_book_ids']))
    except Exception:
        raise HTTPBadRequest('Invalid encoded data')
    dirtied = set()
    for field, value in changes.iteritems():
        dirtied |= db.set_field(field, {book_id: value})
    metadata(dirtied)
    return {bid: book_as_json(db, book_id) for bid in (dirtied & loaded_book_ids) | {book_id}}
Пример #25
0
def interface_data(ctx, rd):
    """
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=date.desc&library_id=<default library>
    """
    ans = {"username": rd.username}
    ans["library_map"], ans["default_library"] = ctx.library_map
    ans["library_id"], db, sorts, orders = get_basic_query_data(ctx, rd.query)
    try:
        num = int(rd.query.get("num", 50))
    except Exception:
        raise HTTPNotFound("Invalid number of books: %r" % rd.query.get("num"))
    with db.safe_read_lock:
        ans["search_result"] = _search(ctx, rd, db, "", num, 0, ",".join(sorts), ",".join(orders))
        ans["field_metadata"] = db.field_metadata.all_metadata()
        # ans['categories'] = ctx.get_categories(rd, db)
        mdata = ans["metadata"] = {}
        for book_id in ans["search_result"]["book_ids"]:
            data = book_as_json(db, book_id)
            mdata[book_id] = data

    return ans
Пример #26
0
def cdb_set_fields(ctx, rd, book_id, library_id):
    db = get_db(ctx, rd, library_id)
    if ctx.restriction_for(rd, db):
        raise HTTPForbidden('Cannot use the set fields interface with a user who has per library restrictions')
    raw = rd.read()
    ct = rd.inheaders.get('Content-Type', all=True)
    ct = {x.lower().partition(';')[0] for x in ct}
    try:
        if MSGPACK_MIME in ct:
            data = msgpack_loads(raw)
        elif 'application/json' in ct:
            data = json_loads(raw)
        else:
            raise HTTPBadRequest('Only JSON or msgpack requests are supported')
        changes, loaded_book_ids = data['changes'], frozenset(map(int, data['loaded_book_ids']))
    except Exception:
        raise HTTPBadRequest('Invalid encoded data')
    dirtied = set()
    cdata = changes.pop('cover', False)
    if cdata is not False:
        if cdata is not None:
            try:
                cdata = standard_b64decode(cdata.split(',', 1)[-1].encode('ascii'))
            except Exception:
                raise HTTPBadRequest('Cover data is not valid base64 encoded data')
            try:
                fmt = what(None, cdata)
            except Exception:
                fmt = None
            if fmt not in ('jpeg', 'png'):
                raise HTTPBadRequest('Cover data must be either JPEG or PNG')
        dirtied |= db.set_cover({book_id: cdata})

    for field, value in changes.iteritems():
        dirtied |= db.set_field(field, {book_id: value})
    ctx.notify_changes(db.backend.library_path, metadata(dirtied))
    return {bid: book_as_json(db, book_id) for bid in (dirtied & loaded_book_ids) | {book_id}}
Пример #27
0
def interface_data(ctx, rd):
    """
    Return the data needed to create the server main UI

    Optional: ?num=50&sort=timestamp.desc&library_id=<default library>
              &search=''&extra_books=''
    """
    ans = {
        "username": rd.username,
        "output_format": prefs["output_format"].upper(),
        "input_formats": {x.upper(): True for x in available_input_formats()},
        "gui_pubdate_display_format": tweaks["gui_pubdate_display_format"],
        "gui_timestamp_display_format": tweaks["gui_timestamp_display_format"],
        "gui_last_modified_display_format": tweaks["gui_last_modified_display_format"],
        "use_roman_numerals_for_series_number": get_use_roman(),
    }
    ans["library_map"], ans["default_library"] = ctx.library_map
    ud = {}
    if rd.username:
        # Override session data with stored values for the authenticated user,
        # if any
        ud = ctx.user_manager.get_session_data(rd.username)
        lid = ud.get("library_id")
        if lid and lid in ans["library_map"]:
            rd.query.set("library_id", lid)
        usort = ud.get("sort")
        if usort:
            rd.query.set("sort", usort)
    ans["library_id"], db, sorts, orders = get_basic_query_data(ctx, rd.query)
    ans["user_session_data"] = ud
    try:
        num = int(rd.query.get("num", DEFAULT_NUMBER_OF_BOOKS))
    except Exception:
        raise HTTPNotFound("Invalid number of books: %r" % rd.query.get("num"))
    with db.safe_read_lock:
        try:
            ans["search_result"] = search_result(
                ctx, rd, db, rd.query.get("search", ""), num, 0, ",".join(sorts), ",".join(orders)
            )
        except ParseException:
            ans["search_result"] = search_result(ctx, rd, db, "", num, 0, ",".join(sorts), ",".join(orders))
        sf = db.field_metadata.ui_sortable_field_keys()
        sf.pop("ondevice", None)
        ans["sortable_fields"] = sorted(
            ((sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
            key=lambda (field, name): sort_key(name),
        )
        ans["field_metadata"] = db.field_metadata.all_metadata()
        ans["icon_map"] = icon_map()
        ans["icon_path"] = ctx.url_for("/icon", which="")
        mdata = ans["metadata"] = {}
        try:
            extra_books = set(int(x) for x in rd.query.get("extra_books", "").split(","))
        except Exception:
            extra_books = ()
        for coll in (ans["search_result"]["book_ids"], extra_books):
            for book_id in coll:
                if book_id not in mdata:
                    data = book_as_json(db, book_id)
                    if data is not None:
                        mdata[book_id] = data

    return ans