diff --git a/src/calibre/library/server/ajax.py b/src/calibre/library/server/ajax.py new file mode 100644 index 0000000000..92ee5a0616 --- /dev/null +++ b/src/calibre/library/server/ajax.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import json +from functools import wraps + +import cherrypy + +from calibre.utils.date import isoformat +from calibre.utils.config import prefs +from calibre.ebooks.metadata.book.json_codec import JsonCodec + +class Endpoint(object): # {{{ + 'Manage mime-type json serialization, etc.' + + def __init__(self, mimetype='application/json; charset=utf-8', + set_last_modified=True): + self.mimetype = mimetype + self.set_last_modified = set_last_modified + + def __call__(eself, func): + + @wraps(func) + def wrapper(self, *args, **kwargs): + # Remove AJAX caching disabling jquery workaround arg + # This arg is put into AJAX queries by jQuery to prevent + # caching in the browser. We dont want it passed to the wrapped + # function + kwargs.pop('_', None) + + ans = func(self, *args, **kwargs) + cherrypy.response.headers['Content-Type'] = eself.mimetype + if eself.set_last_modified: + updated = self.db.last_modified() + cherrypy.response.headers['Last-Modified'] = \ + self.last_modified(max(updated, self.build_time)) + if 'application/json' in eself.mimetype: + ans = json.dumps(ans, indent=2, + ensure_ascii=False).encode('utf-8') + return ans + + return wrapper +# }}} + +class AjaxServer(object): + + def __init__(self): + self.ajax_json_codec = JsonCodec() + + def add_routes(self, connect): + base_href = '/ajax' + connect('ajax_book', base_href+'/book/{book_id}', self.ajax_book) + + # Get book metadata {{{ + @Endpoint() + def ajax_book(self, book_id): + cherrypy.response.headers['Content-Type'] = \ + 'application/json; charset=utf-8' + try: + book_id = int(book_id) + mi = self.db.get_metadata(book_id, index_is_id=True) + except: + raise cherrypy.HTTPError(404, 'No book with id: %r'%book_id) + try: + mi.rating = mi.rating/2. + except: + mi.rating = 0.0 + cherrypy.response.timeout = 3600 + cherrypy.response.headers['Last-Modified'] = \ + self.last_modified(mi.last_modified) + + data = self.ajax_json_codec.encode_book_metadata(mi) + for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime', + 'rights', 'book_producer'): + data.pop(x, None) + + def absurl(url): + return self.opts.url_prefix + url + + data['cover'] = absurl(u'/get/cover/%d'%book_id) + data['thumbnail'] = absurl(u'/get/thumb/%d'%book_id) + mi.format_metadata = {k: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: absurl(u'/get/%s/%d'%(fmt, book_id))} + else: + data['main_format'] = None + data['other_formats'] = {fmt: absurl(u'/get/%s/%d'%(fmt, book_id)) for fmt + in other_fmts} + + return data + # }}} + diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index 9921524343..2f345eb670 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -24,9 +24,9 @@ from calibre.library.server.xml import XMLServer from calibre.library.server.opds import OPDSServer from calibre.library.server.cache import Cache from calibre.library.server.browse import BrowseServer +from calibre.library.server.ajax import AjaxServer from calibre.utils.search_query_parser import saved_searches from calibre import prints -from calibre.ebooks.metadata.book.json_codec import JsonCodec class DispatchController(object): # {{{ @@ -100,7 +100,7 @@ cherrypy.engine.bonjour = BonJour(cherrypy.engine) # }}} class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, - BrowseServer): + BrowseServer, AjaxServer): server_name = __appname__ + '/' + __version__ @@ -164,11 +164,11 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, self.__dispatcher__ = DispatchController(self.opts.url_prefix, wsgi) for x in self.__class__.__bases__: if hasattr(x, 'add_routes'): + x.__init__(self) x.add_routes(self, self.__dispatcher__) root_conf = self.config.get('/', {}) root_conf['request.dispatch'] = self.__dispatcher__.dispatcher self.config['/'] = root_conf - self.json_codec = JsonCodec() def set_database(self, db): self.db = db diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index d98da4b955..e55970ccd7 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -5,12 +5,12 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os, posixpath, json +import re, os, posixpath import cherrypy from calibre import fit_image, guess_type -from calibre.utils.date import fromtimestamp, isoformat +from calibre.utils.date import fromtimestamp from calibre.library.caches import SortKeyGenerator from calibre.library.save_to_disk import find_plugboard from calibre.ebooks.metadata import authors_to_string @@ -18,7 +18,6 @@ from calibre.utils.magick.draw import (save_cover_data_to, Image, thumbnail as generate_thumbnail) from calibre.utils.filenames import ascii_filename from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.utils.config import prefs plugboard_content_server_value = 'content_server' plugboard_content_server_formats = ['epub'] @@ -94,7 +93,7 @@ class ContentServer(object): if what == 'opf': return self.get_metadata_as_opf(id) if what == 'json': - return self.get_metadata_as_json(id) + raise cherrypy.InternalRedirect('/ajax/book/%d'%id) return self.get_format(id, what) def static(self, name): @@ -196,51 +195,6 @@ class ContentServer(object): return data - def get_metadata_as_json(self, id_): - cherrypy.response.headers['Content-Type'] = \ - 'application/json; charset=utf-8' - mi = self.db.get_metadata(id_, index_is_id=True) - try: - mi.rating = mi.rating/2. - except: - mi.rating = 0.0 - cherrypy.response.timeout = 3600 - cherrypy.response.headers['Last-Modified'] = \ - self.last_modified(mi.last_modified) - - data = self.json_codec.encode_book_metadata(mi) - for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime', - 'rights', 'book_producer'): - data.pop(x, None) - - def absurl(url): - return self.opts.url_prefix + url - - data['cover'] = absurl(u'/get/cover/%d'%id_) - data['thumbnail'] = absurl(u'/get/thumb/%d'%id_) - 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: absurl(u'/get/%s/%d'%(fmt, id_))} - else: - data['main_format'] = None - data['other_formats'] = {fmt: absurl(u'/get/%s/%d'%(fmt, id_)) for fmt - in other_fmts} - - return json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8') def get_format(self, id, format): format = format.upper()