Move mobile server templates to lxml from genshi

This commit is contained in:
Kovid Goyal 2010-05-23 21:35:00 -06:00
parent 359c0cd40e
commit 73753b67d8
4 changed files with 171 additions and 110 deletions

View File

@ -451,6 +451,20 @@ def prepare_string_for_xml(raw, attribute=False):
def isbytestring(obj):
return isinstance(obj, (str, bytes))
def human_readable(size):
""" Convert a size in bytes into a human readable form """
divisor, suffix = 1, "B"
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
if size < 1024**(i+1):
divisor, suffix = 1024**(i), candidate
break
size = str(float(size)/divisor)
if size.find(".") > -1:
size = size[:size.find(".")+2]
if size.endswith('.0'):
size = size[:-2]
return size + " " + suffix
if isosx:
import glob, shutil
fdir = os.path.expanduser('~/.fonts')

View File

@ -229,19 +229,6 @@ def info_dialog(parent, title, msg, det_msg='', show=False):
return d
def human_readable(size):
""" Convert a size in bytes into a human readable form """
divisor, suffix = 1, "B"
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
if size < 1024**(i+1):
divisor, suffix = 1024**(i), candidate
break
size = str(float(size)/divisor)
if size.find(".") > -1:
size = size[:size.find(".")+2]
if size.endswith('.0'):
size = size[:-2]
return size + " " + suffix
class Dispatcher(QObject):
'''Convenience class to ensure that a function call always happens in the

View File

@ -13,11 +13,10 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
QAbstractButton, QPainter, QLineEdit, QComboBox, \
QMenu, QStringListModel, QCompleter, QStringList
from calibre.gui2 import human_readable, NONE, \
error_dialog, pixmap_to_data, dynamic
from calibre.gui2 import NONE, error_dialog, pixmap_to_data, dynamic
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
from calibre import fit_image, human_readable
from calibre.utils.fonts import fontconfig
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata.meta import metadata_from_filename

View File

@ -5,34 +5,143 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import re, copy
import re
import __builtin__
import cherrypy
from lxml import html
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, LINK, DIV, IMG, BODY, \
OPTION, SELECT, INPUT, FORM, SPAN, TABLE, TR, TD, A, HR
from calibre.utils.genshi.template import MarkupTemplate
from calibre.library.server.utils import strftime
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import __appname__
from calibre import human_readable
# Templates {{{
MOBILE_BOOK = '''\
<tr xmlns:py="http://genshi.edgewall.org/">
<td class="thumbnail">
<img type="image/jpeg" src="/get/thumb/${r[FM['id']]}" border="0"/>
</td>
<td>
<py:for each="format in r[FM['formats']].split(',')">
<span class="button"><a href="/get/${format}/${authors}-${r[FM['title']]}_${r[FM['id']]}.${format}">${format.lower()}</a></span>&nbsp;
</py:for>
${r[FM['title']]}${(' ['+r[FM['series']]+'-'+r[FM['series_index']]+']') if r[FM['series']] else ''} by ${authors} - ${r[FM['size']]/1024}k - ${r[FM['publisher']] if r[FM['publisher']] else ''} ${pubdate} ${'['+r[FM['tags']]+']' if r[FM['tags']] else ''}
</td>
</tr>
'''
def CLASS(*args, **kwargs): # class is a reserved word in Python
kwargs['class'] = ' '.join(args)
return kwargs
MOBILE = MarkupTemplate('''\
<html xmlns:py="http://genshi.edgewall.org/">
<head>
<style>
def build_search_box(num, search, sort, order): # {{{
div = DIV(id='search_box')
form = FORM('Show ', method='get', action='mobile')
div.append(form)
num_select = SELECT(name='num')
for option in (5, 10, 25, 100):
kwargs = {'value':str(option)}
if option == num:
kwargs['SELECTED'] = 'SELECTED'
num_select.append(OPTION(str(option), **kwargs))
num_select.tail = ' books matching '
form.append(num_select)
searchf = INPUT(name='search', id='s', value=search if search else '')
searchf.tail = ' sorted by '
form.append(searchf)
sort_select = SELECT(name='sort')
for option in ('date','author','title','rating','size','tags','series'):
kwargs = {'value':option}
if option == sort:
kwargs['SELECTED'] = 'SELECTED'
sort_select.append(OPTION(option, **kwargs))
form.append(sort_select)
order_select = SELECT(name='order')
for option in ('ascending','descending'):
kwargs = {'value':option}
if option == order:
kwargs['SELECTED'] = 'SELECTED'
order_select.append(OPTION(option, **kwargs))
form.append(order_select)
form.append(INPUT(id='go', type='submit', value='Search'))
return div
# }}}
def build_navigation(start, num, total, url_base): # {{{
end = min((start+num-1), total)
tagline = SPAN('Books %d to %d of %d'%(start, end, total),
style='display: block; text-align: center;')
left_buttons = TD(CLASS('button', style='text-align:left'))
right_buttons = TD(CLASS('button', style='text-align:right'))
if start > 1:
for t,s in [('First', 1), ('Previous', max(start-(num+1),1))]:
left_buttons.append(A(t, href='%s;start=%d'%(url_base, s)))
if total > start + num:
for t,s in [('Next', start+num), ('Last', total-num+1)]:
right_buttons.append(A(t, href='%s;start=%d'%(url_base, s)))
buttons = TABLE(
TR(left_buttons, right_buttons),
CLASS('buttons'))
return DIV(tagline, buttons, CLASS('navigation'))
# }}}
def build_index(books, num, search, sort, order, start, total, url_base):
logo = DIV(IMG(src='/static/calibre.png', alt=__appname__), id='logo')
search_box = build_search_box(num, search, sort, order)
navigation = build_navigation(start, num, total, url_base)
bookt = TABLE(id='listing')
body = BODY(
logo,
search_box,
navigation,
HR(CLASS('spacer')),
bookt
)
# Book list {{{
for book in books:
thumbnail = TD(
IMG(type='image/jpeg', border='0', src='/get/thumb/%s' %
book['id']),
CLASS('thumbnail'))
data = TD()
last = None
for fmt in book['formats'].split(','):
s = SPAN(
A(
fmt.lower(),
href='/get/%s/%s-%s_%d.%s' % (fmt, book['authors'],
book['title'], book['id'], fmt)
),
CLASS('button'))
s.tail = u'\u202f' # &nbsp;
last = s
data.append(s)
series = u'[%s - %s]'%(book['series'], book['series_index']) \
if book['series'] else ''
tags = u'[%s]'%book['tags'] if book['tags'] else ''
text = u'\u202f%s %s by %s - %s - %s %s' % (book['title'], series,
book['authors'], book['size'], book['timestamp'], tags)
if last is None:
data.text = text
else:
last.tail += text
bookt.append(TR(thumbnail, data))
# }}}
return HTML(
HEAD(
TITLE(__appname__ + ' Library'),
LINK(rel='icon', href='http://calibre-ebook.com/favicon.ico',
type='image/x-icon'),
STYLE( # {{{
'''
.navigation table.buttons {
width: 100%;
}
@ -109,64 +218,11 @@ div.navigation {
clear: both;
}
</style>
<link rel="icon" href="http://calibre-ebook.com/favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="logo">
<img src="/static/calibre.png" alt="Calibre" />
</div>
<div id="search_box">
<form method="get" action="/mobile">
Show <select name="num">
<py:for each="option in [5,10,25,100]">
<option py:if="option == num" value="${option}" SELECTED="SELECTED">${option}</option>
<option py:if="option != num" value="${option}">${option}</option>
</py:for>
</select>
books matching <input name="search" id="s" value="${search}" /> sorted by
''', type='text/css') # }}}
), # End head
body
) # End html
<select name="sort">
<py:for each="option in ['date','author','title','rating','size','tags','series']">
<option py:if="option == sort" value="${option}" SELECTED="SELECTED">${option}</option>
<option py:if="option != sort" value="${option}">${option}</option>
</py:for>
</select>
<select name="order">
<py:for each="option in ['ascending','descending']">
<option py:if="option == order" value="${option}" SELECTED="SELECTED">${option}</option>
<option py:if="option != order" value="${option}">${option}</option>
</py:for>
</select>
<input id="go" type="submit" value="Search"/>
</form>
</div>
<div class="navigation">
<span style="display: block; text-align: center;">Books ${start} to ${ min((start+num-1) , total) } of ${total}</span>
<table class="buttons">
<tr>
<td class="button" style="text-align:left;">
<a py:if="start > 1" href="${url_base};start=1">First</a>
<a py:if="start > 1" href="${url_base};start=${max(start-(num+1),1)}">Previous</a>
</td>
<td class="button" style="text-align: right;">
<a py:if=" total > (start + num) " href="${url_base};start=${start+num}">Next</a>
<a py:if=" total > (start + num) " href="${url_base};start=${total-num+1}">Last</a>
</td>
</tr>
</table>
</div>
<hr class="spacer" />
<table id="listing">
<py:for each="book in books">
${Markup(book)}
</py:for>
</table>
</body>
</html>
''')
# }}}
class MobileServer(object):
'A view optimized for browsers in mobile devices'
@ -195,26 +251,31 @@ class MobileServer(object):
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)
FM = self.db.FIELD_MAP
items = copy.deepcopy([r for r in iter(self.db) if r[FM['id']] in ids])
items = [r for r in iter(self.db) if r[FM['id']] in ids]
if sort is not None:
self.sort(items, sort, (order.lower().strip() == 'ascending'))
book, books = MarkupTemplate(MOBILE_BOOK), []
books = []
for record in items[(start-1):(start-1)+num]:
if record[FM['formats']] is None:
record[FM['formats']] = ''
if record[FM['size']] is None:
record[FM['size']] = 0
book = {'formats':record[FM['formats']], 'size':record[FM['size']]}
if not book['formats']:
book['formats'] = ''
if not book['size']:
book['size'] = 0
book['size'] = human_readable(book['size'])
aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
record[FM['series_index']] = \
fmt_sidx(float(record[FM['series_index']]))
ts, pd = strftime('%Y/%m/%d %H:%M:%S', record[FM['timestamp']]), \
strftime('%Y/%m/%d %H:%M:%S', record[FM['pubdate']])
books.append(book.generate(r=record, authors=authors, timestamp=ts,
pubdate=pd, FM=FM).render('xml').decode('utf-8'))
book['authors'] = authors
book['series_index'] = fmt_sidx(float(record[FM['series_index']]))
book['series'] = record[FM['series']]
book['tags'] = record[FM['tags']]
book['title'] = record[FM['title']]
for x in ('timestamp', 'pubdate'):
book[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]])
book['id'] = record[FM['id']]
books.append(book)
updated = self.db.last_modified()
cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8'
@ -223,8 +284,8 @@ class MobileServer(object):
url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num)
return MOBILE.generate(books=books, start=start, updated=updated,
search=search, sort=sort, order=order, num=num, FM=FM,
total=len(ids), url_base=url_base).render('html')
return html.tostring(build_index(books, num, search, sort, order,
start, len(ids), url_base),
encoding='utf-8', include_meta_content_type=True,
pretty_print=True)