def catalog_generated(self, job): if job.result: # Problems during catalog generation # jobs.results is a list - the first entry is the intended title for the dialog # Subsequent strings are error messages dialog_title = job.result.pop(0) if re.search('warning', job.result[0].lower()): msg = _("Catalog generation complete, with warnings.") warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True) else: job.result.append("Catalog generation terminated.") error_dialog(self.gui, dialog_title, '\n'.join(job.result), show=True) return if job.failed: return self.gui.job_exception(job) if dynamic.get('catalog_add_to_library', True): id = self.gui.library_view.model().add_catalog( job.catalog_file_path, job.catalog_title) self.gui.library_view.model().beginResetModel( ), self.gui.library_view.model().endResetModel() if job.catalog_sync: sync = dynamic.get('catalogs_to_be_synced', set()) sync.add(id) dynamic.set('catalogs_to_be_synced', sync) self.gui.status_bar.show_message(_('Catalog generated.'), 3000) self.gui.sync_catalogs() if not dynamic.get('catalog_add_to_library', True) or job.fmt not in { 'EPUB', 'MOBI', 'AZW3' }: export_dir = choose_dir( self.gui, _('Export catalog folder'), _('Select destination for %(title)s.%(fmt)s') % dict(title=job.catalog_title, fmt=job.fmt.lower())) if export_dir: destination = os.path.join( export_dir, '%s.%s' % (sanitize_file_name(job.catalog_title), job.fmt.lower())) try: shutil.copyfile(job.catalog_file_path, destination) except OSError as err: if getattr(err, 'errno', None) == errno.EACCES: # Permission denied import traceback error_dialog( self.gui, _('Permission denied'), _('Could not open %s. Is it being used by another' ' program?') % destination, det_msg=traceback.format_exc(), show=True) return raise
def choose_save_dir(self, default_dir): savedir = None askagain = True no_save_dir = False if default_dir: no_save_dir = True title = _('Choose destination directory for scrambled ebook') while askagain: savedir = choose_dir(window=self, name='', title=title, default_dir=default_dir, no_save_dir=no_save_dir) askagain = False if savedir is not None: savedir = os.path.normpath(savedir) if savedir.startswith(tuple(self.calibre_libpaths)): askagain = True msg = [] msg.append( 'You have selected a destination inside your Calibre library.' ) msg.append(savedir) msg.append('\nThis is NOT recommended. Try again.') msg.append('\nPlease avoid the following:') [ msg.append(path) for path in sorted(self.calibre_libpaths) ] warning_dialog(self, 'Calibre library chosen', '\n'.join(msg), show=True, show_copy_button=True) return savedir
def dispatch_button_click(self, button): ''' BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] ''' self._log_location() if self.bb.buttonRole(button) == QDialogButtonBox.AcceptRole: requested_name = str(self.calibre_destination_le.text()) if requested_name in self.get_custom_column_names(): self._log("'%s' already in use" % requested_name) warning_dialog( self.gui, _("Already in use"), _("<p>'{0}' is an existing custom column.</p><p>Pick a different name.</p>" ).format(requested_name), show=True, show_copy_button=False) self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() else: source = self.column_type #profile = self.FIELDS[source] self.profile['source'] = source if button.objectName() == 'add_button': self.custom_column_add(requested_name, self.profile) elif button.objectName() == 'rename_button': self.custom_column_rename(requested_name, self.profile) self.accept() elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole: self.close()
def dispatch_button_click(self, button): ''' BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] ''' self._log_location() if self.bb.buttonRole(button) == QDialogButtonBox.AcceptRole: requested_name = str(self.calibre_destination_le.text()) if requested_name in self.get_custom_column_names(): self._log("'%s' already in use" % requested_name) warning_dialog(self.gui, "Already in use", "<p>'%s' is an existing custom column.</p><p>Pick a different name.</p>" % requested_name, show=True, show_copy_button=False) self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() else: source = self.column_type #profile = self.FIELDS[source] self.profile['source'] = source if button.objectName() == 'add_button': self.custom_column_add(requested_name, self.profile) elif button.objectName() == 'rename_button': self.custom_column_rename(requested_name, self.profile) self.accept() elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole: self.close()
def _files_added(self, paths=[], names=[], infos=[], on_card=None): self.gui.tags_view.disable_recounting = False if paths: self.gui.upload_books(paths, list(map(ascii_filename, names)), infos, on_card=on_card) self.gui.status_bar.show_message(_("Uploading books to device."), 2000) if getattr(self._adder, "number_of_books_added", 0) > 0: self.gui.library_view.model().books_added(self._adder.number_of_books_added) self.gui.library_view.set_current_row(0) if hasattr(self.gui, "db_images"): self.gui.db_images.reset() self.gui.tags_view.recount() if getattr(self._adder, "merged_books", False): books = u"\n".join( [ x if isinstance(x, unicode) else x.decode(preferred_encoding, "replace") for x in self._adder.merged_books ] ) info_dialog( self.gui, _("Merged some books"), _( "The following %d duplicate books were found and incoming " "book formats were processed and merged into your " "Calibre database according to your automerge " "settings:" ) % len(self._adder.merged_books), det_msg=books, show=True, ) if getattr(self._adder, "number_of_books_added", 0) > 0 or getattr(self._adder, "merged_books", False): # The formats of the current book could have changed if # automerge is enabled current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx) if getattr(self._adder, "critical", None): det_msg = [] for name, log in self._adder.critical.items(): if isinstance(name, str): name = name.decode(filesystem_encoding, "replace") det_msg.append(name + "\n" + log) warning_dialog( self.gui, _("Failed to read metadata"), _("Failed to read metadata from the following") + ":", det_msg="\n\n".join(det_msg), show=True, ) if hasattr(self._adder, "cleanup"): self._adder.cleanup() self._adder.setParent(None) del self._adder self._adder = None
def done(self, job): if job.result: # Problems during word wise generation # jobs.results is a list - the first entry is the intended # title for the dialog # Subsequent strings are error messages dialog_title = job.result.pop(0) if re.search('warning', job.result[0].lower()): msg = "Word Wise generation complete, with warnings." warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True) else: job.result.append("Word Wise generation terminated.") error_dialog(self.gui, dialog_title, '\n'.join(job.result), show=True) return if job.failed: self.gui.job_exception(job) return # send files to device for book_id in self.ids: data = check_metadata(self.gui.current_db.new_api, book_id, False) if data is None: continue send(self.gui, (book_id, ) + data) self.gui.status_bar.show_message("Word Wise generated.")
def _download_done(self, ret, tb): if not self.isVisible(): return self.reject() if tb is not None: error_dialog(self, _('Download failed'), _( 'Failed to download external resources, click "Show Details" for more information.'), det_msg=tb, show=True) self.reject() else: replacements, failures = ret if failures: tb = ['{}\n\t{}\n'.format(url, err) for url, err in failures.iteritems()] if not replacements: error_dialog(self, _('Download failed'), _( 'Failed to download external resources, click "Show Details" for more information.'), det_msg='\n'.join(tb), show=True) self.reject() return else: warning_dialog(self, _('Some downloads failed'), _( 'Failed to download some external resources, click "Show Details" for more information.'), det_msg='\n'.join(tb), show=True) self.state = 2 self.wait.msg = _('Updating resources in book...') self.wait.start() t = ngettext( 'Successfully processed the external resource', 'Successfully processed {} external resources', len(replacements)).format(len(replacements)) if failures: t += '<br>' + ngettext('Could not download one image', 'Could not download {} images', len(failures)).format(len(failures)) self.success.setText('<p style="text-align:center">' + t) resources = self.choose_resources.resources t = Thread(name='ReplaceResources', target=self.replace_resources, args=(resources, replacements)) t.daemon = True t.start()
def catalog_generated(self, job): if job.result: # Problems during catalog generation # jobs.results is a list - the first entry is the intended title for the dialog # Subsequent strings are error messages dialog_title = job.result.pop(0) if re.match('warning:', job.result[0].lower()): msg = _("Catalog generation complete, with warnings.") warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True) else: job.result.append("Catalog generation terminated.") error_dialog(self.gui, dialog_title,'\n'.join(job.result),show=True) return if job.failed: return self.gui.job_exception(job) id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title) self.gui.library_view.model().reset() if job.catalog_sync: sync = dynamic.get('catalogs_to_be_synced', set([])) sync.add(id) dynamic.set('catalogs_to_be_synced', sync) self.gui.status_bar.show_message(_('Catalog generated.'), 3000) self.gui.sync_catalogs() if job.fmt not in ['EPUB','MOBI']: export_dir = choose_dir(self.gui, _('Export Catalog Directory'), _('Select destination for %(title)s.%(fmt)s') % dict( title=job.catalog_title, fmt=job.fmt.lower())) if export_dir: destination = os.path.join(export_dir, '%s.%s' % ( sanitize_file_name_unicode(job.catalog_title), job.fmt.lower())) shutil.copyfile(job.catalog_file_path, destination)
def show_no_results_found(self): if self.current_search: warning_dialog(self, _('No matches found'), _('No matches were found for: <b>{}</b>').format( self.current_search.text), show=True)
def existing_pb_clicked(self, qitem): item = qitem.data(Qt.UserRole) if qitem.flags() & Qt.ItemIsEnabled: self.edit_format.setCurrentIndex(self.edit_format.findText(item[0])) self.edit_device.setCurrentIndex(self.edit_device.findText(item[1])) else: warning_dialog(self, "", _("The {0} device plugin is disabled.").format(item[1]), show=True)
def cannot_do_dialog(self): warning_dialog( self.gui, _('Not allowed'), _('You cannot use other libraries while using the environment' ' variable CALIBRE_OVERRIDE_DATABASE_PATH.'), show=True)
def start_content_server(self, check_started=True): from calibre.srv.embedded import Server if not gprefs.get('server3_warning_done', False): gprefs.set('server3_warning_done', True) if os.path.exists(os.path.join(config_dir, 'server.py')): try: os.remove(os.path.join(config_dir, 'server.py')) except EnvironmentError: pass warning_dialog( self, _('Content server changed!'), _('calibre 3 comes with a completely re-written content server.' ' As such any custom configuration you have for the content' ' server no longer applies. You should check and refresh your' ' settings in Preferences->Sharing->Sharing over the net' ), show=True) self.content_server = Server( self.library_broker, Dispatcher(self.handle_changes_from_server)) self.content_server.state_callback = Dispatcher( self.iactions['Connect Share'].content_server_state_changed) if check_started: self.content_server.start_failure_callback = \ Dispatcher(self.content_server_start_failed) self.content_server.start()
def show_config_widget(category, name, gui=None, show_restart_msg=False, parent=None, never_shutdown=False): ''' Show the preferences plugin identified by category and name :param gui: gui instance, if None a hidden gui is created :param show_restart_msg: If True and the preferences plugin indicates a restart is required, show a message box telling the user to restart :param parent: The parent of the displayed dialog :return: True iff a restart is required for the changes made by the user to take effect ''' from calibre.gui2 import gprefs pl = get_plugin(category, name) d = ConfigDialog(parent) d.resize(750, 550) conf_name = 'config_widget_dialog_geometry_%s_%s'%(category, name) geom = gprefs.get(conf_name, None) d.setWindowTitle(_('Configure ') + name) d.setWindowIcon(QIcon(I('config.png'))) bb = QDialogButtonBox(d) bb.setStandardButtons(bb.Apply|bb.Cancel|bb.RestoreDefaults) bb.accepted.connect(d.accept) bb.rejected.connect(d.reject) w = pl.create_widget(d) d.set_widget(w) bb.button(bb.RestoreDefaults).clicked.connect(w.restore_defaults) bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults) bb.button(bb.Apply).setEnabled(False) bb.button(bb.Apply).clicked.connect(d.accept) def onchange(): b = bb.button(bb.Apply) b.setEnabled(True) b.setDefault(True) b.setAutoDefault(True) w.changed_signal.connect(onchange) bb.button(bb.Cancel).setFocus(True) l = QVBoxLayout() d.setLayout(l) l.addWidget(w) l.addWidget(bb) mygui = gui is None if gui is None: gui = init_gui() mygui = True w.genesis(gui) w.initialize() if geom is not None: d.restoreGeometry(geom) d.exec_() geom = bytearray(d.saveGeometry()) gprefs[conf_name] = geom rr = getattr(d, 'restart_required', False) if show_restart_msg and rr: from calibre.gui2 import warning_dialog warning_dialog(gui, 'Restart required', 'Restart required', show=True) if mygui and not never_shutdown: gui.shutdown() return rr
def cannot_do_dialog(self): warning_dialog( self.gui, _("Not allowed"), _("You cannot use other libraries while using the environment" " variable CALIBRE_OVERRIDE_DATABASE_PATH."), show=True, )
def _download_done(self, ret, tb): if not self.isVisible(): return self.reject() if tb is not None: error_dialog(self, _('Download failed'), _( 'Failed to download external resources, click "Show details" for more information.'), det_msg=tb, show=True) self.reject() else: replacements, failures = ret if failures: tb = ['{}\n\t{}\n'.format(url, err) for url, err in iteritems(failures)] if not replacements: error_dialog(self, _('Download failed'), _( 'Failed to download external resources, click "Show details" for more information.'), det_msg='\n'.join(tb), show=True) self.reject() return else: warning_dialog(self, _('Some downloads failed'), _( 'Failed to download some external resources, click "Show details" for more information.'), det_msg='\n'.join(tb), show=True) self.state = 2 self.wait.msg = _('Updating resources in book...') self.wait.start() t = ngettext( 'Successfully processed the external resource', 'Successfully processed {} external resources', len(replacements)).format(len(replacements)) if failures: t += '<br>' + ngettext('Could not download one image', 'Could not download {} images', len(failures)).format(len(failures)) self.success.setText('<p style="text-align:center">' + t) resources = self.choose_resources.resources t = Thread(name='ReplaceResources', target=self.replace_resources, args=(resources, replacements)) t.daemon = True t.start()
def do_one(self): try: i, book_ids, pd, only_fmts, errors = self.job_data except (TypeError, AttributeError): return if i >= len(book_ids) or pd.wasCanceled(): pd.setValue(pd.maximum()) pd.hide() self.pd_timer.stop() self.job_data = None self.gui.library_view.model().refresh_ids(book_ids) if errors: det_msg = [_('The {0} format of {1}:\n\n{2}\n').format( (fmt or '').upper(), force_unicode(mi.title), force_unicode(tb)) for mi, fmt, tb in errors] warning_dialog( self.gui, _('Failed for some files'), _( 'Failed to embed metadata into some book files. Click "Show details" for details.'), det_msg='\n\n'.join(det_msg), show=True) return pd.setValue(i) db = self.gui.current_db.new_api def report_error(mi, fmt, tb): errors.append((mi, fmt, tb)) db.embed_metadata((book_ids[i],), only_fmts=only_fmts, report_error=report_error) self.job_data = (i + 1, book_ids, pd, only_fmts, errors)
def do_one(self): try: i, book_ids, pd, only_fmts, errors = self.job_data except (TypeError, AttributeError): return if i >= len(book_ids) or pd.wasCanceled(): pd.setValue(pd.maximum()) pd.hide() self.pd_timer.stop() self.job_data = None self.gui.library_view.model().refresh_ids(book_ids) if i > 0: self.gui.status_bar.show_message(_('Embedded metadata in %d books') % i, 5000) if errors: det_msg = [_('The {0} format of {1}:\n\n{2}\n').format( (fmt or '').upper(), force_unicode(mi.title), force_unicode(tb)) for mi, fmt, tb in errors] warning_dialog( self.gui, _('Failed for some files'), _( 'Failed to embed metadata into some book files. Click "Show details" for details.'), det_msg='\n\n'.join(det_msg), show=True) return pd.setValue(i) db = self.gui.current_db.new_api def report_error(mi, fmt, tb): errors.append((mi, fmt, tb)) db.embed_metadata((book_ids[i],), only_fmts=only_fmts, report_error=report_error) self.job_data = (i + 1, book_ids, pd, only_fmts, errors)
def commit(self): ConfigWidgetBase.commit(self) warning_dialog(self, _('Restart needed'), _('You need to restart the server for changes to' ' take effect'), show=True) return False
def show_no_results_found(self): has_hidden_text = self.last_hidden_text_warning is not None and self.last_hidden_text_warning[1] == self.current_search if self.current_search: if has_hidden_text: msg = _('No displayable matches were found for:') else: msg = _('No matches were found for:') warning_dialog(self, _('No matches found'), msg + ' <b>{}</b>'.format(self.current_search.text), show=True)
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id: set((db.formats(book_id, index_is_id=True) or "").upper().split(",")) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _("Auto convert the following books before sending via email?"), list(titles) ): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions["Convert Books"].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id: db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = ( _("This recipient has no valid formats defined") if nooutput else _("These books have no suitable input formats for conversion") ) det_msg.append("%s - %s" % (to, msg)) det_msg.extend("\t" + titles[bid] for bid in ids) det_msg.append("\n") warning_dialog( self, _("Could not send"), _("Could not send books to some recipients. Click Show Details for more information"), det_msg="\n".join(det_msg), show=True, )
def _files_added(self, paths=[], names=[], infos=[], on_card=None): self.gui.tags_view.disable_recounting = False if paths: self.gui.upload_books(paths, list(map(ascii_filename, names)), infos, on_card=on_card) self.gui.status_bar.show_message( _('Uploading books to device.'), 2000) if getattr(self._adder, 'number_of_books_added', 0) > 0: self.gui.library_view.model().books_added(self._adder.number_of_books_added) self.gui.library_view.set_current_row(0) if hasattr(self.gui, 'db_images'): self.gui.db_images.reset() self.gui.tags_view.recount() if getattr(self._adder, 'merged_books', False): merged = defaultdict(list) for title, author in self._adder.merged_books: merged[author].append(title) lines = [] for author in sorted(merged, key=sort_key): lines.append(author) for title in sorted(merged[author], key=sort_key): lines.append('\t' + title) lines.append('') info_dialog(self.gui, _('Merged some books'), _('The following %d duplicate books were found and incoming ' 'book formats were processed and merged into your ' 'Calibre database according to your automerge ' 'settings:')%len(self._adder.merged_books), det_msg='\n'.join(lines), show=True) if getattr(self._adder, 'number_of_books_added', 0) > 0 or \ getattr(self._adder, 'merged_books', False): # The formats of the current book could have changed if # automerge is enabled current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx) if getattr(self._adder, 'critical', None): det_msg = [] for name, log in self._adder.critical.items(): if isinstance(name, str): name = name.decode(filesystem_encoding, 'replace') det_msg.append(name+'\n'+log) warning_dialog(self.gui, _('Failed to read metadata'), _('Failed to read metadata from the following')+':', det_msg='\n\n'.join(det_msg), show=True) if hasattr(self._adder, 'cleanup'): self._adder.cleanup() self._adder.setParent(None) del self._adder self._adder = None
def existing_pb_clicked(self, qitem): item = qitem.data(Qt.UserRole) if (qitem.flags() & Qt.ItemIsEnabled): self.edit_format.setCurrentIndex(self.edit_format.findText(item[0])) self.edit_device.setCurrentIndex(self.edit_device.findText(item[1])) else: warning_dialog(self, '', _('The {0} device plugin is disabled.').format(item[1]), show=True)
def commit(self): if not self.save_changes(): raise AbortCommit() warning_dialog(self, _('Restart needed'), _('You need to restart the server for changes to' ' take effect'), show=True) return False
def add(self, books): if isinstance(books, basestring): error_dialog( self.pd, _("Path error"), _("The specified directory could not be processed."), det_msg=books, show=True ) return self.canceled() if not books: info_dialog(self.pd, _("No books"), _("No books found"), show=True) return self.canceled() books = [[b] if isinstance(b, basestring) else b for b in books] restricted = set() for i in xrange(len(books)): files = books[i] restrictedi = set(f for f in files if not os.access(f, os.R_OK)) if restrictedi: files = [f for f in files if os.access(f, os.R_OK)] books[i] = files restricted |= restrictedi if restrictedi: det_msg = u"\n".join(restrictedi) warning_dialog( self.pd, _("No permission"), _( "Cannot add some files as you do not have " " permission to access them. Click Show" " Details to see the list of such files." ), det_msg=det_msg, show=True, ) books = list(filter(None, books)) if not books: return self.canceled() self.rfind = None from calibre.ebooks.metadata.worker import read_metadata self.rq = Queue() tasks = [] self.ids = {} self.nmap = {} self.duplicates = [] for i, b in enumerate(books): tasks.append((i, b)) self.ids[i] = b self.nmap[i] = os.path.basename(b[0]) self.worker = read_metadata(tasks, self.rq, spare_server=self.spare_server) self.pd.set_min(0) self.pd.set_max(len(self.ids)) self.pd.value = 0 self.db_adder = DBAdder(self, self.db, self.ids, self.nmap) self.db_adder.start() self.last_added_at = time.time() self.entry_count = len(self.ids) self.continue_updating = True single_shot(self.update)
def add(self, books): if isinstance(books, basestring): error_dialog(self.pd, _('Path error'), _('The specified directory could not be processed.'), det_msg=books, show=True) return self.canceled() if not books: info_dialog(self.pd, _('No books'), _('No books found'), show=True) return self.canceled() books = [[b] if isinstance(b, basestring) else b for b in books] restricted = set() for i in xrange(len(books)): files = books[i] restrictedi = set(f for f in files if not os.access(f, os.R_OK)) if restrictedi: files = [f for f in files if os.access(f, os.R_OK)] books[i] = files restricted |= restrictedi if restrictedi: det_msg = u'\n'.join(restrictedi) warning_dialog(self.pd, _('No permission'), _('Cannot add some files as you do not have ' ' permission to access them. Click Show' ' Details to see the list of such files.'), det_msg=det_msg, show=True) books = list(filter(None, books)) if not books: return self.canceled() self.rfind = None from calibre.ebooks.metadata.worker import read_metadata self.rq = Queue() tasks = [] self.ids = {} self.nmap = {} self.duplicates = [] for i, b in enumerate(books): tasks.append((i, b)) self.ids[i] = b self.nmap[i] = os.path.basename(b[0]) self.worker = read_metadata(tasks, self.rq, spare_server=self.spare_server) self.pd.set_min(0) self.pd.set_max(len(self.ids)) self.pd.value = 0 self.db_adder = DBAdder(self, self.db, self.ids, self.nmap) self.db_adder.start() self.last_added_at = time.time() self.entry_count = len(self.ids) self.continue_updating = True single_shot(self.update)
def commit(self): if not self.save_changes(): raise AbortCommit() warning_dialog( self, _('Restart needed'), _('You need to restart the server for changes to' ' take effect'), show=True ) return False
def unapply_tags(self, node=None): nodes = self.applied_items_box.selectedItems() if node is None else [node] if len(nodes) == 0: warning_dialog(self, _('No items selected'), _('You must select items to unapply'), show=True, show_copy_button=False) return for node in nodes: index = self.all_items[node.data(Qt.ItemDataRole.UserRole)].index self.applied_items.remove(index) self.display_filtered_categories(None)
def check_if_writer_disabled(self, format_name): if format_name in ['device_db', plugboard_any_format_value]: return show_message = True for writer in self.format_to_writers_map[format_name]: if not is_disabled(writer): show_message = False if show_message: warning_dialog(self, '', _('That format has no metadata writers enabled. A plugboard ' 'will probably have no effect.'), show=True)
def search_result_not_found(self, sr): self.results.search_result_not_found(sr) if self.results.count(): now = monotonic() if self.last_hidden_text_warning is None or self.current_search != self.last_hidden_text_warning[1] or now - self.last_hidden_text_warning[0] > 5: self.last_hidden_text_warning = now, self.current_search warning_dialog(self, _('Hidden text'), _( 'Some search results were for hidden or non-reflowable text, they will be removed.'), show=True) elif self.last_hidden_text_warning is not None: self.last_hidden_text_warning = now, self.last_hidden_text_warning[1] if not self.results.count() and not self.spinner.is_running: self.show_no_results_found()
def change_library_allowed(self): if os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None): warning_dialog(self.gui, _('Not allowed'), _('You cannot change libraries while using the environment' ' variable CALIBRE_OVERRIDE_DATABASE_PATH.'), show=True) return False if self.gui.job_manager.has_jobs(): warning_dialog(self.gui, _('Not allowed'), _('You cannot change libraries while jobs' ' are running.'), show=True) return False return True
def _show_success_msg(restorer, parent=None): r = restorer olddb = _('The old database was saved as: %s')%force_unicode(r.olddb, filesystem_encoding) if r.errors_occurred: warning_dialog(parent, _('Success'), _('Restoring the database succeeded with some warnings' ' click "Show details" to see the details. %s')%olddb, det_msg=r.report, show=True) else: info_dialog(parent, _('Success'), _('Restoring database was successful. %s')%olddb, show=True, show_copy_button=False)
def _show_success_msg(restorer, parent=None): r = restorer olddb = _('The old database was saved as: %s')%force_unicode(r.olddb, filesystem_encoding) if r.errors_occurred: warning_dialog(parent, _('Success'), _('Restoring the database succeeded with some warnings' ' click Show details to see the details. %s')%olddb, det_msg=r.report, show=True) else: info_dialog(parent, _('Success'), _('Restoring database was successful. %s')%olddb, show=True, show_copy_button=False)
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id:set((db.formats(book_id, index_is_id=True) or '').upper().split(',')) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _('Auto convert the following books before sending via email?'), list(titles)): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions['Convert Books'].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id:db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = _('This recipient has no valid formats defined') if nooutput else \ _('These books have no suitable input formats for conversion') det_msg.append('%s - %s' % (to, msg)) det_msg.extend('\t' + titles[bid] for bid in ids) det_msg.append('\n') warning_dialog(self, _('Could not send'), _('Could not send books to some recipients. Click Show Details for more information'), det_msg='\n'.join(det_msg), show=True)
def _remove_action(self, indices): names = self.current_actions.model().names(indices) if names: not_removed = self.current_actions.model().remove(indices) ns = {y.name for y in not_removed} removed = set(names) - ns self.all_actions.model().add(removed) if not_removed: warning_dialog(self, _('Cannot remove'), _('Cannot remove the actions %s from this location') % ','.join([a.action_spec[0] for a in not_removed]), show=True) else: self.changed_signal.emit()
def do_queue(self): self.hide() if len(self.bad): res = [] for book_id, error in self.bad.iteritems(): title = self.db.title(book_id, True) res.append('%s (%s)'%(title, error)) msg = '%s' % '\n'.join(res) summary_msg = 'Could not analyse %d of %d books, for reasons shown in details below.' warning_dialog(self.gui, 'Page/word/statistics warnings', summary_msg % (len(res), len(self.book_ids)), msg).exec_() self.gui = None # Queue a job to process these books self.queue(self.tdir, self.books_to_scan, self.statistics_cols_map, self.pages_algorithm, self.use_goodreads)
def remove_action(self, *args): x = self.current_actions.selectionModel().selectedIndexes() names = self.current_actions.model().names(x) if names: not_removed = self.current_actions.model().remove(x) ns = set([y.name for y in not_removed]) removed = set(names) - ns self.all_actions.model().add(removed) if not_removed: warning_dialog(self, _('Cannot remove'), _('Cannot remove the actions %s from this location') % ','.join([a.action_spec[0] for a in not_removed]), show=True) else: self.changed_signal.emit()
def apply_tags(self, node=None): if self.current_cat_name is None: return nodes = self.available_items_box.selectedItems() if node is None else [node] if len(nodes) == 0: warning_dialog(self, _('No items selected'), _('You must select items to apply'), show=True, show_copy_button=False) return for node in nodes: index = self.all_items[node.data(Qt.ItemDataRole.UserRole)].index if index not in self.applied_items: self.applied_items.append(index) self.applied_items.sort(key=lambda x:sort_key(self.all_items[x].name)) self.display_filtered_categories(None)
def do_queue(self): if self.gui is None: # There is a nasty QT bug with the timers/logic above which can # result in the do_queue method being called twice return self.hide() if self.books_to_scan == []: warning_dialog(self.gui, 'Extract ISBN failed', 'Scan aborted as no books with formats found.', show_copy_button=False).exec_() self.gui = None if self.books_to_scan: # Queue a job to process these books self.queue(self.books_to_scan, self.failed_ids, self.no_format_ids)
def do_queue(self): self.hide() if self.bad != []: res = [] for id in self.bad: title = self.db.title(id, True) res.append('%s'%title) msg = '%s' % '\n'.join(res) warning_dialog(self.parent, _('Could not convert some books'), _('Could not convert %(num)d of %(tot)d books, because no suitable ' 'source format was found.') % dict(num=len(res), tot=len(self.book_ids)), msg).exec_() self.parent = None self.jobs.reverse() self.queue(self.jobs, self.changed, self.bad, *self.args)
def create_button_clicked(self): self.changed_signal.emit() name = unicode(self.function_name.currentText()) if name in self.funcs: error_dialog(self.gui, _('Template functions'), _('Name %s already used')%(name,), show=True) return if self.argument_count.value() == 0: box = warning_dialog(self.gui, _('Template functions'), _('Argument count should be -1 or greater than zero. ' 'Setting it to zero means that this function cannot ' 'be used in single function mode.'), det_msg = '', show=False) box.bb.setStandardButtons(box.bb.standardButtons() | QDialogButtonBox.Cancel) box.det_msg_toggle.setVisible(False) if not box.exec_(): return try: prog = unicode(self.program.toPlainText()) cls = compile_user_function(name, unicode(self.documentation.toPlainText()), self.argument_count.value(), prog) self.funcs[name] = cls self.build_function_names_box(scroll_to=name) except: error_dialog(self.gui, _('Template functions'), _('Exception while compiling function'), show=True, det_msg=traceback.format_exc())
def create_button_clicked(self): self.changed_signal.emit() name = unicode(self.function_name.currentText()) if name in self.funcs: error_dialog(self.gui, _('Template functions'), _('Name %s already used')%(name,), show=True) return if self.argument_count.value() == 0: box = warning_dialog(self.gui, _('Template functions'), _('Argument count should be -1 or greater than zero. ' 'Setting it to zero means that this function cannot ' 'be used in single function mode.'), det_msg='', show=False) box.bb.setStandardButtons(box.bb.standardButtons() | QDialogButtonBox.Cancel) box.det_msg_toggle.setVisible(False) if not box.exec_(): return try: prog = unicode(self.program.toPlainText()) cls = compile_user_function(name, unicode(self.documentation.toPlainText()), self.argument_count.value(), prog) self.funcs[name] = cls self.build_function_names_box(scroll_to=name) except: error_dialog(self.gui, _('Template functions'), _('Exception while compiling function'), show=True, det_msg=traceback.format_exc())
def st_function_index_changed(self, txt): txt = str(txt) if self.st_current_program_name: if self.st_current_program_text != self.te_textbox.toPlainText(): box = warning_dialog( self.gui, _('Template functions'), _('Changes to the current template will be lost. OK?'), det_msg='', show=False, show_copy_button=False) box.bb.setStandardButtons( box.bb.standardButtons() | QDialogButtonBox.StandardButton.Cancel) box.det_msg_toggle.setVisible(False) if not box.exec(): self.te_name.blockSignals(True) dex = self.te_name.findText(self.st_current_program_name) self.te_name.setCurrentIndex(dex) self.te_name.blockSignals(False) return self.st_create_button.setEnabled(False) self.st_current_program_name = txt if not txt: self.te_textbox.clear() self.template_editor.new_doc.clear() return func = self.st_funcs[txt] self.st_current_program_text = func.program_text self.template_editor.new_doc.setPlainText(func.doc) self.te_textbox.setPlainText(func.program_text) self.st_template_name_edited(txt)
def _books_saved(self, path, failures, error): self._saver = None if error: return error_dialog(self.gui, _('Error while saving'), _('There was an error while saving.'), error, show=True) if failures: failures = [u'%s\n\t%s'% (title, '\n\t'.join(err.splitlines())) for title, err in failures] warning_dialog(self.gui, _('Could not save some books'), _('Could not save some books') + ', ' + _('Click the show details button to see which ones.'), u'\n\n'.join(failures), show=True) if gprefs['show_files_after_save']: open_local_file(path)
def _add_action(self, indices): names = self.all_actions.model().names(indices) if names: not_added = self.current_actions.model().add(names) ns = {y.name for y in not_added} added = set(names) - ns self.all_actions.model().remove(indices, added) if not_added: warning_dialog(self, _('Cannot add'), _('Cannot add the actions %s to this location') % ','.join([a.action_spec[0] for a in not_added]), show=True) if added: ca = self.current_actions idx = ca.model().index(ca.model().rowCount(None)-1) ca.scrollTo(idx) self.changed_signal.emit()
def add_action(self, *args): x = self.all_actions.selectionModel().selectedIndexes() names = self.all_actions.model().names(x) if names: not_added = self.current_actions.model().add(names) ns = set([y.name for y in not_added]) added = set(names) - ns self.all_actions.model().remove(x, added) if not_added: warning_dialog(self, _('Cannot add'), _('Cannot add the actions %s to this location') % ','.join([a.action_spec[0] for a in not_added]), show=True) if added: ca = self.current_actions idx = ca.model().index(ca.model().rowCount(None)-1) ca.scrollTo(idx) self.changed_signal.emit()
def catalog_generated(self, job): if job.result: # Problems during catalog generation # jobs.results is a list - the first entry is the intended title for the dialog # Subsequent strings are error messages dialog_title = job.result.pop(0) if re.search('warning', job.result[0].lower()): msg = _("Catalog generation complete, with warnings.") warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True) else: job.result.append("Catalog generation terminated.") error_dialog(self.gui, dialog_title,'\n'.join(job.result),show=True) return if job.failed: return self.gui.job_exception(job) if dynamic.get('catalog_add_to_library', True): id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title) self.gui.library_view.model().beginResetModel(), self.gui.library_view.model().endResetModel() if job.catalog_sync: sync = dynamic.get('catalogs_to_be_synced', set([])) sync.add(id) dynamic.set('catalogs_to_be_synced', sync) self.gui.status_bar.show_message(_('Catalog generated.'), 3000) self.gui.sync_catalogs() if not dynamic.get('catalog_add_to_library', True) or job.fmt not in {'EPUB','MOBI', 'AZW3'}: export_dir = choose_dir(self.gui, _('Export Catalog Directory'), _('Select destination for %(title)s.%(fmt)s') % dict( title=job.catalog_title, fmt=job.fmt.lower())) if export_dir: destination = os.path.join(export_dir, '%s.%s' % ( sanitize_file_name(job.catalog_title), job.fmt.lower())) try: shutil.copyfile(job.catalog_file_path, destination) except EnvironmentError as err: if getattr(err, 'errno', None) == errno.EACCES: # Permission denied import traceback error_dialog(self.gui, _('Permission denied'), _('Could not open %s. Is it being used by another' ' program?')%destination, det_msg=traceback.format_exc(), show=True) return raise
def change_library_allowed(self): if os.environ.get("CALIBRE_OVERRIDE_DATABASE_PATH", None): warning_dialog( self.gui, _("Not allowed"), _( "You cannot change libraries while using the environment" " variable CALIBRE_OVERRIDE_DATABASE_PATH." ), show=True, ) return False if self.gui.job_manager.has_jobs(): warning_dialog( self.gui, _("Not allowed"), _("You cannot change libraries while jobs" " are running."), show=True ) return False return True
def catalog_generated(self, job): if job.result: # Problems during catalog generation # jobs.results is a list - the first entry is the intended title for the dialog # Subsequent strings are error messages dialog_title = job.result.pop(0) if re.match('warning:', job.result[0].lower()): msg = _("Catalog generation complete, with warnings.") warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True) else: job.result.append("Catalog generation terminated.") error_dialog(self.gui, dialog_title, '\n'.join(job.result), show=True) return if job.failed: return self.gui.job_exception(job) id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title) self.gui.library_view.model().reset() if job.catalog_sync: sync = dynamic.get('catalogs_to_be_synced', set([])) sync.add(id) dynamic.set('catalogs_to_be_synced', sync) self.gui.status_bar.show_message(_('Catalog generated.'), 3000) self.gui.sync_catalogs() if job.fmt not in ['EPUB', 'MOBI']: export_dir = choose_dir( self.gui, _('Export Catalog Directory'), _('Select destination for %(title)s.%(fmt)s') % dict(title=job.catalog_title, fmt=job.fmt.lower())) if export_dir: destination = os.path.join( export_dir, '%s.%s' % (sanitize_file_name_unicode( job.catalog_title), job.fmt.lower())) shutil.copyfile(job.catalog_file_path, destination)
def start_content_server(self, check_started=True): from calibre.srv.embedded import Server if not gprefs.get('server3_warning_done', False): gprefs.set('server3_warning_done', True) if os.path.exists(os.path.join(config_dir, 'server.py')): try: os.remove(os.path.join(config_dir, 'server.py')) except EnvironmentError: pass warning_dialog(self, _('Content server changed!'), _( 'calibre 3 comes with a completely re-written content server.' ' As such any custom configuration you have for the content' ' server no longer applies. You should check and refresh your' ' settings in Preferences->Sharing->Sharing over the net'), show=True) self.content_server = Server(self.library_broker, Dispatcher(self.handle_changes_from_server)) self.content_server.state_callback = Dispatcher( self.iactions['Connect Share'].content_server_state_changed) if check_started: self.content_server.start_failure_callback = \ Dispatcher(self.content_server_start_failed) self.content_server.start()
def do_test(self): from calibre.ebooks.metadata.meta import metadata_from_filename fname = unicode(self.filename.text()) ext = os.path.splitext(fname)[1][1:].lower() if ext not in BOOK_EXTENSIONS: return warning_dialog( self, _("Test name invalid"), _( "The name <b>%r</b> does not appear to end with a" " file extension. The name must end with a file " " extension like .epub or .mobi" ) % fname, show=True, ) try: pat = self.pattern() except Exception as err: error_dialog(self, _("Invalid regular expression"), _("Invalid regular expression: %s") % err).exec_() return mi = metadata_from_filename(fname, pat) if mi.title: self.title.setText(mi.title) else: self.title.setText(_("No match")) if mi.authors: self.authors.setText(", ".join(mi.authors)) else: self.authors.setText(_("No match")) if mi.series: self.series.setText(mi.series) else: self.series.setText(_("No match")) if mi.series_index is not None: self.series_index.setText(str(mi.series_index)) else: self.series_index.setText(_("No match")) if mi.publisher: self.publisher.setText(mi.publisher) if mi.pubdate: self.pubdate.setText(mi.pubdate.strftime("%Y-%m-%d")) self.isbn.setText(_("No match") if mi.isbn is None else str(mi.isbn))
def do_test(self): from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.meta import metadata_from_filename fname = unicode_type(self.filename.text()) ext = os.path.splitext(fname)[1][1:].lower() if ext not in BOOK_EXTENSIONS: return warning_dialog(self, _('Test file name invalid'), _('The file name <b>%s</b> does not appear to end with a' ' file extension. It must end with a file ' ' extension like .epub or .mobi')%fname, show=True) try: pat = self.pattern() except Exception as err: error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s')%err).exec_() return mi = metadata_from_filename(fname, pat) if mi.title: self.title.setText(mi.title) else: self.title.setText(_('No match')) if mi.authors: self.authors.setText(authors_to_string(mi.authors)) else: self.authors.setText(_('No match')) if mi.series: self.series.setText(mi.series) else: self.series.setText(_('No match')) if mi.series_index is not None: self.series_index.setText(str(mi.series_index)) else: self.series_index.setText(_('No match')) if mi.publisher: self.publisher.setText(mi.publisher) else: self.publisher.setText(_('No match')) if mi.pubdate: self.pubdate.setText(strftime('%Y-%m-%d', mi.pubdate)) else: self.pubdate.setText(_('No match')) self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn)) self.comments.setText(mi.comments if mi.comments else _('No match'))
def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') d = warning_dialog(self, _('Restart needed'), msg, show_copy_button=False) b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True)
def do_user_config(self, parent=None): ''' This method shows a configuration dialog for this plugin. It returns True if the user clicks OK, False otherwise. The changes are automatically applied. ''' from PyQt5.Qt import QDialog, QDialogButtonBox, QVBoxLayout, \ QLabel, Qt, QLineEdit from calibre.gui2 import gprefs prefname = 'plugin config dialog:'+self.type + ':' + self.name geom = gprefs.get(prefname, None) config_dialog = QDialog(parent) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) v = QVBoxLayout(config_dialog) def size_dialog(): if geom is None: config_dialog.resize(config_dialog.sizeHint()) else: config_dialog.restoreGeometry(geom) button_box.accepted.connect(config_dialog.accept) button_box.rejected.connect(config_dialog.reject) config_dialog.setWindowTitle(_('Customize') + ' ' + self.name) try: config_widget = self.config_widget() except NotImplementedError: config_widget = None if isinstance(config_widget, tuple): from calibre.gui2 import warning_dialog warning_dialog(parent, _('Cannot configure'), config_widget[0], det_msg=config_widget[1], show=True) return False if config_widget is not None: v.addWidget(config_widget) v.addWidget(button_box) size_dialog() config_dialog.exec_() if config_dialog.result() == QDialog.Accepted: if hasattr(config_widget, 'validate'): if config_widget.validate(): self.save_settings(config_widget) else: self.save_settings(config_widget) else: from calibre.customize.ui import plugin_customization, \ customize_plugin help_text = self.customization_help(gui=True) help_text = QLabel(help_text, config_dialog) help_text.setWordWrap(True) help_text.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) help_text.setOpenExternalLinks(True) v.addWidget(help_text) sc = plugin_customization(self) if not sc: sc = '' sc = sc.strip() sc = QLineEdit(sc, config_dialog) v.addWidget(sc) v.addWidget(button_box) size_dialog() config_dialog.exec_() if config_dialog.result() == QDialog.Accepted: sc = unicode(sc.text()).strip() customize_plugin(self, sc) geom = bytearray(config_dialog.saveGeometry()) gprefs[prefname] = geom return config_dialog.result()
def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _('Unknown') if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_('E-book:')+ ' '+t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + '\n\n' + t + '\n\t' + _('by') + ' ' + a + '\n\n' + _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) prefix = ascii_filename(t+' - '+a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager) self.status_bar.show_message(_('Sending email to')+' '+to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format is None: dbfmts = self.library_view.model().db.formats(id, index_is_id=True) formats = [f.lower() for f in (dbfmts.split(',') if dbfmts else [])] if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []: auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) else: if specific_format in list(set(fmts).intersection(set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: format = specific_format if specific_format in list(set(fmts).intersection(set(available_output_formats()))) else None if not format: for fmt in fmts: if fmt in list(set(fmts).intersection(set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] if self.auto_convert_question( _('Auto convert the following books to %s before sending via ' 'email?') % format.upper(), autos): self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s'%(i,) for i in bad) d = warning_dialog(self, _('No suitable formats'), _('Could not email the following books ' 'as no suitable formats were found:'), bad) d.exec_()