diff --git a/resources/template-functions.json b/resources/template-functions.json index cf858c7691..7656db4021 100644 --- a/resources/template-functions.json +++ b/resources/template-functions.json @@ -3,10 +3,12 @@ "divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n", "uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n", "strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n", + "in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n", "substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n", "ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n", "booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n", "select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n", + "first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n", "field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n", "subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n", "list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n", diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py index 0ed6d7e222..bea90eeba8 100644 --- a/src/calibre/ebooks/epub/output.py +++ b/src/calibre/ebooks/epub/output.py @@ -413,6 +413,13 @@ class EPUBOutput(OutputFormatPlugin): rule.style.removeProperty('margin-left') # padding-left breaks rendering in webkit and gecko rule.style.removeProperty('padding-left') + # Change whitespace:pre to pre-line to accommodate readers that + # cannot scroll horizontally + for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE): + style = rule.style + ws = style.getPropertyValue('white-space') + if ws == 'pre': + style.setProperty('white-space', 'pre-wrap') # }}} diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index f291959475..7da37ce5af 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -29,7 +29,7 @@ class Worker(Thread): # Get details {{{ Get book details from amazons book page in a separate thread ''' - def __init__(self, url, result_queue, browser, log, relevance, plugin, timeout=20): + def __init__(self, url, result_queue, browser, log, relevance, domain, plugin, timeout=20): Thread.__init__(self) self.daemon = True self.url, self.result_queue = url, result_queue @@ -37,7 +37,7 @@ class Worker(Thread): # Get details {{{ self.relevance, self.plugin = relevance, plugin self.browser = browser.clone_browser() self.cover_url = self.amazon_id = self.isbn = None - self.domain = self.plugin.domain + self.domain = domain months = { 'de': { @@ -199,7 +199,8 @@ class Worker(Thread): # Get details {{{ return mi = Metadata(title, authors) - mi.set_identifier('amazon', asin) + idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain + mi.set_identifier(idtype, asin) self.amazon_id = asin try: @@ -404,12 +405,30 @@ class Amazon(Source): 'country\'s Amazon website.'), choices=AMAZON_DOMAINS), ) + def get_domain_and_asin(self, identifiers): + for key, val in identifiers.iteritems(): + key = key.lower() + if key in ('amazon', 'asin'): + return 'com', val + if key.startswith('amazon_'): + domain = key.split('_')[-1] + if domain and domain in self.AMAZON_DOMAINS: + return domain, val + return None, None + def get_book_url(self, identifiers): # {{{ - asin = identifiers.get('amazon', None) - if asin is None: - asin = identifiers.get('asin', None) - if asin: - return ('amazon', asin, 'http://amzn.com/%s'%asin) + domain, asin = self.get_domain_and_asin(identifiers) + if domain and asin: + url = None + if domain == 'com': + url = 'http://amzn.com/'+asin + elif domain == 'uk': + url = 'http://www.amazon.co.uk/dp/'+asin + else: + url = 'http://www.amazon.%s/dp/%s'%(domain, asin) + if url: + idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain + return (idtype, asin, url) # }}} @property @@ -420,8 +439,14 @@ class Amazon(Source): return domain - def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ - domain = self.domain + def create_query(self, log, title=None, authors=None, identifiers={}, # {{{ + domain=None): + if domain is None: + domain = self.domain + + idomain, asin = self.get_domain_and_asin(identifiers) + if idomain is not None: + domain = idomain # See the amazon detailed search page to get all options q = { 'search-alias' : 'aps', @@ -433,7 +458,6 @@ class Amazon(Source): else: q['sort'] = 'relevancerank' - asin = identifiers.get('amazon', None) isbn = check_isbn(identifiers.get('isbn', None)) if asin is not None: @@ -456,23 +480,22 @@ class Amazon(Source): if not ('field-keywords' in q or 'field-isbn' in q or ('field-title' in q)): # Insufficient metadata to make an identify query - return None + return None, None latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1', 'ignore')) for x, y in q.iteritems()]) + udomain = domain if domain == 'uk': - domain = 'co.uk' - url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q) - return url + udomain = 'co.uk' + url = 'http://www.amazon.%s/s/?'%udomain + urlencode(latin1q) + return url, domain # }}} def get_cached_cover_url(self, identifiers): # {{{ url = None - asin = identifiers.get('amazon', None) - if asin is None: - asin = identifiers.get('asin', None) + domain, asin = self.get_domain_and_asin(identifiers) if asin is None: isbn = identifiers.get('isbn', None) if isbn is not None: @@ -489,7 +512,7 @@ class Amazon(Source): Note this method will retry without identifiers automatically if no match is found with identifiers. ''' - query = self.create_query(log, title=title, authors=authors, + query, domain = self.create_query(log, title=title, authors=authors, identifiers=identifiers) if query is None: log.error('Insufficient metadata to construct query') @@ -571,7 +594,7 @@ class Amazon(Source): log.error('No matches found with query: %r'%query) return - workers = [Worker(url, result_queue, br, log, i, self) for i, url in + workers = [Worker(url, result_queue, br, log, i, domain, self) for i, url in enumerate(matches)] for w in workers: diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py index 95727e442b..98b74b391d 100644 --- a/src/calibre/gui2/dialogs/template_line_editor.py +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -9,7 +9,7 @@ from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel, QDialogButtonBox, QColor, QComboBox, QIcon) from calibre.gui2.dialogs.template_dialog import TemplateDialog -from calibre.gui2.complete import MultiCompleteComboBox +from calibre.gui2.complete import MultiCompleteLineEdit from calibre.gui2 import error_dialog class TemplateLineEditor(QLineEdit): @@ -63,14 +63,16 @@ class TagWizard(QDialog): self.tags = tags l = QGridLayout() self.setLayout(l) - l.addWidget(QLabel(_('Tag Value')), 0, 0, 1, 1) + l.setColumnStretch(0, 1) + l.setColumnMinimumWidth(0, 300) + l.addWidget(QLabel(_('Tags (more than one per box permitted)')), 0, 0, 1, 1) l.addWidget(QLabel(_('Color')), 0, 1, 1, 1) self.tagboxes = [] self.colorboxes = [] self.colors = [unicode(s) for s in list(QColor.colorNames())] self.colors.insert(0, '') for i in range(0, 10): - tb = MultiCompleteComboBox(self) + tb = MultiCompleteLineEdit(self) tb.set_separator(', ') tb.update_items_cache(self.tags) self.tagboxes.append(tb) @@ -101,10 +103,11 @@ class TagWizard(QDialog): def accepted(self): res = ("program:\n#tag wizard -- do not directly edit\n" - " t = field('tags');\n first_non_empty(\n") + " t = field('tags');\n first_non_empty(\n") lines = [] for tb, cb in zip(self.tagboxes, self.colorboxes): - tags = [t.strip() for t in unicode(tb.currentText()).split(',') if t.strip()] + tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()] + tags = '$|^'.join(tags) c = unicode(cb.currentText()).strip() if not tags or not c: continue @@ -113,14 +116,13 @@ class TagWizard(QDialog): _('The color {0} is not valid').format(c), show=True, show_copy_button=False) return False - for t in tags: - lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(t, c)) + lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(tags, c)) res += ',\n'.join(lines) res += ')\n' self.template = res res = '' for tb, cb in zip(self.tagboxes, self.colorboxes): - t = unicode(tb.currentText()).strip() + t = unicode(tb.text()).strip() if t.endswith(','): t = t[:-1] c = unicode(cb.currentText()).strip() diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 483934825d..49bfb1df1a 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -167,8 +167,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): '' 'tutorial on using templates.') + '

' + - _('If you want to color a field based on tags, then right-click ' - 'in an empty template line and choose tags wizard. ' + _('If you want to color a field based on tags, then click the ' + 'button next to an empty line to open the tags wizard. ' 'It will build a template for you. You can later edit that ' 'template with the same wizard. If you edit it by hand, the ' 'wizard might not work or might restore old values.') + diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index a67a3585cb..fe6134f235 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -442,6 +442,9 @@ then the tags will be displayed each on their own line. :/images/wizard.png:/images/wizard.png + + Open the tags wizard. + @@ -456,6 +459,9 @@ then the tags will be displayed each on their own line. :/images/wizard.png:/images/wizard.png + + Open the tags wizard. + @@ -470,6 +476,9 @@ then the tags will be displayed each on their own line. :/images/wizard.png:/images/wizard.png + + Open the tags wizard. + @@ -484,6 +493,9 @@ then the tags will be displayed each on their own line. :/images/wizard.png:/images/wizard.png + + Open the tags wizard. + @@ -498,6 +510,9 @@ then the tags will be displayed each on their own line. :/images/wizard.png:/images/wizard.png + + Open the tags wizard. + diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index 862e724809..319feefa44 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -221,7 +221,12 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, if not ip or ip.startswith('127.'): raise cherrypy.log('Trying to bind to single interface: '+ip) + # Change the host we listen on cherrypy.config.update({'server.socket_host' : ip}) + # This ensures that the change is actually applied + cherrypy.server.socket_host = ip + cherrypy.server.httpserver = cherrypy.server.instance = None + cherrypy.engine.start() self.is_running = True @@ -231,6 +236,8 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, cherrypy.engine.block() except Exception as e: self.exception = e + import traceback + traceback.print_exc() finally: self.is_running = False try: diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 1e7a62b869..9e58d4f638 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -356,7 +356,7 @@ class PostInstall: mimetypes = set([]) for x in all_input_formats(): mt = guess_type('dummy.'+x)[0] - if mt and 'chemical' not in mt: + if mt and 'chemical' not in mt and 'ctc-posml' not in mt: mimetypes.add(mt) def write_mimetypes(f): @@ -376,11 +376,10 @@ class PostInstall: des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', 'calibre-ebook-viewer.desktop') for x in des: - cmd = ['xdg-desktop-menu', 'install', './'+x] - if x != des[-1]: - cmd.insert(2, '--noupdate') + cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x] check_call(' '.join(cmd), shell=True) self.menu_resources.append(x) + check_call(['xdg-desktop-menu', 'forceupdate']) f = open('calibre-mimetypes', 'wb') f.write(MIME) f.close()