mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Move mobile server templates to lxml from genshi
This commit is contained in:
parent
359c0cd40e
commit
73753b67d8
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
</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' #
|
||||
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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user