mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
2c3b34b779
@ -187,6 +187,9 @@ h2.library_name {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toplevel li {
|
||||
@ -194,15 +197,20 @@ h2.library_name {
|
||||
padding: 0.75em;
|
||||
cursor: pointer;
|
||||
font-size: larger;
|
||||
float: left;
|
||||
border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
display: inline;
|
||||
width: 250px;
|
||||
height: 48px;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.toplevel li img {
|
||||
vertical-align: middle;
|
||||
margin-right: 2em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#book_details_dialog .details a {
|
||||
.details .right .formats a {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#book_details_dialog .details a:hover {
|
||||
.details .right .formats a:hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,35 @@
|
||||
|
||||
// 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) {
|
||||
if (typeof value != 'undefined') { // name and value given, set cookie
|
||||
@ -55,7 +85,7 @@ function init_sort_combobox() {
|
||||
selectedList: 1,
|
||||
click: function(event, ui){
|
||||
$(this).multiselect("close");
|
||||
cookie(sort_cookie_name, ui.value, {expires: 365});
|
||||
cookie(sort_cookie_name, ui.value);
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
@ -74,13 +104,25 @@ function init() {
|
||||
}
|
||||
|
||||
// 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() {
|
||||
$(".sort_select").hide();
|
||||
|
||||
$(".toplevel li").click(function() {
|
||||
var href = $(this).children("span").html();
|
||||
var href = $(this).children("span.url").text();
|
||||
window.location = href;
|
||||
});
|
||||
|
||||
toplevel_layout();
|
||||
$(window).resize(toplevel_layout);
|
||||
|
||||
}
|
||||
// }}}
|
||||
|
||||
@ -193,8 +235,10 @@ function load_page(elem) {
|
||||
elem.show();
|
||||
}
|
||||
|
||||
function hidesort() {$("#content > .sort_select").hide();}
|
||||
|
||||
function booklist(hide_sort) {
|
||||
if (hide_sort) $("#content > .sort_select").hide();
|
||||
if (hide_sort) hidesort();
|
||||
var test = $("#booklist #page0").html();
|
||||
if (!test) {
|
||||
$("#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);
|
||||
});
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
<span class="rating_container">{stars}</span>
|
||||
<span class="series">{series}</span>
|
||||
<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 class="title"><strong>{title}</strong></div>
|
||||
<div class="authors">{authors}</div>
|
||||
|
89
resources/recipes/malaysian_mirror.recipe
Normal file
89
resources/recipes/malaysian_mirror.recipe
Normal 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
|
||||
|
88
src/calibre/devices/udisks.py
Normal file
88
src/calibre/devices/udisks.py
Normal 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)
|
||||
|
||||
|
@ -530,16 +530,8 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
return drives
|
||||
|
||||
def node_mountpoint(self, 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
|
||||
from calibre.devices.udisks import node_mountpoint
|
||||
return node_mountpoint(node)
|
||||
|
||||
def find_largest_partition(self, path):
|
||||
node = path.split('/')[-1]
|
||||
@ -585,6 +577,13 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
label += ' (%d)'%extra
|
||||
|
||||
def do_mount(node, label):
|
||||
try:
|
||||
from calibre.devices.udisks import mount
|
||||
mount(node)
|
||||
return 0
|
||||
except:
|
||||
pass
|
||||
|
||||
cmd = 'calibre-mount-helper'
|
||||
if getattr(sys, 'frozen_path', False):
|
||||
cmd = os.path.join(sys.frozen_path, cmd)
|
||||
@ -617,6 +616,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
if not mp.endswith('/'): mp += '/'
|
||||
self._linux_mount_map[main] = mp
|
||||
self._main_prefix = mp
|
||||
self._linux_main_device_node = main
|
||||
cards = [(carda, '_card_a_prefix', 'carda'),
|
||||
(cardb, '_card_b_prefix', 'cardb')]
|
||||
for card, prefix, typ in cards:
|
||||
@ -732,6 +732,11 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
pass
|
||||
|
||||
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()
|
||||
for drive in drives:
|
||||
if drive:
|
||||
|
@ -2,7 +2,7 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, glob, re
|
||||
import os, glob, re, functools
|
||||
from urlparse import urlparse
|
||||
from urllib import unquote
|
||||
from uuid import uuid4
|
||||
@ -11,7 +11,7 @@ from lxml import etree
|
||||
from lxml.builder import ElementMaker
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
class NCXSoup(BeautifulStoneSoup):
|
||||
|
||||
NESTABLE_TAGS = {'navpoint':[]}
|
||||
|
||||
def __init__(self, raw):
|
||||
BeautifulStoneSoup.__init__(self, raw,
|
||||
convertEntities=BeautifulSoup.HTML_ENTITIES,
|
||||
selfClosingTags=['meta', 'content'])
|
||||
|
||||
class TOC(list):
|
||||
|
||||
@ -166,40 +158,60 @@ class TOC(list):
|
||||
|
||||
def read_ncx_toc(self, toc):
|
||||
self.base_path = os.path.dirname(toc)
|
||||
raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True)[0]
|
||||
soup = NCXSoup(raw)
|
||||
raw = xml_to_unicode(open(toc, 'rb').read(), assume_utf8=True,
|
||||
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):
|
||||
play_order = np.get('playOrder', None)
|
||||
if play_order is None:
|
||||
play_order = int(np.get('playorder', 1))
|
||||
try:
|
||||
play_order = int(get_attr(np, 1))
|
||||
except:
|
||||
play_order = 1
|
||||
href = fragment = text = None
|
||||
nl = np.find(re.compile('navlabel'))
|
||||
if nl is not None:
|
||||
nl = nl_path(np)
|
||||
if nl:
|
||||
nl = nl[0]
|
||||
text = u''
|
||||
for txt in nl.findAll(re.compile('text')):
|
||||
text += u''.join([unicode(s) for s in txt.findAll(text=True)])
|
||||
content = np.find(re.compile('content'))
|
||||
if content is None or not content.has_key('src') or not txt:
|
||||
for txt in txt_path(nl):
|
||||
text += etree.tostring(txt, method='text',
|
||||
encoding=unicode, with_tail=False)
|
||||
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
|
||||
|
||||
purl = urlparse(unquote(content['src']))
|
||||
purl = urlparse(unquote(content.get('src')))
|
||||
href, fragment = purl[2], purl[5]
|
||||
nd = dest.add_item(href, fragment, text)
|
||||
nd.play_order = play_order
|
||||
|
||||
for c in np:
|
||||
if 'navpoint' in getattr(c, 'name', ''):
|
||||
process_navpoint(c, nd)
|
||||
for c in np_path(np):
|
||||
process_navpoint(c, nd)
|
||||
|
||||
nm = soup.find(re.compile('navmap'))
|
||||
if nm is None:
|
||||
nm = XPath('//*[re:match(local-name(), "navmap$", "i")]')(root)
|
||||
if not nm:
|
||||
raise ValueError('NCX files must have a <navmap> element.')
|
||||
nm = nm[0]
|
||||
|
||||
for elem in nm:
|
||||
if 'navpoint' in getattr(elem, 'name', ''):
|
||||
process_navpoint(elem, self)
|
||||
|
||||
for child in np_path(nm):
|
||||
process_navpoint(child, self)
|
||||
|
||||
def read_html_toc(self, toc):
|
||||
self.base_path = os.path.dirname(toc)
|
||||
|
@ -6,13 +6,13 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import operator, os, json
|
||||
from urllib import quote
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
import cherrypy
|
||||
|
||||
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.filenames import ascii_filename
|
||||
from calibre.utils.config import prefs
|
||||
@ -133,9 +133,12 @@ def get_category_items(category, items, db, datatype): # {{{
|
||||
desc = ''
|
||||
if i.count > 0:
|
||||
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,
|
||||
xml(desc), xml(quote(href)), rstring)
|
||||
xml(desc), xml(href), rstring)
|
||||
|
||||
items = list(map(item, items))
|
||||
return '\n'.join(['<div class="category-container">'] + items + ['</div>'])
|
||||
@ -194,6 +197,8 @@ class BrowseServer(object):
|
||||
self.browse_search)
|
||||
connect('browse_details', base_href+'/details/{id}',
|
||||
self.browse_details)
|
||||
connect('browse_book', base_href+'/book/{id}',
|
||||
self.browse_book)
|
||||
connect('browse_category_icon', base_href+'/icon/{name}',
|
||||
self.browse_icon)
|
||||
|
||||
@ -218,10 +223,10 @@ class BrowseServer(object):
|
||||
sort_opts, added = [], set([])
|
||||
displayed_custom_fields = custom_fields_to_display(self.db)
|
||||
for x in fm.sortable_field_keys():
|
||||
if x == 'ondevice':
|
||||
if x in ('ondevice', 'formats', 'sort'):
|
||||
continue
|
||||
if fm[x]['is_custom'] and x not in displayed_custom_fields:
|
||||
continue
|
||||
continue
|
||||
if x == 'comments' or fm[x]['datatype'] == 'comments':
|
||||
continue
|
||||
n = fm[x]['name']
|
||||
@ -268,23 +273,31 @@ class BrowseServer(object):
|
||||
|
||||
# Catalogs {{{
|
||||
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['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):
|
||||
categories = self.categories_cache()
|
||||
category_meta = self.db.field_metadata
|
||||
cats = [
|
||||
(_('Newest'), 'newest', 'blank.png'),
|
||||
(_('Newest'), 'newest', 'forward.png'),
|
||||
]
|
||||
|
||||
def getter(x):
|
||||
@ -292,7 +305,7 @@ class BrowseServer(object):
|
||||
|
||||
displayed_custom_fields = custom_fields_to_display(self.db)
|
||||
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:
|
||||
continue
|
||||
if category == 'formats':
|
||||
@ -313,9 +326,10 @@ class BrowseServer(object):
|
||||
icon = 'blank.png'
|
||||
cats.append((meta['name'], category, icon))
|
||||
|
||||
cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" /> {0}'
|
||||
'<span>/browse/category/{1}</span></li>')
|
||||
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')),
|
||||
cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" />'
|
||||
'<span class="label">{0}</span>'
|
||||
'<span class="url">/browse/category/{1}</span></li>')
|
||||
.format(xml(x, True), xml(y), xml(_('Browse books by')),
|
||||
src='/browse/icon/'+z)
|
||||
for x, y, z in cats]
|
||||
|
||||
@ -452,7 +466,8 @@ class BrowseServer(object):
|
||||
sort = 'title'
|
||||
self.sort(items, 'title', True)
|
||||
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)
|
||||
return sort
|
||||
|
||||
@ -464,14 +479,17 @@ class BrowseServer(object):
|
||||
|
||||
if category not in categories and category != 'newest':
|
||||
raise cherrypy.HTTPError(404, 'category not found')
|
||||
fm = self.db.field_metadata
|
||||
try:
|
||||
category_name = self.db.field_metadata[category]['name']
|
||||
category_name = fm[category]['name']
|
||||
dt = fm[category]['datatype']
|
||||
except:
|
||||
if category != 'newest':
|
||||
raise
|
||||
category_name = _('Newest')
|
||||
dt = None
|
||||
|
||||
hide_sort = 'false'
|
||||
hide_sort = 'true' if dt == 'series' else 'false'
|
||||
if category == 'search':
|
||||
which = unhexlify(cid)
|
||||
try:
|
||||
@ -482,11 +500,16 @@ class BrowseServer(object):
|
||||
ids = list(self.db.data.iterallids())
|
||||
hide_sort = 'true'
|
||||
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]
|
||||
if category == 'newest':
|
||||
list_sort = 'timestamp'
|
||||
if dt == 'series':
|
||||
list_sort = category
|
||||
sort = self.browse_sort_book_list(items, list_sort)
|
||||
ids = [x[0] for x in items]
|
||||
html = render_book_list(ids, suffix=_('in') + ' ' + category_name)
|
||||
@ -568,23 +591,19 @@ class BrowseServer(object):
|
||||
args['series'] = args['series']
|
||||
args['details'] = xml(_('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))
|
||||
|
||||
|
||||
return json.dumps('\n'.join(summs), ensure_ascii=False)
|
||||
|
||||
@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)
|
||||
|
||||
def browse_render_details(self, id_):
|
||||
try:
|
||||
mi = self.db.get_metadata(id_, index_is_id=True)
|
||||
except:
|
||||
ans = _('This book has been deleted')
|
||||
return _('This book has been deleted')
|
||||
else:
|
||||
args, fmt, fmts, fname = self.browse_get_book_args(mi, id_)
|
||||
args['formats'] = ''
|
||||
@ -625,13 +644,34 @@ class BrowseServer(object):
|
||||
u'<div class="comment">%s</div></div>') % (xml(c[0]),
|
||||
c[1]) for c in 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,
|
||||
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)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -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_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_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
|
||||
* ``http_proxy`` - Used on linux to specify an HTTP proxy
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user