%PDF- %PDF-
Direktori : /lib/calibre/calibre/devices/usbms/ |
Current File : //lib/calibre/calibre/devices/usbms/books.py |
__license__ = 'GPL 3' __copyright__ = '2009, John Schember <john@nachtimwald.com>' __docformat__ = 'restructuredtext en' import os, re, time, sys from functools import cmp_to_key from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata.book.base import Metadata from calibre.devices.mime import mime_type_ext from calibre.devices.interface import BookList as _BookList from calibre.constants import preferred_encoding from calibre import isbytestring, force_unicode from calibre.utils.config_base import tweaks from calibre.utils.icu import sort_key from polyglot.builtins import string_or_bytes, iteritems, itervalues, cmp def none_cmp(xx, yy): x = xx[1] y = yy[1] if x is None and y is None: # No sort_key needed here, because defaults are ascii try: return cmp(xx[2], yy[2]) except TypeError: return 0 if x is None: return 1 if y is None: return -1 if isinstance(x, string_or_bytes) and isinstance(y, string_or_bytes): x, y = sort_key(force_unicode(x)), sort_key(force_unicode(y)) try: c = cmp(x, y) except TypeError: c = 0 if c != 0: return c # same as above -- no sort_key needed here try: return cmp(xx[2], yy[2]) except TypeError: return 0 class Book(Metadata): def __init__(self, prefix, lpath, size=None, other=None): from calibre.ebooks.metadata.meta import path_to_ext Metadata.__init__(self, '') self._new_book = False self.device_collections = [] self.path = os.path.join(prefix, lpath) if os.sep == '\\': self.path = self.path.replace('/', '\\') self.lpath = lpath.replace('\\', '/') else: self.lpath = lpath self.mime = mime_type_ext(path_to_ext(lpath)) self.size = size # will be set later if None try: self.datetime = time.gmtime(os.path.getctime(self.path)) except: self.datetime = time.gmtime() if other: self.smart_update(other) def __eq__(self, other): # use lpath because the prefix can change, changing path return self.lpath == getattr(other, 'lpath', None) @property def db_id(self): '''The database id in the application database that this file corresponds to''' match = re.search(r'_(\d+)$', self.lpath.rpartition('.')[0]) if match: return int(match.group(1)) return None @property def title_sorter(self): '''String to sort the title. If absent, title is returned''' return title_sort(self.title) @property def thumbnail(self): return None class BookList(_BookList): def __init__(self, oncard, prefix, settings): _BookList.__init__(self, oncard, prefix, settings) self._bookmap = {} def supports_collections(self): return False def add_book(self, book, replace_metadata): return self.add_book_extended(book, replace_metadata, check_for_duplicates=True) def add_book_extended(self, book, replace_metadata, check_for_duplicates): ''' Add the book to the booklist, if needed. Return None if the book is already there and not updated, otherwise return the book. ''' try: b = self.index(book) if check_for_duplicates else None except (ValueError, IndexError): b = None if b is None: self.append(book) return book if replace_metadata: self[b].smart_update(book, replace_metadata=True) return self[b] return None def remove_book(self, book): self.remove(book) def get_collections(self): return {} class CollectionsBookList(BookList): def supports_collections(self): return True def in_category_sort_rules(self, attr): sorts = tweaks['sony_collection_sorting_rules'] for attrs,sortattr in sorts: if attr in attrs or '*' in attrs: return sortattr return None def compute_category_name(self, field_key, field_value, field_meta): from calibre.utils.formatter import EvalFormatter renames = tweaks['sony_collection_renaming_rules'] field_name = renames.get(field_key, None) if field_name is None: if field_meta['is_custom']: field_name = field_meta['name'] else: field_name = '' cat_name = EvalFormatter().safe_format( fmt=tweaks['sony_collection_name_template'], kwargs={'category':field_name, 'value':field_value}, error_value='GET_CATEGORY', book=None) return cat_name.strip() def get_collections(self, collection_attributes): from calibre.devices.usbms.driver import debug_print from calibre.utils.config import device_prefs debug_print('Starting get_collections:', device_prefs['manage_device_metadata']) debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules']) debug_print('Formatting template:', tweaks['sony_collection_name_template']) debug_print('Sorting rules:', tweaks['sony_collection_sorting_rules']) # Complexity: we can use renaming rules only when using automatic # management. Otherwise we don't always have the metadata to make the # right decisions use_renaming_rules = device_prefs['manage_device_metadata'] == 'on_connect' collections = {} # get the special collection names all_by_author = '' all_by_title = '' ca = [] all_by_something = [] for c in collection_attributes: if c.startswith('aba:') and c[4:].strip(): all_by_author = c[4:].strip() elif c.startswith('abt:') and c[4:].strip(): all_by_title = c[4:].strip() elif c.startswith('abs:') and c[4:].strip(): name = c[4:].strip() sby = self.in_category_sort_rules(name) if sby is None: sby = name if name and sby: all_by_something.append((name, sby)) else: ca.append(c.lower()) collection_attributes = ca for book in self: tsval = book.get('_pb_title_sort', book.get('title_sort', book.get('title', 'zzzz'))) asval = book.get('_pb_author_sort', book.get('author_sort', '')) # Make sure we can identify this book via the lpath lpath = getattr(book, 'lpath', None) if lpath is None: continue # Decide how we will build the collections. The default: leave the # book in all existing collections. Do not add any new ones. attrs = ['device_collections'] if getattr(book, '_new_book', False): if device_prefs['manage_device_metadata'] == 'manual': # Ensure that the book is in all the book's existing # collections plus all metadata collections attrs += collection_attributes else: # For new books, both 'on_send' and 'on_connect' do the same # thing. The book's existing collections are ignored. Put # the book in collections defined by its metadata. attrs = collection_attributes elif device_prefs['manage_device_metadata'] == 'on_connect': # For existing books, modify the collections only if the user # specified 'on_connect' attrs = collection_attributes for attr in attrs: attr = attr.strip() # If attr is device_collections, then we cannot use # format_field, because we don't know the fields where the # values came from. if attr == 'device_collections': doing_dc = True val = book.device_collections # is a list else: doing_dc = False ign, val, orig_val, fm = book.format_field_extended(attr) if not val: continue if isbytestring(val): val = val.decode(preferred_encoding, 'replace') if isinstance(val, (list, tuple)): val = list(val) elif fm['datatype'] == 'series': val = [orig_val] elif fm['datatype'] == 'text' and fm['is_multiple']: val = orig_val elif fm['datatype'] == 'composite' and fm['is_multiple']: val = [v.strip() for v in val.split(fm['is_multiple']['ui_to_list'])] else: val = [val] sort_attr = self.in_category_sort_rules(attr) for category in val: is_series = False if doing_dc: # Attempt to determine if this value is a series by # comparing it to the series name. if category == book.series: is_series = True elif fm['is_custom']: # is a custom field if fm['datatype'] == 'text' and len(category) > 1 and \ category[0] == '[' and category[-1] == ']': continue if fm['datatype'] == 'series': is_series = True else: # is a standard field if attr == 'tags' and len(category) > 1 and \ category[0] == '[' and category[-1] == ']': continue if attr == 'series' or \ ('series' in collection_attributes and book.get('series', None) == category): is_series = True if use_renaming_rules: cat_name = self.compute_category_name(attr, category, fm) else: cat_name = category if cat_name not in collections: collections[cat_name] = {} if use_renaming_rules and sort_attr: sort_val = book.get(sort_attr, None) collections[cat_name][lpath] = (book, sort_val, tsval) elif is_series: if doing_dc: collections[cat_name][lpath] = \ (book, book.get('series_index', sys.maxsize), tsval) else: collections[cat_name][lpath] = \ (book, book.get(attr+'_index', sys.maxsize), tsval) else: if lpath not in collections[cat_name]: collections[cat_name][lpath] = (book, tsval, tsval) # All books by author if all_by_author: if all_by_author not in collections: collections[all_by_author] = {} collections[all_by_author][lpath] = (book, asval, tsval) # All books by title if all_by_title: if all_by_title not in collections: collections[all_by_title] = {} collections[all_by_title][lpath] = (book, tsval, asval) for (n, sb) in all_by_something: if n not in collections: collections[n] = {} collections[n][lpath] = (book, book.get(sb, ''), tsval) # Sort collections result = {} for category, lpaths in iteritems(collections): books = sorted(itervalues(lpaths), key=cmp_to_key(none_cmp)) result[category] = [x[0] for x in books] return result def rebuild_collections(self, booklist, oncard): ''' For each book in the booklist for the card oncard, remove it from all its current collections, then add it to the collections specified in device_collections. oncard is None for the main memory, carda for card A, cardb for card B, etc. booklist is the object created by the :method:`books` call above. ''' pass