From e50c6d98a4ab0986c3065ef69841506fd81801c2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Dec 2010 17:40:54 -0700 Subject: [PATCH] Speed up deleting of large numbers of books and show progress while doing so --- src/calibre/gui2/actions/delete.py | 63 ++++++++++++++++++++++++++++-- src/calibre/gui2/library/models.py | 13 +++--- src/calibre/library/database2.py | 9 +++-- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index a541590fd1..1cb1828a0f 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -5,13 +5,65 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QMenu +from functools import partial + +from PyQt4.Qt import QMenu, QObject, QTimer from calibre.gui2 import error_dialog from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.actions import InterfaceAction +single_shot = partial(QTimer.singleShot, 10) + +class MultiDeleter(QObject): + + def __init__(self, gui, rows, callback): + from calibre.gui2.dialogs.progress import ProgressDialog + QObject.__init__(self, gui) + self.model = gui.library_view.model() + self.ids = list(map(self.model.id, rows)) + 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)) + self.pd.setModal(True) + self.pd.show() + + def delete_one(self): + if not self.ids: + self.cleanup() + return + id_ = self.ids.pop() + title = 'id%d'%id_ + try: + title = self.model.db.title(id_, index_is_id=True) + self.model.db.delete_book(id_, notify=False, commit=False) + self.deleted_ids.append(id_) + except: + import traceback + self.failures.append((id_, title, traceback.format_exc())) + single_shot(self.delete_one) + self.pd.value += 1 + self.pd.set_msg(_('Deleted') + ' ' + title) + + def cleanup(self): + self.pd.hide() + self.pd = None + self.model.db.commit() + self.model.db.clean() + self.model.books_deleted() + self.gui.tags_view.recount() + self.callback(self.deleted_ids) + if self.failures: + msg = ['==>'+x[1]+'\n'+x[2] for x in self.failures] + error_dialog(self.gui, _('Failed to delete'), + _('Failed to delete some books, click the Show Details button' + ' for details.'), det_msg='\n\n'.join(msg), show=True) + class DeleteAction(InterfaceAction): name = 'Remove Books' @@ -179,8 +231,13 @@ class DeleteAction(InterfaceAction): row = None if ci.isValid(): row = ci.row() - ids_deleted = view.model().delete_books(rows) - self.library_ids_deleted(ids_deleted, row) + if len(rows) < 5: + ids_deleted = view.model().delete_books(rows) + self.library_ids_deleted(ids_deleted, row) + else: + self.__md = MultiDeleter(self.gui, rows, + partial(self.library_ids_deleted, current_row=row)) + else: if not confirm('

'+_('The selected books will be ' 'permanently deleted ' diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index e82e1dddd4..8478a6d263 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -223,21 +223,22 @@ class BooksModel(QAbstractTableModel): # {{{ def by_author(self): return self.sorted_on[0] == 'authors' + def books_deleted(self): + self.count_changed() + self.clear_caches() + self.reset() + def delete_books(self, indices): ids = map(self.id, indices) for id in ids: self.db.delete_book(id, notify=False) - self.count_changed() - self.clear_caches() - self.reset() + self.books_deleted() return ids def delete_books_by_id(self, ids): for id in ids: self.db.delete_book(id) - self.count_changed() - self.clear_caches() - self.reset() + self.books_deleted() def books_added(self, num): if num > 0: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4efb5e6233..1229b60577 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -953,23 +953,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) return True - def delete_book(self, id, notify=True): + def delete_book(self, id, notify=True, commit=True): ''' Removes book from the result cache and the underlying database. + If you set commit to False, you must call clean() manually afterwards ''' try: path = os.path.join(self.library_path, self.path(id, index_is_id=True)) except: path = None - self.data.remove(id) if path and os.path.exists(path): self.rmtree(path) parent = os.path.dirname(path) if len(os.listdir(parent)) == 0: self.rmtree(parent) self.conn.execute('DELETE FROM books WHERE id=?', (id,)) - self.conn.commit() - self.clean() + if commit: + self.conn.commit() + self.clean() self.data.books_deleted([id]) if notify: self.notify('delete', [id])