diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 951a55da10..56df573cee 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -37,7 +37,13 @@ class SafeFormat(TemplateFormatter): def get_value(self, key, args, kwargs): try: - ign, v = self.book.format_field(key.lower(), series_with_index=False) + b = self.book.get_user_metadata(key, False) + if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: + v = '' + elif b and b['datatype'] == 'float' and b.get(key, 0.0) == 0.0: + v = '' + else: + ign, v = self.book.format_field(key.lower(), series_with_index=False) if v is None: return '' if v == '': @@ -65,7 +71,6 @@ class Metadata(object): ''' _data = copy.deepcopy(NULL_VALUES) object.__setattr__(self, '_data', _data) - _data['_curseq'] = _data['_compseq'] = 0 if other is not None: self.smart_update(other) else: @@ -94,29 +99,22 @@ class Metadata(object): if field in _data['user_metadata'].iterkeys(): d = _data['user_metadata'][field] val = d['#value#'] - if d['datatype'] != 'composite' or \ - (_data['_curseq'] == _data['_compseq'] and val is not None): + if d['datatype'] != 'composite': return val - # Data in the structure has changed. Recompute the composite fields - _data['_compseq'] = _data['_curseq'] - for ck in _data['user_metadata']: - cf = _data['user_metadata'][ck] - if cf['datatype'] != 'composite': - continue - cf['#value#'] = 'RECURSIVE_COMPOSITE FIELD ' + field - cf['#value#'] = composite_formatter.safe_format( - cf['display']['composite_template'], + if val is None: + d['#value#'] = 'RECURSIVE_COMPOSITE FIELD (Metadata) ' + field + val = d['#value#'] = composite_formatter.safe_format( + d['display']['composite_template'], self, _('TEMPLATE ERROR'), self).strip() - return d['#value#'] + return val raise AttributeError( 'Metadata object has no attribute named: '+ repr(field)) def __setattr__(self, field, val, extra=None): _data = object.__getattribute__(self, '_data') - _data['_curseq'] += 1 if field in TOP_LEVEL_CLASSIFIERS: _data['classifiers'].update({field: val}) elif field in STANDARD_METADATA_FIELDS: @@ -124,7 +122,10 @@ class Metadata(object): val = NULL_VALUES.get(field, None) _data[field] = val elif field in _data['user_metadata'].iterkeys(): - _data['user_metadata'][field]['#value#'] = val + if _data['user_metadata'][field]['datatype'] == 'composite': + _data['user_metadata'][field]['#value#'] = None + else: + _data['user_metadata'][field]['#value#'] = val _data['user_metadata'][field]['#extra#'] = extra else: # You are allowed to stick arbitrary attributes onto this object as @@ -294,28 +295,24 @@ class Metadata(object): _data = object.__getattribute__(self, '_data') _data['user_metadata'][field] = metadata - def copy_specific_attributes(self, other, attrs): + def template_to_attribute(self, other, attrs): ''' - Takes a dict {src:dest, src:dest} and copys other[src] to self[dest]. - This is on a best-efforts basis. Some assignments can make no sense. + Takes a dict {src:dest, src:dest}, evaluates the template in the context + of other, then copies the result to self[dest]. This is on a best- + efforts basis. Some assignments can make no sense. ''' if not attrs: return for src in attrs: try: - sfm = other.metadata_for_field(src) + val = composite_formatter.safe_format\ + (src, other, 'PLUGBOARD TEMPLATE ERROR', other) dfm = self.metadata_for_field(attrs[src]) if dfm['is_multiple']: - if sfm['is_multiple']: - self.set(attrs[src], other.get(src)) - else: - self.set(attrs[src], - [f.strip() for f in other.get(src).split(',') - if f.strip()]) - elif sfm['is_multiple']: - self.set(attrs[src], ','.join(other.get(src))) + self.set(attrs[src], + [f.strip() for f in val.split(',') if f.strip()]) else: - self.set(attrs[src], other.get(src)) + self.set(attrs[src], val) except: traceback.print_exc() pass diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 4c866b1855..3da4fddb5d 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -349,7 +349,7 @@ class DeviceManager(Thread): # {{{ with open(f, 'r+b') as stream: if cpb: newmi = mi.deepcopy() - newmi.copy_specific_attributes(mi, cpb) + newmi.template_to_attribute(mi, cpb) else: newmi = mi set_metadata(stream, newmi, stream_type=ext) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 9da5420681..a808fd9c43 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -750,8 +750,10 @@ class BooksModel(QAbstractTableModel): # {{{ self.refresh(reset=True) return True - self.db.set_custom(self.db.id(row), val, extra=s_index, + id = self.db.id(row) + self.db.set_custom(id, val, extra=s_index, label=label, num=None, append=False, notify=True) + self.refresh_ids([id], current_row=row) return True def setData(self, index, value, role): diff --git a/src/calibre/gui2/preferences/plugboard.py b/src/calibre/gui2/preferences/plugboard.py index 124654b643..011131ae48 100644 --- a/src/calibre/gui2/preferences/plugboard.py +++ b/src/calibre/gui2/preferences/plugboard.py @@ -56,18 +56,19 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.formats.insert(1, plugboard_any_format_value) self.new_format.addItems(self.formats) - self.fields = [''] - for f in self.db.all_field_keys(): - if self.db.field_metadata[f].get('rec_index', None) is not None and\ - self.db.field_metadata[f]['datatype'] is not None and \ - self.db.field_metadata[f]['search_terms']: - self.fields.append(f) - self.fields.sort(cmp=field_cmp) + self.source_fields = [''] + for f in self.db.custom_field_keys(): + if self.db.field_metadata[f]['datatype'] == 'composite': + self.source_fields.append(f) + self.source_fields.sort(cmp=field_cmp) + + self.dest_fields = ['', 'authors', 'author_sort', 'publisher', + 'tags', 'title'] self.source_widgets = [] self.dest_widgets = [] for i in range(0, 10): - w = QtGui.QComboBox(self) + w = QtGui.QLineEdit(self) self.source_widgets.append(w) self.fields_layout.addWidget(w, 5+i, 0, 1, 1) w = QtGui.QComboBox(self) @@ -101,14 +102,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.ok_button.setEnabled(True) self.del_button.setEnabled(True) for w in self.source_widgets: - w.addItems(self.fields) + w.clear() for w in self.dest_widgets: - w.addItems(self.fields) + w.addItems(self.dest_fields) def set_field(self, i, src, dst): - idx = self.fields.index(src) - self.source_widgets[i].setCurrentIndex(idx) - idx = self.fields.index(dst) + self.source_widgets[i].setText(src) + idx = self.dest_fields.index(dst) self.dest_widgets[i].setCurrentIndex(idx) def edit_device_changed(self, txt): @@ -216,11 +216,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def ok_clicked(self): pb = {} for i in range(0, len(self.source_widgets)): - s = self.source_widgets[i].currentIndex() - if s != 0: + s = unicode(self.source_widgets[i].text()) + if s: d = self.dest_widgets[i].currentIndex() if d != 0: - pb[self.fields[s]] = self.fields[d] + pb[s] = self.dest_fields[d] if len(pb) == 0: if self.current_format in self.current_plugboards: fpb = self.current_plugboards[self.current_format] @@ -266,9 +266,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if d not in self.current_plugboards[f]: continue ops = [] - for op in self.fields: - if op not in self.current_plugboards[f][d]: - continue + for op in self.current_plugboards[f][d]: ops.append(op + '->' + self.current_plugboards[f][d][op]) txt += '%s:%s [%s]\n'%(f, d, ', '.join(ops)) self.existing_plugboards.setPlainText(txt) diff --git a/src/calibre/gui2/preferences/plugboard.ui b/src/calibre/gui2/preferences/plugboard.ui index f88af8ff50..79a07be1f7 100644 --- a/src/calibre/gui2/preferences/plugboard.ui +++ b/src/calibre/gui2/preferences/plugboard.ui @@ -87,6 +87,9 @@ QPlainTextEdit::NoWrap + + true + @@ -109,7 +112,7 @@ - Source field + Source template Qt::AlignCenter diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index a36dbe57a9..42720c5e83 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -672,7 +672,7 @@ class ResultCache(SearchQueryParser): # {{{ if len(self.composites) > 0: mi = db.get_metadata(id, index_is_id=True) for k,c in self.composites: - self._data[id][c] = mi.format_field(k)[1] + self._data[id][c] = mi.get(k) self._map[0:0] = ids self._map_filtered[0:0] = ids @@ -702,7 +702,7 @@ class ResultCache(SearchQueryParser): # {{{ if len(self.composites) > 0: mi = db.get_metadata(item[0], index_is_id=True) for k,c in self.composites: - item[c] = mi.format_field(k)[1] + item[c] = mi.get(k) self._map = [i[0] for i in self._data if i is not None] if field is not None: diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index a2c8a62694..113ebf823a 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -112,8 +112,6 @@ class SafeFormat(TemplateFormatter): Provides a format function that substitutes '' for any missing value ''' - composite_values = {} - def get_value(self, key, args, kwargs): try: b = self.book.get_user_metadata(key, False) @@ -131,11 +129,6 @@ class SafeFormat(TemplateFormatter): except: return '' - def safe_format(self, fmt, kwargs, error_value, book, sanitize=None): - self.composite_values = {} - return TemplateFormatter.safe_format(self, fmt, kwargs, error_value, - book, sanitize) - safe_formatter = SafeFormat() def get_components(template, mi, id, timefmt='%b %Y', length=250, @@ -279,7 +272,7 @@ def save_book_to_disk(id, db, root, opts, length): try: if cpb: newmi = mi.deepcopy() - newmi.copy_specific_attributes(mi, cpb) + newmi.template_to_attribute(mi, cpb) else: newmi = mi set_metadata(stream, newmi, fmt) diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index f95a6deee5..502574dd3c 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -11,6 +11,10 @@ class TemplateFormatter(string.Formatter): Provides a format function that substitutes '' for any missing value ''' + # Dict to do recursion detection. It is up the the individual get_value + # method to use it. It is cleared when starting to format a template + composite_values = {} + def __init__(self): string.Formatter.__init__(self) self.book = None @@ -114,6 +118,7 @@ class TemplateFormatter(string.Formatter): self.kwargs = kwargs self.book = book self.sanitize = sanitize + self.composite_values = {} try: ans = self.vformat(fmt, [], kwargs).strip() except: