mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement /get/json
This commit is contained in:
parent
5ef9def403
commit
3d2066c5c2
@ -118,8 +118,8 @@ def decode_is_multiple(fm):
|
|||||||
|
|
||||||
class JsonCodec(object):
|
class JsonCodec(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, field_metadata=None):
|
||||||
self.field_metadata = FieldMetadata()
|
self.field_metadata = field_metadata or FieldMetadata()
|
||||||
|
|
||||||
def encode_to_file(self, file_, booklist):
|
def encode_to_file(self, file_, booklist):
|
||||||
file_.write(json.dumps(self.encode_booklist_metadata(booklist),
|
file_.write(json.dumps(self.encode_booklist_metadata(booklist),
|
||||||
|
106
src/calibre/srv/ajax.py
Normal file
106
src/calibre/srv/ajax.py
Normal 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')
|
@ -14,8 +14,9 @@ from calibre.ebooks.metadata import authors_to_string
|
|||||||
from calibre.ebooks.metadata.meta import set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.library.save_to_disk import find_plugboard
|
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.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.srv.utils import http_date
|
||||||
from calibre.utils.config_base import tweaks
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre.utils.date import timestampfromdt
|
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))
|
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(mi.last_modified))
|
||||||
return metadata_to_opf(mi)
|
return metadata_to_opf(mi)
|
||||||
elif what == 'json':
|
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:
|
else:
|
||||||
try:
|
try:
|
||||||
return book_fmt(ctx, rd, library_id, db, book_id, what.lower())
|
return book_fmt(ctx, rd, library_id, db, book_id, what.lower())
|
||||||
|
@ -76,7 +76,7 @@ class Handler(object):
|
|||||||
|
|
||||||
def __init__(self, libraries, opts, testing=False):
|
def __init__(self, libraries, opts, testing=False):
|
||||||
self.router = Router(ctx=Context(libraries, opts, testing=testing), url_prefix=opts.url_prefix)
|
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)
|
module = import_module('calibre.srv.' + module)
|
||||||
self.router.load_routes(vars(module).itervalues())
|
self.router.load_routes(vars(module).itervalues())
|
||||||
self.router.finalize()
|
self.router.finalize()
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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 itertools import izip
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
@ -15,6 +15,13 @@ from calibre.srv.utils import http_date
|
|||||||
|
|
||||||
default_methods = frozenset(('HEAD', 'GET'))
|
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):
|
def route_key(route):
|
||||||
return route.partition('{')[0].rstrip('/')
|
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 number to cache for at most number hours
|
||||||
# Set to a tuple (cache_type, max_age) to explicitly set the
|
# Set to a tuple (cache_type, max_age) to explicitly set the
|
||||||
# Cache-Control header
|
# Cache-Control header
|
||||||
cache_control=False
|
cache_control=False,
|
||||||
|
|
||||||
|
postprocess=None
|
||||||
):
|
):
|
||||||
def annotate(f):
|
def annotate(f):
|
||||||
f.route = route.rstrip('/') or '/'
|
f.route = route.rstrip('/') or '/'
|
||||||
@ -38,6 +47,7 @@ def endpoint(route,
|
|||||||
f.auth_required = auth_required
|
f.auth_required = auth_required
|
||||||
f.android_workaround = android_workaround
|
f.android_workaround = android_workaround
|
||||||
f.cache_control = cache_control
|
f.cache_control = cache_control
|
||||||
|
f.postprocess = postprocess
|
||||||
f.is_endpoint = True
|
f.is_endpoint = True
|
||||||
return f
|
return f
|
||||||
return annotate
|
return annotate
|
||||||
@ -223,6 +233,10 @@ class Router(object):
|
|||||||
self.finalize_session(endpoint_, data, ans)
|
self.finalize_session(endpoint_, data, ans)
|
||||||
outheaders = data.outheaders
|
outheaders = data.outheaders
|
||||||
|
|
||||||
|
pp = endpoint_.postprocess
|
||||||
|
if pp is not None:
|
||||||
|
ans = pp(self.ctx, data, endpoint_, ans)
|
||||||
|
|
||||||
cc = endpoint_.cache_control
|
cc = endpoint_.cache_control
|
||||||
if cc is not False and 'Cache-Control' not in data.outheaders:
|
if cc is not False and 'Cache-Control' not in data.outheaders:
|
||||||
if cc is None or cc == 'no-cache':
|
if cc is None or cc == 'no-cache':
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import httplib, zlib
|
import httplib, zlib, json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from calibre.ebooks.metadata.epub import get_metadata
|
from calibre.ebooks.metadata.epub import get_metadata
|
||||||
@ -164,4 +164,14 @@ class ContentTest(LibraryBaseTest):
|
|||||||
raw = r.read()
|
raw = r.read()
|
||||||
self.ae(zlib.decompress(raw, 16+zlib.MAX_WBITS), data)
|
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)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user