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
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
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
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
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
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
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}
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
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}
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}
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
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}
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
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
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 }
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}
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
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
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
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}}
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
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}}
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