mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement /ajax/books(s)
This commit is contained in:
parent
7fc14ca47f
commit
ebf08b4e22
@ -11,9 +11,11 @@ from binascii import hexlify, unhexlify
|
|||||||
|
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre.ebooks.metadata.book.json_codec import JsonCodec
|
from calibre.ebooks.metadata.book.json_codec import JsonCodec
|
||||||
|
from calibre.srv.errors import HTTPNotFound
|
||||||
from calibre.srv.routes import endpoint, json
|
from calibre.srv.routes import endpoint, json
|
||||||
|
from calibre.srv.utils import http_date
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.utils.date import isoformat
|
from calibre.utils.date import isoformat, timestampfromdt
|
||||||
|
|
||||||
def encode_name(name):
|
def encode_name(name):
|
||||||
if isinstance(name, unicode):
|
if isinstance(name, unicode):
|
||||||
@ -23,6 +25,8 @@ def encode_name(name):
|
|||||||
def decode_name(name):
|
def decode_name(name):
|
||||||
return unhexlify(name).decode('utf-8')
|
return unhexlify(name).decode('utf-8')
|
||||||
|
|
||||||
|
# Book metadata {{{
|
||||||
|
|
||||||
def book_to_json(ctx, rd, db, book_id,
|
def book_to_json(ctx, rd, db, book_id,
|
||||||
get_category_urls=True, device_compatible=False, device_for_template=None):
|
get_category_urls=True, device_compatible=False, device_for_template=None):
|
||||||
mi = db.get_metadata(book_id, get_cover=False)
|
mi = db.get_metadata(book_id, get_cover=False)
|
||||||
@ -101,6 +105,99 @@ def book_to_json(ctx, rd, db, book_id,
|
|||||||
|
|
||||||
return data, mi.last_modified
|
return data, mi.last_modified
|
||||||
|
|
||||||
|
@endpoint('/ajax/book/{book_id}/{library_id=None}', postprocess=json)
|
||||||
|
def book(ctx, rd, book_id, library_id):
|
||||||
|
'''
|
||||||
|
Return the metadata of the book as a JSON dictionary.
|
||||||
|
|
||||||
|
Query parameters: ?category_urls=true&id_is_uuid=false&device_for_template=None
|
||||||
|
|
||||||
|
If category_urls is true the returned dictionary also contains a
|
||||||
|
mapping of category (field) names to URLs that return the list of books in the
|
||||||
|
given category.
|
||||||
|
|
||||||
|
If id_is_uuid is true then the book_id is assumed to be a book uuid instead.
|
||||||
|
'''
|
||||||
|
db = ctx.get_library(library_id)
|
||||||
|
if db is None:
|
||||||
|
raise HTTPNotFound('Library %r not found' % library_id)
|
||||||
|
with db.safe_read_lock:
|
||||||
|
id_is_uuid = rd.query.get('id_is_uuid', 'false')
|
||||||
|
oid = book_id
|
||||||
|
if id_is_uuid == 'true':
|
||||||
|
book_id = db.lookup_by_uuid(book_id)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
book_id = int(book_id)
|
||||||
|
if not db.has_id(book_id):
|
||||||
|
book_id = None
|
||||||
|
except Exception:
|
||||||
|
book_id = None
|
||||||
|
if book_id is None:
|
||||||
|
raise HTTPNotFound('Book with id %r does not exist' % oid)
|
||||||
|
category_urls = rd.query.get('category_urls', 'true').lower()
|
||||||
|
device_compatible = rd.query.get('device_compatible', 'false').lower()
|
||||||
|
device_for_template = rd.query.get('device_for_template', None)
|
||||||
|
|
||||||
|
data, last_modified = book_to_json(ctx, rd, db, book_id,
|
||||||
|
get_category_urls=category_urls == 'true',
|
||||||
|
device_compatible=device_compatible == 'true',
|
||||||
|
device_for_template=device_for_template)
|
||||||
|
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified))
|
||||||
|
return data
|
||||||
|
|
||||||
|
@endpoint('/ajax/books/{library_id=None}', postprocess=json)
|
||||||
|
def books(ctx, rd, library_id):
|
||||||
|
'''
|
||||||
|
Return the metadata for the books as a JSON dictionary.
|
||||||
|
|
||||||
|
Query parameters: ?ids=all&category_urls=true&id_is_uuid=false&device_for_template=None
|
||||||
|
|
||||||
|
If category_urls is true the returned dictionary also contains a
|
||||||
|
mapping of category (field) names to URLs that return the list of books in the
|
||||||
|
given category.
|
||||||
|
|
||||||
|
If id_is_uuid is true then the book_id is assumed to be a book uuid instead.
|
||||||
|
'''
|
||||||
|
db = ctx.get_library(library_id)
|
||||||
|
if db is None:
|
||||||
|
raise HTTPNotFound('Library %r not found' % library_id)
|
||||||
|
with db.safe_read_lock:
|
||||||
|
id_is_uuid = rd.query.get('id_is_uuid', 'false')
|
||||||
|
ids = rd.query.get('ids')
|
||||||
|
if ids is None or ids == 'all':
|
||||||
|
ids = db.all_book_ids()
|
||||||
|
else:
|
||||||
|
ids = ids.split(',')
|
||||||
|
if id_is_uuid == 'true':
|
||||||
|
ids = {db.lookup_by_uuid(x) for x in ids}
|
||||||
|
ids.discard(None)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ids = {int(x) for x in ids}
|
||||||
|
except Exception:
|
||||||
|
raise HTTPNotFound('ids must a comma separated list of integers')
|
||||||
|
last_modified = None
|
||||||
|
category_urls = rd.query.get('category_urls', 'true').lower() == 'true'
|
||||||
|
device_compatible = rd.query.get('device_compatible', 'false').lower() == 'true'
|
||||||
|
device_for_template = rd.query.get('device_for_template', None)
|
||||||
|
ans = {}
|
||||||
|
restricted_to = ctx.restrict_to_ids(db, rd)
|
||||||
|
for book_id in ids:
|
||||||
|
if book_id not in restricted_to:
|
||||||
|
ans[book_id] = None
|
||||||
|
continue
|
||||||
|
data, lm = book_to_json(
|
||||||
|
ctx, rd, db, book_id, get_category_urls=category_urls,
|
||||||
|
device_compatible=device_compatible, device_for_template=device_for_template)
|
||||||
|
last_modified = lm if last_modified is None else max(lm, last_modified)
|
||||||
|
ans[book_id] = data
|
||||||
|
if last_modified is not None:
|
||||||
|
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
@endpoint('/ajax/books_in/{category}/{item}', postprocess=json)
|
@endpoint('/ajax/books_in/{category}/{item}', postprocess=json)
|
||||||
def books_in(ctx, rd, category, item):
|
def books_in(ctx, rd, category, item):
|
||||||
raise NotImplementedError('TODO: Implement this')
|
raise NotImplementedError('TODO: Implement this')
|
||||||
|
@ -72,6 +72,14 @@ class Context(object):
|
|||||||
def get_library(self, library_id=None):
|
def get_library(self, library_id=None):
|
||||||
return self.library_broker.get(library_id)
|
return self.library_broker.get(library_id)
|
||||||
|
|
||||||
|
def restrict_to_ids(self, db, data):
|
||||||
|
# TODO: Implement this based on data.username caching result on the
|
||||||
|
# data object
|
||||||
|
ans = data.restrict_to_ids.get(db.server_library_id)
|
||||||
|
if ans is None:
|
||||||
|
ans = data.restrict_to_ids[db.server_library_id] = db.all_book_ids()
|
||||||
|
return ans
|
||||||
|
|
||||||
class Handler(object):
|
class Handler(object):
|
||||||
|
|
||||||
def __init__(self, libraries, opts, testing=False):
|
def __init__(self, libraries, opts, testing=False):
|
||||||
|
@ -209,6 +209,7 @@ class RequestData(object): # {{{
|
|||||||
self.lang_code = self.gettext_func = self.ngettext_func = None
|
self.lang_code = self.gettext_func = self.ngettext_func = None
|
||||||
self.set_translator(self.get_preferred_language())
|
self.set_translator(self.get_preferred_language())
|
||||||
self.tdir = tdir
|
self.tdir = tdir
|
||||||
|
self.restrict_to_ids = {}
|
||||||
|
|
||||||
def generate_static_output(self, name, generator):
|
def generate_static_output(self, name, generator):
|
||||||
ans = self.static_cache.get(name)
|
ans = self.static_cache.get(name)
|
||||||
|
45
src/calibre/srv/tests/ajax.py
Normal file
45
src/calibre/srv/tests/ajax.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import httplib, zlib, json
|
||||||
|
|
||||||
|
from calibre.srv.tests.base import LibraryBaseTest
|
||||||
|
|
||||||
|
class ContentTest(LibraryBaseTest):
|
||||||
|
|
||||||
|
def test_ajax_book(self): # {{{
|
||||||
|
'Test /ajax/book'
|
||||||
|
with self.create_server() as server:
|
||||||
|
db = server.handler.router.ctx.get_library()
|
||||||
|
conn = server.connect()
|
||||||
|
|
||||||
|
def request(url, headers={}):
|
||||||
|
conn.request('GET', '/ajax/book' + url, headers=headers)
|
||||||
|
r = conn.getresponse()
|
||||||
|
data = r.read()
|
||||||
|
if r.status == httplib.OK and data.startswith(b'{'):
|
||||||
|
data = json.loads(data)
|
||||||
|
return r, data
|
||||||
|
|
||||||
|
r, data = request('/x')
|
||||||
|
self.ae(r.status, httplib.NOT_FOUND)
|
||||||
|
|
||||||
|
r, onedata = request('/1')
|
||||||
|
self.ae(r.status, httplib.OK)
|
||||||
|
self.ae(request('/1/' + db.server_library_id)[1], onedata)
|
||||||
|
self.ae(request('/%s?id_is_uuid=true' % db.field_for('uuid', 1))[1], onedata)
|
||||||
|
|
||||||
|
r, data = request('s')
|
||||||
|
self.ae(set(data.iterkeys()), set(map(str, db.all_book_ids())))
|
||||||
|
r, zdata = request('s', headers={'Accept-Encoding':'gzip'})
|
||||||
|
self.ae(r.getheader('Content-Encoding'), 'gzip')
|
||||||
|
self.ae(json.loads(zlib.decompress(zdata, 16+zlib.MAX_WBITS)), data)
|
||||||
|
r, data = request('s?ids=1,2')
|
||||||
|
self.ae(set(data.iterkeys()), {'1', '2'})
|
||||||
|
|
||||||
|
# }}}
|
Loading…
x
Reference in New Issue
Block a user