Three changes:

1) make get_metadata return an unverified list of formats. Avoids a file system operation per format
2) enhancement request #2845
3) permit composite fields as search/replace source fields.
This commit is contained in:
Charles Haley 2010-09-22 13:22:02 +01:00
parent 2c1debbe79
commit 7893c01807
3 changed files with 55 additions and 21 deletions

View File

@ -32,24 +32,30 @@ class Worker(Thread):
remove, add, au, aus, do_aus, rating, pub, do_series, \ remove, add, au, aus, do_aus, rating, pub, do_series, \
do_autonumber, do_remove_format, remove_format, do_swap_ta, \ do_autonumber, do_remove_format, remove_format, do_swap_ta, \
do_remove_conv, do_auto_author, series, do_series_restart, \ do_remove_conv, do_auto_author, series, do_series_restart, \
series_start_value = self.args series_start_value, do_title_case = self.args
# first loop: do author and title. These will commit at the end of each # first loop: do author and title. These will commit at the end of each
# operation, because each operation modifies the file system. We want to # operation, because each operation modifies the file system. We want to
# try hard to keep the DB and the file system in sync, even in the face # try hard to keep the DB and the file system in sync, even in the face
# of exceptions or forced exits. # of exceptions or forced exits.
for id in self.ids: for id in self.ids:
title_set = False
if do_swap_ta: if do_swap_ta:
title = self.db.title(id, index_is_id=True) title = self.db.title(id, index_is_id=True)
aum = self.db.authors(id, index_is_id=True) aum = self.db.authors(id, index_is_id=True)
if aum: if aum:
aum = [a.strip().replace('|', ',') for a in aum.split(',')] aum = [a.strip().replace('|', ',') for a in aum.split(',')]
new_title = authors_to_string(aum) new_title = authors_to_string(aum)
if do_title_case:
new_title = new_title.title()
self.db.set_title(id, new_title, notify=False) self.db.set_title(id, new_title, notify=False)
title_set = True
if title: if title:
new_authors = string_to_authors(title) new_authors = string_to_authors(title)
self.db.set_authors(id, new_authors, notify=False) self.db.set_authors(id, new_authors, notify=False)
if do_title_case and not title_set:
title = self.db.title(id, index_is_id=True)
self.db.set_title(id, title.title(), notify=False)
if au: if au:
self.db.set_authors(id, string_to_authors(au), notify=False) self.db.set_authors(id, string_to_authors(au), notify=False)
@ -182,19 +188,22 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.search_for.initialize('bulk_edit_search_for') self.search_for.initialize('bulk_edit_search_for')
self.replace_with.initialize('bulk_edit_replace_with') self.replace_with.initialize('bulk_edit_replace_with')
self.test_text.initialize('bulk_edit_test_test') self.test_text.initialize('bulk_edit_test_test')
fields = [''] self.all_fields = ['']
self.writable_fields = ['']
fm = self.db.field_metadata fm = self.db.field_metadata
for f in fm: for f in fm:
if (f in ['author_sort'] or ( if (f in ['author_sort'] or (
fm[f]['datatype'] == 'text' or fm[f]['datatype'] == 'series') fm[f]['datatype'] in ['text', 'series'])
and fm[f].get('search_terms', None) and fm[f].get('search_terms', None)
and f not in ['formats', 'ondevice']): and f not in ['formats', 'ondevice']):
fields.append(f) self.all_fields.append(f)
fields.sort() self.writable_fields.append(f)
self.search_field.addItems(fields) if fm[f]['datatype'] == 'composite':
self.search_field.setMaxVisibleItems(min(len(fields), 20)) self.all_fields.append(f)
self.destination_field.addItems(fields) self.all_fields.sort()
self.destination_field.setMaxVisibleItems(min(len(fields), 20)) self.writable_fields.sort()
self.search_field.setMaxVisibleItems(20)
self.destination_field.setMaxVisibleItems(20)
offset = 10 offset = 10
self.s_r_number_of_books = min(10, len(self.ids)) self.s_r_number_of_books = min(10, len(self.ids))
for i in range(1,self.s_r_number_of_books+1): for i in range(1,self.s_r_number_of_books+1):
@ -262,7 +271,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.replace_func.addItems(sorted(self.s_r_functions.keys())) self.replace_func.addItems(sorted(self.s_r_functions.keys()))
self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed) self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
self.search_field.currentIndexChanged[str].connect(self.s_r_search_field_changed) self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed)
self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed) self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed)
self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results) self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
@ -293,15 +302,18 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
val = [] val = []
return val return val
def s_r_search_field_changed(self, txt): def s_r_search_field_changed(self, idx):
txt = unicode(txt)
for i in range(0, self.s_r_number_of_books): for i in range(0, self.s_r_number_of_books):
w = getattr(self, 'book_%d_text'%(i+1)) w = getattr(self, 'book_%d_text'%(i+1))
mi = self.db.get_metadata(self.ids[i], index_is_id=True) mi = self.db.get_metadata(self.ids[i], index_is_id=True)
src = unicode(self.search_field.currentText()) src = unicode(self.search_field.currentText())
t = self.s_r_get_field(mi, src) t = self.s_r_get_field(mi, src)
w.setText(''.join(t[0:1])) w.setText(''.join(t[0:1]))
self.s_r_paint_results(None)
if self.search_mode.currentIndex() == 0:
self.destination_field.setCurrentIndex(idx)
else:
self.s_r_paint_results(None)
def s_r_destination_field_changed(self, txt): def s_r_destination_field_changed(self, txt):
txt = unicode(txt) txt = unicode(txt)
@ -314,7 +326,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.s_r_paint_results(None) self.s_r_paint_results(None)
def s_r_search_mode_changed(self, val): def s_r_search_mode_changed(self, val):
self.search_field.clear()
self.destination_field.clear()
if val == 0: if val == 0:
self.search_field.addItems(self.writable_fields)
self.destination_field.addItems(self.writable_fields)
self.destination_field.setCurrentIndex(0) self.destination_field.setCurrentIndex(0)
self.destination_field.setVisible(False) self.destination_field.setVisible(False)
self.destination_field_label.setVisible(False) self.destination_field_label.setVisible(False)
@ -324,6 +340,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.comma_separated.setVisible(False) self.comma_separated.setVisible(False)
self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading) self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading)
else: else:
self.search_field.addItems(self.all_fields)
self.destination_field.addItems(self.writable_fields)
self.destination_field.setVisible(True) self.destination_field.setVisible(True)
self.destination_field_label.setVisible(True) self.destination_field_label.setVisible(True)
self.replace_mode.setVisible(True) self.replace_mode.setVisible(True)
@ -367,6 +385,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
return '' return ''
dest = unicode(self.destination_field.currentText()) dest = unicode(self.destination_field.currentText())
if dest == '': if dest == '':
if self.db.metadata_for_field(src)['datatype'] == 'composite':
raise Exception(_('You must specify a destination when source is a composite field'))
dest = src dest = src
dest_mode = self.replace_mode.currentIndex() dest_mode = self.replace_mode.currentIndex()
@ -433,8 +453,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
t = self.s_r_replace_mode_separator().join(t) t = self.s_r_replace_mode_separator().join(t)
wr.setText(t) wr.setText(t)
except Exception as e: except Exception as e:
import traceback
traceback.print_exc()
self.s_r_error = e self.s_r_error = e
self.s_r_set_colors() self.s_r_set_colors()
break break
@ -592,11 +610,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
do_swap_ta = self.swap_title_and_author.isChecked() do_swap_ta = self.swap_title_and_author.isChecked()
do_remove_conv = self.remove_conversion_settings.isChecked() do_remove_conv = self.remove_conversion_settings.isChecked()
do_auto_author = self.auto_author_sort.isChecked() do_auto_author = self.auto_author_sort.isChecked()
do_title_case = self.change_title_to_title_case.isChecked()
args = (remove, add, au, aus, do_aus, rating, pub, do_series, args = (remove, add, au, aus, do_aus, rating, pub, do_series,
do_autonumber, do_remove_format, remove_format, do_swap_ta, do_autonumber, do_remove_format, remove_format, do_swap_ta,
do_remove_conv, do_auto_author, series, do_series_restart, do_remove_conv, do_auto_author, series, do_series_restart,
series_start_value) series_start_value, do_title_case)
bb = BlockingBusy(_('Applying changes to %d books. This may take a while.') bb = BlockingBusy(_('Applying changes to %d books. This may take a while.')
%len(self.ids), parent=self) %len(self.ids), parent=self)

View File

@ -270,6 +270,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0" colspan="2">
<widget class="QCheckBox" name="change_title_to_title_case">
<property name="text">
<string>Change title to title case</string>
</property>
<property name="toolTip">
<string>Force the title to be in title case. If both this and swap authors are checked,
title and author are swapped before the title case is set</string>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2"> <item row="8" column="1" colspan="2">
<layout class="QHBoxLayout" name="HLayout_3"> <layout class="QHBoxLayout" name="HLayout_3">
<item> <item>
@ -340,7 +351,7 @@ Future conversion of these books will use the default settings.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0" colspan="3"> <item row="15" column="0" colspan="3">
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>

View File

@ -535,7 +535,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
### The field-style interface. These use field keys. ### The field-style interface. These use field keys.
def get_field(self, idx, key, default=None, index_is_id=False): def get_field(self, idx, key, default=None, index_is_id=False):
mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True) mi = self.get_metadata(idx, index_is_id=index_is_id,
get_cover=key == 'cover')
return mi.get(key, default) return mi.get(key, default)
def standard_field_keys(self): def standard_field_keys(self):
@ -590,7 +591,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id) mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
mi.uuid = self.uuid(idx, index_is_id=index_is_id) mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id) mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
mi.formats = self.formats(idx, index_is_id=index_is_id) mi.formats = self.formats(idx, index_is_id=index_is_id,
verify_formats=False)
if hasattr(mi.formats, 'split'): if hasattr(mi.formats, 'split'):
mi.formats = mi.formats.split(',') mi.formats = mi.formats.split(',')
else: else:
@ -731,7 +733,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return set([]) return set([])
return set([f[0] for f in formats]) return set([f[0] for f in formats])
def formats(self, index, index_is_id=False): def formats(self, index, index_is_id=False, verify_formats=True):
''' Return available formats as a comma separated list or None if there are no available formats ''' ''' Return available formats as a comma separated list or None if there are no available formats '''
id = index if index_is_id else self.id(index) id = index if index_is_id else self.id(index)
try: try:
@ -739,6 +741,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formats = map(lambda x:x[0], formats) formats = map(lambda x:x[0], formats)
except: except:
return None return None
if not verify_formats:
return formats
ans = [] ans = []
for format in formats: for format in formats:
if self.format_abspath(id, format, index_is_id=True) is not None: if self.format_abspath(id, format, index_is_id=True) is not None: