From 8a0aad39f90399dd78d3b444a9805483d92815d1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 27 Oct 2009 21:47:39 -0600 Subject: [PATCH] Content Server: Add a mobile friendly interface. Now if you access the content server using a mobile browser, a list of books that is easy to browse on small/e-ink screens is returned. Fixes #3870 (Add a mobile user interface for calibre) --- src/calibre/library/server.py | 217 +++++++++++++++++++++++++++++++++- 1 file changed, 214 insertions(+), 3 deletions(-) diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index f615827cde..015df2b017 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -24,7 +24,7 @@ except ImportError: from calibre.constants import __version__, __appname__ from calibre.utils.genshi.template import MarkupTemplate from calibre import fit_image, guess_type, prepare_string_for_xml, \ - strftime as _strftime + strftime as _strftime, prints from calibre.library import server_config as config from calibre.library.database2 import LibraryDatabase2, FIELD_MAP from calibre.utils.config import config_dir @@ -77,6 +77,159 @@ class LibraryServer(object): ''') + MOBILE_UA = re.compile('(?i)(?:iPhone|Opera Mini|NetFront|webOS|Mobile|Android|imode|DoCoMo|Minimo|Blackberry|MIDP|Symbian)') + + MOBILE_BOOK = textwrap.dedent('''\ + + + + + + + ${format.lower()}  + + ${r[1]} by ${authors} - ${r[6]/1024}k - ${r[3] if r[3] else ''} ${pubdate} ${'['+r[7]+']' if r[7] else ''} + + + ''') + + MOBILE = MarkupTemplate(textwrap.dedent('''\ + + + + + + + + + +
+ + + ${Markup(book)} + +
+ + + ''')) + LIBRARY = MarkupTemplate(textwrap.dedent('''\ @@ -534,6 +687,52 @@ class LibraryServer(object): next_link=next_link, updated=updated, id='urn:calibre:main').render('xml') + @expose + def mobile(self, start='1', num='25', sort='date', search='', + _=None, order='descending'): + ''' + Serves metadata from the calibre database as XML. + + :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. + :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax + :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results + :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching + ''' + try: + start = int(start) + except ValueError: + raise cherrypy.HTTPError(400, 'start: %s is not an integer'%start) + try: + num = int(num) + except ValueError: + raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) + ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() + ids = sorted(ids) + items = [r for r in iter(self.db) if r[0] in ids] + if sort is not None: + self.sort(items, sort, (order.lower().strip() == 'ascending')) + + book, books = MarkupTemplate(self.MOBILE_BOOK), [] + for record in items[(start-1):(start-1)+num]: + aus = record[2] if record[2] else __builtin__._('Unknown') + authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) + record[10] = fmt_sidx(float(record[10])) + ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[5]), \ + strftime('%Y/%m/%d %H:%M:%S', record[FIELD_MAP['pubdate']]) + books.append(book.generate(r=record, authors=authors, timestamp=ts, + pubdate=pd).render('xml').decode('utf-8')) + updated = self.db.last_modified() + + cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' + cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) + + + url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num) + + return self.MOBILE.generate(books=books, start=start, updated=updated, search=search, sort=sort, order=order, num=num, + total=len(ids), url_base=url_base).render('html') + + @expose def library(self, start='0', num='50', sort=None, search=None, _=None, order='ascending'): @@ -584,10 +783,22 @@ class LibraryServer(object): cherrypy.request.headers.get('Stanza-Device-Name', 919) != 919 or \ cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919 or \ ua.startswith('Stanza') - return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None), + + # A better search would be great + want_mobile = self.MOBILE_UA.search(ua) is not None + if self.opts.develop and not want_mobile: + prints('User agent:', ua) + + if want_opds: + return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None), tagid=kwargs.get('tagid',None), seriesid=kwargs.get('seriesid',None), - offset=kwargs.get('offset', 0)) if want_opds else self.static('index.html') + offset=kwargs.get('offset', 0)) + + if want_mobile: + return self.mobile() + + return self.static('index.html') @expose