diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index 38c1685b7c..464c9d2cfd 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -355,3 +355,11 @@ draw_hidden_section_indicators = True
# large covers
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"
+
diff --git a/resources/recipes/evangelizo.recipe b/resources/recipes/evangelizo.recipe
new file mode 100644
index 0000000000..81ac74bc25
--- /dev/null
+++ b/resources/recipes/evangelizo.recipe
@@ -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'<font size="-2">([(][0-9]*[)])</font>'), r'\g<1>'),
+ (re.compile(r'([\.!]\n)'), r'\g<1>
'),
+ ]
+
+ def populate_article_metadata(self, article, soup, first):
+ article.title = re.sub(r'([(][0-9]*[)])', r'\g<1>', article.title)
+ return
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 846fdf1322..6af4f79cbb 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -16,7 +16,7 @@ from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode
from calibre.constants import __appname__, __version__, filesystem_encoding
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.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang
@@ -863,6 +863,7 @@ class OPF(object): # {{{
for x in self.XPath(
'descendant::*[local-name() = "identifier" and text()]')(
self.metadata):
+ found_scheme = False
for attr, val in x.attrib.iteritems():
if attr.endswith('scheme'):
typ = icu_lower(val)
@@ -870,7 +871,15 @@ class OPF(object): # {{{
method='text').strip()
if val and typ not in ('calibre', 'uuid'):
identifiers[typ] = val
+ found_scheme = True
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
@dynamic_property
diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py
index 335a43ebb0..b99893ccba 100644
--- a/src/calibre/ebooks/metadata/sources/amazon.py
+++ b/src/calibre/ebooks/metadata/sources/amazon.py
@@ -322,7 +322,7 @@ class Amazon(Source):
# }}}
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
match is found with identifiers.
@@ -436,7 +436,7 @@ if __name__ == '__main__':
( # An e-book ISBN not on Amazon, one of the authors is
# unknown to Amazon, so no popup wrapper
- {'identifiers':{'isbn': '0307459671'},
+ {'identifiers':{'isbn': '9780307459671'},
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
[title_test('The Invisible Gorilla: And Other Ways Our Intuitions Deceive Us',
exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])]
diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py
index 3c320d14b6..55cc996cf7 100644
--- a/src/calibre/ebooks/metadata/sources/base.py
+++ b/src/calibre/ebooks/metadata/sources/base.py
@@ -137,6 +137,16 @@ class Source(Plugin):
'''
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 result_queue: A result Queue, results should be put into it.
Each result is a Metadata object
diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py
index 8dffd3f053..c44ad81b6c 100644
--- a/src/calibre/ebooks/metadata/sources/google.py
+++ b/src/calibre/ebooks/metadata/sources/google.py
@@ -50,7 +50,7 @@ def get_details(browser, url, timeout): # {{{
if gc() != 403:
raise
# Google is throttling us, wait a little
- time.sleep(1)
+ time.sleep(2)
raw = browser.open_novisit(url, timeout=timeout).read()
return raw
@@ -195,7 +195,7 @@ class GoogleBooks(Source):
ans = to_metadata(br, log, i, timeout)
if isinstance(ans, Metadata):
result_queue.put(ans)
- for isbn in ans.all_isbns:
+ for isbn in getattr(ans, 'all_isbns', []):
self.cache_isbn_to_identifier(isbn,
ans.identifiers['google'])
except:
@@ -206,7 +206,7 @@ class GoogleBooks(Source):
break
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,
identifiers=identifiers)
br = self.browser
@@ -225,6 +225,11 @@ class GoogleBooks(Source):
log.exception('Failed to parse identify results')
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
# throttles requests returning 403 Forbidden errors
self.get_all_details(br, log, entries, abort, result_queue, timeout)
@@ -235,13 +240,16 @@ class GoogleBooks(Source):
if __name__ == '__main__':
# 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,
- title_test)
+ title_test, authors_test)
test_identify_plugin(GoogleBooks.name,
[
+
(
- {'identifiers':{'isbn': '0743273567'}},
- [title_test('The great gatsby', exact=True)]
+ {'identifiers':{'isbn': '0743273567'}, 'title':'Great Gatsby',
+ 'authors':['Fitzgerald']},
+ [title_test('The great gatsby', exact=True),
+ authors_test(['Francis Scott Fitzgerald'])]
),
#(
diff --git a/src/calibre/ebooks/metadata/sources/test.py b/src/calibre/ebooks/metadata/sources/test.py
index 69e0c32846..2af9a47078 100644
--- a/src/calibre/ebooks/metadata/sources/test.py
+++ b/src/calibre/ebooks/metadata/sources/test.py
@@ -46,6 +46,16 @@ def authors_test(authors):
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):
'''
:param name: Plugin name
@@ -95,7 +105,7 @@ def test_identify_plugin(name, tests):
prints(mi)
prints('\n\n')
- match_found = None
+ possibles = []
for mi in results:
test_failed = False
for tfunc in test_funcs:
@@ -103,26 +113,23 @@ def test_identify_plugin(name, tests):
test_failed = True
break
if not test_failed:
- match_found = mi
- break
+ possibles.append(mi)
- if match_found is None:
+ if not possibles:
prints('ERROR: No results that passed all tests were found')
prints('Log saved to', lf)
raise SystemExit(1)
- for key in plugin.touched_fields:
- if key.startswith('identifier:'):
- key = key.partition(':')[-1]
- if not match_found.has_identifier(key):
- prints('Failed to find identifier:', key)
- raise SystemExit(1)
- elif match_found.is_null(key):
- prints('Failed to find', key)
+ good = [x for x in possibles if _test_fields(plugin.touched_fields, x) is
+ None]
+ if not good:
+ prints('Failed to find', _test_fields(plugin.touched_fields,
+ possibles[0]))
raise SystemExit(1)
+
prints('Average time per query', sum(times)/len(times))
if os.stat(lf).st_size > 10:
- prints('There were some errors, see log', lf)
+ prints('There were some errors/warnings, see log', lf)
diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py
index 7f3f40184c..3bd936b803 100644
--- a/src/calibre/ebooks/oeb/base.py
+++ b/src/calibre/ebooks/oeb/base.py
@@ -229,7 +229,11 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
if 'style' in el.attrib:
text = el.attrib['style']
if _css_url_re.search(text) is not None:
- stext = parseStyle(text)
+ try:
+ stext = parseStyle(text)
+ except:
+ # Parsing errors are raised by cssutils
+ continue
for p in stext.getProperties(all=True):
v = p.cssValue
if v.CSS_VALUE_LIST == v.cssValueType:
@@ -846,6 +850,7 @@ class Manifest(object):
return data
def _parse_xhtml(self, data):
+ orig_data = data
self.oeb.log.debug('Parsing', self.href, '...')
# Convert to Unicode and normalize line endings
data = self.oeb.decode(data)
@@ -923,6 +928,8 @@ class Manifest(object):
# Handle weird (non-HTML/fragment) files
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)
nroot = etree.fromstring('')
has_body = False
diff --git a/src/calibre/ebooks/textile/functions.py b/src/calibre/ebooks/textile/functions.py
old mode 100644
new mode 100755
index ec675b9b62..891211de30
--- a/src/calibre/ebooks/textile/functions.py
+++ b/src/calibre/ebooks/textile/functions.py
@@ -5,11 +5,13 @@ PyTextile
A Humane Web Text Generator
"""
-__version__ = '2.1.4'
-
-__date__ = '2009/12/04'
+# Last upstream version basis
+# __version__ = '2.1.4'
+#__date__ = '2009/12/04'
__copyright__ = """
+Copyright (c) 2011, Leigh Parry
+Copyright (c) 2011, John Schember
Copyright (c) 2009, Jason Samsa, http://jsamsa.com/
Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/
Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/
@@ -120,6 +122,82 @@ class Textile(object):
btag_lite = ('bq', 'bc', 'p')
glyph_defaults = (
+ ('mac_cent', '¢'),
+ ('mac_pound', '£'),
+ ('mac_yen', '¥'),
+ ('mac_quarter', '¼'),
+ ('mac_half', '½'),
+ ('mac_three-quarter', '¾'),
+ ('mac_cA-grave', 'À'),
+ ('mac_cA-acute', 'Á'),
+ ('mac_cA-circumflex', 'Â'),
+ ('mac_cA-tilde', 'Ã'),
+ ('mac_cA-diaeresis', 'Ä'),
+ ('mac_cA-ring', 'Å'),
+ ('mac_cAE', 'Æ'),
+ ('mac_cC-cedilla', 'Ç'),
+ ('mac_cE-grave', 'È'),
+ ('mac_cE-acute', 'É'),
+ ('mac_cE-circumflex', 'Ê'),
+ ('mac_cE-diaeresis', 'Ë'),
+ ('mac_cI-grave', 'Ì'),
+ ('mac_cI-acute', 'Í'),
+ ('mac_cI-circumflex', 'Î'),
+ ('mac_cI-diaeresis', 'Ï'),
+ ('mac_cEth', 'Ð'),
+ ('mac_cN-tilde', 'Ñ'),
+ ('mac_cO-grave', 'Ò'),
+ ('mac_cO-acute', 'Ó'),
+ ('mac_cO-circumflex', 'Ô'),
+ ('mac_cO-tilde', 'Õ'),
+ ('mac_cO-diaeresis', 'Ö'),
+ ('mac_cO-stroke', 'Ø'),
+ ('mac_cU-grave', 'Ù'),
+ ('mac_cU-acute', 'Ú'),
+ ('mac_cU-circumflex', 'Û'),
+ ('mac_cU-diaeresis', 'Ü'),
+ ('mac_cY-acute', 'Ý'),
+ ('mac_sa-grave', 'à'),
+ ('mac_sa-acute', 'á'),
+ ('mac_sa-circumflex', 'â'),
+ ('mac_sa-tilde', 'ã'),
+ ('mac_sa-diaeresis', 'ä'),
+ ('mac_sa-ring', 'å'),
+ ('mac_sae', 'æ'),
+ ('mac_sc-cedilla', 'ç'),
+ ('mac_se-grave', 'è'),
+ ('mac_se-acute', 'é'),
+ ('mac_se-circumflex', 'ê'),
+ ('mac_se-diaeresis', 'ë'),
+ ('mac_si-grave', 'ì'),
+ ('mac_si-acute', 'í'),
+ ('mac_si-circumflex', 'î'),
+ ('mac_si-diaeresis', 'ï'),
+ ('mac_sn-tilde', 'ñ'),
+ ('mac_so-grave', 'ò'),
+ ('mac_so-acute', 'ó'),
+ ('mac_so-circumflex', 'ô'),
+ ('mac_so-tilde', 'õ'),
+ ('mac_so-diaeresis', 'ö'),
+ ('mac_so-stroke', 'ø'),
+ ('mac_su-grave', 'ù'),
+ ('mac_su-acute', 'ú'),
+ ('mac_su-circumflex', 'û'),
+ ('mac_su-diaeresis', 'ü'),
+ ('mac_sy-acute', 'ý'),
+ ('mac_sy-diaeresis', 'ÿ'),
+ ('mac_cOE', 'Œ'),
+ ('mac_soe', 'œ'),
+ ('mac_bullet', '•'),
+ ('mac_franc', '₣'),
+ ('mac_lira', '₤'),
+ ('mac_rupee', '₨'),
+ ('mac_euro', '€'),
+ ('mac_spade', '♠'),
+ ('mac_club', '♣'),
+ ('mac_heart', '♥'),
+ ('mac_diamond', '♦'),
+ ('txt_dimension', '×'),
('txt_quote_single_open', '‘'),
('txt_quote_single_close', '’'),
('txt_quote_double_open', '“'),
@@ -130,7 +208,6 @@ class Textile(object):
('txt_ellipsis', '…'),
('txt_emdash', '—'),
('txt_endash', '–'),
- ('txt_dimension', '×'),
('txt_trademark', '™'),
('txt_registered', '®'),
('txt_copyright', '©'),
@@ -593,45 +670,210 @@ class Textile(object):
'Cat’s Cradle by Vonnegut
'
"""
- # fix: hackish
+ # fix: hackish
text = re.sub(r'"\Z', '\" ', text)
glyph_search = (
- re.compile(r"(\w)\'(\w)"), # apostrophe's
- re.compile(r'(\s)\'(\d+\w?)\b(?!\')'), # back in '88
- re.compile(r'(\S)\'(?=\s|'+self.pnct+'|<|$)'), # single closing
+ re.compile(r'(\d+\'?\"?)( ?)x( ?)(?=\d+)'), # dimension sign
+ re.compile(r"(\w)\'(\w)"), # apostrophe's
+ re.compile(r'(\s)\'(\d+\w?)\b(?!\')'), # back in '88
+ re.compile(r'(\S)\'(?=\s|'+self.pnct+'|<|$)'), # single closing
re.compile(r'\'/'), # single opening
- re.compile(r'(\S)\"(?=\s|'+self.pnct+'|<|$)'), # double closing
+ re.compile(r'(\")\"'), # double closing - following another
+ re.compile(r'(\S)\"(?=\s|'+self.pnct+'|<|$)'), # double closing
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-Z\'\-]+[A-Z])(?=[\s.,\)>])'), # 3+ uppercase
- 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|$)'), # en dash
- re.compile(r'(\d+)( ?)x( ?)(?=\d+)'), # dimension sign
- re.compile(r'\b ?[([]TM[])]', re.I), # trademark
- re.compile(r'\b ?[([]R[])]', re.I), # registered
- re.compile(r'\b ?[([]C[])]', re.I), # copyright
+ re.compile(r'\b( ?)[([]TM[])]', re.I), # trademark
+ re.compile(r'\b( ?)[([]R[])]', re.I), # registered
+ re.compile(r'\b( ?)[([]C[])]', re.I) # copyright
)
glyph_replace = [x % dict(self.glyph_defaults) for x in (
- r'\1%(txt_apostrophe)s\2', # apostrophe's
- r'\1%(txt_apostrophe)s\2', # back in '88
+ r'\1\2%(txt_dimension)s\3', # dimension sign
+ r'\1%(txt_apostrophe)s\2', # apostrophe's
+ r'\1%(txt_apostrophe)s\2', # back in '88
r'\1%(txt_quote_single_close)s', # single closing
- r'%(txt_quote_single_open)s', # single opening
- r'\1%(txt_quote_double_close)s', # double closing
- r'%(txt_quote_double_open)s', # double 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'%(txt_quote_double_open)s', # double opening
r'\1', # 3+ uppercase acronym
r'\1', # 3+ uppercase
- r'\1%(txt_ellipsis)s', # ellipsis
+ r'\1%(txt_ellipsis)s', # ellipsis
r'\1%(txt_emdash)s\2', # em dash
r' %(txt_endash)s ', # en dash
- r'\1\2%(txt_dimension)s\3', # dimension sign
- r'%(txt_trademark)s', # trademark
- r'%(txt_registered)s', # registered
- r'%(txt_copyright)s', # copyright
+ r'\1%(txt_trademark)s', # trademark
+ r'\1%(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_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 = []
for line in re.compile(r'(<.*?>)', re.U).split(text):
if not re.search(r'<.*>', line):
diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py
index 7b14de8176..a606ca09bc 100644
--- a/src/calibre/gui2/actions/view.py
+++ b/src/calibre/gui2/actions/view.py
@@ -34,6 +34,13 @@ class ViewAction(InterfaceAction):
self.qaction.setMenu(self.view_menu)
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):
enabled = loc == 'library'
for action in list(self.view_menu.actions())[1:]:
@@ -151,6 +158,10 @@ class ViewAction(InterfaceAction):
def view_specific_book(self, 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):
if not rows or len(rows) == 0:
self._launch_viewer()
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 2cbecc134c..215e67c46f 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -1052,11 +1052,13 @@ class DeviceMixin(object): # {{{
except:
pass
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
# as some devices like the Nook Color cannot handle
# periodicals on SD cards properly
- on_card = None
+ on_card = loc if loc in ('carda', 'cardb') else None
self.upload_books(files, names, metadata,
on_card=on_card,
memory=[files, remove])
diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py
index b2561342b8..347eeb7019 100644
--- a/src/calibre/gui2/dialogs/scheduler.py
+++ b/src/calibre/gui2/dialogs/scheduler.py
@@ -442,7 +442,7 @@ class Scheduler(QObject):
if self.oldest > 0:
delta = timedelta(days=self.oldest)
try:
- ids = list(self.recipe_model.db.tags_older_than(_('News'),
+ ids = list(self.db.tags_older_than(_('News'),
delta))
except:
# Happens if library is being switched
diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py
index 5e56435e10..55ff625fdc 100644
--- a/src/calibre/gui2/shortcuts.py
+++ b/src/calibre/gui2/shortcuts.py
@@ -71,7 +71,7 @@ class Customize(QFrame, Ui_Frame):
button = getattr(self, 'button%d'%which)
font = QFont()
button.setFont(font)
- sequence = QKeySequence(code|int(ev.modifiers()))
+ sequence = QKeySequence(code|(int(ev.modifiers())&~Qt.KeypadModifier))
button.setText(sequence.toString())
self.capture = 0
setattr(self, 'shortcut%d'%which, sequence)
@@ -195,7 +195,7 @@ class Shortcuts(QAbstractListModel):
def get_match(self, event_or_sequence, ignore=tuple()):
q = event_or_sequence
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:
if key not in ignore:
for seq in self.get_sequences(key):
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 4af8c1ea54..7b94c1e821 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -656,6 +656,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
pass
time.sleep(2)
self.hide_windows()
+ # Do not report any errors that happen after the shutdown
+ sys.excepthook = sys.__excepthook__
return True
def run_wizard(self, *args):