diff --git a/src/calibre/devices/metadata_serializer.py b/src/calibre/devices/metadata_serializer.py new file mode 100644 index 0000000000..651ba1d678 --- /dev/null +++ b/src/calibre/devices/metadata_serializer.py @@ -0,0 +1,90 @@ +''' +Created on 21 May 2010 + +@author: charles +''' + +from calibre.constants import filesystem_encoding, preferred_encoding +from calibre import isbytestring +import json + +class MetadataSerializer(object): + + SERIALIZED_ATTRS = [ + 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort', + 'title_sort', 'comments', 'category', 'publisher', 'series', + 'series_index', 'rating', 'isbn', 'language', 'application_id', + 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type', + 'uuid', + ] + + def to_json(self): + json = {} + for attr in self.SERIALIZED_ATTRS: + val = getattr(self, attr) + if isbytestring(val): + enc = filesystem_encoding if attr == 'lpath' else preferred_encoding + val = val.decode(enc, 'replace') + elif isinstance(val, (list, tuple)): + val = [x.decode(preferred_encoding, 'replace') if + isbytestring(x) else x for x in val] + json[attr] = val + return json + + def read_json(self, cache_file): + with open(cache_file, 'rb') as f: + js = json.load(f, encoding='utf-8') + return js + + def write_json(self, js, cache_file): + with open(cache_file, 'wb') as f: + json.dump(js, f, indent=2, encoding='utf-8') + + def string_to_value(self, string, col_metadata, column_label=None): + ''' + if column_label is none, col_metadata must be a dict containing custom + column metadata for one column. If column_label is not none, then + col_metadata must be a dict of custom column metadata, with column + labels as keys. Metadata for standard columns is always assumed to be in + the col_metadata dict. If column_label is not standard and is not in + col_metadata, check if it matches a custom column. If so, use that + column metadata. See get_column_metadata below. + ''' + pass + + def value_to_display(self, value, col_metadata, column_label=None): + pass + + def value_to_string (self, value, col_metadata, column_label=None): + pass + + def get_column_metadata(self, column_label = None, from_book=None): + ''' + if column_label is None, then from_book must not be None. Returns the + complete set of custom column metadata for that book. + + If column_label is not None, return the column metadata for the given + column. This works even if the label is for a built-in column. If + from_book is None, then column_label must be a current custom column + label or a standard label. If from_book is not None, then the column + metadata from that metadata set is returned if it exists, otherwise the + standard metadata for that column is returned. If neither is found, + return {} + ''' + pass + + def get_custom_column_labels(self, book): + ''' + returns a list of custom column attributes in the book metadata. + ''' + pass + + def get_standard_column_labels(self): + ''' + returns a list of standard attributes that should be in any book's + metadata + ''' + pass + +metadata_serializer = MetadataSerializer() + diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 6e8811432a..8d79981ad7 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -9,20 +9,14 @@ import os, re, time, sys from calibre.ebooks.metadata import MetaInformation from calibre.devices.mime import mime_type_ext from calibre.devices.interface import BookList as _BookList -from calibre.constants import filesystem_encoding, preferred_encoding +from calibre.devices.metadata_serializer import MetadataSerializer +from calibre.constants import preferred_encoding from calibre import isbytestring -class Book(MetaInformation): +class Book(MetaInformation, MetadataSerializer): BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections'] - JSON_ATTRS = [ - 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort', - 'title_sort', 'comments', 'category', 'publisher', 'series', - 'series_index', 'rating', 'isbn', 'language', 'application_id', - 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type', - 'uuid', - ] def __init__(self, prefix, lpath, size=None, other=None): from calibre.ebooks.metadata.meta import path_to_ext @@ -82,19 +76,6 @@ class Book(MetaInformation): val = getattr(other, attr, None) setattr(self, attr, val) - def to_json(self): - json = {} - for attr in self.JSON_ATTRS: - val = getattr(self, attr) - if isbytestring(val): - enc = filesystem_encoding if attr == 'lpath' else preferred_encoding - val = val.decode(enc, 'replace') - elif isinstance(val, (list, tuple)): - val = [x.decode(preferred_encoding, 'replace') if - isbytestring(x) else x for x in val] - json[attr] = val - return json - class BookList(_BookList): def supports_collections(self): diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 97c212775a..3c30827dbc 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -17,6 +17,7 @@ from itertools import cycle from calibre import prints, isbytestring from calibre.constants import filesystem_encoding +from calibre.devices.metadata_serializer import metadata_serializer as ms from calibre.devices.usbms.cli import CLI from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book @@ -260,8 +261,7 @@ class USBMS(CLI, Device): os.makedirs(self.normalize_path(prefix)) js = [item.to_json() for item in booklists[listid] if hasattr(item, 'to_json')] - with open(self.normalize_path(os.path.join(prefix, self.METADATA_CACHE)), 'wb') as f: - json.dump(js, f, indent=2, encoding='utf-8') + ms.write_json(js, self.normalize_path(os.path.join(prefix, self.METADATA_CACHE))) write_prefix(self._main_prefix, 0) write_prefix(self._card_a_prefix, 1) write_prefix(self._card_b_prefix, 2) @@ -293,8 +293,7 @@ class USBMS(CLI, Device): cache_file = cls.normalize_path(os.path.join(prefix, name)) if os.access(cache_file, os.R_OK): try: - with open(cache_file, 'rb') as f: - js = json.load(f, encoding='utf-8') + js = ms.read_json(cache_file) for item in js: book = cls.book_class(prefix, item.get('lpath', None)) for key in item.keys(): diff --git a/src/calibre/gui2/dialogs/config/create_custom_column.py b/src/calibre/gui2/dialogs/config/create_custom_column.py index 5b470123a4..296a868fbf 100644 --- a/src/calibre/gui2/dialogs/config/create_custom_column.py +++ b/src/calibre/gui2/dialogs/config/create_custom_column.py @@ -8,6 +8,7 @@ from functools import partial from PyQt4.QtCore import SIGNAL from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant +from calibre.devices.metadata_serializer import metadata_serializer from calibre.gui2.dialogs.config.create_custom_column_ui import Ui_QCreateCustomColumn from calibre.gui2 import error_dialog @@ -102,6 +103,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): return self.simple_error('', _('No lookup name was provided')) if not col_heading: return self.simple_error('', _('No column heading was provided')) + if col in metadata_serializer.SERIALIZED_ATTRS: + return self.simple_error('', _('The lookup name %s is reserved and cannot be used')%col) bad_col = False if col in self.parent.custcols: if not self.editing_col or self.parent.custcols[col]['num'] != self.orig_column_number: diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 0fc2c7f7ed..bc0367b766 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -883,7 +883,7 @@ class DeviceBooksModel(BooksModel): # {{{ self.reset() self.last_search = text if self.last_search: - self.searched.emit(False) + self.searched.emit(True) def sort(self, col, order, reset=True): diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 230debd598..575f5563d6 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -136,12 +136,12 @@ class SearchBox2(QComboBox): def text_edited_slot(self, text): if self.as_you_type: text = unicode(text) - self.prev_text = text self.timer = self.startTimer(self.__class__.INTERVAL) def timerEvent(self, event): self.killTimer(event.timerId()) if event.timerId() == self.timer: + self.timer = None self.do_search() @property @@ -190,6 +190,9 @@ class SearchBox2(QComboBox): def set_search_string(self, txt): self.normalize_state() self.setEditText(txt) + if self.timer is not None: # Turn off any timers that got started in setEditText + self.killTimer(self.timer) + self.timer = None self.search.emit(txt, False) self.line_edit.end(False) self.initial_state = False