mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Make item delete and rename take VLs into consideration
This commit is contained in:
parent
fd5dad9bce
commit
5557660bbd
@ -1564,24 +1564,60 @@ class Cache(object):
|
||||
return val_map
|
||||
|
||||
@write_api
|
||||
def rename_items(self, field, item_id_to_new_name_map, change_index=True):
|
||||
def rename_items(self, field, item_id_to_new_name_map, change_index=True,
|
||||
restrict_to_book_ids=None):
|
||||
'''
|
||||
Rename items from a many-one or many-many field such as tags or series.
|
||||
|
||||
:param change_index: When renaming in a series-like field also change the series_index values.
|
||||
:param restrict_to_book_ids: A list of books for which the rename is to be performed. None if the entire library
|
||||
'''
|
||||
|
||||
f = self.fields[field]
|
||||
try:
|
||||
func = f.table.rename_item
|
||||
except AttributeError:
|
||||
raise ValueError('Cannot rename items for one-one fields: %s' % field)
|
||||
affected_books = set()
|
||||
moved_books = set()
|
||||
id_map = {}
|
||||
try:
|
||||
sv = f.metadata['is_multiple']['ui_to_list']
|
||||
except (TypeError, KeyError, AttributeError):
|
||||
sv = None
|
||||
|
||||
if restrict_to_book_ids:
|
||||
# We have a VL. Only change the item name for those books
|
||||
rtb_set = frozenset(restrict_to_book_ids)
|
||||
id_map = {}
|
||||
for old_id, new_name in item_id_to_new_name_map.iteritems():
|
||||
new_names = tuple(x.strip() for x in new_name.split(sv)) if sv else (new_name,)
|
||||
# Get a list of books in the VL with the item
|
||||
books_to_process = f.books_for(old_id) & rtb_set
|
||||
# This should never be empty, but ...
|
||||
if books_to_process:
|
||||
affected_books.update(books_to_process)
|
||||
newvals = {}
|
||||
for book in books_to_process:
|
||||
# Get the current values, remove the one being renamed, then add
|
||||
# the new value(s) back
|
||||
vals = self._field_for(field, book)
|
||||
# Check for is_multiple
|
||||
if isinstance(vals, tuple):
|
||||
vals = set(vals)
|
||||
# Don't need to worry about case here because we
|
||||
# are fetching its one-true spelling
|
||||
vals.remove(self.get_item_name(field, old_id))
|
||||
# This can put the name back with a different case
|
||||
vals.update(new_names)
|
||||
newvals[book] = vals
|
||||
else:
|
||||
newvals[book] = new_names[0]
|
||||
# Allow case changes
|
||||
self._set_field(field, newvals)
|
||||
id_map[old_id] = self.get_item_id(field, new_names[0])
|
||||
return affected_books, id_map
|
||||
|
||||
try:
|
||||
func = f.table.rename_item
|
||||
except AttributeError:
|
||||
raise ValueError('Cannot rename items for one-one fields: %s' % field)
|
||||
moved_books = set()
|
||||
id_map = {}
|
||||
for item_id, new_name in item_id_to_new_name_map.iteritems():
|
||||
new_names = tuple(x.strip() for x in new_name.split(sv)) if sv else (new_name,)
|
||||
books, new_id = func(item_id, new_names[0], self.backend)
|
||||
@ -1606,10 +1642,11 @@ class Cache(object):
|
||||
return affected_books, id_map
|
||||
|
||||
@write_api
|
||||
def remove_items(self, field, item_ids):
|
||||
def remove_items(self, field, item_ids, restrict_to_book_ids=None):
|
||||
''' Delete all items in the specified field with the specified ids. Returns the set of affected book ids. '''
|
||||
field = self.fields[field]
|
||||
affected_books = field.table.remove_items(item_ids, self.backend)
|
||||
affected_books = field.table.remove_items(item_ids, self.backend,
|
||||
restrict_to_book_ids=restrict_to_book_ids)
|
||||
if affected_books:
|
||||
if hasattr(field, 'index_field'):
|
||||
self._set_field(field.index_field.name, {bid:1.0 for bid in affected_books})
|
||||
|
@ -820,8 +820,9 @@ for field in (
|
||||
|
||||
for field in ('authors', 'tags', 'publisher'):
|
||||
def renamer(field):
|
||||
def func(self, old_id, new_name):
|
||||
id_map = self.new_api.rename_items(field, {old_id:new_name})[1]
|
||||
def func(self, old_id, new_name, restrict_to_book_ids=None):
|
||||
id_map = self.new_api.rename_items(field, {old_id:new_name},
|
||||
restrict_to_book_ids=restrict_to_book_ids)[1]
|
||||
if field == 'authors':
|
||||
return id_map[old_id]
|
||||
return func
|
||||
@ -877,8 +878,8 @@ for field in ('author', 'tag', 'series'):
|
||||
for field in ('publisher', 'series', 'tag'):
|
||||
def getter(field):
|
||||
fname = 'tags' if field == 'tag' else field
|
||||
def func(self, item_id):
|
||||
self.new_api.remove_items(fname, (item_id,))
|
||||
def func(self, item_id, restrict_to_book_ids=None):
|
||||
self.new_api.remove_items(fname, (item_id,), restrict_to_book_ids=restrict_to_book_ids)
|
||||
return func
|
||||
setattr(LibraryDatabase, 'delete_%s_using_id' % field, MT(getter(field)))
|
||||
# }}}
|
||||
|
@ -265,8 +265,37 @@ class ManyToOneTable(Table):
|
||||
[(x,) for x in clean])
|
||||
return clean
|
||||
|
||||
def remove_items(self, item_ids, db):
|
||||
def remove_items(self, item_ids, db, restrict_to_book_ids=None):
|
||||
affected_books = set()
|
||||
|
||||
if restrict_to_book_ids:
|
||||
rtb_set = frozenset(restrict_to_book_ids)
|
||||
items_to_process_normally = set()
|
||||
# Check if all the books with the item are in the restriction. If
|
||||
# so, process them normally
|
||||
for item_id in item_ids:
|
||||
books_to_process = self.col_book_map.get(item_id, set())
|
||||
books_not_to_delete = books_to_process - rtb_set
|
||||
if books_not_to_delete:
|
||||
# Some books not in restriction. Must do special processing
|
||||
books_to_delete = books_to_process & rtb_set
|
||||
# remove the books from the old id maps
|
||||
self.col_book_map[item_id] = books_not_to_delete
|
||||
for book_id in books_to_delete:
|
||||
self.book_col_map.pop(book_id, None)
|
||||
# Delete links to the affected books from the link table. As
|
||||
# this is a many-to-one mapping we know that we can delete
|
||||
# links without checking the item ID
|
||||
db.executemany('DELETE FROM {0} WHERE {1}=?'.format(self.link_table,
|
||||
'book'), books_to_delete)
|
||||
affected_books |= books_to_delete
|
||||
else:
|
||||
# Process normally any items where the VL was not significant
|
||||
items_to_process_normally.add(item_id)
|
||||
if items_to_process_normally:
|
||||
affected_books |= self.remove_items(items_to_process_normally, db)
|
||||
return affected_books
|
||||
|
||||
for item_id in item_ids:
|
||||
val = self.id_map.pop(item_id, null)
|
||||
if val is null:
|
||||
@ -373,8 +402,38 @@ class ManyToManyTable(ManyToOneTable):
|
||||
[(x,) for x in clean])
|
||||
return clean
|
||||
|
||||
def remove_items(self, item_ids, db):
|
||||
def remove_items(self, item_ids, db, restrict_to_book_ids=None):
|
||||
affected_books = set()
|
||||
if restrict_to_book_ids:
|
||||
rtb_set = frozenset(restrict_to_book_ids)
|
||||
items_to_process_normally = set()
|
||||
# Check if all the books with the item are in the restriction. If
|
||||
# so, process them normally
|
||||
for item_id in item_ids:
|
||||
books_to_process = self.col_book_map.get(item_id, set())
|
||||
books_not_to_delete = books_to_process - rtb_set
|
||||
if books_not_to_delete:
|
||||
# Some books not in restriction. Must do special processing
|
||||
books_to_delete = books_to_process & rtb_set
|
||||
# remove the books from the old id maps
|
||||
self.col_book_map[item_id] = books_not_to_delete
|
||||
for book_id in books_to_delete:
|
||||
self.book_col_map[book_id] = tuple(
|
||||
x for x in self.book_col_map.get(book_id, ()) if x != item_id)
|
||||
affected_books |= books_to_delete
|
||||
else:
|
||||
items_to_process_normally.add(item_id)
|
||||
# Delete book/item pairs from the link table. We don't need to do
|
||||
# anything with the main table because books with the old ID are
|
||||
# still in the library.
|
||||
db.executemany('DELETE FROM {0} WHERE {1}=? and {2}=?'.format(
|
||||
self.link_table, 'book', self.metadata['link_column']),
|
||||
[(b, i) for b in affected_books for i in item_ids])
|
||||
# Take care of any items where the VL was not significant
|
||||
if items_to_process_normally:
|
||||
affected_books |= self.remove_items(items_to_process_normally, db)
|
||||
return affected_books
|
||||
|
||||
for item_id in item_ids:
|
||||
val = self.id_map.pop(item_id, null)
|
||||
if val is null:
|
||||
|
@ -855,6 +855,11 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
self.drag_drop_finished.emit(ids)
|
||||
# }}}
|
||||
|
||||
def get_book_ids_to_use(self):
|
||||
if self.db.data.get_base_restriction() or self.db.data.get_search_restriction():
|
||||
return self.db.search('', return_matches=True, sort_results=False)
|
||||
return None
|
||||
|
||||
def _get_category_nodes(self, sort):
|
||||
'''
|
||||
Called by __init__. Do not directly call this method.
|
||||
@ -863,11 +868,10 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
self.categories = {}
|
||||
|
||||
# Get the categories
|
||||
if self.db.data.get_base_restriction() or self.db.data.get_search_restriction():
|
||||
try:
|
||||
data = self.db.new_api.get_categories(sort=sort,
|
||||
icon_map=self.category_icon_map,
|
||||
book_ids=self.db.search('', return_matches=True, sort_results=False),
|
||||
book_ids=self.get_book_ids_to_use(),
|
||||
first_letter_sort = self.collapse_model == 'first letter')
|
||||
except:
|
||||
import traceback
|
||||
@ -875,9 +879,6 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
data = self.db.new_api.get_categories(sort=sort, icon_map=self.category_icon_map,
|
||||
first_letter_sort = self.collapse_model == 'first letter')
|
||||
self.restriction_error.emit()
|
||||
else:
|
||||
data = self.db.new_api.get_categories(sort=sort, icon_map=self.category_icon_map,
|
||||
first_letter_sort = self.collapse_model == 'first letter')
|
||||
|
||||
# Reconstruct the user categories, putting them into metadata
|
||||
self.db.field_metadata.remove_dynamic_categories()
|
||||
@ -1042,20 +1043,13 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
item.tag.name = val
|
||||
self.search_item_renamed.emit() # Does a refresh
|
||||
else:
|
||||
if key == 'series':
|
||||
self.db.rename_series(item.tag.id, val)
|
||||
elif key == 'publisher':
|
||||
self.db.rename_publisher(item.tag.id, val)
|
||||
elif key == 'tags':
|
||||
self.db.rename_tag(item.tag.id, val)
|
||||
elif key == 'authors':
|
||||
self.db.rename_author(item.tag.id, val)
|
||||
elif self.db.field_metadata[key]['is_custom']:
|
||||
self.db.rename_custom_item(item.tag.id, val,
|
||||
label=self.db.field_metadata[key]['label'])
|
||||
restrict_to_book_ids=self.get_book_ids_to_use()
|
||||
self.db.new_api.rename_items(key, {item.tag.id: val},
|
||||
restrict_to_book_ids=restrict_to_book_ids)
|
||||
self.tag_item_renamed.emit()
|
||||
item.tag.name = val
|
||||
item.tag.state = TAG_SEARCH_STATES['clear']
|
||||
if not restrict_to_book_ids:
|
||||
self.rename_item_in_all_user_categories(name, key, val)
|
||||
self.refresh_required.emit()
|
||||
return True
|
||||
|
@ -223,14 +223,18 @@ class TagBrowserMixin(object): # {{{
|
||||
if (category in ['tags', 'series', 'publisher'] or
|
||||
db.new_api.field_metadata.is_custom_field(category)):
|
||||
m = self.tags_view.model()
|
||||
restrict_to_book_ids = m.get_book_ids_to_use()
|
||||
if not restrict_to_book_ids:
|
||||
for item in to_delete:
|
||||
m.delete_item_from_all_user_categories(orig_name[item], category)
|
||||
for old_id in to_rename:
|
||||
for old_id in to_rename and not restrict_to_book_ids:
|
||||
m.rename_item_in_all_user_categories(orig_name[old_id],
|
||||
category, unicode(to_rename[old_id]))
|
||||
|
||||
db.new_api.remove_items(category, to_delete)
|
||||
db.new_api.rename_items(category, to_rename, change_index=False)
|
||||
db.new_api.remove_items(category, to_delete,
|
||||
restrict_to_book_ids=restrict_to_book_ids)
|
||||
db.new_api.rename_items(category, to_rename, change_index=False,
|
||||
restrict_to_book_ids=restrict_to_book_ids)
|
||||
|
||||
# Clean up the library view
|
||||
self.do_tag_item_renamed()
|
||||
@ -260,7 +264,9 @@ class TagBrowserMixin(object): # {{{
|
||||
delete_func = partial(db.delete_custom_item_using_id, label=cc_label)
|
||||
m = self.tags_view.model()
|
||||
if delete_func:
|
||||
delete_func(item_id)
|
||||
restrict_to_book_ids=m.get_book_ids_to_use()
|
||||
delete_func(item_id, restrict_to_book_ids=restrict_to_book_ids)
|
||||
if not restrict_to_book_ids:
|
||||
m.delete_item_from_all_user_categories(orig_name, category)
|
||||
|
||||
# Clean up the library view
|
||||
|
Loading…
x
Reference in New Issue
Block a user