Implement /get/json

This commit is contained in:
Kovid Goyal 2015-06-14 14:01:36 +05:30
parent 5ef9def403
commit 3d2066c5c2
6 changed files with 141 additions and 8 deletions

View File

@ -118,8 +118,8 @@ def decode_is_multiple(fm):
class JsonCodec(object):
def __init__(self):
self.field_metadata = FieldMetadata()
def __init__(self, field_metadata=None):
self.field_metadata = field_metadata or FieldMetadata()
def encode_to_file(self, file_, booklist):
file_.write(json.dumps(self.encode_booklist_metadata(booklist),

106
src/calibre/srv/ajax.py Normal file
View File

@ -0,0 +1,106 @@
#!/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>'
from functools import partial
from binascii import hexlify, unhexlify
from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata.book.json_codec import JsonCodec
from calibre.srv.routes import endpoint, json
from calibre.utils.config import prefs, tweaks
from calibre.utils.date import isoformat
def encode_name(name):
if isinstance(name, unicode):
name = name.encode('utf-8')
return hexlify(name)
def decode_name(name):
return unhexlify(name).decode('utf-8')
def book_to_json(ctx, rd, db, book_id,
get_category_urls=True, device_compatible=False, device_for_template=None):
mi = db.get_metadata(book_id, get_cover=False)
codec = JsonCodec(db.field_metadata)
if not device_compatible:
try:
mi.rating = mi.rating/2.
except Exception:
mi.rating = 0.0
data = codec.encode_book_metadata(mi)
for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime',
'rights', 'book_producer'):
data.pop(x, None)
get = partial(ctx.url_for, '/get', book_id=book_id, library_id=db.server_library_id)
data['cover'] = get(what='cover')
data['thumbnail'] = get(what='thumb')
if not device_compatible:
mi.format_metadata = {k.lower():dict(v) for k, v in
mi.format_metadata.iteritems()}
for v in mi.format_metadata.itervalues():
mtime = v.get('mtime', None)
if mtime is not None:
v['mtime'] = isoformat(mtime, as_utc=True)
data['format_metadata'] = mi.format_metadata
fmts = set(x.lower() for x in mi.format_metadata.iterkeys())
pf = prefs['output_format'].lower()
other_fmts = list(fmts)
try:
fmt = pf if pf in fmts else other_fmts[0]
except:
fmt = None
if fmts and fmt:
other_fmts = [x for x in fmts if x != fmt]
data['formats'] = sorted(fmts)
if fmt:
data['main_format'] = {fmt:get(what=fmt)}
else:
data['main_format'] = None
data['other_formats'] = {fmt:get(what=fmt) for fmt in other_fmts}
if get_category_urls:
category_urls = data['category_urls'] = {}
for key in mi.all_field_keys():
fm = mi.metadata_for_field(key)
if (fm and fm['is_category'] and not fm['is_csp'] and
key != 'formats' and fm['datatype'] != 'rating'):
categories = mi.get(key) or []
if isinstance(categories, basestring):
categories = [categories]
idmap = db.get_item_ids(key, categories)
category_urls[key] = dbtags = {}
for category, category_id in idmap.iteritems():
if category_id is not None:
dbtags[category] = ctx.url_for(
'/ajax/books_in', category=encode_name(key), item=encode_name(str(category_id)))
else:
series = data.get('series', None) or ''
if series:
tsorder = tweaks['save_template_title_series_sorting']
series = title_sort(series, order=tsorder)
data['_series_sort_'] = series
if device_for_template:
import posixpath
from calibre.devices.utils import create_upload_path
from calibre.utils.filenames import ascii_filename as sanitize
from calibre.customize.ui import device_plugins
for device_class in device_plugins():
if device_class.__class__.__name__ == device_for_template:
template = device_class.save_template()
data['_filename_'] = create_upload_path(mi, book_id,
template, sanitize, path_type=posixpath)
break
return data, mi.last_modified
@endpoint('/ajax/books_in/{category}/{item}', postprocess=json)
def books_in(ctx, rd, category, item):
raise NotImplementedError('TODO: Implement this')

View File

@ -14,8 +14,9 @@ from calibre.ebooks.metadata import authors_to_string
from calibre.ebooks.metadata.meta import set_metadata
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.library.save_to_disk import find_plugboard
from calibre.srv.ajax import book_to_json
from calibre.srv.errors import HTTPNotFound
from calibre.srv.routes import endpoint
from calibre.srv.routes import endpoint, json
from calibre.srv.utils import http_date
from calibre.utils.config_base import tweaks
from calibre.utils.date import timestampfromdt
@ -162,7 +163,9 @@ def get(ctx, rd, what, book_id, library_id):
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(mi.last_modified))
return metadata_to_opf(mi)
elif what == 'json':
raise NotImplementedError('TODO: Implement this')
data, last_modified = book_to_json(ctx, rd, db, book_id)
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified))
return json(ctx, rd, get, data)
else:
try:
return book_fmt(ctx, rd, library_id, db, book_id, what.lower())

View File

@ -76,7 +76,7 @@ class Handler(object):
def __init__(self, libraries, opts, testing=False):
self.router = Router(ctx=Context(libraries, opts, testing=testing), url_prefix=opts.url_prefix)
for module in ('content',):
for module in ('content', 'ajax'):
module = import_module('calibre.srv.' + module)
self.router.load_routes(vars(module).itervalues())
self.router.finalize()

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import httplib, sys, inspect, re, time, numbers
import httplib, sys, inspect, re, time, numbers, json as jsonlib
from itertools import izip
from operator import attrgetter
@ -15,6 +15,13 @@ from calibre.srv.utils import http_date
default_methods = frozenset(('HEAD', 'GET'))
def json(ctx, rd, endpoint, output):
rd.outheaders['Content-Type'] = 'application/json; charset=UTF-8'
ans = jsonlib.dumps(output, ensure_ascii=False)
if not isinstance(ans, bytes):
ans = ans.encode('utf-8')
return ans
def route_key(route):
return route.partition('{')[0].rstrip('/')
@ -29,7 +36,9 @@ def endpoint(route,
# Set to a number to cache for at most number hours
# Set to a tuple (cache_type, max_age) to explicitly set the
# Cache-Control header
cache_control=False
cache_control=False,
postprocess=None
):
def annotate(f):
f.route = route.rstrip('/') or '/'
@ -38,6 +47,7 @@ def endpoint(route,
f.auth_required = auth_required
f.android_workaround = android_workaround
f.cache_control = cache_control
f.postprocess = postprocess
f.is_endpoint = True
return f
return annotate
@ -223,6 +233,10 @@ class Router(object):
self.finalize_session(endpoint_, data, ans)
outheaders = data.outheaders
pp = endpoint_.postprocess
if pp is not None:
ans = pp(self.ctx, data, endpoint_, ans)
cc = endpoint_.cache_control
if cc is not False and 'Cache-Control' not in data.outheaders:
if cc is None or cc == 'no-cache':

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import httplib, zlib
import httplib, zlib, json
from io import BytesIO
from calibre.ebooks.metadata.epub import get_metadata
@ -164,4 +164,14 @@ class ContentTest(LibraryBaseTest):
raw = r.read()
self.ae(zlib.decompress(raw, 16+zlib.MAX_WBITS), data)
# Test serving metadata as json
r, data = get('json', 1)
self.ae(r.status, httplib.OK)
self.ae(db.field_for('title', 1), json.loads(data)['title'])
conn.request('GET', '/get/json/1', headers={'Accept-Encoding':'gzip'})
r = conn.getresponse()
self.ae(r.status, httplib.OK), self.ae(r.getheader('Content-Encoding'), 'gzip')
raw = r.read()
self.ae(zlib.decompress(raw, 16+zlib.MAX_WBITS), data)
# }}}