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):