From 571f50be431f69504a0f72366be298ec84e5dc90 Mon Sep 17 00:00:00 2001
From: Kovid Goyal '+ans%('', '')
-
- # }}}
-
-class Amazon(MetadataSource): # {{{
-
- name = 'Amazon'
- metadata_type = 'social'
- description = _('Downloads social metadata from amazon.com')
-
- has_html_comments = True
-
- def fetch(self):
- if not self.isbn:
- return
- from calibre.ebooks.metadata.amazon import get_social_metadata
- try:
- self.results = get_social_metadata(self.title, self.book_author,
- self.publisher, self.isbn)
- except Exception as e:
- self.exception = e
- self.tb = traceback.format_exc()
-
- # }}}
-
-class KentDistrictLibrary(MetadataSource): # {{{
-
- name = 'Kent District Library'
- metadata_type = 'social'
- description = _('Downloads series information from ww2.kdl.org. '
- 'This website cannot handle large numbers of queries, '
- 'so the plugin is disabled by default.')
-
- def fetch(self):
- if not self.title or not self.book_author:
- return
- from calibre.ebooks.metadata.kdl import get_series
- try:
- self.results = get_series(self.title, self.book_author)
- except Exception as e:
- import traceback
- traceback.print_exc()
- self.exception = e
- self.tb = traceback.format_exc()
-
- # }}}
-
-
-def result_index(source, result):
- if not result.isbn:
- return -1
- for i, x in enumerate(source):
- if x.isbn == result.isbn:
- return i
- return -1
-
-def merge_results(one, two):
- if two is not None and one is not None:
- for x in two:
- idx = result_index(one, x)
- if idx < 0:
- one.append(x)
- else:
- one[idx].smart_update(x)
-
-class MetadataSources(object):
-
- def __init__(self, sources):
- self.sources = sources
-
- def __enter__(self):
- for s in self.sources:
- s.__enter__()
- return self
-
- def __exit__(self, *args):
- for s in self.sources:
- s.__exit__()
-
- def __call__(self, *args, **kwargs):
- for s in self.sources:
- s(*args, **kwargs)
-
- def join(self):
- for s in self.sources:
- s.join()
-
-def filter_metadata_results(item):
- keywords = ["audio", "tape", "cassette", "abridged", "playaway"]
- for keyword in keywords:
- if item.publisher and keyword in item.publisher.lower():
- return False
- return True
-
-def do_cover_check(item):
- item.has_cover = False
- try:
- item.has_cover = check_for_cover(item)
- except:
- pass # Cover not found
-
-def check_for_covers(items):
- threads = [Thread(target=do_cover_check, args=(item,)) for item in items]
- for t in threads: t.start()
- for t in threads: t.join()
-
-def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
- verbose=0):
- assert not(title is None and author is None and publisher is None and \
- isbn is None)
- from calibre.customize.ui import metadata_sources, migrate_isbndb_key
- migrate_isbndb_key()
- if isbn is not None:
- isbn = re.sub(r'[^a-zA-Z0-9]', '', isbn).upper()
- fetchers = list(metadata_sources(isbndb_key=isbndb_key))
- with MetadataSources(fetchers) as manager:
- manager(title, author, publisher, isbn, verbose)
- manager.join()
-
- results = list(fetchers[0].results) if fetchers else []
- for fetcher in fetchers[1:]:
- merge_results(results, fetcher.results)
-
- results = list(filter(filter_metadata_results, results))
-
- check_for_covers(results)
-
- words = ("the", "a", "an", "of", "and")
- prefix_pat = re.compile(r'^(%s)\s+'%("|".join(words)))
- trailing_paren_pat = re.compile(r'\(.*\)$')
- whitespace_pat = re.compile(r'\s+')
-
- def sort_func(x, y):
-
- def cleanup_title(s):
- if s is None:
- s = _('Unknown')
- s = s.strip().lower()
- s = prefix_pat.sub(' ', s)
- s = trailing_paren_pat.sub('', s)
- s = whitespace_pat.sub(' ', s)
- return s.strip()
-
- t = cleanup_title(title)
- x_title = cleanup_title(x.title)
- y_title = cleanup_title(y.title)
-
- # prefer titles that start with the search title
- tx = cmp(t, x_title)
- ty = cmp(t, y_title)
- result = 0 if abs(tx) == abs(ty) else abs(tx) - abs(ty)
-
- # then prefer titles that have a cover image
- if result == 0:
- result = -cmp(x.has_cover, y.has_cover)
-
- # then prefer titles with the longest comment, with in 10%
- if result == 0:
- cx = len(x.comments.strip() if x.comments else '')
- cy = len(y.comments.strip() if y.comments else '')
- t = (cx + cy) / 20
- result = cy - cx
- if abs(result) < t:
- result = 0
-
- return result
-
- results = sorted(results, cmp=sort_func)
-
- # if for some reason there is no comment in the top selection, go looking for one
- if len(results) > 1:
- if not results[0].comments or len(results[0].comments) == 0:
- for r in results[1:]:
- try:
- if title and title.lower() == r.title[:len(title)].lower() \
- and r.comments and len(r.comments):
- results[0].comments = r.comments
- break
- except:
- pass
- # Find a pubdate
- pubdate = None
- for r in results:
- if r.pubdate is not None:
- pubdate = r.pubdate
- break
- if pubdate is not None:
- for r in results:
- if r.pubdate is None:
- r.pubdate = pubdate
-
- def fix_case(x):
- if x:
- x = titlecase(x)
- return x
-
- for r in results:
- r.title = fix_case(r.title)
- if r.authors:
- r.authors = list(map(fix_case, r.authors))
-
- return results, [(x.name, x.exception, x.tb) for x in fetchers]
-
-def get_social_metadata(mi, verbose=0):
- from calibre.customize.ui import metadata_sources
- fetchers = list(metadata_sources(metadata_type='social'))
- with MetadataSources(fetchers) as manager:
- manager(mi.title, mi.authors, mi.publisher, mi.isbn, verbose)
- manager.join()
- ratings, tags, comments, series, series_index = [], set([]), set([]), None, None
- for fetcher in fetchers:
- if fetcher.results:
- dmi = fetcher.results
- if dmi.rating is not None:
- ratings.append(dmi.rating)
- if dmi.tags:
- for t in dmi.tags:
- tags.add(t)
- if mi.pubdate is None and dmi.pubdate is not None:
- mi.pubdate = dmi.pubdate
- if dmi.comments:
- comments.add(dmi.comments)
- if dmi.series is not None:
- series = dmi.series
- if dmi.series_index is not None:
- series_index = dmi.series_index
- if ratings:
- rating = sum(ratings)/float(len(ratings))
- if mi.rating is None or mi.rating < 0.1:
- mi.rating = rating
- else:
- mi.rating = (mi.rating + rating)/2.0
- if tags:
- if not mi.tags:
- mi.tags = []
- mi.tags += list(tags)
- mi.tags = list(sorted(list(set(mi.tags))))
- if comments:
- if not mi.comments or len(mi.comments)+20 < len(' '.join(comments)):
- mi.comments = ''
- for x in comments:
- mi.comments += x+'\n\n'
- if series and series_index is not None:
- mi.series = series
- mi.series_index = series_index
-
- return [(x.name, x.exception, x.tb) for x in fetchers if x.exception is not
- None]
-
-
-
-def option_parser():
- parser = OptionParser(textwrap.dedent(
- '''\
- %prog [options]
-
- Fetch book metadata from online sources. You must specify at least one
- of title, author, publisher or ISBN. If you specify ISBN, the others
- are ignored.
- '''
- ))
- parser.add_option('-t', '--title', help='Book title')
- parser.add_option('-a', '--author', help='Book author(s)')
- parser.add_option('-p', '--publisher', help='Book publisher')
- parser.add_option('-i', '--isbn', help='Book ISBN')
- parser.add_option('-m', '--max-results', default=10,
- help='Maximum number of results to fetch')
- parser.add_option('-k', '--isbndb-key',
- help=('The access key for your ISBNDB.com account. '
- 'Only needed if you want to search isbndb.com '
- 'and you haven\'t customized the IsbnDB plugin.'))
- parser.add_option('-v', '--verbose', default=0, action='count',
- help='Be more verbose about errors')
- return parser
-
-def main(args=sys.argv):
- parser = option_parser()
- opts, args = parser.parse_args(args)
- results, exceptions = search(opts.title, opts.author, opts.publisher,
- opts.isbn, opts.isbndb_key, opts.verbose)
- social_exceptions = []
- for result in results:
- social_exceptions.extend(get_social_metadata(result, opts.verbose))
- prints(unicode(result))
- print
-
- for name, exception, tb in exceptions+social_exceptions:
- if exception is not None:
- print 'WARNING: Fetching from', name, 'failed with error:'
- print exception
- print tb
-
- return 0
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/src/calibre/ebooks/metadata/fictionwise.py b/src/calibre/ebooks/metadata/fictionwise.py
deleted file mode 100644
index 145e39768d..0000000000
--- a/src/calibre/ebooks/metadata/fictionwise.py
+++ /dev/null
@@ -1,390 +0,0 @@
-from __future__ import with_statement
-__license__ = 'GPL 3'
-__copyright__ = '2010, sengian
]+>)*(?P
]*>.{,15}publisher\s*:', re.I)
- self.repub = re.compile(r'.*publisher\s*:\s*', re.I)
- self.redate = re.compile(r'.*release\s*date\s*:\s*', re.I)
- self.retag = re.compile(r'.*book\s*category\s*:\s*', re.I)
- self.resplitbr = re.compile(r'
]*>', re.I)
- self.recomment = re.compile(r'(?s)')
- self.reimg = re.compile(r']*>', re.I)
- self.resanitize = re.compile(r'\[HTML_REMOVED\]\s*', re.I)
- self.renbcom = re.compile('(?P
]+>|?div[^>]*>)', re.I)
- self.reisbn = re.compile(r'.*ISBN\s*:\s*', re.I)
-
- def strip_tags_etree(self, etreeobj, invalid_tags):
- for (itag, rmv) in invalid_tags.iteritems():
- if rmv:
- for elts in etreeobj.getiterator(itag):
- elts.drop_tree()
- else:
- for elts in etreeobj.getiterator(itag):
- elts.drop_tag()
-
- def clean_entry(self, entry, invalid_tags = {'script': True},
- invalid_id = (), invalid_class=(), invalid_xpath = ()):
- #invalid_tags: remove tag and keep content if False else remove
- #remove tags
- if invalid_tags:
- self.strip_tags_etree(entry, invalid_tags)
- #remove xpath
- if invalid_xpath:
- for eltid in invalid_xpath:
- elt = entry.xpath(eltid)
- for el in elt:
- el.drop_tree()
- #remove id
- if invalid_id:
- for eltid in invalid_id:
- elt = entry.get_element_by_id(eltid)
- if elt is not None:
- elt.drop_tree()
- #remove class
- if invalid_class:
- for eltclass in invalid_class:
- elts = entry.find_class(eltclass)
- if elts is not None:
- for elt in elts:
- elt.drop_tree()
-
- def output_entry(self, entry, prettyout = True, htmlrm="\d+"):
- out = tostring(entry, pretty_print=prettyout)
- #try to work around tostring to remove this encoding for exemle
- reclean = re.compile('(\n+|\t+|\r+|'+htmlrm+';)')
- return reclean.sub('', out)
-
- def get_title(self, entry):
- title = entry.findtext('./')
- return self.retitle.sub('', title).strip()
-
- def get_authors(self, entry):
- authortext = entry.find('./br').tail
- if not self.rechkauth.search(authortext):
- return []
- authortext = self.rechkauth.sub('', authortext)
- return [a.strip() for a in authortext.split('&')]
-
- def get_rating(self, entrytable, verbose):
- nbcomment = tostring(entrytable.getprevious())
- try:
- nbcomment = self.renbcom.search(nbcomment).group("nbcom")
- except:
- report(verbose)
- return None
- hval = dict((self.COLOR_VALUES[self.recolor.search(image.get('src', default='NA.gif')).group("ncolor")],
- float(image.get('height', default=0))) \
- for image in entrytable.getiterator('img'))
- #ratings as x/5
- return float(1.25*sum(k*v for (k, v) in hval.iteritems())/sum(hval.itervalues()))
-
- def get_description(self, entry):
- description = self.output_entry(entry.xpath('./p')[1],htmlrm="")
- description = self.redesc.search(description)
- if not description or not description.group("desc"):
- return None
- #remove invalid tags
- description = self.reimg.sub('', description.group("desc"))
- description = self.recomment.sub('', description)
- description = self.resanitize.sub('', sanitize_comments_html(description))
- return _('SUMMARY:\n %s') % re.sub(r'\n\s+
'+_('Could not download metadata and/or covers for %d of the books. Click'
- ' "Show details" to see which books.')%len(failed_ids)
+ ' "Show details" to see which books.')%num
payload = (id_map, failed_ids, failed_covers)
from calibre.gui2.dialogs.message_box import ProceedNotification
@@ -158,49 +143,6 @@ class EditMetadataAction(InterfaceAction):
self.apply_metadata_changes(id_map)
- def download_metadata_old(self, checked, covers=True, set_metadata=True,
- set_social_metadata=None):
- rows = self.gui.library_view.selectionModel().selectedRows()
- if not rows or len(rows) == 0:
- d = error_dialog(self.gui, _('Cannot download metadata'),
- _('No books selected'))
- d.exec_()
- return
- db = self.gui.library_view.model().db
- ids = [db.id(row.row()) for row in rows]
- self.do_download_metadata(ids, covers=covers,
- set_metadata=set_metadata,
- set_social_metadata=set_social_metadata)
-
- def do_download_metadata(self, ids, covers=True, set_metadata=True,
- set_social_metadata=None):
- m = self.gui.library_view.model()
- db = m.db
- if set_social_metadata is None:
- get_social_metadata = config['get_social_metadata']
- else:
- get_social_metadata = set_social_metadata
- from calibre.gui2.metadata.bulk_download import DoDownload
- if set_social_metadata is not None and set_social_metadata:
- x = _('social metadata')
- else:
- x = _('covers') if covers and not set_metadata else _('metadata')
- title = _('Downloading {0} for {1} book(s)').format(x, len(ids))
- self._download_book_metadata = DoDownload(self.gui, title, db, ids,
- get_covers=covers, set_metadata=set_metadata,
- get_social_metadata=get_social_metadata)
- m.stop_metadata_backup()
- try:
- self._download_book_metadata.exec_()
- finally:
- m.start_metadata_backup()
- cr = self.gui.library_view.currentIndex().row()
- x = self._download_book_metadata
- if x.updated:
- self.gui.library_view.model().refresh_ids(
- x.updated, cr)
- if self.gui.cover_flow:
- self.gui.cover_flow.dataChanged()
# }}}
def edit_metadata(self, checked, bulk=None):
@@ -227,9 +169,7 @@ class EditMetadataAction(InterfaceAction):
list(range(self.gui.library_view.model().rowCount(QModelIndex())))
current_row = row_list.index(cr)
- func = (self.do_edit_metadata if test_eight_code else
- self.do_edit_metadata_old)
- changed, rows_to_refresh = func(row_list, current_row)
+ changed, rows_to_refresh = self.do_edit_metadata(row_list, current_row)
m = self.gui.library_view.model()
@@ -244,36 +184,6 @@ class EditMetadataAction(InterfaceAction):
m.current_changed(current, previous)
self.gui.tags_view.recount()
- def do_edit_metadata_old(self, row_list, current_row):
- changed = set([])
- db = self.gui.library_view.model().db
-
- while True:
- prev = next_ = None
- if current_row > 0:
- prev = db.title(row_list[current_row-1])
- if current_row < len(row_list) - 1:
- next_ = db.title(row_list[current_row+1])
-
- d = MetadataSingleDialog(self.gui, row_list[current_row], db,
- prev=prev, next_=next_)
- d.view_format.connect(lambda
- fmt:self.gui.iactions['View'].view_format(row_list[current_row],
- fmt))
- ret = d.exec_()
- d.break_cycles()
- if ret != d.Accepted:
- break
-
- changed.add(d.id)
- self.gui.library_view.model().refresh_ids(list(d.books_to_refresh))
- if d.row_delta == 0:
- break
- current_row += d.row_delta
- self.gui.library_view.set_current_row(current_row)
- self.gui.library_view.scroll_to_row(current_row)
- return changed, set()
-
def do_edit_metadata(self, row_list, current_row):
from calibre.gui2.metadata.single import edit_metadata
db = self.gui.library_view.model().db
diff --git a/src/calibre/gui2/dialogs/fetch_metadata.py b/src/calibre/gui2/dialogs/fetch_metadata.py
deleted file mode 100644
index 426c7b1d60..0000000000
--- a/src/calibre/gui2/dialogs/fetch_metadata.py
+++ /dev/null
@@ -1,271 +0,0 @@
-__license__ = 'GPL v3'
-__copyright__ = '2008, Kovid Goyal '+ _('Warning')+':'+\
- _('Could not fetch metadata from:')+\
- ' There was an error reading from file: ' + textwrap.fill(base+' '+textwrap.fill(base + ' ' + textwrap.fill(base+' '+textwrap.fill(base + ' '+_('The download of metadata for the %d selected book(s) will'
+ ' run in the background. Proceed?')%len(ids) +
+ ' '+_('You can monitor the progress of the download '
+ 'by clicking the rotating spinner in the bottom right '
+ 'corner.') +
+ ' '+_('When the download completes you will be asked for'
+ ' confirmation before calibre applies the downloaded metadata.')
+ )
+ t.setWordWrap(True)
+ l.addWidget(t, 0, 1)
+ l.setColumnStretch(0, 1)
+ l.setColumnStretch(1, 100)
+
+ self.identify = self.covers = True
+ self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
+ self.bb.rejected.connect(self.reject)
+ b = self.bb.addButton(_('Download only &metadata'),
+ self.bb.AcceptRole)
+ b.clicked.connect(self.only_metadata)
+ b.setIcon(QIcon(I('edit_input.png')))
+ b = self.bb.addButton(_('Download only &covers'),
+ self.bb.AcceptRole)
+ b.clicked.connect(self.only_covers)
+ b.setIcon(QIcon(I('default_cover.png')))
+ b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole)
+ b.setIcon(QIcon(I('config.png')))
+ b.clicked.connect(partial(show_config, parent, self))
+ l.addWidget(self.bb, 1, 0, 1, 2)
+ b = self.bb.addButton(_('Download &both'),
+ self.bb.AcceptRole)
+ b.clicked.connect(self.accept)
+ b.setDefault(True)
+ b.setAutoDefault(True)
+ b.setIcon(QIcon(I('ok.png')))
+
+ self.resize(self.sizeHint())
+ b.setFocus(Qt.OtherFocusReason)
+
+ def only_metadata(self):
+ self.covers = False
+ self.accept()
+
+ def only_covers(self):
+ self.identify = False
+ self.accept()
+
+def start_download(gui, ids, callback):
+ d = ConfirmDialog(ids, gui)
+ ret = d.exec_()
+ d.b.clicked.disconnect()
+ if ret != d.Accepted:
+ return
+
+ job = ThreadedJob('metadata bulk download',
+ _('Download metadata for %d books')%len(ids),
+ download, (ids, gui.current_db, d.identify, d.covers), {}, callback)
+ gui.job_manager.run_threaded_job(job)
+ gui.status_bar.show_message(_('Metadata download started'), 3000)
+# }}}
+
+def get_job_details(job):
+ id_map, failed_ids, failed_covers, title_map, all_failed = job.result
+ det_msg = []
+ for i in failed_ids | failed_covers:
+ title = title_map[i]
+ if i in failed_ids:
+ title += (' ' + _('(Failed metadata)'))
+ if i in failed_covers:
+ title += (' ' + _('(Failed cover)'))
+ det_msg.append(title)
+ det_msg = '\n'.join(det_msg)
+ return id_map, failed_ids, failed_covers, all_failed, det_msg
+
+def merge_result(oldmi, newmi):
+ dummy = Metadata(_('Unknown'))
+ for f in msprefs['ignore_fields']:
+ if ':' not in f:
+ setattr(newmi, f, getattr(dummy, f))
+ fields = set()
+ for plugin in metadata_plugins(['identify']):
+ fields |= plugin.touched_fields
+
+ for f in fields:
+ # Optimize so that set_metadata does not have to do extra work later
+ if not f.startswith('identifier:'):
+ if (not newmi.is_null(f) and getattr(newmi, f) == getattr(oldmi, f)):
+ setattr(newmi, f, getattr(dummy, f))
+
+ newmi.last_modified = oldmi.last_modified
+
+ return newmi
+
+def download(ids, db, do_identify, covers,
+ log=None, abort=None, notifications=None):
+ ids = list(ids)
+ metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
+ for i in ids]
+ failed_ids = set()
+ failed_covers = set()
+ title_map = {}
+ ans = {}
+ count = 0
+ all_failed = True
+ '''
+ # Test apply dialog
+ all_failed = do_identify = covers = False
+ '''
+ for i, mi in izip(ids, metadata):
+ if abort.is_set():
+ log.error('Aborting...')
+ break
+ title, authors, identifiers = mi.title, mi.authors, mi.identifiers
+ title_map[i] = title
+ if do_identify:
+ results = []
try:
- cdata, errors = download_cover(mi)
- if cdata:
- self.results.put((id, mi, True, cdata))
- else:
- msg = []
- for e in errors:
- if not e[0]:
- msg.append(e[-1] + ' - ' + e[1])
- self.results.put((id, mi, False, '\n'.join(msg)))
+ results = identify(log, Event(), title=title, authors=authors,
+ identifiers=identifiers)
except:
- self.results.put((id, mi, False, traceback.format_exc()))
-
- def __enter__(self):
- self.start()
- return self
-
- def __exit__(self, *args):
- self.jobs.put((False, False))
-
-
-class DownloadMetadata(Thread):
- 'Metadata downloader'
-
- def __init__(self, db, ids, get_covers, set_metadata=True,
- get_social_metadata=True):
- Thread.__init__(self)
- self.daemon = True
- self.metadata = {}
- self.covers = {}
- self.set_metadata = set_metadata
- self.get_social_metadata = get_social_metadata
- self.social_metadata_exceptions = []
- self.db = db
- self.updated = set([])
- self.get_covers = get_covers
- self.worker = Worker()
- self.results = Queue()
- self.keep_going = True
- for id in ids:
- self.metadata[id] = db.get_metadata(id, index_is_id=True)
- self.metadata[id].rating = None
- self.total = len(ids)
- if self.get_covers:
- self.total += len(ids)
- self.fetched_metadata = {}
- self.fetched_covers = {}
- self.failures = {}
- self.cover_failures = {}
- self.exception = self.tb = None
-
- def run(self):
- try:
- self._run()
- except Exception as e:
- self.exception = e
- self.tb = traceback.format_exc()
-
- def _run(self):
- self.key = get_isbndb_key()
- if not self.key:
- self.key = None
- with self.worker:
- for id, mi in self.metadata.items():
- if not self.keep_going:
- break
- args = {}
- if mi.isbn:
- args['isbn'] = mi.isbn
- else:
- if mi.is_null('title'):
- self.failures[id] = \
- _('Book has neither title nor ISBN')
- continue
- args['title'] = mi.title
- if mi.authors and mi.authors[0] != _('Unknown'):
- args['author'] = mi.authors[0]
- if self.key:
- args['isbndb_key'] = self.key
- results, exceptions = search(**args)
- if results:
- fmi = results[0]
- self.fetched_metadata[id] = fmi
- if self.get_covers:
- if fmi.isbn:
- self.worker.jobs.put((id, fmi))
- else:
- self.results.put((id, 'cover', False, mi.title))
- if (not config['overwrite_author_title_metadata']):
- fmi.authors = mi.authors
- fmi.author_sort = mi.author_sort
- fmi.title = mi.title
- mi.smart_update(fmi)
- if mi.isbn and self.get_social_metadata:
- self.social_metadata_exceptions = get_social_metadata(mi)
- if mi.rating:
- mi.rating *= 2
- if not self.get_social_metadata:
- mi.tags = []
- self.results.put((id, 'metadata', True, mi.title))
- else:
- self.failures[id] = _('No matches found for this book')
- self.results.put((id, 'metadata', False, mi.title))
- self.results.put((id, 'cover', False, mi.title))
- self.commit_covers()
-
- self.commit_covers(True)
-
- def commit_covers(self, all=False):
- if all:
- self.worker.jobs.put((False, False))
- while True:
- try:
- id, fmi, ok, cdata = self.worker.results.get_nowait()
- if ok:
- self.fetched_covers[id] = cdata
- self.results.put((id, 'cover', ok, fmi.title))
- else:
- self.results.put((id, 'cover', ok, fmi.title))
- try:
- self.cover_failures[id] = unicode(cdata)
- except:
- self.cover_failures[id] = repr(cdata)
- except Empty:
- if not all or not self.worker.is_alive():
- return
-
-class DoDownload(QObject):
-
- def __init__(self, parent, title, db, ids, get_covers, set_metadata=True,
- get_social_metadata=True):
- QObject.__init__(self, parent)
- self.pd = ProgressDialog(title, min=0, max=0, parent=parent)
- self.pd.canceled_signal.connect(self.cancel)
- self.downloader = None
- self.create = partial(DownloadMetadata, db, ids, get_covers,
- set_metadata=set_metadata,
- get_social_metadata=get_social_metadata)
- self.get_covers = get_covers
- self.db = db
- self.updated = set([])
- self.total = len(ids)
- self.keep_going = True
-
- def exec_(self):
- QTimer.singleShot(50, self.do_one)
- ret = self.pd.exec_()
- if getattr(self.downloader, 'exception', None) is not None and \
- ret == self.pd.Accepted:
- error_dialog(self.parent(), _('Failed'),
- _('Failed to download metadata'), show=True)
- else:
- self.show_report()
- return ret
-
- def cancel(self, *args):
- self.keep_going = False
- self.downloader.keep_going = False
- self.pd.reject()
-
- def do_one(self):
- try:
- if not self.keep_going:
- return
- if self.downloader is None:
- self.downloader = self.create()
- self.downloader.start()
- self.pd.set_min(0)
- self.pd.set_max(self.downloader.total)
- try:
- r = self.downloader.results.get_nowait()
- self.handle_result(r)
- except Empty:
pass
- if not self.downloader.is_alive():
- while True:
- try:
- r = self.downloader.results.get_nowait()
- self.handle_result(r)
- except Empty:
- break
- self.pd.accept()
- return
- except:
- self.cancel()
- raise
- QTimer.singleShot(50, self.do_one)
-
- def handle_result(self, r):
- id_, typ, ok, title = r
- what = _('cover') if typ == 'cover' else _('metadata')
- which = _('Downloaded') if ok else _('Failed to get')
- if self.get_covers or typ != 'cover' or ok:
- # Do not show message when cover fetch fails if user didn't ask to
- # download covers
- self.pd.set_msg(_('%s %s for: %s') % (which, what, title))
- self.pd.value += 1
- if ok:
- self.updated.add(id_)
- if typ == 'cover':
- try:
- self.db.set_cover(id_,
- self.downloader.fetched_covers.pop(id_))
- except:
- self.downloader.cover_failures[id_] = \
- traceback.format_exc()
+ if results:
+ all_failed = False
+ mi = merge_result(mi, results[0])
+ identifiers = mi.identifiers
+ if not mi.is_null('rating'):
+ # set_metadata expects a rating out of 10
+ mi.rating *= 2
else:
- try:
- self.set_metadata(id_)
- except:
- self.downloader.failures[id_] = \
- traceback.format_exc()
-
- def set_metadata(self, id_):
- mi = self.downloader.metadata[id_]
- if self.downloader.set_metadata:
- self.db.set_metadata(id_, mi)
- if not self.downloader.set_metadata and self.downloader.get_social_metadata:
- if mi.rating:
- self.db.set_rating(id_, mi.rating)
- if mi.tags:
- self.db.set_tags(id_, mi.tags)
- if mi.comments:
- self.db.set_comment(id_, mi.comments)
- if mi.series:
- self.db.set_series(id_, mi.series)
- if mi.series_index is not None:
- self.db.set_series_index(id_, mi.series_index)
-
- def show_report(self):
- f, cf = self.downloader.failures, self.downloader.cover_failures
- report = []
- if f:
- report.append(
- ' '+_('The download of metadata for the %d selected book(s) will'
- ' run in the background. Proceed?')%len(ids) +
- ' '+_('You can monitor the progress of the download '
- 'by clicking the rotating spinner in the bottom right '
- 'corner.') +
- ' '+_('When the download completes you will be asked for'
- ' confirmation before calibre applies the downloaded metadata.')
- )
- t.setWordWrap(True)
- l.addWidget(t, 0, 1)
- l.setColumnStretch(0, 1)
- l.setColumnStretch(1, 100)
-
- self.identify = self.covers = True
- self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
- self.bb.rejected.connect(self.reject)
- b = self.bb.addButton(_('Download only &metadata'),
- self.bb.AcceptRole)
- b.clicked.connect(self.only_metadata)
- b.setIcon(QIcon(I('edit_input.png')))
- b = self.bb.addButton(_('Download only &covers'),
- self.bb.AcceptRole)
- b.clicked.connect(self.only_covers)
- b.setIcon(QIcon(I('default_cover.png')))
- b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole)
- b.setIcon(QIcon(I('config.png')))
- b.clicked.connect(partial(show_config, parent, self))
- l.addWidget(self.bb, 1, 0, 1, 2)
- b = self.bb.addButton(_('Download &both'),
- self.bb.AcceptRole)
- b.clicked.connect(self.accept)
- b.setDefault(True)
- b.setAutoDefault(True)
- b.setIcon(QIcon(I('ok.png')))
-
- self.resize(self.sizeHint())
- b.setFocus(Qt.OtherFocusReason)
-
- def only_metadata(self):
- self.covers = False
- self.accept()
-
- def only_covers(self):
- self.identify = False
- self.accept()
-
-def start_download(gui, ids, callback):
- d = ConfirmDialog(ids, gui)
- ret = d.exec_()
- d.b.clicked.disconnect()
- if ret != d.Accepted:
- return
-
- job = ThreadedJob('metadata bulk download',
- _('Download metadata for %d books')%len(ids),
- download, (ids, gui.current_db, d.identify, d.covers), {}, callback)
- gui.job_manager.run_threaded_job(job)
- gui.status_bar.show_message(_('Metadata download started'), 3000)
-# }}}
-
-def get_job_details(job):
- id_map, failed_ids, failed_covers, title_map, all_failed = job.result
- det_msg = []
- for i in failed_ids | failed_covers:
- title = title_map[i]
- if i in failed_ids:
- title += (' ' + _('(Failed metadata)'))
- if i in failed_covers:
- title += (' ' + _('(Failed cover)'))
- det_msg.append(title)
- det_msg = '\n'.join(det_msg)
- return id_map, failed_ids, failed_covers, all_failed, det_msg
-
-def merge_result(oldmi, newmi):
- dummy = Metadata(_('Unknown'))
- for f in msprefs['ignore_fields']:
- if ':' not in f:
- setattr(newmi, f, getattr(dummy, f))
- fields = set()
- for plugin in metadata_plugins(['identify']):
- fields |= plugin.touched_fields
-
- for f in fields:
- # Optimize so that set_metadata does not have to do extra work later
- if not f.startswith('identifier:'):
- if (not newmi.is_null(f) and getattr(newmi, f) == getattr(oldmi, f)):
- setattr(newmi, f, getattr(dummy, f))
-
- newmi.last_modified = oldmi.last_modified
-
- return newmi
-
-def download(ids, db, do_identify, covers,
- log=None, abort=None, notifications=None):
- ids = list(ids)
- metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
- for i in ids]
- failed_ids = set()
- failed_covers = set()
- title_map = {}
- ans = {}
- count = 0
- all_failed = True
- '''
- # Test apply dialog
- all_failed = do_identify = covers = False
- '''
- for i, mi in izip(ids, metadata):
- if abort.is_set():
- log.error('Aborting...')
- break
- title, authors, identifiers = mi.title, mi.authors, mi.identifiers
- title_map[i] = title
- if do_identify:
- results = []
- try:
- results = identify(log, Event(), title=title, authors=authors,
- identifiers=identifiers)
- except:
- pass
- if results:
- all_failed = False
- mi = merge_result(mi, results[0])
- identifiers = mi.identifiers
- if not mi.is_null('rating'):
- # set_metadata expects a rating out of 10
- mi.rating *= 2
- else:
- log.error('Failed to download metadata for', title)
- failed_ids.add(i)
- # We don't want set_metadata operating on anything but covers
- mi = merge_result(mi, mi)
- if covers:
- cdata = download_cover(log, title=title, authors=authors,
- identifiers=identifiers)
- if cdata is not None:
- with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f:
- f.write(cdata[-1])
- mi.cover = f.name
- all_failed = False
- else:
- failed_covers.add(i)
- ans[i] = mi
- count += 1
- notifications.put((count/len(ids),
- _('Downloaded %d of %d')%(count, len(ids))))
- log('Download complete, with %d failures'%len(failed_ids))
- return (ans, failed_ids, failed_covers, title_map, all_failed)
-
-
-
diff --git a/src/calibre/gui2/preferences/behavior.py b/src/calibre/gui2/preferences/behavior.py
index b376d067bc..e062ae2662 100644
--- a/src/calibre/gui2/preferences/behavior.py
+++ b/src/calibre/gui2/preferences/behavior.py
@@ -19,7 +19,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.oeb.iterator import is_supported
from calibre.constants import iswindows
from calibre.utils.icu import sort_key
-from calibre.utils.config import test_eight_code
class OutputFormatSetting(Setting):
@@ -40,12 +39,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('network_timeout', prefs)
-
- r('overwrite_author_title_metadata', config)
- r('get_social_metadata', config)
- if test_eight_code:
- self.opt_overwrite_author_title_metadata.setVisible(False)
- self.opt_get_social_metadata.setVisible(False)
r('new_version_notification', config)
r('upload_news_to_device', config)
r('delete_news_from_library_on_upload', config)
@@ -67,13 +60,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
signal.connect(self.internally_viewed_formats_changed)
r('bools_are_tristate', db.prefs, restart_required=True)
- if test_eight_code:
- r = self.register
- choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1')]
- r('edit_metadata_single_layout', gprefs, choices=choices)
- else:
- self.opt_edit_metadata_single_layout.setVisible(False)
- self.edit_metadata_single_label.setVisible(False)
+ r = self.register
+ choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1')]
+ r('edit_metadata_single_layout', gprefs, choices=choices)
def initialize(self):
ConfigWidgetBase.initialize(self)
diff --git a/src/calibre/gui2/preferences/behavior.ui b/src/calibre/gui2/preferences/behavior.ui
index 69ebce6acf..ffd59d72bb 100644
--- a/src/calibre/gui2/preferences/behavior.ui
+++ b/src/calibre/gui2/preferences/behavior.ui
@@ -14,41 +14,14 @@
'.join(['%s: %s'%(name, exc) for name,exc in warnings])
- self.warning.setText('
'+warnings+'
") + _file + "
"+str(e))
- d.exec_()
- if cover:
- pix = QPixmap()
- pix.loadFromData(cover)
- if pix.isNull():
- d = error_dialog(self,
- _("Not a valid picture"),
- _file + _(" is not a valid picture"))
- d.exec_()
- else:
- self.cover.setPixmap(pix)
- self.update_cover_tooltip()
- self.cover_changed = True
- self.cpixmap = pix
- self.cover_data = cover
-
- def generate_cover(self, *args):
- from calibre.ebooks import calibre_cover
- from calibre.ebooks.metadata import fmt_sidx
- from calibre.gui2 import config
- title = unicode(self.title.text()).strip()
- author = unicode(self.authors.text()).strip()
- if author.endswith('&'):
- author = author[:-1].strip()
- if not title or not author:
- return error_dialog(self, _('Specify title and author'),
- _('You must specify a title and author before generating '
- 'a cover'), show=True)
- series = unicode(self.series.text()).strip()
- series_string = None
- if series:
- series_string = _('Book %s of %s')%(
- fmt_sidx(self.series_index.value(),
- use_roman=config['use_roman_numerals_for_series_number']), series)
- self.cover_data = calibre_cover(title, author,
- series_string=series_string)
- pix = QPixmap()
- pix.loadFromData(self.cover_data)
- self.cover.setPixmap(pix)
- self.update_cover_tooltip()
- self.cover_changed = True
- self.cpixmap = pix
-
- def cover_dropped(self, cover_data):
- self.cover_changed = True
- self.cover_data = cover_data
- self.update_cover_tooltip()
-
- def fetch_cover(self):
- isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())).strip()
- self.fetch_cover_button.setEnabled(False)
- self.setCursor(Qt.WaitCursor)
- title, author = map(unicode, (self.title.text(), self.authors.text()))
- self.cover_fetcher = CoverFetcher(None, None, isbn,
- self.timeout, title, author)
- self.cover_fetcher.start()
- self.cf_start_time = time.time()
- self.pi.start(_('Downloading cover...'))
- QTimer.singleShot(100, self.hangcheck)
-
- def hangcheck(self):
- cf = self.cover_fetcher
- if cf is None:
- # Called after dialog closed
- return
-
- if cf.is_alive() and \
- time.time()-self.cf_start_time < self.COVER_FETCH_TIMEOUT:
- QTimer.singleShot(100, self.hangcheck)
- return
-
- try:
- if cf.is_alive():
- error_dialog(self, _('Cannot fetch cover'),
- _('Could not fetch cover.
')+
- _('The download timed out.')).exec_()
- return
- if cf.needs_isbn:
- error_dialog(self, _('Cannot fetch cover'),
- _('Could not find cover for this book. Try '
- 'specifying the ISBN first.')).exec_()
- return
- if cf.exception is not None:
- err = cf.exception
- error_dialog(self, _('Cannot fetch cover'),
- _('Could not fetch cover.
')+unicode(err)).exec_()
- return
- if cf.errors and cf.cover_data is None:
- details = u'\n\n'.join([e[-1] + ': ' + e[1] for e in cf.errors])
- error_dialog(self, _('Cannot fetch cover'),
- _('Could not fetch cover.
') +
- _('For the error message from each cover source, '
- 'click Show details below.'), det_msg=details, show=True)
- return
-
- pix = QPixmap()
- pix.loadFromData(cf.cover_data)
- if pix.isNull():
- error_dialog(self, _('Bad cover'),
- _('The cover is not a valid picture')).exec_()
- else:
- self.cover.setPixmap(pix)
- self.update_cover_tooltip()
- self.cover_changed = True
- self.cpixmap = pix
- self.cover_data = cf.cover_data
- finally:
- self.fetch_cover_button.setEnabled(True)
- self.unsetCursor()
- if self.pi is not None:
- self.pi.stop()
-
-
- # }}}
-
- # Formats processing {{{
- def add_format(self, x):
- files = choose_files(self, 'add formats dialog',
- _("Choose formats for ") + unicode((self.title.text())),
- [(_('Books'), BOOK_EXTENSIONS)])
- self._add_formats(files)
-
- def _add_formats(self, paths):
- added = False
- if not paths:
- return added
- bad_perms = []
- for _file in paths:
- _file = os.path.abspath(_file)
- if not os.access(_file, os.R_OK):
- bad_perms.append(_file)
- continue
-
- nfile = run_plugins_on_import(_file)
- if nfile is not None:
- _file = nfile
- stat = os.stat(_file)
- size = stat.st_size
- ext = os.path.splitext(_file)[1].lower().replace('.', '')
- timestamp = utcfromtimestamp(stat.st_mtime)
- for row in range(self.formats.count()):
- fmt = self.formats.item(row)
- if fmt.ext.lower() == ext:
- self.formats.takeItem(row)
- break
- Format(self.formats, ext, size, path=_file, timestamp=timestamp)
- self.formats_changed = True
- added = True
- if bad_perms:
- error_dialog(self, _('No permission'),
- _('You do not have '
- 'permission to read the following files:'),
- det_msg='\n'.join(bad_perms), show=True)
-
- return added
-
- def formats_dropped(self, event, paths):
- if self._add_formats(paths):
- event.accept()
-
- def remove_format(self, *args):
- rows = self.formats.selectionModel().selectedRows(0)
- for row in rows:
- self.formats.takeItem(row.row())
- self.formats_changed = True
-
- def get_selected_format_metadata(self):
- from calibre.ebooks.metadata.meta import get_metadata
- old = prefs['read_file_metadata']
- if not old:
- prefs['read_file_metadata'] = True
- try:
- row = self.formats.currentRow()
- fmt = self.formats.item(row)
- if fmt is None:
- if self.formats.count() == 1:
- fmt = self.formats.item(0)
- if fmt is None:
- error_dialog(self, _('No format selected'),
- _('No format selected')).exec_()
- return None, None
- ext = fmt.ext.lower()
- if fmt.path is None:
- stream = self.db.format(self.row, ext, as_file=True)
- else:
- stream = open(fmt.path, 'r+b')
- try:
- mi = get_metadata(stream, ext)
- return mi, ext
- except:
- error_dialog(self, _('Could not read metadata'),
- _('Could not read metadata from %s format')%ext).exec_()
- return None, None
- finally:
- if old != prefs['read_file_metadata']:
- prefs['read_file_metadata'] = old
-
- def set_metadata_from_format(self):
- mi, ext = self.get_selected_format_metadata()
- if mi is None:
- return
- if mi.title:
- self.title.setText(mi.title)
- if mi.authors:
- self.authors.setEditText(authors_to_string(mi.authors))
- if mi.author_sort:
- self.author_sort.setText(mi.author_sort)
- if mi.rating is not None:
- try:
- self.rating.setValue(mi.rating)
- except:
- pass
- if mi.publisher:
- self.publisher.setEditText(mi.publisher)
- if mi.tags:
- self.tags.setText(', '.join(mi.tags))
- if mi.isbn:
- self.isbn.setText(mi.isbn)
- if mi.pubdate:
- self.pubdate.setDate(QDate(mi.pubdate.year, mi.pubdate.month,
- mi.pubdate.day))
- if mi.series and mi.series.strip():
- self.series.setEditText(mi.series)
- if mi.series_index is not None:
- self.series_index.setValue(float(mi.series_index))
- if mi.comments and mi.comments.strip():
- comments = comments_to_html(mi.comments)
- self.comments.html = comments
-
-
- def sync_formats(self):
- old_extensions, new_extensions, paths = set(), set(), {}
- for row in range(self.formats.count()):
- fmt = self.formats.item(row)
- ext, path = fmt.ext.lower(), fmt.path
- if 'unknown' in ext.lower():
- ext = None
- if path:
- new_extensions.add(ext)
- paths[ext] = path
- else:
- old_extensions.add(ext)
- for ext in new_extensions:
- self.db.add_format(self.row, ext, open(paths[ext], 'rb'), notify=False)
- dbfmts = self.db.formats(self.row)
- db_extensions = set([f.lower() for f in (dbfmts.split(',') if dbfmts
- else [])])
- extensions = new_extensions.union(old_extensions)
- for ext in db_extensions:
- if ext not in extensions and ext in self.original_formats:
- self.db.remove_format(self.row, ext, notify=False)
-
- def show_format(self, item, *args):
- fmt = item.ext
- self.view_format.emit(fmt)
-
- # }}}
-
- def __init__(self, window, row, db, prev=None,
- next_=None):
- ResizableDialog.__init__(self, window)
- self.cover_fetcher = None
- self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
- base = unicode(self.author_sort.toolTip())
- ok_tooltip = '
'+
- _(' The green color indicates that the current '
- 'author sort matches the current author'))
- bad_tooltip = '
'+
- _(' The red color indicates that the current '
- 'author sort does not match the current author. '
- 'No action is required if this is what you want.'))
- self.aus_tooltips = (ok_tooltip, bad_tooltip)
-
- base = unicode(self.title_sort.toolTip())
- ok_tooltip = '
'+
- _(' The green color indicates that the current '
- 'title sort matches the current title'))
- bad_tooltip = '
'+
- _(' The red color warns that the current '
- 'title sort does not match the current title. '
- 'No action is required if this is what you want.'))
- self.ts_tooltips = (ok_tooltip, bad_tooltip)
- self.row_delta = 0
- if prev:
- self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
- self)
- self.button_box.addButton(self.prev_button, self.button_box.ActionRole)
- tip = (_('Save changes and edit the metadata of %s')+' [Alt+Left]')%prev
- self.prev_button.setToolTip(tip)
- self.prev_button.clicked.connect(partial(self.next_triggered,
- -1))
- self.prev_button.setShortcut(QKeySequence('Alt+Left'))
- if next_:
- self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
- self)
- self.button_box.addButton(self.next_button, self.button_box.ActionRole)
- tip = (_('Save changes and edit the metadata of %s')+' [Alt+Right]')%next_
- self.next_button.setToolTip(tip)
- self.next_button.clicked.connect(partial(self.next_triggered, 1))
- self.next_button.setShortcut(QKeySequence('Alt+Right'))
-
- self.splitter.setStretchFactor(100, 1)
- self.read_state()
- self.db = db
- self.pi = ProgressIndicator(self)
- self.id = db.id(row)
- self.row = row
- self.cover_data = None
- self.formats_changed = False
- self.formats.setAcceptDrops(True)
- self.cover_changed = False
- self.cpixmap = None
- self.pubdate.setMinimumDate(UNDEFINED_QDATE)
- pubdate_format = tweaks['gui_pubdate_display_format']
- if pubdate_format is not None:
- self.pubdate.setDisplayFormat(pubdate_format)
- self.date.setMinimumDate(UNDEFINED_QDATE)
- self.pubdate.setSpecialValueText(_('Undefined'))
- self.date.setSpecialValueText(_('Undefined'))
- self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
-
-
- self.connect(self.cover, SIGNAL('cover_changed(PyQt_PyObject)'), self.cover_dropped)
- QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
- self.select_cover)
- QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), \
- self.add_format)
- self.connect(self.formats,
- SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'),
- self.formats_dropped)
- QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \
- self.remove_format)
- QObject.connect(self.fetch_metadata_button, SIGNAL('clicked()'),
- self.fetch_metadata)
-
- QObject.connect(self.fetch_cover_button, SIGNAL('clicked()'),
- self.fetch_cover)
- QObject.connect(self.tag_editor_button, SIGNAL('clicked()'),
- self.edit_tags)
- QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
- self.remove_unused_series)
- QObject.connect(self.auto_author_sort, SIGNAL('clicked()'),
- self.deduce_author_sort)
- QObject.connect(self.auto_title_sort, SIGNAL('clicked()'),
- self.deduce_title_sort)
- self.trim_cover_button.clicked.connect(self.trim_cover)
- self.connect(self.title_sort, SIGNAL('textChanged(const QString&)'),
- self.title_sort_box_changed)
- self.connect(self.title, SIGNAL('textChanged(const QString&)'),
- self.title_box_changed)
- self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'),
- self.author_sort_box_changed)
- self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'),
- self.authors_box_changed)
- self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
- self.show_format)
- self.connect(self.formats, SIGNAL('delete_format()'), self.remove_format)
- self.connect(self.button_set_cover, SIGNAL('clicked()'), self.set_cover)
- self.connect(self.button_set_metadata, SIGNAL('clicked()'),
- self.set_metadata_from_format)
- self.connect(self.reset_cover, SIGNAL('clicked()'), self.do_reset_cover)
- self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
- self.timeout = float(prefs['network_timeout'])
-
-
- self.title.setText(db.title(row))
- self.title_sort.setText(db.title_sort(row))
- isbn = db.isbn(self.id, index_is_id=True)
- if not isbn:
- isbn = ''
- self.isbn.textChanged.connect(self.validate_isbn)
- self.isbn.setText(isbn)
- aus = self.db.author_sort(row)
- self.author_sort.setText(aus if aus else '')
- tags = self.db.tags(row)
- self.original_tags = ', '.join(tags.split(',')) if tags else ''
- self.tags.setText(self.original_tags)
- self.tags.update_items_cache(self.db.all_tags())
- rating = self.db.rating(row)
- if rating > 0:
- self.rating.setValue(int(rating/2.))
- comments = self.db.comments(row)
- if comments and comments.strip():
- comments = comments_to_html(comments)
- self.comments.html = comments
- cover = self.db.cover(row)
- pubdate = db.pubdate(self.id, index_is_id=True)
- self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
- pubdate.day))
- timestamp = db.timestamp(self.id, index_is_id=True)
- self.date.setDate(QDate(timestamp.year, timestamp.month,
- timestamp.day))
- self.orig_date = qt_to_dt(self.date.date())
-
- exts = self.db.formats(row)
- self.original_formats = []
- if exts:
- exts = exts.split(',')
- for ext in exts:
- if not ext:
- ext = ''
- size = self.db.sizeof_format(row, ext)
- timestamp = self.db.format_last_modified(self.id, ext)
- if size is None:
- continue
- Format(self.formats, ext, size, timestamp=timestamp)
- self.original_formats.append(ext.lower())
-
-
- self.initialize_combos()
- si = self.db.series_index(row)
- if si is None:
- si = 1.0
- try:
- self.series_index.setValue(float(si))
- except:
- self.series_index.setValue(1.0)
- QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
- QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
- self.series.lineEdit().editingFinished.connect(self.increment_series_index)
-
- pm = QPixmap()
- if cover:
- pm.loadFromData(cover)
- if pm.isNull():
- pm = QPixmap(I('default_cover.png'))
- else:
- self.cover_data = cover
- self.cover.setPixmap(pm)
- self.update_cover_tooltip()
- self.original_series_name = unicode(self.series.text()).strip()
- if len(db.custom_column_label_map) == 0:
- self.central_widget.tabBar().setVisible(False)
- self.central_widget.setTabEnabled(1, False)
- else:
- self.create_custom_column_editors()
- self.generate_cover_button.clicked.connect(self.generate_cover)
-
- self.original_author = unicode(self.authors.text()).strip()
- self.original_title = unicode(self.title.text()).strip()
- self.books_to_refresh = set()
-
- self.show()
-
- def clear_pubdate(self, *args):
- self.pubdate.setDate(UNDEFINED_QDATE)
-
- def create_custom_column_editors(self):
- w = self.central_widget.widget(1)
- layout = w.layout()
- self.custom_column_widgets, self.__cc_spacers = \
- populate_metadata_page(layout, self.db, self.id, parent=w, bulk=False,
- two_column=tweaks['metadata_single_use_2_cols_for_custom_fields'])
- self.__custom_col_layouts = [layout]
- ans = self.custom_column_widgets
- for i in range(len(ans)-1):
- if len(ans[i+1].widgets) == 2:
- w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1])
- else:
- w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0])
- for c in range(2, len(ans[i].widgets), 2):
- w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1])
-
- def title_box_changed(self, txt):
- ts = unicode(txt)
- ts = title_sort(ts)
- self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips,
- normal=(unicode(self.title_sort.text()) == ts))
-
- def title_sort_box_changed(self, txt):
- ts = unicode(txt)
- self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips,
- normal=(title_sort(unicode(self.title.text())) == ts))
-
- def authors_box_changed(self, txt):
- aus = unicode(txt)
- aus = re.sub(r'\s+et al\.$', '', aus)
- aus = self.db.author_sort_from_authors(string_to_authors(aus))
- self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips,
- normal=(unicode(self.author_sort.text()) == aus))
-
- def author_sort_box_changed(self, txt):
- au = unicode(self.authors.text())
- au = re.sub(r'\s+et al\.$', '', au)
- au = self.db.author_sort_from_authors(string_to_authors(au))
- self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips,
- normal=(au == txt))
-
- def mark_box_as_ok(self, control, tt, normal=True):
- if normal:
- col = 'rgb(0, 255, 0, 20%)'
- else:
- col = 'rgb(255, 0, 0, 20%)'
- control.setStyleSheet('QLineEdit { color: black; '
- 'background-color: %s; }'%col)
- tt = tt[0] if normal else tt[1]
- control.setToolTip(tt)
-
- def validate_isbn(self, isbn):
- isbn = unicode(isbn).strip()
- if not isbn:
- self.isbn.setStyleSheet('QLineEdit { background-color: rgba(0,255,0,0%) }')
- self.isbn.setToolTip(_('This ISBN number is valid'))
- return
-
- if check_isbn(isbn):
- self.isbn.setStyleSheet('QLineEdit { background-color: rgba(0,255,0,20%) }')
- self.isbn.setToolTip(_('This ISBN number is valid'))
- else:
- self.isbn.setStyleSheet('QLineEdit { background-color: rgba(255,0,0,20%) }')
- self.isbn.setToolTip(_('This ISBN number is invalid'))
-
- def deduce_author_sort(self):
- au = unicode(self.authors.text())
- au = re.sub(r'\s+et al\.$', '', au)
- authors = string_to_authors(au)
- self.author_sort.setText(self.db.author_sort_from_authors(authors))
-
- def deduce_title_sort(self):
- ts = unicode(self.title.text())
- self.title_sort.setText(title_sort(ts))
-
- def swap_title_author(self):
- title = self.title.text()
- self.title.setText(self.authors.text())
- self.authors.setText(title)
- self.deduce_author_sort()
- self.deduce_title_sort()
-
- def initialize_combos(self):
- self.initalize_authors()
- self.initialize_series()
- self.initialize_publisher()
-
- self.layout().activate()
-
- def initalize_authors(self):
- all_authors = self.db.all_authors()
- all_authors.sort(key=lambda x : sort_key(x[1]))
- for i in all_authors:
- id, name = i
- name = [name.strip().replace('|', ',') for n in name.split(',')]
- self.authors.addItem(authors_to_string(name))
-
- au = self.db.authors(self.row)
- if not au:
- au = _('Unknown')
- au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
- self.authors.setEditText(au)
-
- self.authors.set_separator('&')
- self.authors.set_space_before_sep(True)
- self.authors.set_add_separator(tweaks['authors_completer_append_separator'])
- self.authors.update_items_cache(self.db.all_author_names())
-
- def initialize_series(self):
- self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
- all_series = self.db.all_series()
- all_series.sort(key=lambda x : sort_key(x[1]))
- self.series.set_separator(None)
- self.series.update_items_cache([x[1] for x in all_series])
- series_id = self.db.series_id(self.row)
- idx, c = None, 0
- for i in all_series:
- id, name = i
- if id == series_id:
- idx = c
- self.series.addItem(name)
- c += 1
-
- self.series.lineEdit().setText('')
- if idx is not None:
- self.series.setCurrentIndex(idx)
- self.enable_series_index()
-
- def initialize_publisher(self):
- all_publishers = self.db.all_publishers()
- all_publishers.sort(key=lambda x : sort_key(x[1]))
- self.publisher.set_separator(None)
- self.publisher.update_items_cache([x[1] for x in all_publishers])
- publisher_id = self.db.publisher_id(self.row)
- idx, c = None, 0
- for i in all_publishers:
- id, name = i
- if id == publisher_id:
- idx = c
- self.publisher.addItem(name)
- c += 1
-
- self.publisher.setEditText('')
- if idx is not None:
- self.publisher.setCurrentIndex(idx)
-
- def edit_tags(self):
- if self.tags.text() != self.original_tags:
- if question_dialog(self, _('Tags changed'),
- _('You have changed the tags. In order to use the tags'
- ' editor, you must either discard or apply these '
- 'changes. Apply changes?'), show_copy_button=False):
- self.books_to_refresh |= self.apply_tags(commit=True,
- notify=True)
- self.original_tags = unicode(self.tags.text())
- else:
- self.tags.setText(self.original_tags)
- d = TagEditor(self, self.db, self.id)
- d.exec_()
- if d.result() == QDialog.Accepted:
- tag_string = ', '.join(d.tags)
- self.tags.setText(tag_string)
- self.tags.update_items_cache(self.db.all_tags())
-
-
- def fetch_metadata(self):
- isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text()))
- title = unicode(self.title.text())
- try:
- author = string_to_authors(unicode(self.authors.text()))[0]
- except:
- author = ''
- publisher = unicode(self.publisher.currentText())
- if isbn or title or author or publisher:
- d = FetchMetadata(self, isbn, title, author, publisher, self.timeout)
- self._fetch_metadata_scope = d
- with d:
- if d.exec_() == QDialog.Accepted:
- book = d.selected_book()
- if book:
- if d.opt_get_social_metadata.isChecked():
- d2 = SocialMetadata(book, self)
- d2.exec_()
- if d2.timed_out:
- warning_dialog(self, _('Timed out'),
- _('The download of social'
- ' metadata timed out, the servers are'
- ' probably busy. Try again later.'),
- show=True)
- elif d2.exceptions:
- det = '\n'.join([x[0]+'\n\n'+x[-1]+'\n\n\n' for
- x in d2.exceptions])
- warning_dialog(self, _('There were errors'),
- _('There were errors downloading social metadata'),
- det_msg=det, show=True)
- else:
- book.tags = []
- if d.opt_overwrite_author_title_metadata.isChecked():
- self.title.setText(book.title)
- self.authors.setText(authors_to_string(book.authors))
- if book.author_sort: self.author_sort.setText(book.author_sort)
- if book.publisher: self.publisher.setEditText(book.publisher)
- if book.isbn: self.isbn.setText(book.isbn)
- if book.pubdate:
- dt = book.pubdate
- self.pubdate.setDate(QDate(dt.year, dt.month, dt.day))
- summ = book.comments
- if summ:
- prefix = self.comments.html
- if prefix:
- prefix += '\n'
- self.comments.html = prefix + comments_to_html(summ)
- if book.rating is not None:
- self.rating.setValue(int(book.rating))
- if book.tags:
- self.tags.setText(', '.join(book.tags))
- if book.series is not None:
- if self.series.text() is None or self.series.text() == '':
- self.series.setText(book.series)
- if book.series_index is not None:
- self.series_index.setValue(book.series_index)
- if book.has_cover:
- if d.opt_auto_download_cover.isChecked():
- self.fetch_cover()
- else:
- self.fetch_cover_button.setFocus(Qt.OtherFocusReason)
- else:
- error_dialog(self, _('Cannot fetch metadata'),
- _('You must specify at least one of ISBN, Title, '
- 'Authors or Publisher'), show=True)
- self.title.setFocus(Qt.OtherFocusReason)
-
- def enable_series_index(self, *args):
- self.series_index.setEnabled(True)
-
- def increment_series_index(self):
- if self.db is not None:
- try:
- series = unicode(self.series.text()).strip()
- if series and series != self.original_series_name:
- ns = 1
- if tweaks['series_index_auto_increment'] != 'const':
- ns = self.db.get_next_series_num_for(series)
- self.series_index.setValue(ns)
- self.original_series_name = series
- except:
- traceback.print_exc()
-
- def remove_unused_series(self):
- self.db.remove_unused_series()
- idx = unicode(self.series.currentText())
- self.series.clear()
- self.initialize_series()
- if idx:
- for i in range(self.series.count()):
- if unicode(self.series.itemText(i)) == idx:
- self.series.setCurrentIndex(i)
- break
-
- def apply_tags(self, commit=False, notify=False):
- return self.db.set_tags(self.id, [x.strip() for x in
- unicode(self.tags.text()).split(',')],
- notify=notify, commit=commit, allow_case_change=True)
-
- def next_triggered(self, row_delta, *args):
- self.row_delta = row_delta
- self.accept()
-
- def accept(self):
- try:
- if self.formats_changed:
- self.sync_formats()
- title = unicode(self.title.text()).strip()
- if title != self.original_title:
- self.db.set_title(self.id, title, notify=False)
- # This must be after setting the title because of the DB update trigger
- ts = unicode(self.title_sort.text()).strip()
- if ts:
- self.db.set_title_sort(self.id, ts, notify=False, commit=False)
- au = unicode(self.authors.text()).strip()
- if au and au != self.original_author:
- self.books_to_refresh |= self.db.set_authors(self.id,
- string_to_authors(au),
- notify=False,
- allow_case_change=True)
- aus = unicode(self.author_sort.text()).strip()
- if aus:
- self.db.set_author_sort(self.id, aus, notify=False, commit=False)
- self.db.set_isbn(self.id,
- re.sub(r'[^0-9a-zA-Z]', '',
- unicode(self.isbn.text()).strip()),
- notify=False, commit=False)
- self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
- commit=False)
- self.books_to_refresh |= self.apply_tags()
- self.books_to_refresh |= self.db.set_publisher(self.id,
- unicode(self.publisher.currentText()).strip(),
- notify=False, commit=False, allow_case_change=True)
- self.books_to_refresh |= self.db.set_series(self.id,
- unicode(self.series.currentText()).strip(), notify=False,
- commit=False, allow_case_change=True)
- self.db.set_series_index(self.id, self.series_index.value(),
- notify=False, commit=False)
- self.db.set_comment(self.id,
- self.comments.html,
- notify=False, commit=False)
- d = self.pubdate.date()
- d = qt_to_dt(d)
- self.db.set_pubdate(self.id, d, notify=False, commit=False)
- d = self.date.date()
- d = qt_to_dt(d)
- if d != self.orig_date:
- self.db.set_timestamp(self.id, d, notify=False, commit=False)
- self.db.commit()
-
- if self.cover_changed:
- if self.cover_data is not None:
- self.db.set_cover(self.id, self.cover_data)
- else:
- self.db.remove_cover(self.id)
- for w in getattr(self, 'custom_column_widgets', []):
- self.books_to_refresh |= w.commit(self.id)
- self.db.commit()
- except (IOError, OSError) as err:
- if getattr(err, 'errno', -1) == 13: # Permission denied
- fname = err.filename if err.filename else 'file'
- return error_dialog(self, _('Permission denied'),
- _('Could not open %s. Is it being used by another'
- ' program?')%fname, det_msg=traceback.format_exc(),
- show=True)
- raise
- self.save_state()
- self.cover_fetcher = None
- QDialog.accept(self)
-
- def reject(self, *args):
- self.save_state()
- self.cover_fetcher = None
- QDialog.reject(self, *args)
-
- def read_state(self):
- wg = dynamic.get('metasingle_window_geometry2', None)
- ss = dynamic.get('metasingle_splitter_state2', None)
- if wg is not None:
- self.restoreGeometry(wg)
- if ss is not None:
- self.splitter.restoreState(ss)
-
- def save_state(self):
- dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry()))
- dynamic.set('metasingle_splitter_state2',
- bytes(self.splitter.saveState()))
-
- def break_cycles(self):
- # Break any reference cycles that could prevent python
- # from garbage collecting this dialog
- def disconnect(signal):
- try:
- signal.disconnect()
- except:
- pass # Fails if view format was never connected
- disconnect(self.view_format)
- for b in ('next_button', 'prev_button'):
- x = getattr(self, b, None)
- if x is not None:
- disconnect(x.clicked)
-
-if __name__ == '__main__':
- from calibre.library import db
- from PyQt4.Qt import QApplication
- from calibre.utils.mem import memory
- import gc
-
-
- app = QApplication([])
- db = db()
-
- # Initialize all Qt Objects once
- d = MetadataSingleDialog(None, 4, db)
- d.break_cycles()
- d.reject()
- del d
-
- for i in range(5):
- gc.collect()
- before = memory()
-
- d = MetadataSingleDialog(None, 4, db)
- d.reject()
- d.break_cycles()
- del d
-
- for i in range(5):
- gc.collect()
- print 'Used memory:', memory(before)/1024.**2, 'MB'
-
-
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
deleted file mode 100644
index ced5030f94..0000000000
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ /dev/null
@@ -1,937 +0,0 @@
-
-Failed to download metadata for the following:
')
- for id_, err in f.items():
- mi = self.downloader.metadata[id_]
- report.append('
')
- if cf:
- report.append(
- '%s
Failed to download cover for the following:
')
- for id_, err in cf.items():
- mi = self.downloader.metadata[id_]
- report.append('
')
-
- if len(self.updated) != self.total or report:
- d = QDialog(self.parent())
- bb = QDialogButtonBox(QDialogButtonBox.Ok, parent=d)
- v1 = QVBoxLayout()
- d.setLayout(v1)
- d.setWindowTitle(_('Done'))
- v1.addWidget(QLabel(_('Successfully downloaded metadata for %d out of %d books') %
- (len(self.updated), self.total)))
- gb = QGroupBox(_('Details'), self.parent())
- v2 = QVBoxLayout()
- gb.setLayout(v2)
- b = QTextBrowser(self.parent())
- v2.addWidget(b)
- b.setHtml('\n'.join(report))
- v1.addWidget(gb)
- v1.addWidget(bb)
- bb.accepted.connect(d.accept)
- d.resize(800, 600)
- d.exec_()
-
+ log.error('Failed to download metadata for', title)
+ failed_ids.add(i)
+ # We don't want set_metadata operating on anything but covers
+ mi = merge_result(mi, mi)
+ if covers:
+ cdata = download_cover(log, title=title, authors=authors,
+ identifiers=identifiers)
+ if cdata is not None:
+ with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f:
+ f.write(cdata[-1])
+ mi.cover = f.name
+ all_failed = False
+ else:
+ failed_covers.add(i)
+ ans[i] = mi
+ count += 1
+ notifications.put((count/len(ids),
+ _('Downloaded %d of %d')%(count, len(ids))))
+ log('Download complete, with %d failures'%len(failed_ids))
+ return (ans, failed_ids, failed_covers, title_map, all_failed)
diff --git a/src/calibre/gui2/metadata/bulk_download2.py b/src/calibre/gui2/metadata/bulk_download2.py
deleted file mode 100644
index 2a307fc902..0000000000
--- a/src/calibre/gui2/metadata/bulk_download2.py
+++ /dev/null
@@ -1,195 +0,0 @@
-#!/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 %s