def add_books_from_device(self, view, paths=None): backloading_err = self.gui.device_manager.device.BACKLOADING_ERROR_MESSAGE if backloading_err is not None: return error_dialog(self.gui, _('Add to library'), backloading_err, show=True) if paths is None: rows = view.selectionModel().selectedRows() if not rows or len(rows) == 0: d = error_dialog(self.gui, _('Add to library'), _('No book selected')) d.exec_() return paths = [p for p in view.model().paths(rows) if p is not None] ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS def ext(x): ans = os.path.splitext(x)[1] ans = ans[1:] if len(ans) > 1 else ans return ans.lower() remove = {p for p in paths if ext(p) in ve} if remove: paths = [p for p in paths if p not in remove] vmsg = getattr(self.gui.device_manager.device, 'VIRTUAL_BOOK_EXTENSION_MESSAGE', None) or _( 'The following books are virtual and cannot be added' ' to the calibre library:') info_dialog(self.gui, _('Not Implemented'), vmsg, '\n'.join(remove), show=True) if not paths: return if not paths or len(paths) == 0: d = error_dialog(self.gui, _('Add to library'), _('No book files found')) d.exec_() return self.gui.device_manager.prepare_addable_books(self.Dispatcher(partial( self.books_prepared, view)), paths) self.bpd = ProgressDialog(_('Downloading books'), msg=_('Downloading books from device'), parent=self.gui, cancelable=False) QTimer.singleShot(1000, self.show_bpd)
def __init__(self, gui, ids, callback): from calibre.gui2.dialogs.progress import ProgressDialog QObject.__init__(self, gui) self.model = gui.library_view.model() self.ids = ids self.permanent = False if can_recycle and len(ids) > 100: if question_dialog(gui, _('Are you sure?'), '<p>'+ _('You are trying to delete %d books. ' 'Sending so many files to the Recycle' ' Bin <b>can be slow</b>. Should calibre skip the' ' Recycle Bin? If you click Yes the files' ' will be <b>permanently deleted</b>.')%len(ids)): self.permanent = True self.gui = gui self.failures = [] self.deleted_ids = [] self.callback = callback single_shot(self.delete_one) self.pd = ProgressDialog(_('Deleting...'), parent=gui, cancelable=False, min=0, max=len(self.ids), icon='trash.png') self.pd.setModal(True) self.pd.show()
def apply_metadata_changes(self, id_map, title=None, msg='', callback=None, merge_tags=True, merge_comments=False, icon=None): ''' Apply the metadata changes in id_map to the database synchronously id_map must be a mapping of ids to Metadata objects. Set any fields you do not want updated in the Metadata object to null. An easy way to do that is to create a metadata object as Metadata(_('Unknown')) and then only set the fields you want changed on this object. callback can be either None or a function accepting a single argument, in which case it is called after applying is complete with the list of changed ids. id_map can also be a mapping of ids to 2-tuple's where each 2-tuple contains the absolute paths to an OPF and cover file respectively. If either of the paths is None, then the corresponding metadata is not updated. ''' if title is None: title = _('Applying changed metadata') self.apply_id_map = list(id_map.iteritems()) self.apply_current_idx = 0 self.apply_failures = [] self.applied_ids = set() self.apply_pd = None self.apply_callback = callback if len(self.apply_id_map) > 1: from calibre.gui2.dialogs.progress import ProgressDialog self.apply_pd = ProgressDialog(title, msg, min=0, max=len(self.apply_id_map)-1, parent=self.gui, cancelable=False, icon=icon) self.apply_pd.setModal(True) self.apply_pd.show() self._am_merge_tags = merge_tags self._am_merge_comments = merge_comments self.do_one_apply()
def do_copy(self, ids, db, loc, delete_after, add_duplicates=False): aname = _('Moving to') if delete_after else _('Copying to') dtitle = '%s %s' % (aname, os.path.basename(loc)) self.pd = ProgressDialog(dtitle, min=0, max=len(ids) - 1, parent=self.gui, cancelable=True, icon='lt.png') def progress(idx, title): self.pd.set_msg(title) self.pd.set_value(idx) self.worker = Worker(ids, db, loc, Dispatcher(progress), Dispatcher(self.pd.accept), delete_after, add_duplicates) self.worker.start() self.pd.canceled_signal.connect(self.worker.cancel_processing) self.pd.exec_() self.pd.canceled_signal.disconnect() if self.worker.left_after_cancel: msg = _( 'The copying process was interrupted. {} books were copied.' ).format(len(self.worker.processed)) if delete_after: msg += ' ' + _('No books were deleted from this library.') msg += ' ' + _( 'The best way to resume this operation is to re-copy all the books with the option to' ' "Check for duplicates when copying to library" in Preferences->Import/export->Adding books turned on.' ) warning_dialog(self.gui, _('Canceled'), msg, show=True) return if self.worker.error is not None: e, tb = self.worker.error error_dialog(self.gui, _('Failed'), _('Could not copy books: ') + e, det_msg=tb, show=True) return if delete_after: donemsg = ngettext('Moved the book to {loc}', 'Moved {num} books to {loc}', len(self.worker.processed)) else: donemsg = ngettext('Copied the book to {loc}', 'Copied {num} books to {loc}', len(self.worker.processed)) self.gui.status_bar.show_message( donemsg.format(num=len(self.worker.processed), loc=loc), 2000) if self.worker.auto_merged_ids: books = '\n'.join(self.worker.auto_merged_ids.itervalues()) info_dialog(self.gui, _('Auto merged'), _('Some books were automatically merged into existing ' 'records in the target library. Click "Show ' 'details" to see which ones. This behavior is ' 'controlled by the Auto-merge option in ' 'Preferences->Import/export->Adding books.'), det_msg=books, show=True) if delete_after and self.worker.processed: v = self.gui.library_view ci = v.currentIndex() row = None if ci.isValid(): row = ci.row() v.model().delete_books_by_id(self.worker.processed, permanent=True) self.gui.iactions['Remove Books'].library_ids_deleted( self.worker.processed, row) if self.worker.failed_books: def fmt_err(book_id): err, tb = self.worker.failed_books[book_id] title = db.title(book_id, index_is_id=True) return _('Copying: {0} failed, with error:\n{1}').format( title, tb) title, msg = _('Failed to copy some books'), _( 'Could not copy some books, click "Show Details" for more information.' ) tb = '\n\n'.join(map(fmt_err, self.worker.failed_books)) tb = ngettext('Failed to copy a book, see below for details', 'Failed to copy {} books, see below for details', len(self.worker.failed_books)).format( len(self.worker.failed_books)) + '\n\n' + tb if len(ids) == len(self.worker.failed_books): title, msg = _('Failed to copy books'), _( 'Could not copy any books, click "Show Details" for more information.' ) error_dialog(self.gui, title, msg, det_msg=tb, show=True) return self.worker.duplicate_ids
def __init__(self, source, single_book_per_directory=True, db=None, parent=None, callback=None, pool=None, list_of_archives=False): if isinstance(source, str): source = make_long_path_useable(source) else: source = list(map(make_long_path_useable, source)) if not validate_source(source, parent): return QObject.__init__(self, parent) self.author_map_rules = None if gprefs.get('author_map_on_add_rules'): from calibre.ebooks.metadata.author_mapper import compile_rules as acr self.author_map_rules = acr(gprefs['author_map_on_add_rules']) self.single_book_per_directory = single_book_per_directory self.ignore_opf = False self.list_of_archives = list_of_archives self.callback = callback self.add_formats_to_existing = prefs['add_formats_to_existing'] self.do_one_signal.connect(self.tick, type=Qt.ConnectionType.QueuedConnection) self.pool = pool self.pd = ProgressDialog(_('Adding books...'), _('Scanning for files...'), min=0, max=0, parent=parent, icon='add_book.png') self.win_id = None if parent is not None and hasattr(parent, 'effectiveWinId'): self.win_id = parent.effectiveWinId() if self.win_id is not None: self.win_id = int(self.win_id) self.db = getattr(db, 'new_api', None) if self.db is not None: self.dbref = weakref.ref(db) self.source = source self.tdir = PersistentTemporaryDirectory('_add_books') self.scan_error = None self.file_groups = OrderedDict() self.abort_scan = False self.duplicates = [] self.report = [] self.items = [] self.added_book_ids = set() self.merged_formats_added_to = set() self.merged_books = set() self.added_duplicate_info = set() self.pd.show() self.scan_thread = Thread(target=self.scan, name='ScanBooks') self.scan_thread.daemon = True self.scan_thread.start() self.do_one = self.monitor_scan self.do_one_signal.emit() if DEBUG: self.start_time = time.time()
def copy_to_library(self, loc, delete_after=False): rows = self.gui.library_view.selectionModel().selectedRows() if not rows or len(rows) == 0: return error_dialog(self.gui, _('Cannot copy'), _('No books selected'), show=True) ids = list(map(self.gui.library_view.model().id, rows)) db = self.gui.library_view.model().db if not db.exists_at(loc): return error_dialog(self.gui, _('No library'), _('No library found at %s') % loc, show=True) self.pd = ProgressDialog(_('Copying'), min=0, max=len(ids) - 1, parent=self.gui, cancelable=False) def progress(idx, title): self.pd.set_msg(_('Copying') + ' ' + title) self.pd.set_value(idx) self.worker = Worker(ids, db, loc, Dispatcher(progress), Dispatcher(self.pd.accept), delete_after) self.worker.start() self.pd.exec_() if self.worker.error is not None: e, tb = self.worker.error error_dialog(self.gui, _('Failed'), _('Could not copy books: ') + e, det_msg=tb, show=True) else: self.gui.status_bar.show_message( _('Copied %(num)d books to %(loc)s') % dict(num=len(ids), loc=loc), 2000) if self.worker.auto_merged_ids: books = '\n'.join(self.worker.auto_merged_ids.itervalues()) info_dialog( self.gui, _('Auto merged'), _('Some books were automatically merged into existing ' 'records in the target library. Click Show ' 'details to see which ones. This behavior is ' 'controlled by the Auto merge option in ' 'Preferences->Adding books.'), det_msg=books, show=True) if delete_after and self.worker.processed: v = self.gui.library_view ci = v.currentIndex() row = None if ci.isValid(): row = ci.row() v.model().delete_books_by_id(self.worker.processed, permanent=True) self.gui.iactions['Remove Books'].library_ids_deleted( self.worker.processed, row)
def do_copy(self, ids, db, loc, delete_after, add_duplicates=False): aname = _('Moving to') if delete_after else _('Copying to') dtitle = '%s %s' % (aname, os.path.basename(loc)) self.pd = ProgressDialog(dtitle, min=0, max=len(ids) - 1, parent=self.gui, cancelable=False, icon='lt.png') def progress(idx, title): self.pd.set_msg(title) self.pd.set_value(idx) self.worker = Worker(ids, db, loc, Dispatcher(progress), Dispatcher(self.pd.accept), delete_after, add_duplicates) self.worker.start() self.pd.exec_() donemsg = _('Copied %(num)d books to %(loc)s') if delete_after: donemsg = _('Moved %(num)d books to %(loc)s') if self.worker.error is not None: e, tb = self.worker.error error_dialog(self.gui, _('Failed'), _('Could not copy books: ') + e, det_msg=tb, show=True) return self.gui.status_bar.show_message( donemsg % dict(num=len(self.worker.processed), loc=loc), 2000) if self.worker.auto_merged_ids: books = '\n'.join(self.worker.auto_merged_ids.itervalues()) info_dialog(self.gui, _('Auto merged'), _('Some books were automatically merged into existing ' 'records in the target library. Click Show ' 'details to see which ones. This behavior is ' 'controlled by the Auto merge option in ' 'Preferences->Adding books.'), det_msg=books, show=True) if delete_after and self.worker.processed: v = self.gui.library_view ci = v.currentIndex() row = None if ci.isValid(): row = ci.row() v.model().delete_books_by_id(self.worker.processed, permanent=True) self.gui.iactions['Remove Books'].library_ids_deleted( self.worker.processed, row) if self.worker.failed_books: def fmt_err(book_id): err, tb = self.worker.failed_books[book_id] title = db.title(book_id, index_is_id=True) return _('Copying: {0} failed, with error:\n{1}').format( title, tb) title, msg = _('Failed to copy some books'), _( 'Could not copy some books, click "Show Details" for more information.' ) tb = '\n\n'.join(map(fmt_err, self.worker.failed_books)) tb = _('Failed to copy {0} book(s), see below for details').format( len(self.worker.failed_books)) + '\n\n' + tb if len(ids) == len(self.worker.failed_books): title, msg = _('Failed to copy books'), _( 'Could not copy any books, click "Show Details" for more information.' ) error_dialog(self.gui, title, msg, det_msg=tb, show=True) return self.worker.duplicate_ids