Implement undo popup for book format deletion

This commit is contained in:
Kovid Goyal 2023-04-13 13:31:38 +05:30
parent 78904f6b20
commit 5470d311a3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 35 additions and 10 deletions

View File

@ -1545,14 +1545,18 @@ class DB:
def remove_formats(self, remove_map, metadata_map): def remove_formats(self, remove_map, metadata_map):
self.ensure_trash_dir() self.ensure_trash_dir()
removed_map = {}
for book_id, removals in iteritems(remove_map): for book_id, removals in iteritems(remove_map):
paths = set() paths = set()
removed_map[book_id] = set()
for fmt, fname, path in removals: for fmt, fname, path in removals:
path = self.format_abspath(book_id, fmt, fname, path) path = self.format_abspath(book_id, fmt, fname, path)
if path: if path:
paths.add(path) paths.add(path)
removed_map[book_id].add(fmt.upper())
if paths: if paths:
self.move_book_files_to_trash(book_id, paths, metadata_map[book_id]) self.move_book_files_to_trash(book_id, paths, metadata_map[book_id])
return removed_map
def cover_last_modified(self, path): def cover_last_modified(self, path):
path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME))

View File

@ -1848,9 +1848,11 @@ class Cache:
:param formats_map: A mapping of book_id to a list of formats to be removed from the book. :param formats_map: A mapping of book_id to a list of formats to be removed from the book.
:param db_only: If True, only remove the record for the format from the db, do not delete the actual format file from the filesystem. :param db_only: If True, only remove the record for the format from the db, do not delete the actual format file from the filesystem.
:return: A map of book id to set of formats actually deleted from the filesystem for that book
''' '''
table = self.fields['formats'].table table = self.fields['formats'].table
formats_map = {book_id:frozenset((f or '').upper() for f in fmts) for book_id, fmts in iteritems(formats_map)} formats_map = {book_id:frozenset((f or '').upper() for f in fmts) for book_id, fmts in iteritems(formats_map)}
removed_map = {}
for book_id, fmts in iteritems(formats_map): for book_id, fmts in iteritems(formats_map):
for fmt in fmts: for fmt in fmts:
@ -1874,7 +1876,7 @@ class Cache:
if removes[book_id]: if removes[book_id]:
metadata_map[book_id] = {'title': self._field_for('title', book_id), 'authors': self._field_for('authors', book_id)} metadata_map[book_id] = {'title': self._field_for('title', book_id), 'authors': self._field_for('authors', book_id)}
if removes: if removes:
self.backend.remove_formats(removes, metadata_map) removed_map = self.backend.remove_formats(removes, metadata_map)
size_map = table.remove_formats(formats_map, self.backend) size_map = table.remove_formats(formats_map, self.backend)
self.fields['size'].table.update_sizes(size_map) self.fields['size'].table.update_sizes(size_map)
@ -1884,6 +1886,7 @@ class Cache:
self._update_last_modified(tuple(formats_map)) self._update_last_modified(tuple(formats_map))
self.event_dispatcher(EventType.formats_removed, formats_map) self.event_dispatcher(EventType.formats_removed, formats_map)
return removed_map
@read_api @read_api
def get_next_series_num_for(self, series, field='series', current_indices=False): def get_next_series_num_for(self, series, field='series', current_indices=False):

View File

@ -166,7 +166,7 @@ class DeleteAction(InterfaceAction):
return set(map(self.gui.library_view.model().id, rows)) return set(map(self.gui.library_view.model().id, rows))
def _remove_formats_from_ids(self, fmts, ids): def _remove_formats_from_ids(self, fmts, ids):
self.gui.library_view.model().db.new_api.remove_formats({bid: fmts for bid in ids}) self.show_undo_for_deleted_formats(self.gui.library_view.model().db.new_api.remove_formats({bid: fmts for bid in ids}))
self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().refresh_ids(ids)
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex()) self.gui.library_view.currentIndex())
@ -174,7 +174,7 @@ class DeleteAction(InterfaceAction):
def remove_format_by_id(self, book_id, fmt): def remove_format_by_id(self, book_id, fmt):
title = self.gui.current_db.title(book_id, index_is_id=True) title = self.gui.current_db.title(book_id, index_is_id=True)
if not confirm('<p>'+(_( if not confirm('<p>'+(_(
'The %(fmt)s format will be <b>permanently deleted</b> from ' 'The %(fmt)s format will be <b>deleted</b> from '
'%(title)s. Are you sure?')%dict(fmt=fmt, title=title)) + '%(title)s. Are you sure?')%dict(fmt=fmt, title=title)) +
'</p>', 'library_delete_specific_format', self.gui): '</p>', 'library_delete_specific_format', self.gui):
return return
@ -193,8 +193,8 @@ class DeleteAction(InterfaceAction):
return error_dialog(self.gui, _('Format not found'), _('The {} format is not present in the selected books.').format(fmt), show=True) return error_dialog(self.gui, _('Format not found'), _('The {} format is not present in the selected books.').format(fmt), show=True)
if not confirm( if not confirm(
'<p>'+ ngettext( '<p>'+ ngettext(
_('The {fmt} format will be <b>permanently deleted</b> from {title}.'), _('The {fmt} format will be <b>deleted</b> from {title}.'),
_('The {fmt} format will be <b>permanently deleted</b> from all {num} selected books.'), _('The {fmt} format will be <b>deleted</b> from all {num} selected books.'),
len(ids)).format(fmt=fmt.upper(), num=len(ids), title=self.gui.current_db.title(next(iter(ids)), index_is_id=True) len(ids)).format(fmt=fmt.upper(), num=len(ids), title=self.gui.current_db.title(next(iter(ids)), index_is_id=True)
) + ' ' + _('Are you sure?'), 'library_delete_specific_format_from_selected', self.gui ) + ' ' + _('Are you sure?'), 'library_delete_specific_format_from_selected', self.gui
): ):
@ -217,7 +217,7 @@ class DeleteAction(InterfaceAction):
if not fmts: if not fmts:
return return
m = self.gui.library_view.model() m = self.gui.library_view.model()
m.db.new_api.remove_formats({book_id:fmts for book_id in ids}) self.show_undo_for_deleted_formats(m.db.new_api.remove_formats({book_id:fmts for book_id in ids}))
m.refresh_ids(ids) m.refresh_ids(ids)
m.current_changed(self.gui.library_view.currentIndex(), m.current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex()) self.gui.library_view.currentIndex())
@ -247,7 +247,7 @@ class DeleteAction(InterfaceAction):
# formats # formats
removals[id] = rfmts removals[id] = rfmts
if removals: if removals:
m.db.new_api.remove_formats(removals) self.show_undo_for_deleted_formats(m.db.new_api.remove_formats(removals))
m.refresh_ids(ids) m.refresh_ids(ids)
m.current_changed(self.gui.library_view.currentIndex(), m.current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex()) self.gui.library_view.currentIndex())
@ -270,7 +270,7 @@ class DeleteAction(InterfaceAction):
if fmts: if fmts:
removals[id] = fmts.split(',') removals[id] = fmts.split(',')
if removals: if removals:
db.new_api.remove_formats(removals) self.show_undo_for_deleted_formats(db.new_api.remove_formats(removals))
self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().refresh_ids(ids)
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex()) self.gui.library_view.currentIndex())
@ -367,9 +367,17 @@ class DeleteAction(InterfaceAction):
if not hasattr(self, 'message_popup'): if not hasattr(self, 'message_popup'):
self.message_popup = MessagePopup(self.gui) self.message_popup = MessagePopup(self.gui)
self.message_popup.undo_requested.connect(self.undelete) self.message_popup.undo_requested.connect(self.undelete)
self.message_popup(ngettext('One book deleted.', '{} books deleted.', len(ids_deleted)).format(len(ids_deleted)), self.message_popup(ngettext('One book deleted from library.', '{} books deleted from library.', len(ids_deleted)).format(len(ids_deleted)),
show_undo=(self.gui.current_db.new_api.library_id, ids_deleted)) show_undo=(self.gui.current_db.new_api.library_id, ids_deleted))
def show_undo_for_deleted_formats(self, removed_map):
if not hasattr(self, 'message_popup'):
self.message_popup = MessagePopup(self.gui)
self.message_popup.undo_requested.connect(self.undelete)
num = sum(map(len, removed_map.values()))
self.message_popup(ngettext('One book format deleted.', '{} book formats deleted.', num).format(num),
show_undo=(self.gui.current_db.new_api.library_id, removed_map))
def library_changed(self, db): def library_changed(self, db):
if hasattr(self, 'message_popup'): if hasattr(self, 'message_popup'):
self.message_popup.hide() self.message_popup.hide()
@ -377,7 +385,17 @@ class DeleteAction(InterfaceAction):
def undelete(self, what): def undelete(self, what):
library_id, book_ids = what library_id, book_ids = what
db = self.gui.current_db.new_api db = self.gui.current_db.new_api
if library_id == db.library_id: if library_id != db.library_id:
return
current_idx = self.gui.library_view.currentIndex()
if isinstance(book_ids, dict):
with BusyCursor():
for book_id, fmts in book_ids.items():
for fmt in fmts:
db.move_format_from_trash(book_id, fmt)
if current_idx.isValid():
self.gui.library_view.model().current_changed(current_idx, current_idx)
else:
with BusyCursor(): with BusyCursor():
for book_id in book_ids: for book_id in book_ids:
db.move_book_from_trash(book_id) db.move_book_from_trash(book_id)