Merge from trunk

This commit is contained in:
Charles Haley 2011-03-18 09:40:02 +00:00
commit 4ce44843ec
14 changed files with 379 additions and 52 deletions

View File

@ -355,3 +355,11 @@ draw_hidden_section_indicators = True
# large covers # large covers
maximum_cover_size = (1200, 1600) maximum_cover_size = (1200, 1600)
#: Where to send downloaded news
# When automatically sending downloaded news to a connected device, calibre
# will by default send it to the main memory. By changing this tweak, you can
# control where it is sent. Valid values are "main", "carda", "cardb". Note
# that if there isn't enough free space available on the location you choose,
# the files will be sent to the location with the most free space.
send_news_to_device_location = "main"

View File

@ -0,0 +1,21 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class Evangelizo(BasicNewsRecipe):
title = 'Evangelizo.org'
oldest_article = 2
max_articles_per_feed = 30
language = 'de'
__author__ = 'Bobus'
feeds = [
('EvangleliumTagfuerTag', 'http://www.evangeliumtagfuertag.org/rss/evangelizo_rss-de.xml'),
]
use_embedded_content = True
preprocess_regexps = [
(re.compile(r'&lt;font size="-2"&gt;([(][0-9]*[)])&lt;/font&gt;'), r'\g<1>'),
(re.compile(r'([\.!]\n)'), r'\g<1><br />'),
]
def populate_article_metadata(self, article, soup, first):
article.title = re.sub(r'<font size="-2">([(][0-9]*[)])</font>', r'\g<1>', article.title)
return

View File

@ -16,7 +16,7 @@ from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
from calibre.constants import __appname__, __version__, filesystem_encoding from calibre.constants import __appname__, __version__, filesystem_encoding
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import string_to_authors, MetaInformation from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_date, isoformat from calibre.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang from calibre.utils.localization import get_lang
@ -863,6 +863,7 @@ class OPF(object): # {{{
for x in self.XPath( for x in self.XPath(
'descendant::*[local-name() = "identifier" and text()]')( 'descendant::*[local-name() = "identifier" and text()]')(
self.metadata): self.metadata):
found_scheme = False
for attr, val in x.attrib.iteritems(): for attr, val in x.attrib.iteritems():
if attr.endswith('scheme'): if attr.endswith('scheme'):
typ = icu_lower(val) typ = icu_lower(val)
@ -870,7 +871,15 @@ class OPF(object): # {{{
method='text').strip() method='text').strip()
if val and typ not in ('calibre', 'uuid'): if val and typ not in ('calibre', 'uuid'):
identifiers[typ] = val identifiers[typ] = val
found_scheme = True
break break
if not found_scheme:
val = etree.tostring(x, with_tail=False, encoding=unicode,
method='text').strip()
if val.lower().startswith('urn:isbn:'):
val = check_isbn(val.split(':')[-1])
if val is not None:
identifiers['isbn'] = val
return identifiers return identifiers
@dynamic_property @dynamic_property

View File

@ -322,7 +322,7 @@ class Amazon(Source):
# }}} # }}}
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
identifiers={}, timeout=20): identifiers={}, timeout=30):
''' '''
Note this method will retry without identifiers automatically if no Note this method will retry without identifiers automatically if no
match is found with identifiers. match is found with identifiers.
@ -436,7 +436,7 @@ if __name__ == '__main__':
( # An e-book ISBN not on Amazon, one of the authors is ( # An e-book ISBN not on Amazon, one of the authors is
# unknown to Amazon, so no popup wrapper # unknown to Amazon, so no popup wrapper
{'identifiers':{'isbn': '0307459671'}, {'identifiers':{'isbn': '9780307459671'},
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']}, 'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
[title_test('The Invisible Gorilla: And Other Ways Our Intuitions Deceive Us', [title_test('The Invisible Gorilla: And Other Ways Our Intuitions Deceive Us',
exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])] exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])]

View File

@ -137,6 +137,16 @@ class Source(Plugin):
''' '''
Identify a book by its title/author/isbn/etc. Identify a book by its title/author/isbn/etc.
If identifiers(s) are specified and no match is found and this metadata
source does not store all related identifiers (for example, all ISBNs
of a book), this method should retry with just the title and author
(assuming they were specified).
If this metadata source also provides covers, the URL to the cover
should be cached so that a subsequent call to the get covers API with
the same ISBN/special identifier does not need to get the cover URL
again. Use the caching API for this.
:param log: A log object, use it to output debugging information/errors :param log: A log object, use it to output debugging information/errors
:param result_queue: A result Queue, results should be put into it. :param result_queue: A result Queue, results should be put into it.
Each result is a Metadata object Each result is a Metadata object

View File

@ -50,7 +50,7 @@ def get_details(browser, url, timeout): # {{{
if gc() != 403: if gc() != 403:
raise raise
# Google is throttling us, wait a little # Google is throttling us, wait a little
time.sleep(1) time.sleep(2)
raw = browser.open_novisit(url, timeout=timeout).read() raw = browser.open_novisit(url, timeout=timeout).read()
return raw return raw
@ -195,7 +195,7 @@ class GoogleBooks(Source):
ans = to_metadata(br, log, i, timeout) ans = to_metadata(br, log, i, timeout)
if isinstance(ans, Metadata): if isinstance(ans, Metadata):
result_queue.put(ans) result_queue.put(ans)
for isbn in ans.all_isbns: for isbn in getattr(ans, 'all_isbns', []):
self.cache_isbn_to_identifier(isbn, self.cache_isbn_to_identifier(isbn,
ans.identifiers['google']) ans.identifiers['google'])
except: except:
@ -206,7 +206,7 @@ class GoogleBooks(Source):
break break
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
identifiers={}, timeout=20): identifiers={}, timeout=30):
query = self.create_query(log, title=title, authors=authors, query = self.create_query(log, title=title, authors=authors,
identifiers=identifiers) identifiers=identifiers)
br = self.browser br = self.browser
@ -225,6 +225,11 @@ class GoogleBooks(Source):
log.exception('Failed to parse identify results') log.exception('Failed to parse identify results')
return as_unicode(e) return as_unicode(e)
if not entries and identifiers and title and authors and \
not abort.is_set():
return self.identify(log, result_queue, abort, title=title,
authors=authors, timeout=timeout)
# There is no point running these queries in threads as google # There is no point running these queries in threads as google
# throttles requests returning 403 Forbidden errors # throttles requests returning 403 Forbidden errors
self.get_all_details(br, log, entries, abort, result_queue, timeout) self.get_all_details(br, log, entries, abort, result_queue, timeout)
@ -235,13 +240,16 @@ class GoogleBooks(Source):
if __name__ == '__main__': if __name__ == '__main__':
# To run these test use: calibre-debug -e src/calibre/ebooks/metadata/sources/google.py # To run these test use: calibre-debug -e src/calibre/ebooks/metadata/sources/google.py
from calibre.ebooks.metadata.sources.test import (test_identify_plugin, from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
title_test) title_test, authors_test)
test_identify_plugin(GoogleBooks.name, test_identify_plugin(GoogleBooks.name,
[ [
( (
{'identifiers':{'isbn': '0743273567'}}, {'identifiers':{'isbn': '0743273567'}, 'title':'Great Gatsby',
[title_test('The great gatsby', exact=True)] 'authors':['Fitzgerald']},
[title_test('The great gatsby', exact=True),
authors_test(['Francis Scott Fitzgerald'])]
), ),
#( #(

View File

@ -46,6 +46,16 @@ def authors_test(authors):
return test return test
def _test_fields(touched_fields, mi):
for key in touched_fields:
if key.startswith('identifier:'):
key = key.partition(':')[-1]
if not mi.has_identifier(key):
return 'identifier: ' + key
elif mi.is_null(key):
return key
def test_identify_plugin(name, tests): def test_identify_plugin(name, tests):
''' '''
:param name: Plugin name :param name: Plugin name
@ -95,7 +105,7 @@ def test_identify_plugin(name, tests):
prints(mi) prints(mi)
prints('\n\n') prints('\n\n')
match_found = None possibles = []
for mi in results: for mi in results:
test_failed = False test_failed = False
for tfunc in test_funcs: for tfunc in test_funcs:
@ -103,26 +113,23 @@ def test_identify_plugin(name, tests):
test_failed = True test_failed = True
break break
if not test_failed: if not test_failed:
match_found = mi possibles.append(mi)
break
if match_found is None: if not possibles:
prints('ERROR: No results that passed all tests were found') prints('ERROR: No results that passed all tests were found')
prints('Log saved to', lf) prints('Log saved to', lf)
raise SystemExit(1) raise SystemExit(1)
for key in plugin.touched_fields: good = [x for x in possibles if _test_fields(plugin.touched_fields, x) is
if key.startswith('identifier:'): None]
key = key.partition(':')[-1] if not good:
if not match_found.has_identifier(key): prints('Failed to find', _test_fields(plugin.touched_fields,
prints('Failed to find identifier:', key) possibles[0]))
raise SystemExit(1)
elif match_found.is_null(key):
prints('Failed to find', key)
raise SystemExit(1) raise SystemExit(1)
prints('Average time per query', sum(times)/len(times)) prints('Average time per query', sum(times)/len(times))
if os.stat(lf).st_size > 10: if os.stat(lf).st_size > 10:
prints('There were some errors, see log', lf) prints('There were some errors/warnings, see log', lf)

View File

@ -229,7 +229,11 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
if 'style' in el.attrib: if 'style' in el.attrib:
text = el.attrib['style'] text = el.attrib['style']
if _css_url_re.search(text) is not None: if _css_url_re.search(text) is not None:
try:
stext = parseStyle(text) stext = parseStyle(text)
except:
# Parsing errors are raised by cssutils
continue
for p in stext.getProperties(all=True): for p in stext.getProperties(all=True):
v = p.cssValue v = p.cssValue
if v.CSS_VALUE_LIST == v.cssValueType: if v.CSS_VALUE_LIST == v.cssValueType:
@ -846,6 +850,7 @@ class Manifest(object):
return data return data
def _parse_xhtml(self, data): def _parse_xhtml(self, data):
orig_data = data
self.oeb.log.debug('Parsing', self.href, '...') self.oeb.log.debug('Parsing', self.href, '...')
# Convert to Unicode and normalize line endings # Convert to Unicode and normalize line endings
data = self.oeb.decode(data) data = self.oeb.decode(data)
@ -923,6 +928,8 @@ class Manifest(object):
# Handle weird (non-HTML/fragment) files # Handle weird (non-HTML/fragment) files
if barename(data.tag) != 'html': if barename(data.tag) != 'html':
if barename(data.tag) == 'ncx':
return self._parse_xml(orig_data)
self.oeb.log.warn('File %r does not appear to be (X)HTML'%self.href) self.oeb.log.warn('File %r does not appear to be (X)HTML'%self.href)
nroot = etree.fromstring('<html></html>') nroot = etree.fromstring('<html></html>')
has_body = False has_body = False

264
src/calibre/ebooks/textile/functions.py Normal file → Executable file
View File

@ -5,11 +5,13 @@ PyTextile
A Humane Web Text Generator A Humane Web Text Generator
""" """
__version__ = '2.1.4' # Last upstream version basis
# __version__ = '2.1.4'
__date__ = '2009/12/04' #__date__ = '2009/12/04'
__copyright__ = """ __copyright__ = """
Copyright (c) 2011, Leigh Parry
Copyright (c) 2011, John Schember <john@nachtimwald.com>
Copyright (c) 2009, Jason Samsa, http://jsamsa.com/ Copyright (c) 2009, Jason Samsa, http://jsamsa.com/
Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/ Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/
Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/ Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/
@ -120,6 +122,82 @@ class Textile(object):
btag_lite = ('bq', 'bc', 'p') btag_lite = ('bq', 'bc', 'p')
glyph_defaults = ( glyph_defaults = (
('mac_cent', '&#162;'),
('mac_pound', '&#163;'),
('mac_yen', '&#165;'),
('mac_quarter', '&#188;'),
('mac_half', '&#189;'),
('mac_three-quarter', '&#190;'),
('mac_cA-grave', '&#192;'),
('mac_cA-acute', '&#193;'),
('mac_cA-circumflex', '&#194;'),
('mac_cA-tilde', '&#195;'),
('mac_cA-diaeresis', '&#196;'),
('mac_cA-ring', '&#197;'),
('mac_cAE', '&#198;'),
('mac_cC-cedilla', '&#199;'),
('mac_cE-grave', '&#200;'),
('mac_cE-acute', '&#201;'),
('mac_cE-circumflex', '&#202;'),
('mac_cE-diaeresis', '&#203;'),
('mac_cI-grave', '&#204;'),
('mac_cI-acute', '&#205;'),
('mac_cI-circumflex', '&#206;'),
('mac_cI-diaeresis', '&#207;'),
('mac_cEth', '&#208;'),
('mac_cN-tilde', '&#209;'),
('mac_cO-grave', '&#210;'),
('mac_cO-acute', '&#211;'),
('mac_cO-circumflex', '&#212;'),
('mac_cO-tilde', '&#213;'),
('mac_cO-diaeresis', '&#214;'),
('mac_cO-stroke', '&#216;'),
('mac_cU-grave', '&#217;'),
('mac_cU-acute', '&#218;'),
('mac_cU-circumflex', '&#219;'),
('mac_cU-diaeresis', '&#220;'),
('mac_cY-acute', '&#221;'),
('mac_sa-grave', '&#224;'),
('mac_sa-acute', '&#225;'),
('mac_sa-circumflex', '&#226;'),
('mac_sa-tilde', '&#227;'),
('mac_sa-diaeresis', '&#228;'),
('mac_sa-ring', '&#229;'),
('mac_sae', '&#230;'),
('mac_sc-cedilla', '&#231;'),
('mac_se-grave', '&#232;'),
('mac_se-acute', '&#233;'),
('mac_se-circumflex', '&#234;'),
('mac_se-diaeresis', '&#235;'),
('mac_si-grave', '&#236;'),
('mac_si-acute', '&#237;'),
('mac_si-circumflex', '&#238;'),
('mac_si-diaeresis', '&#239;'),
('mac_sn-tilde', '&#241;'),
('mac_so-grave', '&#242;'),
('mac_so-acute', '&#243;'),
('mac_so-circumflex', '&#244;'),
('mac_so-tilde', '&#245;'),
('mac_so-diaeresis', '&#246;'),
('mac_so-stroke', '&#248;'),
('mac_su-grave', '&#249;'),
('mac_su-acute', '&#250;'),
('mac_su-circumflex', '&#251;'),
('mac_su-diaeresis', '&#252;'),
('mac_sy-acute', '&#253;'),
('mac_sy-diaeresis', '&#255;'),
('mac_cOE', '&#338;'),
('mac_soe', '&#339;'),
('mac_bullet', '&#8226;'),
('mac_franc', '&#8355;'),
('mac_lira', '&#8356;'),
('mac_rupee', '&#8360;'),
('mac_euro', '&#8364;'),
('mac_spade', '&#9824;'),
('mac_club', '&#9827;'),
('mac_heart', '&#9829;'),
('mac_diamond', '&#9830;'),
('txt_dimension', '&#215;'),
('txt_quote_single_open', '&#8216;'), ('txt_quote_single_open', '&#8216;'),
('txt_quote_single_close', '&#8217;'), ('txt_quote_single_close', '&#8217;'),
('txt_quote_double_open', '&#8220;'), ('txt_quote_double_open', '&#8220;'),
@ -130,7 +208,6 @@ class Textile(object):
('txt_ellipsis', '&#8230;'), ('txt_ellipsis', '&#8230;'),
('txt_emdash', '&#8212;'), ('txt_emdash', '&#8212;'),
('txt_endash', '&#8211;'), ('txt_endash', '&#8211;'),
('txt_dimension', '&#215;'),
('txt_trademark', '&#8482;'), ('txt_trademark', '&#8482;'),
('txt_registered', '&#174;'), ('txt_registered', '&#174;'),
('txt_copyright', '&#169;'), ('txt_copyright', '&#169;'),
@ -597,10 +674,12 @@ class Textile(object):
text = re.sub(r'"\Z', '\" ', text) text = re.sub(r'"\Z', '\" ', text)
glyph_search = ( glyph_search = (
re.compile(r'(\d+\'?\"?)( ?)x( ?)(?=\d+)'), # dimension sign
re.compile(r"(\w)\'(\w)"), # apostrophe's re.compile(r"(\w)\'(\w)"), # apostrophe's
re.compile(r'(\s)\'(\d+\w?)\b(?!\')'), # back in '88 re.compile(r'(\s)\'(\d+\w?)\b(?!\')'), # back in '88
re.compile(r'(\S)\'(?=\s|'+self.pnct+'|<|$)'), # single closing re.compile(r'(\S)\'(?=\s|'+self.pnct+'|<|$)'), # single closing
re.compile(r'\'/'), # single opening re.compile(r'\'/'), # single opening
re.compile(r'(\")\"'), # double closing - following another
re.compile(r'(\S)\"(?=\s|'+self.pnct+'|<|$)'), # double closing re.compile(r'(\S)\"(?=\s|'+self.pnct+'|<|$)'), # double closing
re.compile(r'"'), # double opening re.compile(r'"'), # double opening
re.compile(r'\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])'), # 3+ uppercase acronym re.compile(r'\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])'), # 3+ uppercase acronym
@ -608,17 +687,18 @@ class Textile(object):
re.compile(r'\b(\s{0,1})?\.{3}'), # ellipsis re.compile(r'\b(\s{0,1})?\.{3}'), # ellipsis
re.compile(r'(\s?)--(\s?)'), # em dash re.compile(r'(\s?)--(\s?)'), # em dash
re.compile(r'\s-(?:\s|$)'), # en dash re.compile(r'\s-(?:\s|$)'), # en dash
re.compile(r'(\d+)( ?)x( ?)(?=\d+)'), # dimension sign re.compile(r'\b( ?)[([]TM[])]', re.I), # trademark
re.compile(r'\b ?[([]TM[])]', re.I), # trademark re.compile(r'\b( ?)[([]R[])]', re.I), # registered
re.compile(r'\b ?[([]R[])]', re.I), # registered re.compile(r'\b( ?)[([]C[])]', re.I) # copyright
re.compile(r'\b ?[([]C[])]', re.I), # copyright
) )
glyph_replace = [x % dict(self.glyph_defaults) for x in ( glyph_replace = [x % dict(self.glyph_defaults) for x in (
r'\1\2%(txt_dimension)s\3', # dimension sign
r'\1%(txt_apostrophe)s\2', # apostrophe's r'\1%(txt_apostrophe)s\2', # apostrophe's
r'\1%(txt_apostrophe)s\2', # back in '88 r'\1%(txt_apostrophe)s\2', # back in '88
r'\1%(txt_quote_single_close)s', # single closing r'\1%(txt_quote_single_close)s', # single closing
r'%(txt_quote_single_open)s', # single opening r'%(txt_quote_single_open)s', # single opening
r'\1%(txt_quote_double_close)s', # double closing - following another
r'\1%(txt_quote_double_close)s', # double closing r'\1%(txt_quote_double_close)s', # double closing
r'%(txt_quote_double_open)s', # double opening r'%(txt_quote_double_open)s', # double opening
r'<acronym title="\2">\1</acronym>', # 3+ uppercase acronym r'<acronym title="\2">\1</acronym>', # 3+ uppercase acronym
@ -626,10 +706,172 @@ class Textile(object):
r'\1%(txt_ellipsis)s', # ellipsis r'\1%(txt_ellipsis)s', # ellipsis
r'\1%(txt_emdash)s\2', # em dash r'\1%(txt_emdash)s\2', # em dash
r' %(txt_endash)s ', # en dash r' %(txt_endash)s ', # en dash
r'\1\2%(txt_dimension)s\3', # dimension sign r'\1%(txt_trademark)s', # trademark
r'%(txt_trademark)s', # trademark r'\1%(txt_registered)s', # registered
r'%(txt_registered)s', # registered r'\1%(txt_copyright)s' # copyright
)]
if re.search(r'{.+?}', text):
glyph_search += (
re.compile(r'{(c\||\|c)}'), # cent
re.compile(r'{(L-|-L)}'), # pound
re.compile(r'{(Y=|=Y)}'), # yen
re.compile(r'{\(c\)}'), # copyright
re.compile(r'{\(r\)}'), # registered
re.compile(r'{1/4}'), # quarter
re.compile(r'{1/2}'), # half
re.compile(r'{3/4}'), # three-quarter
re.compile(r'{(A`|`A)}'), # 192;
re.compile(r'{(A\'|\'A)}'), # 193;
re.compile(r'{(A\^|\^A)}'), # 194;
re.compile(r'{(A~|~A)}'), # 195;
re.compile(r'{(A\"|\"A)}'), # 196;
re.compile(r'{(Ao|oA)}'), # 197;
re.compile(r'{(AE)}'), # 198;
re.compile(r'{(C,|,C)}'), # 199;
re.compile(r'{(E`|`E)}'), # 200;
re.compile(r'{(E\'|\'E)}'), # 201;
re.compile(r'{(E\^|\^E)}'), # 202;
re.compile(r'{(E\"|\"E)}'), # 203;
re.compile(r'{(I`|`I)}'), # 204;
re.compile(r'{(I\'|\'I)}'), # 205;
re.compile(r'{(I\^|\^I)}'), # 206;
re.compile(r'{(I\"|\"I)}'), # 207;
re.compile(r'{(D-|-D)}'), # 208;
re.compile(r'{(N~|~N)}'), # 209;
re.compile(r'{(O`|`O)}'), # 210;
re.compile(r'{(O\'|\'O)}'), # 211;
re.compile(r'{(O\^|\^O)}'), # 212;
re.compile(r'{(O~|~O)}'), # 213;
re.compile(r'{(O\"|\"O)}'), # 214;
re.compile(r'{(O\/|\/O)}'), # 215;
re.compile(r'{(U`|`U)}'), # 216;
re.compile(r'{(U\'|\'U)}'), # 217;
re.compile(r'{(U\^|\^U)}'), # 218;
re.compile(r'{(U\"|\"U)}'), # 219;
re.compile(r'{(Y\'|\'Y)}'), # 220;
re.compile(r'{(a`|`a)}'), # a-grace
re.compile(r'{(a\'|\'a)}'), # a-acute
re.compile(r'{(a\^|\^a)}'), # a-circumflex
re.compile(r'{(a~|~a)}'), # a-tilde
re.compile(r'{(a\"|\"a)}'), # a-diaeresis
re.compile(r'{(ao|oa)}'), # a-ring
re.compile(r'{ae}'), # ae
re.compile(r'{(c,|,c)}'), # c-cedilla
re.compile(r'{(e`|`e)}'), # e-grace
re.compile(r'{(e\'|\'e)}'), # e-acute
re.compile(r'{(e\^|\^e)}'), # e-circumflex
re.compile(r'{(e\"|\"e)}'), # e-diaeresis
re.compile(r'{(i`|`i)}'), # i-grace
re.compile(r'{(i\'|\'i)}'), # i-acute
re.compile(r'{(i\^|\^i)}'), # i-circumflex
re.compile(r'{(i\"|\"i)}'), # i-diaeresis
re.compile(r'{(n~|~n)}'), # n-tilde
re.compile(r'{(o`|`o)}'), # o-grace
re.compile(r'{(o\'|\'o)}'), # o-acute
re.compile(r'{(o\^|\^o)}'), # o-circumflex
re.compile(r'{(o~|~o)}'), # o-tilde
re.compile(r'{(o\"|\"o)}'), # o-diaeresis
re.compile(r'{(o\/|\/o)}'), # o-stroke
re.compile(r'{(u`|`u)}'), # u-grace
re.compile(r'{(u\'|\'u)}'), # u-acute
re.compile(r'{(u\^|\^u)}'), # u-circumflex
re.compile(r'{(u\"|\"u)}'), # u-diaeresis
re.compile(r'{(y\'|\'y)}'), # y-acute
re.compile(r'{(y\"|\"y)}'), # y-diaeresis
re.compile(r'{OE}'), # y-diaeresis
re.compile(r'{oe}'), # y-diaeresis
re.compile(r'{\*}'), # bullet
re.compile(r'{Fr}'), # Franc
re.compile(r'{(L=|=L)}'), # Lira
re.compile(r'{Rs}'), # Rupee
re.compile(r'{(C=|=C)}'), # euro
re.compile(r'{tm}'), # euro
re.compile(r'{spade}'), # spade
re.compile(r'{club}'), # club
re.compile(r'{heart}'), # heart
re.compile(r'{diamond}') # diamond
)
glyph_replace += [x % dict(self.glyph_defaults) for x in (
r'%(mac_cent)s', # cent
r'%(mac_pound)s', # pound
r'%(mac_yen)s', # yen
r'%(txt_copyright)s', # copyright r'%(txt_copyright)s', # copyright
r'%(txt_registered)s', # registered
r'%(mac_quarter)s', # quarter
r'%(mac_half)s', # half
r'%(mac_three-quarter)s', # three-quarter
r'%(mac_cA-grave)s', # 192;
r'%(mac_cA-acute)s', # 193;
r'%(mac_cA-circumflex)s', # 194;
r'%(mac_cA-tilde)s', # 195;
r'%(mac_cA-diaeresis)s', # 196;
r'%(mac_cA-ring)s', # 197;
r'%(mac_cAE)s', # 198;
r'%(mac_cC-cedilla)s', # 199;
r'%(mac_cE-grave)s', # 200;
r'%(mac_cE-acute)s', # 201;
r'%(mac_cE-circumflex)s', # 202;
r'%(mac_cE-diaeresis)s', # 203;
r'%(mac_cI-grave)s', # 204;
r'%(mac_cI-acute)s', # 205;
r'%(mac_cI-circumflex)s', # 206;
r'%(mac_cI-diaeresis)s', # 207;
r'%(mac_cEth)s', # 208;
r'%(mac_cN-tilde)s', # 209;
r'%(mac_cO-grave)s', # 210;
r'%(mac_cO-acute)s', # 211;
r'%(mac_cO-circumflex)s', # 212;
r'%(mac_cO-tilde)s', # 213;
r'%(mac_cO-diaeresis)s', # 214;
r'%(mac_cO-stroke)s', # 216;
r'%(mac_cU-grave)s', # 217;
r'%(mac_cU-acute)s', # 218;
r'%(mac_cU-circumflex)s', # 219;
r'%(mac_cU-diaeresis)s', # 220;
r'%(mac_cY-acute)s', # 221;
r'%(mac_sa-grave)s', # 224;
r'%(mac_sa-acute)s', # 225;
r'%(mac_sa-circumflex)s', # 226;
r'%(mac_sa-tilde)s', # 227;
r'%(mac_sa-diaeresis)s', # 228;
r'%(mac_sa-ring)s', # 229;
r'%(mac_sae)s', # 230;
r'%(mac_sc-cedilla)s', # 231;
r'%(mac_se-grave)s', # 232;
r'%(mac_se-acute)s', # 233;
r'%(mac_se-circumflex)s', # 234;
r'%(mac_se-diaeresis)s', # 235;
r'%(mac_si-grave)s', # 236;
r'%(mac_si-acute)s', # 237;
r'%(mac_si-circumflex)s', # 238;
r'%(mac_si-diaeresis)s', # 239;
r'%(mac_sn-tilde)s', # 241;
r'%(mac_so-grave)s', # 242;
r'%(mac_so-acute)s', # 243;
r'%(mac_so-circumflex)s', # 244;
r'%(mac_so-tilde)s', # 245;
r'%(mac_so-diaeresis)s', # 246;
r'%(mac_so-stroke)s', # 248;
r'%(mac_su-grave)s', # 249;
r'%(mac_su-acute)s', # 250;
r'%(mac_su-circumflex)s', # 251;
r'%(mac_su-diaeresis)s', # 252;
r'%(mac_sy-acute)s', # 253;
r'%(mac_sy-diaeresis)s', # 255;
r'%(mac_cOE)s', # 338;
r'%(mac_soe)s', # 339;
r'%(mac_bullet)s', # bullet
r'%(mac_franc)s', # franc
r'%(mac_lira)s', # lira
r'%(mac_rupee)s', # rupee
r'%(mac_euro)s', # euro
r'%(txt_trademark)s', # trademark
r'%(mac_spade)s', # spade
r'%(mac_club)s', # club
r'%(mac_heart)s', # heart
r'%(mac_diamond)s' # diamond
)] )]
result = [] result = []

View File

@ -34,6 +34,13 @@ class ViewAction(InterfaceAction):
self.qaction.setMenu(self.view_menu) self.qaction.setMenu(self.view_menu)
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection) ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
self.view_menu.addSeparator()
ac = self.create_action(spec=(_('Read a random book'), 'catalog.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.view_random)
self.view_menu.addAction(ac)
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'
for action in list(self.view_menu.actions())[1:]: for action in list(self.view_menu.actions())[1:]:
@ -151,6 +158,10 @@ class ViewAction(InterfaceAction):
def view_specific_book(self, index): def view_specific_book(self, index):
self._view_books([index]) self._view_books([index])
def view_random(self, *args):
self.gui.iactions['Choose Library'].pick_random()
self._view_books([self.gui.library_view.currentIndex()])
def _view_books(self, rows): def _view_books(self, rows):
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
self._launch_viewer() self._launch_viewer()

View File

@ -1052,11 +1052,13 @@ class DeviceMixin(object): # {{{
except: except:
pass pass
total_size = self.location_manager.free[0] total_size = self.location_manager.free[0]
if self.location_manager.free[0] > total_size + (1024**2): loc = tweaks['send_news_to_device_location']
loc_index = {"carda": 1, "cardb": 2}.get(loc, 0)
if self.location_manager.free[loc_index] > total_size + (1024**2):
# Send news to main memory if enough space available # Send news to main memory if enough space available
# as some devices like the Nook Color cannot handle # as some devices like the Nook Color cannot handle
# periodicals on SD cards properly # periodicals on SD cards properly
on_card = None on_card = loc if loc in ('carda', 'cardb') else None
self.upload_books(files, names, metadata, self.upload_books(files, names, metadata,
on_card=on_card, on_card=on_card,
memory=[files, remove]) memory=[files, remove])

View File

@ -442,7 +442,7 @@ class Scheduler(QObject):
if self.oldest > 0: if self.oldest > 0:
delta = timedelta(days=self.oldest) delta = timedelta(days=self.oldest)
try: try:
ids = list(self.recipe_model.db.tags_older_than(_('News'), ids = list(self.db.tags_older_than(_('News'),
delta)) delta))
except: except:
# Happens if library is being switched # Happens if library is being switched

View File

@ -71,7 +71,7 @@ class Customize(QFrame, Ui_Frame):
button = getattr(self, 'button%d'%which) button = getattr(self, 'button%d'%which)
font = QFont() font = QFont()
button.setFont(font) button.setFont(font)
sequence = QKeySequence(code|int(ev.modifiers())) sequence = QKeySequence(code|(int(ev.modifiers())&~Qt.KeypadModifier))
button.setText(sequence.toString()) button.setText(sequence.toString())
self.capture = 0 self.capture = 0
setattr(self, 'shortcut%d'%which, sequence) setattr(self, 'shortcut%d'%which, sequence)
@ -195,7 +195,7 @@ class Shortcuts(QAbstractListModel):
def get_match(self, event_or_sequence, ignore=tuple()): def get_match(self, event_or_sequence, ignore=tuple()):
q = event_or_sequence q = event_or_sequence
if isinstance(q, QKeyEvent): if isinstance(q, QKeyEvent):
q = QKeySequence(q.key()|int(q.modifiers())) q = QKeySequence(q.key()|(int(q.modifiers())&~Qt.KeypadModifier))
for key in self.order: for key in self.order:
if key not in ignore: if key not in ignore:
for seq in self.get_sequences(key): for seq in self.get_sequences(key):

View File

@ -656,6 +656,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
pass pass
time.sleep(2) time.sleep(2)
self.hide_windows() self.hide_windows()
# Do not report any errors that happen after the shutdown
sys.excepthook = sys.__excepthook__
return True return True
def run_wizard(self, *args): def run_wizard(self, *args):