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):
|
||||
|
||||
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
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.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())
|
||||
|
@ -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()
|
||||
|
@ -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':
|
||||
|
@ -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)
|
||||
|
||||
# }}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user