diff --git a/src/calibre/srv/ajax.py b/src/calibre/srv/ajax.py index ae33941a2f..c8b43d45dc 100644 --- a/src/calibre/srv/ajax.py +++ b/src/calibre/srv/ajax.py @@ -16,7 +16,6 @@ from calibre.db.view import sanitize_sort_field_name from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata.book.json_codec import JsonCodec from calibre.srv.errors import HTTPNotFound -from calibre.srv.metadata import book_as_json from calibre.srv.routes import endpoint, json from calibre.srv.content import get as get_content, icon as get_icon from calibre.srv.utils import http_date, custom_fields_to_display, encode_name, decode_name @@ -524,7 +523,7 @@ def books_in(ctx, rd, encoded_category, encoded_item, library_id): # }}} # Search {{{ -def _search(ctx, rd, db, query, num, offset, sort, sort_order): +def search_result(ctx, rd, db, query, num, offset, sort, sort_order): multisort = [(sanitize_sort_field_name(db.field_metadata, s), ensure_val(o, 'asc', 'desc') == 'asc') for s, o in zip(sort.split(','), cycle(sort_order.split(',')))] skeys = db.field_metadata.sortable_field_keys() @@ -558,65 +557,7 @@ def search(ctx, rd, library_id): query = rd.query.get('query') num, offset = get_pagination(rd.query) with db.safe_read_lock: - return _search(ctx, rd, db, query, num, offset, rd.query.get('sort', 'title'), rd.query.get('sort_order', 'asc')) + return search_result(ctx, rd, db, query, num, offset, rd.query.get('sort', 'title'), rd.query.get('sort_order', 'asc')) # }}} -def get_basic_query_data(ctx, query): - library_id = query.get('library_id') - library_map, default_library = ctx.library_map - if library_id not in library_map: - library_id = default_library - db = get_db(ctx, library_id) - skeys = db.field_metadata.sortable_field_keys() - sorts, orders = [], [] - for x in query.get('sort', '').split(','): - if x: - s, o = x.partition('.')[::2] - if o not in ('asc', 'desc'): - o = 'asc' - if s.startswith('_'): - s = '#' + s[1:] - s = sanitize_sort_field_name(db.field_metadata, s) - if s in skeys: - sorts.append(s), orders.append(o) - if not sorts: - sorts, orders = ['date'], ['desc'] - return library_id, db, sorts, orders - -@endpoint('/ajax/interface-data', postprocess=json) -def interface_data(ctx, rd): - ''' - Return the data needed to create the server main UI - - Optional: ?num=50&sort=date.desc&library_id= - ''' - 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 diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 391354fe39..e420e2a010 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -7,9 +7,14 @@ from __future__ import (unicode_literals, division, absolute_import, import re from functools import partial from threading import Lock +from json import load as load_json_file -from calibre import prepare_string_for_xml -from calibre.srv.routes import endpoint +from calibre import prepare_string_for_xml, as_unicode +from calibre.db.view import sanitize_sort_field_name +from calibre.srv.ajax import get_db, search_result +from calibre.srv.errors import HTTPNotFound, HTTPBadRequest +from calibre.srv.metadata import book_as_json +from calibre.srv.routes import endpoint, json html_cache = {} cache_lock = Lock() @@ -45,3 +50,99 @@ def index(ctx, rd): return rd.generate_static_output('/', partial( get_html, 'content-server/index.html', getattr(rd.opts, 'auto_reload_port', 0), ENTRY_POINT='book list', LOADING_MSG=prepare_string_for_xml(_('Loading library, please wait')))) + +def get_library_data(ctx, query): + library_id = query.get('library_id') + library_map, default_library = ctx.library_map + if library_id not in library_map: + library_id = default_library + db = get_db(ctx, library_id) + return db, library_id, library_map, default_library + +def get_basic_query_data(ctx, query): + db, library_id, library_map, default_library = get_library_data(ctx, query) + skeys = db.field_metadata.sortable_field_keys() + sorts, orders = [], [] + for x in query.get('sort', '').split(','): + if x: + s, o = x.partition('.')[::2] + if o not in ('asc', 'desc'): + o = 'asc' + if s.startswith('_'): + s = '#' + s[1:] + s = sanitize_sort_field_name(db.field_metadata, s) + if s in skeys: + sorts.append(s), orders.append(o) + if not sorts: + sorts, orders = ['date'], ['desc'] + return library_id, db, sorts, orders + +DEFAULT_NUMBER_OF_BOOKS = 50 + +@endpoint('/interface-data/init', postprocess=json) +def interface_data(ctx, rd): + ''' + Return the data needed to create the server main UI + + Optional: ?num=50&sort=date.desc&library_id= + ''' + 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)) + 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 + +@endpoint('/interface-data/more-books', postprocess=json, methods={'GET', 'HEAD', 'POST'}) +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= + ''' + 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, sorts, orders = search_query['query'], 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, 0, sorts, orders) + 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 diff --git a/src/calibre/srv/errors.py b/src/calibre/srv/errors.py index 211458d0e1..42295f7c94 100644 --- a/src/calibre/srv/errors.py +++ b/src/calibre/srv/errors.py @@ -39,5 +39,10 @@ class HTTPAuthRequired(HTTPSimpleResponse): def __init__(self, payload, log=None): HTTPSimpleResponse.__init__(self, httplib.UNAUTHORIZED, authenticate=payload, log=log) +class HTTPBadRequest(HTTPSimpleResponse): + + def __init__(self, message, close_connection=False): + HTTPSimpleResponse.__init__(self, httplib.BAD_REQUEST, message, close_connection) + class InvalidCredentials(ValueError): pass diff --git a/src/pyj/srv.pyj b/src/pyj/srv.pyj index a37d290ede..b1645702f4 100644 --- a/src/pyj/srv.pyj +++ b/src/pyj/srv.pyj @@ -31,7 +31,7 @@ def on_library_load_progress(loaded, total): def load_book_list(): temp = SessionData() # So that settings for anonymous users are preserved query = {'library_id':temp.get('library_id'), 'sort':temp.get('sort')} - ajax('ajax/interface-data', on_library_loaded, on_library_load_progress, query=query).send() + ajax('interface-data/init', on_library_loaded, on_library_load_progress, query=query).send() def on_load(): if window.calibre_entry_point == 'book list':