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
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.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

View File

@ -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'])]

View File

@ -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

View File

@ -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'])]
),
#(

View File

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

View File

@ -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('<html></html>')
has_body = False

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

@ -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 <john@nachtimwald.com>
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', '&#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_close', '&#8217;'),
('txt_quote_double_open', '&#8220;'),
@ -130,7 +208,6 @@ class Textile(object):
('txt_ellipsis', '&#8230;'),
('txt_emdash', '&#8212;'),
('txt_endash', '&#8211;'),
('txt_dimension', '&#215;'),
('txt_trademark', '&#8482;'),
('txt_registered', '&#174;'),
('txt_copyright', '&#169;'),
@ -593,45 +670,210 @@ class Textile(object):
'<p><cite>Cat&#8217;s Cradle</cite> by Vonnegut</p>'
"""
# 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'<acronym title="\2">\1</acronym>', # 3+ uppercase acronym
r'<span class="caps">\1</span>', # 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):

View File

@ -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()

View File

@ -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])

View File

@ -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

View File

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

View File

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