Merge from trunk

This commit is contained in:
Charles Haley 2010-10-17 05:36:52 +01:00
commit 2c3b34b779
9 changed files with 381 additions and 80 deletions

View File

@ -187,6 +187,9 @@ h2.library_name {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
margin-left: auto;
margin-right: auto;
display: block;
} }
.toplevel li { .toplevel li {
@ -194,15 +197,20 @@ h2.library_name {
padding: 0.75em; padding: 0.75em;
cursor: pointer; cursor: pointer;
font-size: larger; font-size: larger;
float: left;
border-radius: 15px; border-radius: 15px;
-moz-border-radius: 15px; -moz-border-radius: 15px;
-webkit-border-radius: 15px; -webkit-border-radius: 15px;
display: inline;
width: 250px;
height: 48px;
overflow: hidden;
} }
.toplevel li img { .toplevel li img {
vertical-align: middle; vertical-align: middle;
margin-right: 2em; margin-right: 1em;
} }
.toplevel li:hover { .toplevel li:hover {
@ -214,7 +222,10 @@ h2.library_name {
} }
.toplevel li span { display: none } .toplevel li span.url { display: none }
.toplevel li span.label {
}
/* }}} */ /* }}} */
@ -406,12 +417,12 @@ h2.library_name {
margin-bottom: 2ex; margin-bottom: 2ex;
} }
#book_details_dialog .details a { .details .right .formats a {
color: blue; color: blue;
text-decoration: none; text-decoration: none;
} }
#book_details_dialog .details a:hover { .details .right .formats a:hover {
color: red; color: red;
} }

View File

@ -1,5 +1,35 @@
// Cookies {{{ // Cookies {{{
/**
* Create a cookie with the given name and value and other optional parameters.
*
* @example $.cookie('the_cookie', 'the_value');
* @desc Set the value of a cookie.
* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
* @desc Create a cookie with all available options.
* @example $.cookie('the_cookie', 'the_value');
* @desc Create a session cookie.
* @example $.cookie('the_cookie', null);
* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
* used when the cookie was set.
*
* @param String name The name of the cookie.
* @param String value The value of the cookie.
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
* If set to null or omitted, the cookie will be a session cookie and will not be retained
* when the the browser exits.
* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
* require a secure protocol (like HTTPS).
* @type undefined
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
function cookie(name, value, options) { function cookie(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie if (typeof value != 'undefined') { // name and value given, set cookie
@ -55,7 +85,7 @@ function init_sort_combobox() {
selectedList: 1, selectedList: 1,
click: function(event, ui){ click: function(event, ui){
$(this).multiselect("close"); $(this).multiselect("close");
cookie(sort_cookie_name, ui.value, {expires: 365}); cookie(sort_cookie_name, ui.value);
window.location.reload(); window.location.reload();
} }
}); });
@ -74,13 +104,25 @@ function init() {
} }
// Top-level feed {{{ // Top-level feed {{{
function toplevel_layout() {
var last = $(".toplevel li").last();
var title = $('.toplevel h3').first();
var bottom = last.position().top + last.height() - title.position().top;
$("#main").height(Math.max(200, bottom));
}
function toplevel() { function toplevel() {
$(".sort_select").hide(); $(".sort_select").hide();
$(".toplevel li").click(function() { $(".toplevel li").click(function() {
var href = $(this).children("span").html(); var href = $(this).children("span.url").text();
window.location = href; window.location = href;
}); });
toplevel_layout();
$(window).resize(toplevel_layout);
} }
// }}} // }}}
@ -193,8 +235,10 @@ function load_page(elem) {
elem.show(); elem.show();
} }
function hidesort() {$("#content > .sort_select").hide();}
function booklist(hide_sort) { function booklist(hide_sort) {
if (hide_sort) $("#content > .sort_select").hide(); if (hide_sort) hidesort();
var test = $("#booklist #page0").html(); var test = $("#booklist #page0").html();
if (!test) { if (!test) {
$("#booklist").html(render_error("No books found")); $("#booklist").html(render_error("No books found"));
@ -233,3 +277,13 @@ function show_details(a_dom) {
} }
// }}} // }}}
function book() {
hidesort();
$('.details .left img').load(function() {
var img = $('.details .left img');
var height = $('#main').height();
height = Math.max(height, img.height() + 100);
$('#main').height(height);
});
}

View File

@ -8,6 +8,7 @@
<span class="rating_container">{stars}</span> <span class="rating_container">{stars}</span>
<span class="series">{series}</span> <span class="series">{series}</span>
<a href="#" onclick="show_details(this); return false;" title="{details_tt}">{details}</a> <a href="#" onclick="show_details(this); return false;" title="{details_tt}">{details}</a>
<a href="/browse/book/{id}" title="{permalink_tt}">{permalink}</a>
</div> </div>
<div class="title"><strong>{title}</strong></div> <div class="title"><strong>{title}</strong></div>
<div class="authors">{authors}</div> <div class="authors">{authors}</div>

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Tony Stegall'
__copyright__ = '2010, Tony Stegall or Tonythebookworm on mobiread.com'
__version__ = '1'
__date__ = '16, October 2010'
__docformat__ = 'English'
from calibre.web.feeds.news import BasicNewsRecipe
class MalaysianMirror(BasicNewsRecipe):
title = 'MalaysianMirror'
__author__ = 'Tonythebookworm'
description = 'The Pulse of the Nation'
language = 'en'
no_stylesheets = True
publisher = 'Tonythebookworm'
category = 'news'
use_embedded_content= False
no_stylesheets = True
oldest_article = 24
remove_javascript = True
remove_empty_feeds = True
conversion_options = {'linearize_tables' : True}
extra_css = '''
#content_heading{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
td{text-align:right; font-size:small;margin-top:0px;margin-bottom: 0px;}
#content_body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
keep_only_tags = [dict(name='table', attrs={'class':['contentpaneopen']})
]
remove_tags = [dict(name='table', attrs={'class':['buttonheading']})]
#######################################################################################################################
max_articles_per_feed = 10
'''
Make a variable that will hold the url for the main site because our links do not include the index
'''
INDEX = 'http://www.malaysianmirror.com'
def parse_index(self):
feeds = []
for title, url in [
(u"Media Buzz", u"http://www.malaysianmirror.com/media-buzz-front"),
(u"Life Style", u"http://www.malaysianmirror.com/lifestylefront"),
(u"Features", u"http://www.malaysianmirror.com/featurefront"),
]:
articles = self.make_links(url)
if articles:
feeds.append((title, articles))
return feeds
def make_links(self, url):
title = 'Temp'
current_articles = []
soup = self.index_to_soup(url)
# print 'The soup is: ', soup
for item in soup.findAll('div', attrs={'class':'contentheading'}):
#print 'item is: ', item
link = item.find('a')
#print 'the link is: ', link
if link:
url = self.INDEX + link['href']
title = self.tag_to_string(link)
#print 'the title is: ', title
#print 'the url is: ', url
#print 'the title is: ', title
current_articles.append({'title': title, 'url': url, 'description':'', 'date':''}) # append all this
return current_articles
def preprocess_html(self, soup):
for item in soup.findAll(attrs={'style':True}):
del item['style']
return soup

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import dbus
import os
def node_mountpoint(node):
def de_mangle(raw):
return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012',
'\n').replace('\\0134', '\\')
for line in open('/proc/mounts').readlines():
line = line.split()
if line[0] == node:
return de_mangle(line[1])
return None
class UDisks(object):
def __init__(self):
if os.environ.get('CALIBRE_DISABLE_UDISKS', False):
raise Exception('User has aborted use of UDISKS')
self.bus = dbus.SystemBus()
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
def device(self, device_node_path):
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
return dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
devpath), 'org.freedesktop.UDisks.Device')
def mount(self, device_node_path):
d = self.device(device_node_path)
try:
return unicode(d.FilesystemMount('',
['auth_no_user_interaction', 'rw', 'noexec', 'nosuid',
'sync', 'nodev', 'uid=1000', 'gid=1000']))
except:
# May be already mounted, check
mp = node_mountpoint(str(device_node_path))
if mp is None:
raise
return mp
def unmount(self, device_node_path):
d = self.device(device_node_path)
d.FilesystemUnmount(['force'])
def eject(self, device_node_path):
parent = device_node_path
while parent[-1] in '0123456789':
parent = parent[:-1]
devices = [str(x) for x in self.main.EnumerateDeviceFiles()]
for d in devices:
if d.startswith(parent) and d != parent:
try:
self.unmount(d)
except:
import traceback
print 'Failed to unmount:', d
traceback.print_exc()
d = self.device(parent)
d.DriveEject([])
def mount(node_path):
u = UDisks()
u.mount(node_path)
def eject(node_path):
u = UDisks()
u.eject(node_path)
if __name__ == '__main__':
import sys
dev = sys.argv[1]
print 'Testing with node', dev
u = UDisks()
print 'Mounted at:', u.mount(dev)
print 'Ejecting'
u.eject(dev)

View File

@ -530,16 +530,8 @@ class Device(DeviceConfig, DevicePlugin):
return drives return drives
def node_mountpoint(self, node): def node_mountpoint(self, node):
from calibre.devices.udisks import node_mountpoint
def de_mangle(raw): return node_mountpoint(node)
return raw.replace('\\040', ' ').replace('\\011', '\t').replace('\\012',
'\n').replace('\\0134', '\\')
for line in open('/proc/mounts').readlines():
line = line.split()
if line[0] == node:
return de_mangle(line[1])
return None
def find_largest_partition(self, path): def find_largest_partition(self, path):
node = path.split('/')[-1] node = path.split('/')[-1]
@ -585,6 +577,13 @@ class Device(DeviceConfig, DevicePlugin):
label += ' (%d)'%extra label += ' (%d)'%extra
def do_mount(node, label): def do_mount(node, label):
try:
from calibre.devices.udisks import mount
mount(node)
return 0
except:
pass
cmd = 'calibre-mount-helper' cmd = 'calibre-mount-helper'
if getattr(sys, 'frozen_path', False): if getattr(sys, 'frozen_path', False):
cmd = os.path.join(sys.frozen_path, cmd) cmd = os.path.join(sys.frozen_path, cmd)
@ -617,6 +616,7 @@ class Device(DeviceConfig, DevicePlugin):
if not mp.endswith('/'): mp += '/' if not mp.endswith('/'): mp += '/'
self._linux_mount_map[main] = mp self._linux_mount_map[main] = mp
self._main_prefix = mp self._main_prefix = mp
self._linux_main_device_node = main
cards = [(carda, '_card_a_prefix', 'carda'), cards = [(carda, '_card_a_prefix', 'carda'),
(cardb, '_card_b_prefix', 'cardb')] (cardb, '_card_b_prefix', 'cardb')]
for card, prefix, typ in cards: for card, prefix, typ in cards:
@ -732,6 +732,11 @@ class Device(DeviceConfig, DevicePlugin):
pass pass
def eject_linux(self): def eject_linux(self):
try:
from calibre.devices.udisks import eject
return eject(self._linux_main_device_node)
except:
pass
drives = self.find_device_nodes() drives = self.find_device_nodes()
for drive in drives: for drive in drives:
if drive: if drive:

View File

@ -2,7 +2,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
import os, glob, re import os, glob, re, functools
from urlparse import urlparse from urlparse import urlparse
from urllib import unquote from urllib import unquote
from uuid import uuid4 from uuid import uuid4
@ -11,7 +11,7 @@ from lxml import etree
from lxml.builder import ElementMaker from lxml.builder import ElementMaker
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
NCX_NS = "http://www.daisy.org/z3986/2005/ncx/" NCX_NS = "http://www.daisy.org/z3986/2005/ncx/"
@ -26,14 +26,6 @@ E = ElementMaker(namespace=NCX_NS, nsmap=NSMAP)
C = ElementMaker(namespace=CALIBRE_NS, nsmap=NSMAP) C = ElementMaker(namespace=CALIBRE_NS, nsmap=NSMAP)
class NCXSoup(BeautifulStoneSoup):
NESTABLE_TAGS = {'navpoint':[]}
def __init__(self, raw):
BeautifulStoneSoup.__init__(self, raw,
convertEntities=BeautifulSoup.HTML_ENTITIES,
selfClosingTags=['meta', 'content'])
class TOC(list): class TOC(list):
@ -166,40 +158,60 @@ class TOC(list):
def read_ncx_toc(self, toc): def read_ncx_toc(self, toc):
self.base_path = os.path.dirname(toc) self.base_path = os.path.dirname(toc)
raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True)[0] raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True,
soup = NCXSoup(raw) strip_encoding_pats=True)[0]
root = etree.fromstring(raw, parser=etree.XMLParser(recover=True,
no_network=True))
xpn = {'re': 'http://exslt.org/regular-expressions'}
XPath = functools.partial(etree.XPath, namespaces=xpn)
def get_attr(node, default=None, attr='playorder'):
for name, val in node.attrib.items():
if name and val and name.lower().endswith(attr):
return val
return default
nl_path = XPath('./*[re:match(local-name(), "navlabel$", "i")]')
txt_path = XPath('./*[re:match(local-name(), "text$", "i")]')
content_path = XPath('./*[re:match(local-name(), "content$", "i")]')
np_path = XPath('./*[re:match(local-name(), "navpoint$", "i")]')
def process_navpoint(np, dest): def process_navpoint(np, dest):
play_order = np.get('playOrder', None) try:
if play_order is None: play_order = int(get_attr(np, 1))
play_order = int(np.get('playorder', 1)) except:
play_order = 1
href = fragment = text = None href = fragment = text = None
nl = np.find(re.compile('navlabel')) nl = nl_path(np)
if nl is not None: if nl:
nl = nl[0]
text = u'' text = u''
for txt in nl.findAll(re.compile('text')): for txt in txt_path(nl):
text += u''.join([unicode(s) for s in txt.findAll(text=True)]) text += etree.tostring(txt, method='text',
content = np.find(re.compile('content')) encoding=unicode, with_tail=False)
if content is None or not content.has_key('src') or not txt: content = content_path(np)
if not content or not text:
return
content = content[0]
src = get_attr(content, attr='src')
if src is None:
return return
purl = urlparse(unquote(content['src'])) purl = urlparse(unquote(content.get('src')))
href, fragment = purl[2], purl[5] href, fragment = purl[2], purl[5]
nd = dest.add_item(href, fragment, text) nd = dest.add_item(href, fragment, text)
nd.play_order = play_order nd.play_order = play_order
for c in np: for c in np_path(np):
if 'navpoint' in getattr(c, 'name', ''): process_navpoint(c, nd)
process_navpoint(c, nd)
nm = soup.find(re.compile('navmap')) nm = XPath('//*[re:match(local-name(), "navmap$", "i")]')(root)
if nm is None: if not nm:
raise ValueError('NCX files must have a <navmap> element.') raise ValueError('NCX files must have a <navmap> element.')
nm = nm[0]
for elem in nm: for child in np_path(nm):
if 'navpoint' in getattr(elem, 'name', ''): process_navpoint(child, self)
process_navpoint(elem, self)
def read_html_toc(self, toc): def read_html_toc(self, toc):
self.base_path = os.path.dirname(toc) self.base_path = os.path.dirname(toc)

View File

@ -6,13 +6,13 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import operator, os, json import operator, os, json
from urllib import quote
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import cherrypy import cherrypy
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
from calibre import isbytestring, force_unicode, prepare_string_for_xml as xml from calibre import isbytestring, force_unicode, fit_image, \
prepare_string_for_xml as xml
from calibre.utils.ordered_dict import OrderedDict from calibre.utils.ordered_dict import OrderedDict
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
from calibre.utils.config import prefs from calibre.utils.config import prefs
@ -133,9 +133,12 @@ def get_category_items(category, items, db, datatype): # {{{
desc = '' desc = ''
if i.count > 0: if i.count > 0:
desc += '[' + _('%d books')%i.count + ']' desc += '[' + _('%d books')%i.count + ']'
href = '/browse/matches/%s/%s'%(category, id_) q = i.category
if not q:
q = category
href = '/browse/matches/%s/%s'%(q, id_)
return templ.format(xml(name), rating, return templ.format(xml(name), rating,
xml(desc), xml(quote(href)), rstring) xml(desc), xml(href), rstring)
items = list(map(item, items)) items = list(map(item, items))
return '\n'.join(['<div class="category-container">'] + items + ['</div>']) return '\n'.join(['<div class="category-container">'] + items + ['</div>'])
@ -194,6 +197,8 @@ class BrowseServer(object):
self.browse_search) self.browse_search)
connect('browse_details', base_href+'/details/{id}', connect('browse_details', base_href+'/details/{id}',
self.browse_details) self.browse_details)
connect('browse_book', base_href+'/book/{id}',
self.browse_book)
connect('browse_category_icon', base_href+'/icon/{name}', connect('browse_category_icon', base_href+'/icon/{name}',
self.browse_icon) self.browse_icon)
@ -218,10 +223,10 @@ class BrowseServer(object):
sort_opts, added = [], set([]) sort_opts, added = [], set([])
displayed_custom_fields = custom_fields_to_display(self.db) displayed_custom_fields = custom_fields_to_display(self.db)
for x in fm.sortable_field_keys(): for x in fm.sortable_field_keys():
if x == 'ondevice': if x in ('ondevice', 'formats', 'sort'):
continue continue
if fm[x]['is_custom'] and x not in displayed_custom_fields: if fm[x]['is_custom'] and x not in displayed_custom_fields:
continue continue
if x == 'comments' or fm[x]['datatype'] == 'comments': if x == 'comments' or fm[x]['datatype'] == 'comments':
continue continue
n = fm[x]['name'] n = fm[x]['name']
@ -268,23 +273,31 @@ class BrowseServer(object):
# Catalogs {{{ # Catalogs {{{
def browse_icon(self, name='blank.png'): def browse_icon(self, name='blank.png'):
try:
data = I(name, data=True)
except:
raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
img = Image()
img.load(data)
img.size = (48, 48)
cherrypy.response.headers['Content-Type'] = 'image/png' cherrypy.response.headers['Content-Type'] = 'image/png'
cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time) cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time)
return img.export('png') if not hasattr(self, '__browse_icon_cache__'):
self.__browse_icon_cache__ = {}
if name not in self.__browse_icon_cache__:
try:
data = I(name, data=True)
except:
raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
img = Image()
img.load(data)
width, height = img.size
scaled, width, height = fit_image(width, height, 48, 48)
if scaled:
img.size = (width, height)
self.__browse_icon_cache__[name] = img.export('png')
return self.__browse_icon_cache__[name]
def browse_toplevel(self): def browse_toplevel(self):
categories = self.categories_cache() categories = self.categories_cache()
category_meta = self.db.field_metadata category_meta = self.db.field_metadata
cats = [ cats = [
(_('Newest'), 'newest', 'blank.png'), (_('Newest'), 'newest', 'forward.png'),
] ]
def getter(x): def getter(x):
@ -292,7 +305,7 @@ class BrowseServer(object):
displayed_custom_fields = custom_fields_to_display(self.db) displayed_custom_fields = custom_fields_to_display(self.db)
for category in sorted(categories, for category in sorted(categories,
cmp=lambda x,y: cmp(getter(x), getter(y))): cmp=lambda x,y: cmp(getter(x), getter(y))):
if len(categories[category]) == 0: if len(categories[category]) == 0:
continue continue
if category == 'formats': if category == 'formats':
@ -313,9 +326,10 @@ class BrowseServer(object):
icon = 'blank.png' icon = 'blank.png'
cats.append((meta['name'], category, icon)) cats.append((meta['name'], category, icon))
cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" /> {0}' cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" />'
'<span>/browse/category/{1}</span></li>') '<span class="label">{0}</span>'
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), '<span class="url">/browse/category/{1}</span></li>')
.format(xml(x, True), xml(y), xml(_('Browse books by')),
src='/browse/icon/'+z) src='/browse/icon/'+z)
for x, y, z in cats] for x, y, z in cats]
@ -452,7 +466,8 @@ class BrowseServer(object):
sort = 'title' sort = 'title'
self.sort(items, 'title', True) self.sort(items, 'title', True)
if sort != 'title': if sort != 'title':
ascending = fm[sort]['datatype'] not in ('rating', 'datetime') ascending = fm[sort]['datatype'] not in ('rating', 'datetime',
'series')
self.sort(items, sort, ascending) self.sort(items, sort, ascending)
return sort return sort
@ -464,14 +479,17 @@ class BrowseServer(object):
if category not in categories and category != 'newest': if category not in categories and category != 'newest':
raise cherrypy.HTTPError(404, 'category not found') raise cherrypy.HTTPError(404, 'category not found')
fm = self.db.field_metadata
try: try:
category_name = self.db.field_metadata[category]['name'] category_name = fm[category]['name']
dt = fm[category]['datatype']
except: except:
if category != 'newest': if category != 'newest':
raise raise
category_name = _('Newest') category_name = _('Newest')
dt = None
hide_sort = 'false' hide_sort = 'true' if dt == 'series' else 'false'
if category == 'search': if category == 'search':
which = unhexlify(cid) which = unhexlify(cid)
try: try:
@ -482,11 +500,16 @@ class BrowseServer(object):
ids = list(self.db.data.iterallids()) ids = list(self.db.data.iterallids())
hide_sort = 'true' hide_sort = 'true'
else: else:
ids = self.db.get_books_for_category(category, cid) q = category
if q == 'news':
q = 'tags'
ids = self.db.get_books_for_category(q, cid)
items = [self.db.data._data[x] for x in ids] items = [self.db.data._data[x] for x in ids]
if category == 'newest': if category == 'newest':
list_sort = 'timestamp' list_sort = 'timestamp'
if dt == 'series':
list_sort = category
sort = self.browse_sort_book_list(items, list_sort) sort = self.browse_sort_book_list(items, list_sort)
ids = [x[0] for x in items] ids = [x[0] for x in items]
html = render_book_list(ids, suffix=_('in') + ' ' + category_name) html = render_book_list(ids, suffix=_('in') + ' ' + category_name)
@ -568,23 +591,19 @@ class BrowseServer(object):
args['series'] = args['series'] args['series'] = args['series']
args['details'] = xml(_('Details'), True) args['details'] = xml(_('Details'), True)
args['details_tt'] = xml(_('Show book details'), True) args['details_tt'] = xml(_('Show book details'), True)
args['permalink'] = xml(_('Permalink'), True)
args['permalink_tt'] = xml(_('A permanent link to this book'), True)
summs.append(self.browse_summary_template.format(**args)) summs.append(self.browse_summary_template.format(**args))
return json.dumps('\n'.join(summs), ensure_ascii=False) return json.dumps('\n'.join(summs), ensure_ascii=False)
@Endpoint(mimetype='application/json; charset=utf-8') def browse_render_details(self, id_):
def browse_details(self, id=None):
try:
id_ = int(id)
except:
raise cherrypy.HTTPError(404, 'invalid id: %r'%id)
try: try:
mi = self.db.get_metadata(id_, index_is_id=True) mi = self.db.get_metadata(id_, index_is_id=True)
except: except:
ans = _('This book has been deleted') return _('This book has been deleted')
else: else:
args, fmt, fmts, fname = self.browse_get_book_args(mi, id_) args, fmt, fmts, fname = self.browse_get_book_args(mi, id_)
args['formats'] = '' args['formats'] = ''
@ -625,13 +644,34 @@ class BrowseServer(object):
u'<div class="comment">%s</div></div>') % (xml(c[0]), u'<div class="comment">%s</div></div>') % (xml(c[0]),
c[1]) for c in comments] c[1]) for c in comments]
comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments)) comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments))
ans = self.browse_details_template.format(id=id_,
return self.browse_details_template.format(id=id_,
title=xml(mi.title, True), fields=fields, title=xml(mi.title, True), fields=fields,
formats=args['formats'], comments=comments) formats=args['formats'], comments=comments)
@Endpoint(mimetype='application/json; charset=utf-8')
def browse_details(self, id=None):
try:
id_ = int(id)
except:
raise cherrypy.HTTPError(404, 'invalid id: %r'%id)
ans = self.browse_render_details(id_)
return json.dumps(ans, ensure_ascii=False) return json.dumps(ans, ensure_ascii=False)
@Endpoint()
def browse_book(self, id=None, category_sort=None):
try:
id_ = int(id)
except:
raise cherrypy.HTTPError(404, 'invalid id: %r'%id)
ans = self.browse_render_details(id_)
return self.browse_template('').format(
title='', script='book();', main=ans)
# }}} # }}}

View File

@ -24,6 +24,7 @@ Environment variables
* ``CALIBRE_OVERRIDE_DATABASE_PATH`` - allows you to specify the full path to metadata.db. Using this variable you can have metadata.db be in a location other than the library folder. Useful if your library folder is on a networked drive that does not support file locking. * ``CALIBRE_OVERRIDE_DATABASE_PATH`` - allows you to specify the full path to metadata.db. Using this variable you can have metadata.db be in a location other than the library folder. Useful if your library folder is on a networked drive that does not support file locking.
* ``CALIBRE_DEVELOP_FROM`` - Used to run from a calibre development environment. See :ref:`develop`. * ``CALIBRE_DEVELOP_FROM`` - Used to run from a calibre development environment. See :ref:`develop`.
* ``CALIBRE_OVERRIDE_LANG`` - Used to force the language used by the interface (ISO 639 language code) * ``CALIBRE_OVERRIDE_LANG`` - Used to force the language used by the interface (ISO 639 language code)
* ``CALIBRE_DISABLE_UDISKS`` - Used to disable the use of udisks for mounting/ejecting. Set it to 1 to use calibre-mount-helper instead.
* ``SYSFS_PATH`` - Use if sysfs is mounted somewhere other than /sys * ``SYSFS_PATH`` - Use if sysfs is mounted somewhere other than /sys
* ``http_proxy`` - Used on linux to specify an HTTP proxy * ``http_proxy`` - Used on linux to specify an HTTP proxy