Finish search and replace.

Fix a bug in database2 that seems to be triggered by interactions with the cover cache.
This commit is contained in:
Charles Haley 2010-09-20 17:03:53 +01:00
parent e721bd44ee
commit ea44e9053f
2 changed files with 51 additions and 32 deletions

View File

@ -11,11 +11,11 @@ from PyQt4 import QtGui
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.ebooks.metadata import string_to_authors, \ from calibre.ebooks.metadata import string_to_authors, authors_to_string
authors_to_string, MetaInformation
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2.dialogs.progress import BlockingBusy from calibre.gui2.dialogs.progress import BlockingBusy
from calibre.gui2 import error_dialog, Dispatcher from calibre.gui2 import error_dialog, Dispatcher
from calibre.utils.config import dynamic
class Worker(Thread): class Worker(Thread):
@ -208,26 +208,27 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.book_1_text.setObjectName(name) self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 2, 1, 1) self.testgrid.addWidget(w, i+offset, 2, 1, 1)
self.s_r_heading.setText('<p>'+ self.s_r_heading.setText('<p>'+ _(
_('<b>You can destroy your library</b> ' '<b>You can destroy your library using this feature.</b> '
'using this feature. Changes are permanent. There ' 'Changes are permanent. There is no undo function. '
'is no undo function. You are strongly encouraged ' ' This feature is experimental, and there may be bugs. '
'to back up your library before proceeding.' 'You are strongly encouraged to back up your library '
) + '<p>' + _( 'before proceeding.'
'Search and replace in text fields using ' ) + '<p>' + _(
'regular expressions. The search text is an ' 'Search and replace in text fields using character matching '
'arbitrary python-compatible regular expression. ' 'or regular expressions. In character mode, search text '
'The replacement text can contain backreferences ' 'found in the specified field is replaced with replace '
'to parenthesized expressions in the pattern. ' 'text. In regular expression mode, the search text is an '
'The search is not anchored, and can match and ' 'arbitrary python-compatible regular expression. The '
'replace multiple times on the same string. See ' 'replacement text can contain backreferences to parenthesized '
'<a href="http://docs.python.org/library/re.html"> ' 'expressions in the pattern. The search is not anchored, '
'this reference</a> ' 'and can match and replace multiple times on the same string. '
'for more information, and in particular the \'sub\' ' 'See <a href="http://docs.python.org/library/re.html"> '
'function.' 'this reference</a> for more information, and in particular '
)) 'the \'sub\' function.'
))
self.search_mode.addItems(self.s_r_match_modes) self.search_mode.addItems(self.s_r_match_modes)
self.search_mode.setCurrentIndex(0) self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0))
self.replace_mode.addItems(self.s_r_replace_modes) self.replace_mode.addItems(self.s_r_replace_modes)
self.replace_mode.setCurrentIndex(0) self.replace_mode.setCurrentIndex(0)
@ -252,7 +253,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive) self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive)
self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive) self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive)
self.s_r_search_mode_changed(0) self.s_r_search_mode_changed(self.search_mode.currentIndex())
def s_r_get_field(self, mi, field): def s_r_get_field(self, mi, field):
if field: if field:
@ -303,6 +304,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.replace_mode.setVisible(True) self.replace_mode.setVisible(True)
self.replace_mode_label.setVisible(True) self.replace_mode_label.setVisible(True)
self.comma_separated.setVisible(True) self.comma_separated.setVisible(True)
self.s_r_paint_results(None)
def s_r_set_colors(self): def s_r_set_colors(self):
if self.s_r_error is not None: if self.s_r_error is not None:
@ -325,8 +327,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
src_field = unicode(self.search_field.currentText()) src_field = unicode(self.search_field.currentText())
src = self.s_r_get_field(mi, src_field) src = self.s_r_get_field(mi, src_field)
result = [] result = []
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
for s in src: for s in src:
result.append(self.s_r_obj.sub(self.s_r_func, s)) t = self.s_r_obj.sub(self.s_r_func, s)
if self.search_mode.currentIndex() == 0:
t = rfunc(t)
result.append(t)
return result return result
def s_r_do_destination(self, mi, val): def s_r_do_destination(self, mi, val):
@ -374,7 +380,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
flags = re.I flags = re.I
try: try:
self.s_r_obj = re.compile(unicode(self.search_for.text()), flags) if self.search_mode.currentIndex() == 0:
self.s_r_obj = re.compile(re.escape(unicode(self.search_for.text())), flags)
else:
self.s_r_obj = re.compile(unicode(self.search_for.text()), flags)
except Exception as e: except Exception as e:
self.s_r_obj = None self.s_r_obj = None
self.s_r_error = e self.s_r_error = e
@ -411,7 +420,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
dest = unicode(self.destination_field.currentText()) dest = unicode(self.destination_field.currentText())
if not dest: if not dest:
dest = source dest = source
dfm = self.db.field_metadata[source] dfm = self.db.field_metadata[dest]
for id in self.ids: for id in self.ids:
mi = self.db.get_metadata(id, index_is_id=True,) mi = self.db.get_metadata(id, index_is_id=True,)
@ -439,6 +448,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
setter = getattr(self.db, 'set_'+dest) setter = getattr(self.db, 'set_'+dest)
setter(id, val, notify=False, commit=False) setter(id, val, notify=False, commit=False)
self.db.commit() self.db.commit()
dynamic['s_r_search_mode'] = self.search_mode.currentIndex()
def create_custom_column_editors(self): def create_custom_column_editors(self):
w = self.central_widget.widget(1) w = self.central_widget.widget(1)

View File

@ -464,11 +464,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# change case don't cause any changes to the directories in the file # change case don't cause any changes to the directories in the file
# system. This can lead to having the directory names not match the # system. This can lead to having the directory names not match the
# title/author, which leads to trouble when libraries are copied to # title/author, which leads to trouble when libraries are copied to
# a case-sensitive system. The following code fixes this by checking # a case-sensitive system. The following code attempts to fix this
# each segment. If they are different because of case, then rename # by checking each segment. If they are different because of case,
# the segment to some temp file name, then rename it back to the # then rename the segment to some temp file name, then rename it
# correct name. Note that the code above correctly handles files in # back to the correct name. Note that the code above correctly
# the directories, so no need to do them here. # handles files in the directories, so no need to do them here.
for oldseg, newseg in zip(c1, c2): for oldseg, newseg in zip(c1, c2):
if oldseg.lower() == newseg.lower() and oldseg != newseg: if oldseg.lower() == newseg.lower() and oldseg != newseg:
while True: while True:
@ -476,8 +476,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tempname = os.path.join(curpath, 'TEMP.%f'%time.time()) tempname = os.path.join(curpath, 'TEMP.%f'%time.time())
if not os.path.exists(tempname): if not os.path.exists(tempname):
break break
os.rename(os.path.join(curpath, oldseg), tempname) try:
os.rename(tempname, os.path.join(curpath, newseg)) os.rename(os.path.join(curpath, oldseg), tempname)
except (IOError, OSError):
# Windows (at least) sometimes refuses to do the rename
# probably because a file such a cover is open in the
# hierarchy. Just go on -- nothing is hurt beyond the
# case of the filesystem not matching the case in
# name stored by calibre
print 'rename of library component failed'
else:
os.rename(tempname, os.path.join(curpath, newseg))
curpath = os.path.join(curpath, newseg) curpath = os.path.join(curpath, newseg)
def add_listener(self, listener): def add_listener(self, listener):