Speed up deleting of large numbers of books and show progress while doing so

This commit is contained in:
Kovid Goyal 2010-12-07 17:40:54 -07:00
parent 418b036d4f
commit e50c6d98a4
3 changed files with 72 additions and 13 deletions

View File

@ -5,13 +5,65 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __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 import error_dialog
from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.actions import InterfaceAction 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): class DeleteAction(InterfaceAction):
name = 'Remove Books' name = 'Remove Books'
@ -179,8 +231,13 @@ class DeleteAction(InterfaceAction):
row = None row = None
if ci.isValid(): if ci.isValid():
row = ci.row() row = ci.row()
if len(rows) < 5:
ids_deleted = view.model().delete_books(rows) ids_deleted = view.model().delete_books(rows)
self.library_ids_deleted(ids_deleted, row) self.library_ids_deleted(ids_deleted, row)
else:
self.__md = MultiDeleter(self.gui, rows,
partial(self.library_ids_deleted, current_row=row))
else: else:
if not confirm('<p>'+_('The selected books will be ' if not confirm('<p>'+_('The selected books will be '
'<b>permanently deleted</b> ' '<b>permanently deleted</b> '

View File

@ -223,21 +223,22 @@ class BooksModel(QAbstractTableModel): # {{{
def by_author(self): def by_author(self):
return self.sorted_on[0] == 'authors' return self.sorted_on[0] == 'authors'
def books_deleted(self):
self.count_changed()
self.clear_caches()
self.reset()
def delete_books(self, indices): def delete_books(self, indices):
ids = map(self.id, indices) ids = map(self.id, indices)
for id in ids: for id in ids:
self.db.delete_book(id, notify=False) self.db.delete_book(id, notify=False)
self.count_changed() self.books_deleted()
self.clear_caches()
self.reset()
return ids return ids
def delete_books_by_id(self, ids): def delete_books_by_id(self, ids):
for id in ids: for id in ids:
self.db.delete_book(id) self.db.delete_book(id)
self.count_changed() self.books_deleted()
self.clear_caches()
self.reset()
def books_added(self, num): def books_added(self, num):
if num > 0: if num > 0:

View File

@ -953,21 +953,22 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.notify('metadata', [id]) self.notify('metadata', [id])
return True 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. Removes book from the result cache and the underlying database.
If you set commit to False, you must call clean() manually afterwards
''' '''
try: try:
path = os.path.join(self.library_path, self.path(id, index_is_id=True)) path = os.path.join(self.library_path, self.path(id, index_is_id=True))
except: except:
path = None path = None
self.data.remove(id)
if path and os.path.exists(path): if path and os.path.exists(path):
self.rmtree(path) self.rmtree(path)
parent = os.path.dirname(path) parent = os.path.dirname(path)
if len(os.listdir(parent)) == 0: if len(os.listdir(parent)) == 0:
self.rmtree(parent) self.rmtree(parent)
self.conn.execute('DELETE FROM books WHERE id=?', (id,)) self.conn.execute('DELETE FROM books WHERE id=?', (id,))
if commit:
self.conn.commit() self.conn.commit()
self.clean() self.clean()
self.data.books_deleted([id]) self.data.books_deleted([id])