diff --git a/recipes/perfil.recipe b/recipes/perfil.recipe index 7db86f9d4a..1104202318 100644 --- a/recipes/perfil.recipe +++ b/recipes/perfil.recipe @@ -1,5 +1,5 @@ __license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' +__copyright__ = '2010-2011, Darko Miletic ' ''' perfil.com ''' @@ -39,9 +39,9 @@ class Perfil(BasicNewsRecipe): dict(name=['iframe','embed','object','base','meta','link']) ,dict(name='a', attrs={'href':'#comentarios'}) ,dict(name='div', attrs={'class':'foto3'}) - ,dict(name='img', attrs={'alt':'ampliar'}) + ,dict(name='img', attrs={'alt':['ampliar','Ampliar']}) ] - keep_only_tags=[dict(attrs={'class':['bd468a','cuerpoSuperior']})] + keep_only_tags=[dict(attrs={'class':['articulo','cuerpoSuperior']})] remove_attributes=['onload','lang','width','height','border'] feeds = [ diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index c3aca457ad..1799072045 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -281,16 +281,17 @@ def get_parsed_proxy(typ='http', debug=True): def random_user_agent(): choices = [ - 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)', + 'Mozilla/5.0 (Windows NT 5.2; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19', 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11', - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.14) Gecko/20080409 Camino/1.6 (like Firefox/2.0.0.14)', - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.1) Gecko/20060118 Camino/1.0b2+', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3', 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.78 Safari/532.5', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)', ] + #return choices[-1] return choices[random.randint(0, len(choices)-1)] diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index 9ee6f76449..11647043d3 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -62,24 +62,22 @@ class OEB2HTML(object): self.links[aid] = 'calibre_link-%s' % len(self.links.keys()) return self.links[aid] - def rewrite_links(self, tag, attribs, page): + def rewrite_link(self, tag, attribs, page): # Rewrite ids. if 'id' in attribs: attribs['id'] = self.get_link_id(page.href, attribs['id']) # Rewrite links. - if tag == 'a': - href = attribs['href'] - href = page.abshref(href) + if tag == 'a' and 'href' in attribs: + href = page.abshref(attribs['href']) if self.url_is_relative(href): - if '#' not in href: - href += '#' - if href not in self.links: - self.links[href] = 'calibre_link-%s' % len(self.links.keys()) - href = '#%s' % self.links[href] - attribs['href'] = href + id = '' + if '#' in href: + href, n, id = href.partition('#') + href = '#%s' % self.get_link_id(href, id) + attribs['href'] = href return attribs - def rewrite_images(self, tag, attribs, page): + def rewrite_image(self, tag, attribs, page): if tag == 'img': src = attribs.get('src', None) if src: @@ -131,6 +129,10 @@ class OEB2HTMLNoCSSizer(OEB2HTML): tags = [] tag = barename(elem.tag) attribs = elem.attrib + + attribs = self.rewrite_link(tag, attribs, page) + attribs = self.rewrite_image(tag, attribs, page) + if tag == 'body': tag = 'div' attribs['id'] = self.get_link_id(page.href, '') @@ -147,9 +149,6 @@ class OEB2HTMLNoCSSizer(OEB2HTML): if 'style' in attribs: del attribs['style'] - attribs = self.rewrite_links(tag, attribs, page) - attribs = self.rewrite_images(tag, attribs, page) - # Turn the rest of the attributes into a string we can write with the tag. at = '' for k, v in attribs.items(): @@ -219,6 +218,9 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): tag = barename(elem.tag) attribs = elem.attrib + attribs = self.rewrite_link(tag, attribs, page) + attribs = self.rewrite_image(tag, attribs, page) + style_a = '%s' % style if tag == 'body': tag = 'div' @@ -233,9 +235,6 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): if 'style' in attribs: del attribs['style'] - attribs = self.rewrite_links(tag, attribs, page) - attribs = self.rewrite_images(tag, attribs, page) - # Turn the rest of the attributes into a string we can write with the tag. at = '' for k, v in attribs.items(): @@ -312,6 +311,9 @@ class OEB2HTMLClassCSSizer(OEB2HTML): tag = barename(elem.tag) attribs = elem.attrib + attribs = self.rewrite_link(tag, attribs, page) + attribs = self.rewrite_image(tag, attribs, page) + if tag == 'body': tag = 'div' attribs['id'] = self.get_link_id(page.href, '') @@ -321,9 +323,6 @@ class OEB2HTMLClassCSSizer(OEB2HTML): if 'style' in attribs: del attribs['style'] - attribs = self.rewrite_links(tag, attribs, page) - attribs = self.rewrite_images(tag, attribs, page) - # Turn the rest of the attributes into a string we can write with the tag. at = '' for k, v in attribs.items(): diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 61b555b041..d1c8f24da6 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -64,7 +64,7 @@ class Worker(Thread): # Get details {{{ raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] - # open('/t/t.html', 'wb').write(raw) + #open('/t/t.html', 'wb').write(raw) if '404 - ' in raw: self.log.error('URL malformed: %r'%self.url) @@ -218,6 +218,9 @@ class Worker(Thread): # Get details {{{ ' @class="emptyClear" or @href]'): c.getparent().remove(c) desc = tostring(desc, method='html', encoding=unicode).strip() + # Encoding bug in Amazon data U+fffd (replacement char) + # in some examples it is present in place of ' + desc = desc.replace('\ufffd', "'") # remove all attributes from tags desc = re.sub(r'<([a-zA-Z0-9]+)\s[^>]+>', r'<\1>', desc) # Collapse whitespace @@ -410,6 +413,18 @@ class Amazon(Source): if 'bulk pack' not in title: matches.append(a.get('href')) break + if not matches: + # This can happen for some user agents that Amazon thinks are + # mobile/less capable + log('Trying alternate results page markup') + for td in root.xpath( + r'//div[@id="Results"]/descendant::td[starts-with(@id, "search:Td:")]'): + for a in td.xpath(r'descendant::td[@class="dataColumn"]/descendant::a[@href]/span[@class="srTitle"]/..'): + title = tostring(a, method='text', encoding=unicode).lower() + if 'bulk pack' not in title: + matches.append(a.get('href')) + break + # Keep only the top 5 matches as the matches are sorted by relevance by # Amazon so lower matches are not likely to be very relevant diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 5903a5e710..30b804a76e 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -17,10 +17,10 @@ from calibre.utils.config import JSONConfig from calibre.utils.titlecase import titlecase from calibre.ebooks.metadata import check_isbn -msprefs = JSONConfig('metadata_sources.json') +msprefs = JSONConfig('metadata_sources/global.json') msprefs.defaults['txt_comments'] = False msprefs.defaults['ignore_fields'] = [] -msprefs.defaults['max_tags'] = 10 +msprefs.defaults['max_tags'] = 20 msprefs.defaults['wait_after_first_identify_result'] = 30 # seconds def create_log(ostream=None): @@ -95,7 +95,7 @@ class InternalMetadataCompareKeyGen(object): def get_cached_cover_urls(mi): from calibre.customize.ui import metadata_plugins - plugins = list(metadata_plugins['identify']) + plugins = list(metadata_plugins(['identify'])) for p in plugins: url = p.get_cached_cover_url(mi.identifiers) if url: diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 71554595ad..b04a697ed8 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -34,10 +34,12 @@ class Worker(Thread): self.log = create_log(self.buf) def run(self): + start = time.time() try: self.plugin.identify(self.log, self.rq, self.abort, **self.kwargs) except: self.log.exception('Plugin', self.plugin.name, 'failed') + self.plugin.dl_time_spent = time.time() - start def is_worker_alive(workers): for w in workers: @@ -57,13 +59,13 @@ class ISBNMerge(object): def isbn_in_pool(self, isbn): if isbn: - for p in self.pools: - if isbn in p: - return p + for isbns, pool in self.pools.iteritems(): + if isbn in isbns: + return pool return None def pool_has_result_from_same_source(self, pool, result): - results = self.pools[pool][1] + results = pool[1] for r in results: if r.identify_plugin is result.identify_plugin: return True @@ -77,7 +79,7 @@ class ISBNMerge(object): isbns, min_year = xisbn.get_isbn_pool(isbn) if not isbns: isbns = frozenset([isbn]) - self.pool[isbns] = pool = (min_year, []) + self.pools[isbns] = pool = (min_year, []) if not self.pool_has_result_from_same_source(pool, result): pool[1].append(result) @@ -102,7 +104,7 @@ class ISBNMerge(object): def merge_isbn_results(self): self.results = [] - for min_year, results in self.pool.itervalues(): + for min_year, results in self.pools.itervalues(): if results: self.results.append(self.merge(results, min_year)) @@ -169,11 +171,11 @@ class ISBNMerge(object): min_date = datetime(min_year, 1, 2, tzinfo=utc_tz) ans.pubdate = min_date else: - min_date = datetime(10000, 1, 1, tzinfo=utc_tz) + min_date = datetime(3001, 1, 1, tzinfo=utc_tz) for r in results: if r.pubdate is not None and r.pubdate < min_date: min_date = r.pubdate - if min_date.year < 10000: + if min_date.year < 3000: ans.pubdate = min_date # Identifiers @@ -183,7 +185,7 @@ class ISBNMerge(object): # Merge any other fields with no special handling (random merge) touched_fields = set() for r in results: - touched_fields |= r.plugin.touched_fields + touched_fields |= r.identify_plugin.touched_fields for f in touched_fields: if f.startswith('identifier:') or not ans.is_null(f): @@ -208,9 +210,9 @@ def merge_identify_results(result_map, log): # }}} -def identify(log, abort, title=None, authors=None, identifiers=[], timeout=30): +def identify(log, abort, title=None, authors=None, identifiers={}, timeout=30): start_time = time.time() - plugins = list(metadata_plugins['identify']) + plugins = list(metadata_plugins(['identify'])) kwargs = { 'title': title, @@ -222,14 +224,17 @@ def identify(log, abort, title=None, authors=None, identifiers=[], timeout=30): log('Running identify query with parameters:') log(kwargs) log('Using plugins:', ', '.join([p.name for p in plugins])) - log('The log (if any) from individual plugins is below') + log('The log from individual plugins is below') workers = [Worker(p, kwargs, abort) for p in plugins] for w in workers: w.start() first_result_at = None - results = dict.fromkeys(plugins, []) + results = {} + for p in plugins: + results[p] = [] + logs = dict([(w.plugin, w.buf) for w in workers]) def get_results(): found = False @@ -253,34 +258,50 @@ def identify(log, abort, title=None, authors=None, identifiers=[], timeout=30): if not is_worker_alive(workers): break - if (first_result_at is not None and time.time() - first_result_at < + if (first_result_at is not None and time.time() - first_result_at > wait_time): log('Not waiting any longer for more results') abort.set() break - get_results() + while not abort.is_set() and get_results(): + pass + sort_kwargs = dict(kwargs) for k in list(sort_kwargs.iterkeys()): if k not in ('title', 'authors', 'identifiers'): sort_kwargs.pop(k) - for plugin, results in results.iteritems(): - results.sort(key=plugin.identify_results_keygen(**sort_kwargs)) - plog = plugin.buf.getvalue().strip() + longest, lp = -1, '' + for plugin, presults in results.iteritems(): + presults.sort(key=plugin.identify_results_keygen(**sort_kwargs)) + plog = logs[plugin].getvalue().strip() + log('\n'+'*'*30, plugin.name, '*'*30) + log('Request extra headers:', plugin.browser.addheaders) + log('Found %d results'%len(presults)) + time_spent = getattr(plugin, 'dl_time_spent', None) + if time_spent is None: + log('Downloading was aborted') + longest, lp = -1, plugin.name + else: + log('Downloading from', plugin.name, 'took', time_spent) + if time_spent > longest: + longest, lp = time_spent, plugin.name + for r in presults: + log('\n\n---') + log(unicode(r)) if plog: - log('\n'+'*'*35, plugin.name, '*'*35) - log('Found %d results'%len(results)) log(plog) - log('\n'+'*'*80) + log('\n'+'*'*80) - for i, result in enumerate(results): + for i, result in enumerate(presults): result.relevance_in_source = i result.has_cached_cover_url = \ plugin.get_cached_cover_url(result.identifiers) is not None result.identify_plugin = plugin log('The identify phase took %.2f seconds'%(time.time() - start_time)) + log('The longest time (%f) was taken by:'%longest, lp) log('Merging results from different sources and finding earliest', 'publication dates') start_time = time.time() @@ -295,10 +316,10 @@ def identify(log, abort, title=None, authors=None, identifiers=[], timeout=30): dummy = Metadata(_('Unknown')) max_tags = msprefs['max_tags'] - for f in msprefs['ignore_fields']: - for r in results: + for r in results: + for f in msprefs['ignore_fields']: setattr(r, f, getattr(dummy, f)) - r.tags = r.tags[:max_tags] + r.tags = r.tags[:max_tags] return results @@ -307,8 +328,7 @@ if __name__ == '__main__': # tests {{{ # src/calibre/ebooks/metadata/sources/identify.py from calibre.ebooks.metadata.sources.test import (test_identify, title_test, authors_test) - test_identify( - [ + tests = [ ( # An e-book ISBN not on Amazon, one of the authors is # unknown to Amazon @@ -319,10 +339,10 @@ if __name__ == '__main__': # tests {{{ ), - ( # This isbn not on amazon - {'identifiers':{'isbn': '8324616489'}, 'title':'Learning Python', + ( # Test absence of identifiers + {'title':'Learning Python', 'authors':['Lutz']}, - [title_test('Learning Python, 3rd Edition', + [title_test('Learning Python', exact=True), authors_test(['Mark Lutz']) ] @@ -330,14 +350,14 @@ if __name__ == '__main__': # tests {{{ ( # Sophisticated comment formatting {'identifiers':{'isbn': '9781416580829'}}, - [title_test('Angels & Demons - Movie Tie-In: A Novel', + [title_test('Angels & Demons', exact=True), authors_test(['Dan Brown'])] ), ( # No specific problems {'identifiers':{'isbn': '0743273567'}}, [title_test('The great gatsby', exact=True), - authors_test(['F. Scott Fitzgerald'])] + authors_test(['Francis Scott Fitzgerald'])] ), ( # A newer book @@ -347,6 +367,8 @@ if __name__ == '__main__': # tests {{{ ), - ]) + ] + #test_identify(tests[1:2]) + test_identify(tests) # }}} diff --git a/src/calibre/ebooks/metadata/sources/isbndb.py b/src/calibre/ebooks/metadata/sources/isbndb.py new file mode 100644 index 0000000000..3cd9d96c81 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/isbndb.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from calibre.ebooks.metadata.sources.base import Source + +class ISBNDB(Source): + + name = 'ISBNDB' + description = _('Downloads metadata from isbndb.com') + + capabilities = frozenset(['identify']) + touched_fields = frozenset(['title', 'authors', + 'identifier:isbn', 'comments', 'publisher']) + supports_gzip_transfer_encoding = True + + def __init__(self, *args, **kwargs): + Source.__init__(self, *args, **kwargs) + + prefs = self.prefs + prefs.defaults['key_migrated'] = False + prefs.defaults['isbndb_key'] = None + + if not prefs['key_migrated']: + prefs['key_migrated'] = True + try: + from calibre.customize.ui import config + key = config['plugin_customization']['IsbnDB'] + prefs['isbndb_key'] = key + except: + pass + + self.isbndb_key = prefs['isbndb_key'] + + diff --git a/src/calibre/ebooks/metadata/sources/test.py b/src/calibre/ebooks/metadata/sources/test.py index a7dcc2fa14..2e72f86c47 100644 --- a/src/calibre/ebooks/metadata/sources/test.py +++ b/src/calibre/ebooks/metadata/sources/test.py @@ -64,10 +64,14 @@ def test_identify(tests): # {{{ from calibre.ebooks.metadata.sources.identify import identify tdir, lf, log, abort = init_test('Full Identify') + prints('Log saved to', lf) times = [] for kwargs, test_funcs in tests: + log('#'*80) + log('### Running test with:', kwargs) + log('#'*80) prints('Running test with:', kwargs) args = (log, abort) start_time = time.time() @@ -107,10 +111,11 @@ def test_identify(tests): # {{{ prints('Most relevant result failed the tests') raise SystemExit(1) + log('\n\n') + prints('Average time per query', sum(times)/len(times)) - if os.stat(lf).st_size > 10: - prints('There were some errors/warnings, see log', lf) + prints('Full log is at:', lf) # }}} @@ -129,6 +134,7 @@ def test_identify_plugin(name, tests): # {{{ plugin = x break prints('Testing the identify function of', plugin.name) + prints('Using extra headers:', plugin.browser.addheaders) tdir, lf, log, abort = init_test(plugin.name) prints('Log saved to', lf) diff --git a/src/calibre/ebooks/metadata/xisbn.py b/src/calibre/ebooks/metadata/xisbn.py index 69cc3f7cb3..56156c034e 100644 --- a/src/calibre/ebooks/metadata/xisbn.py +++ b/src/calibre/ebooks/metadata/xisbn.py @@ -73,7 +73,11 @@ class xISBN(object): def get_isbn_pool(self, isbn): data = self.get_data(isbn) - isbns = frozenset([x.get('isbn') for x in data if 'isbn' in x]) + raw = tuple(x.get('isbn') for x in data if 'isbn' in x) + isbns = [] + for x in raw: + isbns += x + isbns = frozenset(isbns) min_year = 100000 for x in data: try: diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index fccaad8811..5f4c47cdf3 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -282,8 +282,8 @@ class Serializer(object): buffer.write('="') self.serialize_text(val, quot=True) buffer.write('"') + buffer.write('>') if elem.text or len(elem) > 0: - buffer.write('>') if elem.text: self.anchor_offset = None self.serialize_text(elem.text) @@ -292,9 +292,7 @@ class Serializer(object): if child.tail: self.anchor_offset = None self.serialize_text(child.tail) - buffer.write('</%s>' % tag) - else: - buffer.write('/>') + buffer.write('</%s>' % tag) def serialize_text(self, text, quot=False): text = text.replace('&', '&') diff --git a/src/calibre/ebooks/txt/markdownml.py b/src/calibre/ebooks/txt/markdownml.py index c179378049..fe76757eab 100644 --- a/src/calibre/ebooks/txt/markdownml.py +++ b/src/calibre/ebooks/txt/markdownml.py @@ -37,7 +37,7 @@ class MarkdownMLizer(object): if not self.opts.keep_links: html = re.sub(r'<\s*/*\s*a[^>]*>', '', html) if not self.opts.keep_image_references: - html = re.sub(r'<\s*img[^>]*>', '', html)\ + html = re.sub(r'<\s*img[^>]*>', '', html) text = html2text(html) diff --git a/src/calibre/ebooks/txt/output.py b/src/calibre/ebooks/txt/output.py index d021cbbba6..4e54a97b45 100644 --- a/src/calibre/ebooks/txt/output.py +++ b/src/calibre/ebooks/txt/output.py @@ -11,7 +11,7 @@ from lxml import etree from calibre.customize.conversion import OutputFormatPlugin, \ OptionRecommendation -from calibre.ebooks.oeb.base import OEB_IMAGES +from calibre.ebooks.oeb.base import OEB_IMAGES from calibre.ebooks.txt.txtml import TXTMLizer from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines from calibre.ptempfile import TemporaryDirectory, TemporaryFile diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 635a037482..bab9073588 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' @@ -7,10 +9,10 @@ __docformat__ = 'restructuredtext en' import textwrap, re, os -from PyQt4.Qt import Qt, QDateEdit, QDate, \ - QIcon, QToolButton, QWidget, QLabel, QGridLayout, \ - QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \ - QPushButton, QSpinBox, QLineEdit +from PyQt4.Qt import (Qt, QDateEdit, QDate, + QIcon, QToolButton, QWidget, QLabel, QGridLayout, + QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, + QPushButton, QSpinBox, QLineEdit, QSizePolicy) from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox @@ -22,7 +24,7 @@ from calibre.ebooks.metadata.meta import get_metadata from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \ choose_files, error_dialog, choose_images, question_dialog from calibre.utils.date import local_tz, qt_to_dt -from calibre import strftime +from calibre import strftime, fit_image from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import run_plugins_on_import from calibre.utils.date import utcfromtimestamp @@ -480,6 +482,7 @@ class FormatsManager(QWidget): # {{{ def initialize(self, db, id_): self.changed = False + self.formats.clear() exts = db.formats(id_, index_is_id=True) self.original_val = set([]) if exts: @@ -638,6 +641,23 @@ class Cover(ImageView): # {{{ self.trim_cover_button, self.download_cover_button, self.generate_cover_button] + self.frame_size = (300, 400) + self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, + QSizePolicy.Preferred)) + + def frame_resized(self, ev): + sz = ev.size() + self.frame_size = (sz.width()//3, sz.height()) + + def sizeHint(self): + sz = ImageView.sizeHint(self) + w, h = sz.width(), sz.height() + resized, nw, nh = fit_image(w, h, self.frame_size[0], + self.frame_size[1]) + if resized: + sz = QSize(nw, nh) + return sz + def select_cover(self, *args): files = choose_images(self, 'change cover dialog', _('Choose cover for ') + @@ -882,8 +902,11 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ # }}} -class ISBNEdit(QLineEdit): # {{{ - LABEL = _('IS&BN:') +class IdentifiersEdit(QLineEdit): # {{{ + LABEL = _('I&ds:') + BASE_TT = _('Edit the identifiers for this book. ' + 'For example: \n\n%s')%( + 'isbn:1565927249, doi:10.1000/182, amazon:1565927249') def __init__(self, parent): QLineEdit.__init__(self, parent) @@ -893,32 +916,44 @@ class ISBNEdit(QLineEdit): # {{{ @dynamic_property def current_val(self): def fget(self): - return self.pat.sub('', unicode(self.text()).strip()) + raw = unicode(self.text()).strip() + parts = [x.strip() for x in raw.split(',')] + ans = {} + for x in parts: + c = x.split(':') + if len(c) == 2: + ans[c[0]] = c[1] + return ans def fset(self, val): if not val: - val = '' - self.setText(val.strip()) + val = {} + txt = ', '.join(['%s:%s'%(k, v) for k, v in val.iteritems()]) + self.setText(txt.strip()) return property(fget=fget, fset=fset) def initialize(self, db, id_): - self.current_val = db.isbn(id_, index_is_id=True) + self.current_val = db.get_identifiers(id_, index_is_id=True) self.original_val = self.current_val def commit(self, db, id_): - db.set_isbn(id_, self.current_val, notify=False, commit=False) + if self.original_val != self.current_val: + db.set_identifiers(id_, self.current_val, notify=False, commit=False) return True def validate(self, *args): - isbn = self.current_val - tt = _('This ISBN number is valid') + identifiers = self.current_val + isbn = identifiers.get('isbn', '') + tt = self.BASE_TT + extra = '' if not isbn: col = 'rgba(0,255,0,0%)' elif check_isbn(isbn) is not None: col = 'rgba(0,255,0,20%)' + extra = '\n\n'+_('This ISBN number is valid') else: col = 'rgba(255,0,0,20%)' - tt = _('This ISBN number is invalid') - self.setToolTip(tt) + extra = '\n\n' + _('This ISBN number is invalid') + self.setToolTip(tt+extra) self.setStyleSheet('QLineEdit { background-color: %s }'%col) # }}} diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 5b17b454e7..70307eb3b1 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' @@ -8,17 +10,17 @@ __docformat__ = 'restructuredtext en' import os from functools import partial -from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \ - QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \ - QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, \ - QSizePolicy, QPalette, QFrame, QSize, QKeySequence +from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, + QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, + QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, + QSizePolicy, QPalette, QFrame, QSize, QKeySequence) from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.gui2 import ResizableDialog, error_dialog, gprefs -from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \ - AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \ - RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \ - BuddyLabel, DateEdit, PubdateEdit +from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit, + AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit, + RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, + BuddyLabel, DateEdit, PubdateEdit) from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.utils.config import tweaks @@ -145,8 +147,8 @@ class MetadataSingleDialogBase(ResizableDialog): self.tags_editor_button.clicked.connect(self.tags_editor) self.basic_metadata_widgets.append(self.tags) - self.isbn = ISBNEdit(self) - self.basic_metadata_widgets.append(self.isbn) + self.identifiers = IdentifiersEdit(self) + self.basic_metadata_widgets.append(self.identifiers) self.publisher = PublisherEdit(self) self.basic_metadata_widgets.append(self.publisher) @@ -280,8 +282,8 @@ class MetadataSingleDialogBase(ResizableDialog): self.publisher.current_val = mi.publisher if not mi.is_null('tags'): self.tags.current_val = mi.tags - if not mi.is_null('isbn'): - self.isbn.current_val = mi.isbn + if not mi.is_null('identifiers'): + self.identifiers.current_val = mi.identifiers if not mi.is_null('pubdate'): self.pubdate.current_val = mi.pubdate if not mi.is_null('series') and mi.series.strip(): @@ -385,6 +387,14 @@ class MetadataSingleDialogBase(ResizableDialog): disconnect(x.clicked) # }}} +class Splitter(QSplitter): + + frame_resized = pyqtSignal(object) + + def resizeEvent(self, ev): + self.frame_resized.emit(ev) + return QSplitter.resizeEvent(self, ev) + class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ def do_layout(self): @@ -437,8 +447,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ tl.addWidget(self.formats_manager, 0, 6, 3, 1) - self.splitter = QSplitter(Qt.Horizontal, self) + self.splitter = Splitter(Qt.Horizontal, self) self.splitter.addWidget(self.cover) + self.splitter.frame_resized.connect(self.cover.frame_resized) l.addWidget(self.splitter) self.tabs[0].gb = gb = QGroupBox(_('Change cover'), self) gb.l = l = QGridLayout() @@ -475,9 +486,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ create_row2(1, self.rating) sto(self.rating, self.tags) create_row2(2, self.tags, self.tags_editor_button) - sto(self.tags_editor_button, self.isbn) - create_row2(3, self.isbn) - sto(self.isbn, self.timestamp) + sto(self.tags_editor_button, self.identifiers) + create_row2(3, self.identifiers) + sto(self.identifiers, self.timestamp) create_row2(4, self.timestamp, self.timestamp.clear_button) sto(self.timestamp.clear_button, self.pubdate) create_row2(5, self.pubdate, self.pubdate.clear_button) @@ -562,9 +573,9 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.timestamp) - create_row(10, self.timestamp, self.isbn, + create_row(10, self.timestamp, self.identifiers, button=self.timestamp.clear_button, icon='trash.png') - create_row(11, self.isbn, self.comments) + create_row(11, self.identifiers, self.comments) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), 12, 1, 1 ,1) @@ -580,7 +591,7 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ sr.setWidget(w) gbl.addWidget(sr) self.tabs[0].l.addWidget(gb, 0, 1, 1, 1) - sto(self.isbn, gb) + sto(self.identifiers, gb) w = QGroupBox(_('&Comments'), tab0) sp = QSizePolicy() diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index e5f1c94342..ea0d2570e5 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -312,6 +312,7 @@ class ImageView(QWidget, ImageDropMixin): p.setPen(pen) if self.draw_border: p.drawRect(target) + #p.drawRect(self.rect()) p.end() class CoverView(QGraphicsView, ImageDropMixin):