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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
|
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
|
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:
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user