%PDF- %PDF-
| Direktori : /lib/calibre/calibre/srv/ |
| Current File : //lib/calibre/calibre/srv/legacy.py |
#!/usr/bin/env python3
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
from lxml.html import tostring
from lxml.html.builder import E as E_
from calibre import strftime
from calibre.constants import __appname__
from calibre.db.view import sanitize_sort_field_name
from calibre.ebooks.metadata import authors_to_string
from calibre.srv.content import book_filename, get
from calibre.srv.errors import HTTPBadRequest, HTTPRedirect
from calibre.srv.routes import endpoint
from calibre.srv.utils import get_library_data, http_date
from calibre.utils.cleantext import clean_xml_chars
from calibre.utils.date import dt_as_local, is_date_undefined, timestampfromdt
from polyglot.builtins import iteritems, string_or_bytes, as_bytes
from polyglot.urllib import urlencode
# /mobile {{{
def clean(x):
if isinstance(x, string_or_bytes):
x = clean_xml_chars(x)
return x
def E(tag, *children, **attribs):
children = list(map(clean, children))
attribs = {k.rstrip('_').replace('_', '-'):clean(v) for k, v in iteritems(attribs)}
return getattr(E_, tag)(*children, **attribs)
for tag in 'HTML HEAD TITLE LINK DIV IMG BODY OPTION SELECT INPUT FORM SPAN TABLE TR TD A HR META'.split():
setattr(E, tag, partial(E, tag))
tag = tag.lower()
setattr(E, tag, partial(E, tag))
def html(ctx, rd, endpoint, output):
rd.outheaders.set('Content-Type', 'text/html; charset=UTF-8', replace_all=True)
if isinstance(output, bytes):
ans = output # Assume output is already UTF-8 encoded html
else:
ans = tostring(output, include_meta_content_type=True, pretty_print=True, encoding='utf-8', doctype='<!DOCTYPE html>', with_tail=False)
if not isinstance(ans, bytes):
ans = ans.encode('utf-8')
return ans
def build_search_box(num, search, sort, order, ctx, field_metadata, library_id): # {{{
div = E.div(id='search_box')
form = E.form(_('Show '), method='get', action=ctx.url_for('/mobile'))
form.set('accept-charset', 'UTF-8')
div.append(form)
num_select = E.select(name='num')
for option in (5, 10, 25, 100):
kwargs = {'value':str(option)}
if option == num:
kwargs['SELECTED'] = 'SELECTED'
num_select.append(E.option(str(option), **kwargs))
num_select.tail = ' books matching '
form.append(num_select)
searchf = E.input(name='search', id='s', value=search if search else '')
searchf.tail = _(' sorted by ')
form.append(searchf)
sort_select = E.select(name='sort')
for option in ('date','author','title','rating','size','tags','series'):
q = sanitize_sort_field_name(field_metadata, option)
kwargs = {'value':option}
if q == sanitize_sort_field_name(field_metadata, sort):
kwargs['SELECTED'] = 'SELECTED'
sort_select.append(E.option(option, **kwargs))
form.append(sort_select)
order_select = E.select(name='order')
for option in ('ascending','descending'):
kwargs = {'value':option}
if option == order:
kwargs['SELECTED'] = 'SELECTED'
order_select.append(E.option(option, **kwargs))
form.append(order_select)
if library_id:
form.append(E.input(name='library_id', type='hidden', value=library_id))
form.append(E.input(id='go', type='submit', value=_('Search')))
return div
# }}}
def build_navigation(start, num, total, url_base): # {{{
end = min((start+num-1), total)
tagline = E.span('Books %d to %d of %d'%(start, end, total),
style='display: block; text-align: center;')
left_buttons = E.td(class_='button', style='text-align:left')
right_buttons = E.td(class_='button', style='text-align:right')
if start > 1:
for t,s in [('First', 1), ('Previous', max(start-num,1))]:
left_buttons.append(E.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(E.a(t, href='%s&start=%d'%(url_base, s)))
buttons = E.table(
E.tr(left_buttons, right_buttons),
class_='buttons')
return E.div(tagline, buttons, class_='navigation')
# }}}
def build_choose_library(ctx, library_map):
select = E.select(name='library_id')
for library_id, library_name in iteritems(library_map):
select.append(E.option(library_name, value=library_id))
return E.div(
E.form(
_('Change library to: '), select, ' ', E.input(type='submit', value=_('Change library')),
method='GET', action=ctx.url_for('/mobile'), accept_charset='UTF-8'
),
id='choose_library')
def build_index(rd, books, num, search, sort, order, start, total, url_base, field_metadata, ctx, library_map, library_id): # {{{
logo = E.div(E.img(src=ctx.url_for('/static', what='calibre.png'), alt=__appname__), id='logo')
search_box = build_search_box(num, search, sort, order, ctx, field_metadata, library_id)
navigation = build_navigation(start, num, total, url_base)
navigation2 = build_navigation(start, num, total, url_base)
if library_map:
choose_library = build_choose_library(ctx, library_map)
books_table = E.table(id='listing')
body = E.body(
logo,
search_box,
navigation,
E.hr(class_='spacer'),
books_table,
E.hr(class_='spacer'),
navigation2
)
for book in books:
thumbnail = E.td(
E.img(type='image/jpeg', border='0', src=ctx.url_for('/get', what='thumb', book_id=book.id, library_id=library_id),
class_='thumbnail')
)
data = E.td()
for fmt in book.formats or ():
if not fmt or fmt.lower().startswith('original_'):
continue
s = E.span(
E.a(
fmt.lower(),
href=ctx.url_for('/legacy/get', what=fmt, book_id=book.id, library_id=library_id, filename=book_filename(rd, book.id, book, fmt))
),
class_='button')
s.tail = ''
data.append(s)
div = E.div(class_='data-container')
data.append(div)
series = ('[%s - %s]'%(book.series, book.series_index)) if book.series else ''
tags = ('Tags=[%s]'%', '.join(book.tags)) if book.tags else ''
ctext = ''
for key in filter(ctx.is_field_displayable, field_metadata.ignorable_field_keys()):
fm = field_metadata[key]
if fm['datatype'] == 'comments':
continue
name, val = book.format_field(key)
if val:
ctext += '%s=[%s] '%(name, val)
first = E.span('{} {} by {}'.format(book.title, series,
authors_to_string(book.authors)), class_='first-line')
div.append(first)
ds = '' if is_date_undefined(book.timestamp) else strftime('%d %b, %Y', t=dt_as_local(book.timestamp).timetuple())
second = E.span(f'{ds} {tags} {ctext}', class_='second-line')
div.append(second)
books_table.append(E.tr(thumbnail, data))
if library_map:
body.append(choose_library)
body.append(E.div(
E.a(_('Switch to the full interface (non-mobile interface)'),
href=ctx.url_for(None),
style="text-decoration: none; color: blue",
title=_('The full interface gives you many more features, '
'but it may not work well on a small screen')),
style="text-align:center")
)
return E.html(
E.head(
E.title(__appname__ + ' Library'),
E.link(rel='icon', href=ctx.url_for('/favicon.png'), type='image/png'),
E.link(rel='stylesheet', type='text/css', href=ctx.url_for('/static', what='mobile.css')),
E.link(rel='apple-touch-icon', href=ctx.url_for("/static", what='calibre.png')),
E.meta(name="robots", content="noindex")
), # End head
body
) # End html
# }}}
@endpoint('/mobile', postprocess=html)
def mobile(ctx, rd):
db, library_id, library_map, default_library = get_library_data(ctx, rd)
try:
start = max(1, int(rd.query.get('start', 1)))
except ValueError:
raise HTTPBadRequest('start is not an integer')
try:
num = max(0, int(rd.query.get('num', 25)))
except ValueError:
raise HTTPBadRequest('num is not an integer')
search = rd.query.get('search') or ''
with db.safe_read_lock:
book_ids = ctx.search(rd, db, search)
total = len(book_ids)
ascending = rd.query.get('order', '').lower().strip() == 'ascending'
sort_by = sanitize_sort_field_name(db.field_metadata, rd.query.get('sort') or 'date')
try:
book_ids = db.multisort([(sort_by, ascending)], book_ids)
except Exception:
sort_by = 'date'
book_ids = db.multisort([(sort_by, ascending)], book_ids)
books = [db.get_metadata(book_id) for book_id in book_ids[(start-1):(start-1)+num]]
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(db.last_modified()))
order = 'ascending' if ascending else 'descending'
q = {b'search':search.encode('utf-8'), b'order':order.encode('ascii'), b'sort':sort_by.encode('utf-8'), b'num':as_bytes(num), 'library_id':library_id}
url_base = ctx.url_for('/mobile') + '?' + urlencode(q)
lm = {k:v for k, v in iteritems(library_map) if k != library_id}
return build_index(rd, books, num, search, sort_by, order, start, total, url_base, db.field_metadata, ctx, lm, library_id)
# }}}
@endpoint('/browse/{+rest=""}')
def browse(ctx, rd, rest):
if rest.startswith('book/'):
# implementation of https://bugs.launchpad.net/calibre/+bug/1698411
# redirect old server book URLs to new URLs
redirect = ctx.url_for(None) + '#book_id=' + rest[5:] + "&panel=book_details"
from lxml import etree as ET
return html(ctx, rd, endpoint,
E.html(E.head(
ET.XML('<meta http-equiv="refresh" content="0;url=' + redirect + '"/>'),
ET.XML('<script language="javascript">' +
'window.location.href = "' + redirect + '"' +
'</script>'
))))
else:
raise HTTPRedirect(ctx.url_for(None))
@endpoint('/stanza/{+rest=""}')
def stanza(ctx, rd, rest):
raise HTTPRedirect(ctx.url_for('/opds'))
@endpoint('/legacy/get/{what}/{book_id}/{library_id}/{+filename=""}', android_workaround=True)
def legacy_get(ctx, rd, what, book_id, library_id, filename):
# See https://www.mobileread.com/forums/showthread.php?p=3531644 for why
# this is needed for Kobo browsers
return get(ctx, rd, what, book_id, library_id)