def set_title(book_id_val_map, db, field, *args): ans = one_one_in_books(book_id_val_map, db, field, *args) # Set the title sort field if the title changed field.title_sort_field.writer.set_books( {k: title_sort(v) for k, v in book_id_val_map.items() if k in ans}, db) return ans
def commit(self, save_defaults=False): ''' Settings are stored in two attributes: `opf_file` and `cover_file`. Both may be None. Also returns a recommendation dictionary. ''' recs = self.commit_options(save_defaults) self.user_mi = mi = self.get_metadata() self.cover_file = self.opf_file = None if self.db is not None: if mi.title == self.db.title(self.book_id, index_is_id=True): mi.title_sort = self.db.title_sort(self.book_id, index_is_id=True) else: # Regenerate title sort taking into account book language languages = self.db.languages(self.book_id, index_is_id=True) if languages: lang = languages.split(',')[0] else: lang = None mi.title_sort = title_sort(mi.title, lang=lang) self.db.set_metadata(self.book_id, self.user_mi) self.mi, self.opf_file = create_opf_file(self.db, self.book_id) if self.cover_changed and self.cover_data is not None: self.db.set_cover(self.book_id, self.cover_data) cover = self.db.cover(self.book_id, index_is_id=True) if cover: cf = PersistentTemporaryFile('.jpeg') cf.write(cover) cf.close() self.cover_file = cf return recs
def _get_metadata(self, book, pdf_stats): ''' Return a populated Book object with available metadata ''' from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort from calibre.ebooks.metadata.pdf import get_metadata self._log_location(repr(book)) pdf_path = os.path.join(self.temp_dir, pdf_stats['path']) with open(pdf_path, 'rb') as f: stream = cStringIO.StringIO(f.read()) mi = get_metadata(stream, cover=False) this_book = Book(mi.title, authors=mi.authors) this_book.author_sort = author_to_author_sort(mi.authors[0]) this_book.datetime = datetime.fromtimestamp( int(pdf_stats['stats']['st_birthtime'])).timetuple() this_book.dateadded = int(pdf_stats['stats']['st_birthtime']) this_book.path = book this_book.size = int(pdf_stats['stats']['st_size']) this_book.thumbnail = self._get_goodreader_thumb(book) if this_book.thumbnail: this_book.thumb_data = base64.b64encode(this_book.thumbnail) else: this_book.thumb_data = None this_book.title_sort = title_sort(mi.title) this_book.uuid = None return this_book
def _get_metadata(self, book, pdf_stats): """ Return a populated Book object with available metadata """ from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort from calibre.ebooks.metadata.pdf import get_metadata self._log_location(repr(book)) pdf_path = os.path.join(self.temp_dir, pdf_stats["path"]) with open(pdf_path, "rb") as f: stream = cStringIO.StringIO(f.read()) mi = get_metadata(stream, cover=False) this_book = Book(mi.title, " & ".join(mi.authors)) this_book.author_sort = author_to_author_sort(mi.authors[0]) this_book.datetime = datetime.fromtimestamp(int(pdf_stats["stats"]["st_birthtime"])).timetuple() this_book.dateadded = int(pdf_stats["stats"]["st_birthtime"]) this_book.path = book this_book.size = int(pdf_stats["stats"]["st_size"]) this_book.thumbnail = self._get_goodreader_thumb(book) if this_book.thumbnail: this_book.thumb_data = base64.b64encode(this_book.thumbnail) else: this_book.thumb_data = None this_book.title_sort = title_sort(mi.title) this_book.uuid = None return this_book
def to_mi(self, mi): val = self.value mi.set(self.field, val) if self.field == 'title': mi.set('title_sort', title_sort(val, lang=mi.language)) elif self.field == 'authors': mi.set('author_sort', authors_to_sort_string(val))
def set_title(book_id_val_map, db, field, *args): ans = one_one_in_books(book_id_val_map, db, field, *args) # Set the title sort field field.title_sort_field.writer.set_books( {k: title_sort(v) for k, v in iteritems(book_id_val_map)}, db) return ans
def __getitem__(self, col): if self._must_do: is_comp = False if isinstance(col, slice): start = 0 if col.start is None else col.start step = 1 if col.stop is None else col.stop for c in range(start, col.stop, step): if c in self._composites: is_comp = True break elif col in self._composites: is_comp = True if is_comp: id_ = list.__getitem__(self, 0) self._must_do = False mi = self.db.get_metadata(id_, index_is_id=True, get_user_categories=False) for c in self._composites: self[c] = mi.get(self._composites[c]) if col == self._series_sort_col and self._series_sort is None: if self[self._series_col]: self._series_sort = title_sort(self[self._series_col]) self[self._series_sort_col] = self._series_sort else: self._series_sort = '' self[self._series_sort_col] = '' return list.__getitem__(self, col)
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' tags_model = self.tags_view.model() result = tags_model.get_category_editor_data(category) if result is None: return if category == 'series': key = lambda x: sort_key(title_sort(x)) else: key = sort_key db = self.library_view.model().db d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, data=result, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name rename_func = None if category == 'tags': rename_func = db.rename_tag delete_func = db.delete_tag_using_id elif category == 'series': rename_func = db.rename_series delete_func = db.delete_series_using_id elif category == 'publisher': rename_func = db.rename_publisher delete_func = db.delete_publisher_using_id else: # must be custom cc_label = db.field_metadata[category]['label'] rename_func = partial(db.rename_custom_item, label=cc_label) delete_func = partial(db.delete_custom_item_using_id, label=cc_label) m = self.tags_view.model() if rename_func: for item in to_delete: delete_func(item) m.delete_item_from_all_user_categories( orig_name[item], category) for old_id in to_rename: rename_func(old_id, new_name=unicode(to_rename[old_id])) m.rename_item_in_all_user_categories( orig_name[old_id], category, unicode(to_rename[old_id])) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def sort_key_for_series(self, book_id, lang_map, series_sort_order): sid = self.table.book_col_map.get(book_id, None) if sid is None: return self._default_sort_key lang = lang_map.get(book_id, None) or None if lang: lang = lang[0] return self._sort_key(title_sort(self.table.id_map[sid], order=series_sort_order, lang=lang))
def get_series_sort(self, idx, index_is_id=True, default_value=''): book_id = idx if index_is_id else self.index_to_id(idx) with self.cache.read_lock: lang_map = self.cache.fields['languages'].book_value_map lang = lang_map.get(book_id, None) or None if lang: lang = lang[0] return title_sort(self.cache._field_for('series', book_id, default_value=''), order=tweaks['title_series_sorting'], lang=lang)
def get_sort(book_id): if args.languages: lang = args.languages[0] else: try: lang = lang_map[book_id][0] except (KeyError, IndexError, TypeError, AttributeError): lang = 'eng' return title_sort(title_map[book_id], lang=lang)
def get_series_sort(self, idx, index_is_id=True, default_value=''): book_id = idx if index_is_id else self.index_to_id(idx) with self.cache.safe_read_lock: lang_map = self.cache.fields['languages'].book_value_map lang = lang_map.get(book_id, None) or None if lang: lang = lang[0] return title_sort(self.cache._field_for('series', book_id, default_value=''), order=tweaks['title_series_sorting'], lang=lang)
def sync_booklists(self, booklists, end_session=True): ''' Update metadata on device. @param booklists: A tuple containing the result of calls to (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). prefs['manage_device_metadata']: ['manual'|'on_send'|'on_connect'] booklist will reflect library metadata only when manage_device_metadata=='on_connect', otherwise booklist metadata comes from device ''' from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort self._log_location() for booklist in booklists: if not booklist: continue # Update db title/author from booklist title/author con = sqlite3.connect(self.local_metadata) with con: con.row_factory = sqlite3.Row cur = con.cursor() for book in booklist: cur.execute('''SELECT authors, filename, title FROM metadata WHERE filename = {0} '''.format( self._quote_sqlite_identifier(book.path))) cached_book = cur.fetchone() if cached_book: if (book.title != cached_book[b'title'] or book.authors != [cached_book[b'authors']]): self._log("updating metadata for %s" % repr(book.path)) cur.execute('''UPDATE metadata SET authors = "{0}", author_sort = "{1}", title = "{2}", title_sort = "{3}" WHERE filename = {4} '''.format(self._escape_delimiters(' & '.join(book.authors)), self._escape_delimiters(author_to_author_sort(book.authors[0])), self._escape_delimiters(book.title), self._escape_delimiters(title_sort(book.title)), self._quote_sqlite_identifier(book.path))) con.commit() # Copy the updated db to the iDevice self._log("updating remote_metadata") self.ios.copy_to_idevice(str(self.local_metadata), str(self.remote_metadata))
def sync_booklists(self, booklists, end_session=True): ''' Update metadata on device. @param booklists: A tuple containing the result of calls to (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). prefs['manage_device_metadata']: ['manual'|'on_send'|'on_connect'] booklist will reflect library metadata only when manage_device_metadata=='on_connect', otherwise booklist metadata comes from device ''' from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort self._log_location() for booklist in booklists: if not booklist: continue # Update db title/author from booklist title/author con = sqlite3.connect(self.local_metadata) with con: con.row_factory = sqlite3.Row cur = con.cursor() for book in booklist: cur.execute('''SELECT authors, filename, title FROM metadata WHERE filename = {0} '''.format(json.dumps(book.path))) cached_book = cur.fetchone() if cached_book: if (book.title != cached_book[b'title'] or book.authors != [cached_book[b'authors']]): self._log("updating metadata for %s" % repr(book.path)) cur.execute('''UPDATE metadata SET authors = "{0}", author_sort = "{1}", title = "{2}", title_sort = "{3}" WHERE filename = {4} '''.format(self._escape_delimiters('; '.join(book.authors)), self._escape_delimiters(author_to_author_sort(book.authors[0])), self._escape_delimiters(book.title), self._escape_delimiters(title_sort(book.title)), json.dumps(book.path))) con.commit() # Copy the updated db to the iDevice self._log("updating remote_metadata") self.ios.copy_to_idevice(str(self.local_metadata), str(self.remote_metadata))
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' db = self.current_db def get_book_ids(use_virtual_library): book_ids = None if not use_virtual_library else self.tags_view.model( ).get_book_ids_to_use() data = db.new_api.get_categories(book_ids=book_ids) if category in data: result = [(t.id, t.original_name, t.count) for t in data[category] if t.count > 0] else: result = None return result if category == 'series': key = lambda x: sort_key(title_sort(x)) else: key = sort_key d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, get_book_ids=get_book_ids, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name if (category in ['tags', 'series', 'publisher'] or db.new_api.field_metadata.is_custom_field(category)): m = self.tags_view.model() for item in to_delete: m.delete_item_from_all_user_categories( orig_name[item], category) for old_id in to_rename: m.rename_item_in_all_user_categories( orig_name[old_id], category, unicode_type(to_rename[old_id])) db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def one_one_in_books(book_id_val_map, db, field, *args): 'Set a one-one field in the books table' if book_id_val_map: sequence = ((sqlite_datetime(v), k) for k, v in book_id_val_map.iteritems()) db.conn.executemany( 'UPDATE books SET %s=? WHERE id=?'%field.metadata['column'], sequence) field.table.book_col_map.update(book_id_val_map) if field.name == 'title': # Set the title sort field field.title_sort_field.writer.set_books( {k:title_sort(v) for k, v in book_id_val_map.iteritems()}, db) return set(book_id_val_map)
def update_state(self, *args): ts = title_sort(self.title_edit.current_val, lang=self.book_lang) normal = ts == self.current_val if normal: col = 'rgb(0, 255, 0, 20%)' else: col = 'rgb(255, 0, 0, 20%)' self.setStyleSheet('QLineEdit { color: black; ' 'background-color: %s; }' % col) tt = self.tooltips[0 if normal else 1] self.setToolTip(tt) self.setWhatsThis(tt)
def update_state(self, *args): ts = title_sort(self.title_edit.current_val, lang=self.book_lang) normal = ts == self.current_val if normal: col = 'rgb(0, 255, 0, 20%)' else: col = 'rgb(255, 0, 0, 20%)' self.setStyleSheet('QLineEdit { color: black; ' 'background-color: %s; }'%col) tt = self.tooltips[0 if normal else 1] self.setToolTip(tt) self.setWhatsThis(tt)
def do_set_metadata(opts, mi, stream, stream_type): mi = MetaInformation(mi) for x in ('guide', 'toc', 'manifest', 'spine'): setattr(mi, x, None) from_opf = getattr(opts, 'from_opf', None) if from_opf is not None: from calibre.ebooks.metadata.opf2 import OPF opf_mi = OPF(open(from_opf, 'rb')).to_book_metadata() mi.smart_update(opf_mi) for pref in config().option_set.preferences: if pref.name in ('to_opf', 'from_opf', 'authors', 'title_sort', 'author_sort', 'get_cover', 'cover', 'tags', 'lrf_bookid', 'identifiers'): continue val = getattr(opts, pref.name, None) if val is not None: setattr(mi, pref.name, val) if getattr(opts, 'authors', None) is not None: mi.authors = string_to_authors(opts.authors) mi.author_sort = authors_to_sort_string(mi.authors) if getattr(opts, 'author_sort', None) is not None: mi.author_sort = opts.author_sort if getattr(opts, 'title_sort', None) is not None: mi.title_sort = opts.title_sort elif getattr(opts, 'title', None) is not None: mi.title_sort = title_sort(opts.title) if getattr(opts, 'tags', None) is not None: mi.tags = [t.strip() for t in opts.tags.split(',')] if getattr(opts, 'series', None) is not None: mi.series = opts.series.strip() if getattr(opts, 'series_index', None) is not None: mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'pubdate', None) is not None: mi.pubdate = parse_date(opts.pubdate, assume_utc=False, as_utc=False) if getattr(opts, 'identifiers', None): val = { k.strip(): v.strip() for k, v in (x.partition(':')[0::2] for x in opts.identifiers) } if val: orig = mi.get_identifiers() orig.update(val) val = {k: v for k, v in orig.iteritems() if k and v} mi.set_identifiers(val) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() mi.cover_data = (ext, open(opts.cover, 'rb').read()) with force_identifiers: set_metadata(stream, mi, stream_type)
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' tags_model = self.tags_view.model() result = tags_model.get_category_editor_data(category) if result is None: return if category == 'series': key = lambda x:sort_key(title_sort(x)) else: key = sort_key db=self.library_view.model().db d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, data=result, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name rename_func = None if category == 'tags': rename_func = db.rename_tag delete_func = db.delete_tag_using_id elif category == 'series': rename_func = db.rename_series delete_func = db.delete_series_using_id elif category == 'publisher': rename_func = db.rename_publisher delete_func = db.delete_publisher_using_id else: # must be custom cc_label = db.field_metadata[category]['label'] rename_func = partial(db.rename_custom_item, label=cc_label) delete_func = partial(db.delete_custom_item_using_id, label=cc_label) m = self.tags_view.model() if rename_func: for item in to_delete: delete_func(item) m.delete_item_from_all_user_categories(orig_name[item], category) for old_id in to_rename: rename_func(old_id, new_name=unicode(to_rename[old_id])) m.rename_item_in_all_user_categories(orig_name[old_id], category, unicode(to_rename[old_id])) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def to_mi(self, mi): val = unicode(self.text()).strip() ism = self.metadata['is_multiple'] if ism: if not val: val = [] else: val = [x.strip() for x in val.split(ism['list_to_ui']) if x.strip()] mi.set(self.field, val) if self.field == 'title': mi.set('title_sort', title_sort(val, lang=mi.language)) elif self.field == 'authors': mi.set('author_sort', authors_to_sort_string(val))
def to_mi(self, mi): val = unicode(self.text()).strip() ism = self.metadata["is_multiple"] if ism: if not val: val = [] else: val = [x.strip() for x in val.split(ism["list_to_ui"]) if x.strip()] mi.set(self.field, val) if self.field == "title": mi.set("title_sort", title_sort(val, lang=mi.language)) elif self.field == "authors": mi.set("author_sort", authors_to_sort_string(val))
def _get_metadata(self, book, book_stats): ''' Return a populated Book object with available metadata ''' from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort self._log_location(repr(book)) format = book.rsplit('.')[1].lower() if format == 'mobi': from calibre.ebooks.metadata.mobi import get_metadata as get_mobi_metadata path = os.path.join(self.temp_dir, book_stats['path']) with open(path, 'rb') as f: stream = cStringIO.StringIO(f.read()) mi = get_mobi_metadata(stream) elif format == 'pdf': from calibre.ebooks.metadata.pdf import get_metadata as get_pdf_metadata path = os.path.join(self.temp_dir, book_stats['path']) with open(path, 'rb') as f: stream = cStringIO.StringIO(f.read()) mi = get_pdf_metadata(stream) else: self._log("unsupported format: '{}'".format(format)) return Book() if False: ''' Perform a bit of voodoo to match Kindle multiple author style ''' ks_authors = [] for a in mi.authors: if "," in a: # Already ln, fn ks_authors.append(a) else: ks_authors.append(author_to_author_sort(a)) this_book = Book(mi.title, '; '.join(mi.authors)) this_book.author_sort = '; '.join(mi.authors) this_book.datetime = datetime.fromtimestamp( int(book_stats['stats']['st_birthtime'])).timetuple() this_book.dateadded = int(book_stats['stats']['st_birthtime']) this_book.path = book this_book.size = int(book_stats['stats']['st_size']) this_book.thumbnail = self._get_kindle_thumb(mi.cover_data[1]) if this_book.thumbnail: this_book.thumb_data = base64.b64encode(this_book.thumbnail) else: this_book.thumb_data = None this_book.title_sort = title_sort(mi.title) this_book.uuid = None return this_book
def do_set_metadata(opts, mi, stream, stream_type): mi = MetaInformation(mi) for x in ('guide', 'toc', 'manifest', 'spine'): setattr(mi, x, None) from_opf = getattr(opts, 'from_opf', None) if from_opf is not None: from calibre.ebooks.metadata.opf2 import OPF opf_mi = OPF(open(from_opf, 'rb')).to_book_metadata() mi.smart_update(opf_mi) for pref in config().option_set.preferences: if pref.name in ('to_opf', 'from_opf', 'authors', 'title_sort', 'author_sort', 'get_cover', 'cover', 'tags', 'lrf_bookid', 'identifiers'): continue val = getattr(opts, pref.name, None) if val is not None: setattr(mi, pref.name, val) if getattr(opts, 'authors', None) is not None: mi.authors = string_to_authors(opts.authors) mi.author_sort = authors_to_sort_string(mi.authors) if getattr(opts, 'author_sort', None) is not None: mi.author_sort = opts.author_sort if getattr(opts, 'title_sort', None) is not None: mi.title_sort = opts.title_sort elif getattr(opts, 'title', None) is not None: mi.title_sort = title_sort(opts.title) if getattr(opts, 'tags', None) is not None: mi.tags = [t.strip() for t in opts.tags.split(',')] if getattr(opts, 'series', None) is not None: mi.series = opts.series.strip() if getattr(opts, 'series_index', None) is not None: mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'pubdate', None) is not None: mi.pubdate = parse_date(opts.pubdate, assume_utc=False, as_utc=False) if getattr(opts, 'identifiers', None): val = {k.strip():v.strip() for k, v in (x.partition(':')[0::2] for x in opts.identifiers)} if val: orig = mi.get_identifiers() orig.update(val) val = {k:v for k, v in iteritems(orig) if k and v} mi.set_identifiers(val) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() mi.cover_data = (ext, open(opts.cover, 'rb').read()) with force_identifiers: set_metadata(stream, mi, stream_type)
def _get_metadata(self, book, book_stats): ''' Return a populated Book object with available metadata ''' from calibre.ebooks.metadata import author_to_author_sort, authors_to_string, title_sort self._log_location(repr(book)) format = book.rsplit('.')[1].lower() if format == 'mobi': from calibre.ebooks.metadata.mobi import get_metadata as get_mobi_metadata path = os.path.join(self.temp_dir, book_stats['path']) with open(path, 'rb') as f: stream = cStringIO.StringIO(f.read()) mi = get_mobi_metadata(stream) elif format == 'pdf': from calibre.ebooks.metadata.pdf import get_metadata as get_pdf_metadata path = os.path.join(self.temp_dir, book_stats['path']) with open(path, 'rb') as f: stream = cStringIO.StringIO(f.read()) mi = get_pdf_metadata(stream) else: self._log("unsupported format: '{}'".format(format)) return Book() if False: ''' Perform a bit of voodoo to match Kindle multiple author style ''' ks_authors = [] for a in mi.authors: if "," in a: # Already ln, fn ks_authors.append(a) else: ks_authors.append(author_to_author_sort(a)) this_book = Book(mi.title, '; '.join(mi.authors)) this_book.author_sort = '; '.join(mi.authors) this_book.datetime = datetime.fromtimestamp(int(book_stats['stats']['st_birthtime'])).timetuple() this_book.dateadded = int(book_stats['stats']['st_birthtime']) this_book.path = book this_book.size = int(book_stats['stats']['st_size']) this_book.thumbnail = self._get_kindle_thumb(mi.cover_data[1]) if this_book.thumbnail: this_book.thumb_data = base64.b64encode(this_book.thumbnail) else: this_book.thumb_data = None this_book.title_sort = title_sort(mi.title) this_book.uuid = None return this_book
def one_one_in_books(book_id_val_map, db, field, *args): 'Set a one-one field in the books table' if book_id_val_map: sequence = ((sqlite_datetime(v), k) for k, v in book_id_val_map.iteritems()) db.conn.executemany( 'UPDATE books SET %s=? WHERE id=?' % field.metadata['column'], sequence) field.table.book_col_map.update(book_id_val_map) if field.name == 'title': # Set the title sort field field.title_sort_field.writer.set_books( {k: title_sort(v) for k, v in book_id_val_map.iteritems()}, db) return set(book_id_val_map)
def category_sort_value(self, item_id, book_ids, lang_map): lang = None tss = tweaks['title_series_sorting'] if tss != 'strictly_alphabetic': c = Counter() for book_id in book_ids: l = lang_map.get(book_id, None) if l: c[l[0]] += 1 if c: lang = c.most_common(1)[0][0] val = self.table.id_map[item_id] return title_sort(val, order=tss, lang=lang)
def do_tags_list_edit(self, tag, category, is_first_letter=False): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' db = self.current_db if category == 'series': key = lambda x: sort_key(title_sort(x)) else: key = sort_key d = TagListEditor(self, category=category, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, get_book_ids=partial(self.get_book_ids, db=db, category=category), sorter=key, ttm_is_first_letter=is_first_letter, fm=db.field_metadata[category]) d.exec_() if d.result() == QDialog.DialogCode.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name if (category in ['tags', 'series', 'publisher'] or db.new_api.field_metadata.is_custom_field(category)): m = self.tags_view.model() for item in to_delete: m.delete_item_from_all_user_categories( orig_name[item], category) for old_id in to_rename: m.rename_item_in_all_user_categories( orig_name[old_id], category, unicode_type(to_rename[old_id])) db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' tags_model = self.tags_view.model() result = tags_model.get_category_editor_data(category) if result is None: return if category == 'series': key = lambda x: sort_key(title_sort(x)) else: key = sort_key db = self.library_view.model().db d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, data=result, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name if (category in ['tags', 'series', 'publisher'] or db.new_api.field_metadata.is_custom_field(category)): m = self.tags_view.model() for item in to_delete: m.delete_item_from_all_user_categories( orig_name[item], category) for old_id in to_rename: m.rename_item_in_all_user_categories( orig_name[old_id], category, unicode(to_rename[old_id])) db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def pre_commit_check(self): if self.db is None: return True db = self.db.new_api title, authors = self.get_title_and_authors() try: if title != db.field_for('title', self.book_id): db.set_field('title', {self.book_id: title}) langs = db.field_for('languages', self.book_id) if langs: db.set_field('sort', {self.book_id: title_sort(title, langs[0])}) if list(authors) != list(db.field_for('authors', self.book_id)): db.set_field('authors', {self.book_id: authors}) if self.cover_changed and self.cover_data is not None: self.db.set_cover(self.book_id, self.cover_data) except EnvironmentError as err: if getattr(err, 'errno', None) == errno.EACCES: # Permission denied import traceback fname = getattr(err, 'filename', None) or 'file' error_dialog(self, _('Permission denied'), _('Could not open %s. Is it being used by another' ' program?') % fname, det_msg=traceback.format_exc(), show=True) return False publisher = self.publisher.text().strip() if publisher != db.field_for('publisher', self.book_id): db.set_field('publisher', {self.book_id: publisher}) author_sort = self.author_sort.text().strip() if author_sort != db.field_for('author_sort', self.book_id): db.set_field('author_sort', {self.book_id: author_sort}) tags = [t.strip() for t in self.tags.text().strip().split(',')] if tags != list(db.field_for('tags', self.book_id)): db.set_field('tags', {self.book_id: tags}) series_index = float(self.series_index.value()) series = self.series.currentText().strip() if series != db.field_for('series', self.book_id): db.set_field('series', {self.book_id: series}) if series and series_index != db.field_for('series_index', self.book_id): db.set_field('series_index', {self.book_id: series_index}) return True
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' db = self.current_db def get_book_ids(use_virtual_library): book_ids = None if not use_virtual_library else self.tags_view.model().get_book_ids_to_use() data = db.new_api.get_categories(book_ids=book_ids) if category in data: result = [(t.id, t.original_name, t.count) for t in data[category] if t.count > 0] else: result = None return result if category == 'series': key = lambda x:sort_key(title_sort(x)) else: key = sort_key d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, get_book_ids=get_book_ids, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name if (category in ['tags', 'series', 'publisher'] or db.new_api.field_metadata.is_custom_field(category)): m = self.tags_view.model() for item in to_delete: m.delete_item_from_all_user_categories(orig_name[item], category) for old_id in to_rename: m.rename_item_in_all_user_categories(orig_name[old_id], category, unicode_type(to_rename[old_id])) db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the dialog will position the editor on that item. ''' tags_model = self.tags_view.model() result = tags_model.get_category_editor_data(category) if result is None: return if category == 'series': key = lambda x:sort_key(title_sort(x)) else: key = sort_key db=self.library_view.model().db d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], tag_to_match=tag, data=result, sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name to_delete = d.to_delete # list of ids orig_name = d.original_names # dict of id: name if (category in ['tags', 'series', 'publisher'] or db.new_api.field_metadata.is_custom_field(category)): m = self.tags_view.model() for item in to_delete: m.delete_item_from_all_user_categories(orig_name[item], category) for old_id in to_rename: m.rename_item_in_all_user_categories(orig_name[old_id], category, unicode(to_rename[old_id])) db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def pre_commit_check(self): if self.db is None: return True db = self.db.new_api title, authors = self.get_title_and_authors() try: if title != db.field_for('title', self.book_id): db.set_field('title', {self.book_id:title}) langs = db.field_for('languages', self.book_id) if langs: db.set_field('sort', {self.book_id:title_sort(title, langs[0])}) if list(authors) != list(db.field_for('authors', self.book_id)): db.set_field('authors', {self.book_id:authors}) if self.cover_changed and self.cover_data is not None: self.db.set_cover(self.book_id, self.cover_data) except EnvironmentError as err: if getattr(err, 'errno', None) == errno.EACCES: # Permission denied import traceback fname = getattr(err, 'filename', None) or 'file' error_dialog(self, _('Permission denied'), _('Could not open %s. Is it being used by another' ' program?')%fname, det_msg=traceback.format_exc(), show=True) return False publisher = self.publisher.text().strip() if publisher != db.field_for('publisher', self.book_id): db.set_field('publisher', {self.book_id:publisher}) author_sort = self.author_sort.text().strip() if author_sort != db.field_for('author_sort', self.book_id): db.set_field('author_sort', {self.book_id:author_sort}) tags = [t.strip() for t in self.tags.text().strip().split(',')] if tags != list(db.field_for('tags', self.book_id)): db.set_field('tags', {self.book_id:tags}) series_index = float(self.series_index.value()) series = self.series.currentText().strip() if series != db.field_for('series', self.book_id): db.set_field('series', {self.book_id:series}) if series and series_index != db.field_for('series_index', self.book_id): db.set_field('series_index', {self.book_id:series_index}) return True
def auto_generate(self, *args): self.current_val = title_sort(self.title_edit.current_val, lang=self.book_lang)
def title_sorter(self): ans = getattr(self, 'title_sort', None) if not ans or self.is_null('title_sort') or ans == _('Unknown'): ans = '' return ans or title_sort(self.title or '')
def book_to_json(ctx, rd, db, book_id, get_category_urls=True, device_compatible=False, device_for_template=None): mi = db.get_metadata(book_id, get_cover=False) codec = JsonCodec(db.field_metadata) if not device_compatible: try: mi.rating = mi.rating/2. except Exception: mi.rating = 0.0 data = codec.encode_book_metadata(mi) for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime', 'rights', 'book_producer'): data.pop(x, None) get = partial(ctx.url_for, get_content, book_id=book_id, library_id=db.server_library_id) data['cover'] = get(what='cover') data['thumbnail'] = get(what='thumb') if not device_compatible: mi.format_metadata = {k.lower():dict(v) for k, v in mi.format_metadata.iteritems()} for v in mi.format_metadata.itervalues(): mtime = v.get('mtime', None) if mtime is not None: v['mtime'] = isoformat(mtime, as_utc=True) data['format_metadata'] = mi.format_metadata fmts = set(x.lower() for x in mi.format_metadata.iterkeys()) pf = prefs['output_format'].lower() other_fmts = list(fmts) try: fmt = pf if pf in fmts else other_fmts[0] except: fmt = None if fmts and fmt: other_fmts = [x for x in fmts if x != fmt] data['formats'] = sorted(fmts) if fmt: data['main_format'] = {fmt:get(what=fmt)} else: data['main_format'] = None data['other_formats'] = {fmt:get(what=fmt) for fmt in other_fmts} if get_category_urls: category_urls = data['category_urls'] = {} all_cats = ctx.get_categories(rd, db) for key in mi.all_field_keys(): fm = mi.metadata_for_field(key) if (fm and fm['is_category'] and not fm['is_csp'] and key != 'formats' and fm['datatype'] != 'rating'): categories = mi.get(key) or [] if isinstance(categories, basestring): categories = [categories] category_urls[key] = dbtags = {} for category in categories: for tag in all_cats.get(key, ()): if tag.original_name == category: dbtags[category] = ctx.url_for( books_in, encoded_category=encode_name(tag.category if tag.category else key), encoded_item=encode_name(tag.original_name if tag.id is None else unicode(tag.id)), library_id=db.server_library_id ) break else: series = data.get('series', None) or '' if series: tsorder = tweaks['save_template_title_series_sorting'] series = title_sort(series, order=tsorder) data['_series_sort_'] = series if device_for_template: import posixpath from calibre.devices.utils import create_upload_path from calibre.utils.filenames import ascii_filename as sanitize from calibre.customize.ui import device_plugins for device_class in device_plugins(): if device_class.__class__.__name__ == device_for_template: template = device_class.save_template() data['_filename_'] = create_upload_path(mi, book_id, template, sanitize, path_type=posixpath) break return data, mi.last_modified
def itervals(self, record): for name, fm in self.entries: dt = fm['datatype'] val = record[fm['rec_index']] if dt == 'composite': sb = fm['display'].get('composite_sort', 'text') if sb == 'date': try: val = parse_date(val) except: val = UNDEFINED_DATE dt = 'datetime' elif sb == 'number': try: p = 1 for i, candidate in enumerate( ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')): if val.endswith(candidate): p = 1024**(i) val = val[:-len(candidate)].strip() break val = locale.atof(val) * p except: val = 0.0 dt = 'float' elif sb == 'bool': val = force_to_bool(val) dt = 'bool' if dt == 'datetime': if val is None: val = UNDEFINED_DATE if tweaks['sort_dates_using_visible_fields']: format = None if name == 'timestamp': format = tweaks['gui_timestamp_display_format'] elif name == 'pubdate': format = tweaks['gui_pubdate_display_format'] elif name == 'last_modified': format = tweaks['gui_last_modified_display_format'] elif fm['is_custom']: format = fm['display'].get('date_format', None) val = clean_date_for_sort(val, format) elif dt == 'series': if val is None: val = ('', 1) else: if self.library_order: try: lang = record[self.lang_idx].partition(u',')[0] except (AttributeError, ValueError, KeyError, IndexError, TypeError): lang = None val = title_sort(val, order='library_order', lang=lang) sidx_fm = self.field_metadata[name + '_index'] sidx = record[sidx_fm['rec_index']] val = (self.string_sort_key(val), sidx) elif dt in ('text', 'comments', 'composite', 'enumeration'): if val: if fm['is_multiple']: jv = fm['is_multiple']['list_to_ui'] sv = fm['is_multiple']['cache_to_list'] if '&' in jv: val = jv.join( [author_to_author_sort(v) for v in val.split(sv)]) else: val = jv.join(sorted(val.split(sv), key=self.string_sort_key)) val = self.string_sort_key(val) elif dt == 'bool': if not self.db_prefs.get('bools_are_tristate'): val = {True: 1, False: 2, None: 2}.get(val, 2) else: val = {True: 1, False: 2, None: 3}.get(val, 3) yield val
def set_title(book_id_val_map, db, field, *args): ans = one_one_in_books(book_id_val_map, db, field, *args) # Set the title sort field field.title_sort_field.writer.set_books( {k:title_sort(v) for k, v in book_id_val_map.iteritems()}, db) return ans
def title_sorter(self): return title_sort(self.title)
def test_many_many_basic(self): # {{{ 'Test the different code paths for writing to a many-many field' cl = self.cloned_library cache = self.init_cache(cl) ae, af, sf = self.assertEqual, self.assertFalse, cache.set_field # Tags ae(sf('#tags', {1:cache.field_for('tags', 1), 2:cache.field_for('tags', 2)}), {1, 2}) for name in ('tags', '#tags'): f = cache.fields[name] af(sf(name, {1:('News', 'tag one')}, allow_case_change=False)) ae(sf(name, {1:'tag one, News'}), {1, 2}) ae(sf(name, {3:('tag two', 'sep,sep2')}), {2, 3}) ae(len(f.table.id_map), 4) ae(sf(name, {1:None}), {1}) cache2 = self.init_cache(cl) for c in (cache, cache2): ae(c.field_for(name, 3), ('tag two', 'sep;sep2')) ae(len(c.fields[name].table.id_map), 3) ae(len(c.fields[name].table.id_map), 3) ae(c.field_for(name, 1), ()) ae(c.field_for(name, 2), ('tag two', 'tag one')) del cache2 # Authors ae(sf('#authors', {k:cache.field_for('authors', k) for k in (1,2,3)}), {1,2,3}) for name in ('authors', '#authors'): f = cache.fields[name] ae(len(f.table.id_map), 3) af(cache.set_field(name, {3:'Unknown'})) ae(cache.set_field(name, {3:'Kovid Goyal & Divok Layog'}), {3}) ae(cache.set_field(name, {1:'', 2:'An, Author'}), {1,2}) cache2 = self.init_cache(cl) for c in (cache, cache2): ae(len(c.fields[name].table.id_map), 4 if name =='authors' else 3) ae(c.field_for(name, 3), ('Kovid Goyal', 'Divok Layog')) ae(c.field_for(name, 2), ('An, Author',)) ae(c.field_for(name, 1), (_('Unknown'),) if name=='authors' else ()) if name == 'authors': ae(c.field_for('author_sort', 1), author_to_author_sort(_('Unknown'))) ae(c.field_for('author_sort', 2), author_to_author_sort('An, Author')) ae(c.field_for('author_sort', 3), author_to_author_sort('Kovid Goyal') + ' & ' + author_to_author_sort('Divok Layog')) del cache2 ae(cache.set_field('authors', {1:'KoviD GoyaL'}), {1, 3}) ae(cache.field_for('author_sort', 1), 'GoyaL, KoviD') ae(cache.field_for('author_sort', 3), 'GoyaL, KoviD & Layog, Divok') # Languages f = cache.fields['languages'] ae(f.table.id_map, {1: 'eng', 2: 'deu'}) ae(sf('languages', {1:''}), {1}) ae(cache.field_for('languages', 1), ()) ae(sf('languages', {2:('und',)}), {2}) af(f.table.id_map) ae(sf('languages', {1:'eng,fra,deu', 2:'es,Dutch', 3:'English'}), {1, 2, 3}) ae(cache.field_for('languages', 1), ('eng', 'fra', 'deu')) ae(cache.field_for('languages', 2), ('spa', 'nld')) ae(cache.field_for('languages', 3), ('eng',)) ae(sf('languages', {3:None}), {3}) ae(cache.field_for('languages', 3), ()) ae(sf('languages', {1:'deu,fra,eng'}), {1}, 'Changing order failed') ae(sf('languages', {2:'deu,eng,eng'}), {2}) cache2 = self.init_cache(cl) for c in (cache, cache2): ae(cache.field_for('languages', 1), ('deu', 'fra', 'eng')) ae(cache.field_for('languages', 2), ('deu', 'eng')) del cache2 # Identifiers f = cache.fields['identifiers'] ae(sf('identifiers', {3: 'one:1,two:2'}), {3}) ae(sf('identifiers', {2:None}), {2}) ae(sf('identifiers', {1: {'test':'1', 'two':'2'}}), {1}) cache2 = self.init_cache(cl) for c in (cache, cache2): ae(c.field_for('identifiers', 3), {'one':'1', 'two':'2'}) ae(c.field_for('identifiers', 2), {}) ae(c.field_for('identifiers', 1), {'test':'1', 'two':'2'}) del cache2 # Test setting of title sort ae(sf('title', {1:'The Moose', 2:'Cat'}), {1, 2}) cache2 = self.init_cache(cl) for c in (cache, cache2): ae(c.field_for('sort', 1), title_sort('The Moose')) ae(c.field_for('sort', 2), title_sort('Cat')) # Test setting with the same value repeated ae(sf('tags', {3: ('a', 'b', 'a')}), {3}) ae(sf('tags', {3: ('x', 'X')}), {3}, 'Failed when setting tag twice with different cases') ae(('x',), cache.field_for('tags', 3)) # Test setting of authors with | in their names (for legacy database # format compatibility | is replaced by ,) ae(sf('authors', {3: ('Some| Author',)}), {3}) ae(('Some, Author',), cache.field_for('authors', 3))
def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=ascii_filename, replace_whitespace=False, to_lowercase=False, safe_format=True, last_has_extension=True, single_dir=False): tsorder = tweaks['save_template_title_series_sorting'] format_args = FORMAT_ARGS.copy() format_args.update(mi.all_non_none_fields()) if mi.title: if tsorder == 'strictly_alphabetic': v = mi.title else: # title_sort might be missing or empty. Check both conditions v = mi.get('title_sort', None) if not v: v = title_sort(mi.title, order=tsorder) format_args['title'] = v if mi.authors: format_args['authors'] = mi.format_authors() format_args['author'] = format_args['authors'] if mi.tags: format_args['tags'] = mi.format_tags() if format_args['tags'].startswith('/'): format_args['tags'] = format_args['tags'][1:] else: format_args['tags'] = '' if mi.series: format_args['series'] = title_sort(mi.series, order=tsorder) if mi.series_index is not None: format_args['series_index'] = mi.format_series_index() else: template = re.sub(r'\{series_index[^}]*?\}', '', template) if mi.rating is not None: format_args['rating'] = mi.format_rating(divide_by=2.0) if mi.identifiers: format_args['identifiers'] = mi.format_field_extended('identifiers')[1] else: format_args['identifiers'] = '' if hasattr(mi.timestamp, 'timetuple'): format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple()) if hasattr(mi.pubdate, 'timetuple'): format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple()) if hasattr(mi, 'last_modified') and hasattr(mi.last_modified, 'timetuple'): format_args['last_modified'] = strftime(timefmt, mi.last_modified.timetuple()) format_args['id'] = unicode_type(id) # Now format the custom fields custom_metadata = mi.get_all_user_metadata(make_copy=False) for key in custom_metadata: if key in format_args: cm = custom_metadata[key] if cm['datatype'] == 'series': format_args[key] = title_sort(format_args[key], order=tsorder) if key+'_index' in format_args: format_args[key+'_index'] = fmt_sidx(format_args[key+'_index']) elif cm['datatype'] == 'datetime': format_args[key] = strftime(timefmt, as_local_time(format_args[key]).timetuple()) elif cm['datatype'] == 'bool': format_args[key] = _('yes') if format_args[key] else _('no') elif cm['datatype'] == 'rating': format_args[key] = mi.format_rating(format_args[key], divide_by=2.0) elif cm['datatype'] in ['int', 'float']: if format_args[key] != 0: format_args[key] = unicode_type(format_args[key]) else: format_args[key] = '' if safe_format: components = Formatter().safe_format(template, format_args, 'G_C-EXCEPTION!', mi) else: components = Formatter().unsafe_format(template, format_args, mi) components = [x.strip() for x in components.split('/')] components = [sanitize_func(x) for x in components if x] if not components: components = [unicode_type(id)] if to_lowercase: components = [x.lower() for x in components] if replace_whitespace: components = [re.sub(r'\s', '_', x) for x in components] if single_dir: components = components[-1:] return shorten_components_to(length, components, last_has_extension=last_has_extension)
def ajax_book_to_json(self, book_id, get_category_urls=True, device_compatible=False, device_for_template=None): mi = self.db.get_metadata(book_id, index_is_id=True) if not device_compatible: try: mi.rating = mi.rating/2. except: mi.rating = 0.0 data = self.ajax_json_codec.encode_book_metadata(mi) for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime', 'rights', 'book_producer'): data.pop(x, None) data['cover'] = absurl(self.opts.url_prefix, u'/get/cover/%d'%book_id) data['thumbnail'] = absurl(self.opts.url_prefix, u'/get/thumb/%d'%book_id) if not device_compatible: mi.format_metadata = {k.lower():dict(v) for k, v in mi.format_metadata.iteritems()} for v in mi.format_metadata.itervalues(): mtime = v.get('mtime', None) if mtime is not None: v['mtime'] = isoformat(mtime, as_utc=True) data['format_metadata'] = mi.format_metadata fmts = set(x.lower() for x in mi.format_metadata.iterkeys()) pf = prefs['output_format'].lower() other_fmts = list(fmts) try: fmt = pf if pf in fmts else other_fmts[0] except: fmt = None if fmts and fmt: other_fmts = [x for x in fmts if x != fmt] data['formats'] = sorted(fmts) if fmt: data['main_format'] = {fmt: absurl(self.opts.url_prefix, u'/get/%s/%d'%(fmt, book_id))} else: data['main_format'] = None data['other_formats'] = {fmt: absurl(self.opts.url_prefix, u'/get/%s/%d'%(fmt, book_id)) for fmt in other_fmts} if get_category_urls: category_urls = data['category_urls'] = {} ccache = self.categories_cache() for key in mi.all_field_keys(): fm = mi.metadata_for_field(key) if (fm and fm['is_category'] and not fm['is_csp'] and key != 'formats' and fm['datatype'] not in ['rating']): categories = mi.get(key) if isinstance(categories, basestring): categories = [categories] if categories is None: categories = [] dbtags = {} for category in categories: for tag in ccache.get(key, []): if tag.original_name == category: dbtags[category] = books_in_url(self.opts.url_prefix, tag.category if tag.category else key, tag.original_name if tag.id is None else unicode(tag.id)) break category_urls[key] = dbtags else: series = data.get('series', None) if series: tsorder = tweaks['save_template_title_series_sorting'] series = title_sort(series, order=tsorder) else: series = '' data['_series_sort_'] = series if device_for_template: import posixpath from calibre.devices.utils import create_upload_path from calibre.utils.filenames import ascii_filename as sanitize from calibre.customize.ui import device_plugins for device_class in device_plugins(): if device_class.__class__.__name__ == device_for_template: template = device_class.save_template() data['_filename_'] = create_upload_path(mi, book_id, template, sanitize, path_type=posixpath) break return data, mi.last_modified
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var): """ Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. """ # It seems that a Sony device can sometimes know what timezone it is in, # and apparently converts the dates to GMT when it writes them to its # DB. We can detect that a device is timezone-aware because there is a # 'tz' variable in the Sony DB, which we can set to "0" to tell the # device to ignore its own timezone when comparing mtime to the date in # the DB. # Unfortunately, if there is no tz variable in the DB, then we can't # tell when the device applies a timezone conversion. We use a horrible # heuristic to work around this problem. First, set dates only for new # books, trying to avoid upsetting the sony. Second, voting: if a book # is not new, compare its Sony DB date against localtime and gmtime. # Count the matches. When we must set a date, use the one with the most # matches. Use localtime if the case of a tie, and hope it is right. try: timestamp = os.path.getmtime(path) except: debug_print("Failed to get timestamp for:", path) timestamp = time.time() rec_date = record.get("date", None) def clean(x): if isbytestring(x): x = x.decode(preferred_encoding, "replace") x.replace(u"\0", "") return x def record_set(k, v): try: record.set(k, clean(v)) except: # v is not suitable for XML, ignore pass if not getattr(book, "_new_book", False): # book is not new if record.get("tz", None) is not None: use_tz_var = True if strftime(timestamp, zone=time.gmtime) == rec_date: gtz_count += 1 elif strftime(timestamp, zone=time.localtime) == rec_date: ltz_count += 1 else: # book is new. Set the time using the current votes if use_tz_var: tz = time.localtime record.set("tz", "0") debug_print("Use localtime TZ and tz='0' for new book", book.lpath) elif ltz_count >= gtz_count: tz = time.localtime debug_print("Use localtime TZ for new book", book.lpath) else: tz = time.gmtime debug_print("Use GMT TZ for new book", book.lpath) date = strftime(timestamp, zone=tz) record.set("date", clean(date)) try: record.set("size", clean(str(os.stat(path).st_size))) except: record.set("size", "0") title = book.title if book.title else _("Unknown") record_set("title", title) ts = book.title_sort if not ts: ts = title_sort(title) record_set("titleSorter", ts) if self.use_author_sort: if book.author_sort: aus = book.author_sort else: debug_print("Author_sort is None for book", book.lpath) aus = authors_to_sort_string(book.authors) record_set("author", aus) else: record_set("author", authors_to_string(book.authors)) ext = os.path.splitext(path)[1] if ext: ext = ext[1:].lower() mime = MIME_MAP.get(ext, None) if mime is None: mime = guess_type("a." + ext)[0] if mime is not None: record.set("mime", clean(mime)) if "sourceid" not in record.attrib: record.set("sourceid", "1") if "id" not in record.attrib: num = self.max_id(record.getroottree().getroot()) record.set("id", str(num + 1)) return (gtz_count, ltz_count, use_tz_var)
def ajax_book_to_json(self, book_id, get_category_urls=True, device_compatible=False, device_for_template=None): mi = self.db.get_metadata(book_id, index_is_id=True) if not device_compatible: try: mi.rating = mi.rating / 2. except: mi.rating = 0.0 data = self.ajax_json_codec.encode_book_metadata(mi) for x in ('publication_type', 'size', 'db_id', 'lpath', 'mime', 'rights', 'book_producer'): data.pop(x, None) data['cover'] = absurl(self.opts.url_prefix, u'/get/cover/%d' % book_id) data['thumbnail'] = absurl(self.opts.url_prefix, u'/get/thumb/%d' % book_id) if not device_compatible: mi.format_metadata = { k.lower(): dict(v) for k, v in mi.format_metadata.iteritems() } for v in mi.format_metadata.itervalues(): mtime = v.get('mtime', None) if mtime is not None: v['mtime'] = isoformat(mtime, as_utc=True) data['format_metadata'] = mi.format_metadata fmts = set(x.lower() for x in mi.format_metadata.iterkeys()) pf = prefs['output_format'].lower() other_fmts = list(fmts) try: fmt = pf if pf in fmts else other_fmts[0] except: fmt = None if fmts and fmt: other_fmts = [x for x in fmts if x != fmt] data['formats'] = sorted(fmts) if fmt: data['main_format'] = { fmt: absurl(self.opts.url_prefix, u'/get/%s/%d' % (fmt, book_id)) } else: data['main_format'] = None data['other_formats'] = { fmt: absurl(self.opts.url_prefix, u'/get/%s/%d' % (fmt, book_id)) for fmt in other_fmts } if get_category_urls: category_urls = data['category_urls'] = {} ccache = self.categories_cache() for key in mi.all_field_keys(): fm = mi.metadata_for_field(key) if (fm and fm['is_category'] and not fm['is_csp'] and key != 'formats' and fm['datatype'] not in ['rating']): categories = mi.get(key) if isinstance(categories, basestring): categories = [categories] if categories is None: categories = [] dbtags = {} for category in categories: for tag in ccache.get(key, []): if tag.original_name == category: dbtags[category] = books_in_url( self.opts.url_prefix, tag.category if tag.category else key, tag.original_name if tag.id is None else unicode(tag.id)) break category_urls[key] = dbtags else: series = data.get('series', None) if series: tsorder = tweaks['save_template_title_series_sorting'] series = title_sort(series, order=tsorder) else: series = '' data['_series_sort_'] = series if device_for_template: import posixpath from calibre.devices.utils import create_upload_path from calibre.utils.filenames import ascii_filename as sanitize from calibre.customize.ui import device_plugins for device_class in device_plugins(): if device_class.__class__.__name__ == device_for_template: template = device_class.save_template() data['_filename_'] = create_upload_path( mi, book_id, template, sanitize, path_type=posixpath) break return data, mi.last_modified
def fget(self): return title_sort(self.title)
def __init__(self, text): tableItem.__init__(self, text) self.sort = title_sort(text.lower())
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var): ''' Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. ''' # It seems that a Sony device can sometimes know what timezone it is in, # and apparently converts the dates to GMT when it writes them to its # DB. We can detect that a device is timezone-aware because there is a # 'tz' variable in the Sony DB, which we can set to "0" to tell the # device to ignore its own timezone when comparing mtime to the date in # the DB. # Unfortunately, if there is no tz variable in the DB, then we can't # tell when the device applies a timezone conversion. We use a horrible # heuristic to work around this problem. First, set dates only for new # books, trying to avoid upsetting the sony. Second, voting: if a book # is not new, compare its Sony DB date against localtime and gmtime. # Count the matches. When we must set a date, use the one with the most # matches. Use localtime if the case of a tie, and hope it is right. try: timestamp = os.path.getmtime(path) except: debug_print('Failed to get timestamp for:', path) timestamp = time.time() rec_date = record.get('date', None) def clean(x): if isbytestring(x): x = x.decode(preferred_encoding, 'replace') x.replace(u'\0', '') return x def record_set(k, v): try: record.set(k, clean(v)) except: # v is not suitable for XML, ignore pass if not getattr(book, '_new_book', False): # book is not new if record.get('tz', None) is not None: use_tz_var = True if strftime(timestamp, zone=time.gmtime) == rec_date: gtz_count += 1 elif strftime(timestamp, zone=time.localtime) == rec_date: ltz_count += 1 else: # book is new. Set the time using the current votes if use_tz_var: tz = time.localtime record.set('tz', '0') debug_print("Use localtime TZ and tz='0' for new book", book.lpath) elif ltz_count >= gtz_count: tz = time.localtime debug_print("Use localtime TZ for new book", book.lpath) else: tz = time.gmtime debug_print("Use GMT TZ for new book", book.lpath) date = strftime(timestamp, zone=tz) record.set('date', clean(date)) try: record.set('size', clean(str(os.stat(path).st_size))) except: record.set('size', '0') title = book.title if book.title else _('Unknown') record_set('title', title) ts = book.title_sort if not ts: ts = title_sort(title) record_set('titleSorter', ts) if self.use_author_sort: if book.author_sort: aus = book.author_sort else: debug_print('Author_sort is None for book', book.lpath) aus = authors_to_sort_string(book.authors) record_set('author', aus) else: record_set('author', authors_to_string(book.authors)) ext = os.path.splitext(path)[1] if ext: ext = ext[1:].lower() mime = MIME_MAP.get(ext, None) if mime is None: mime = guess_type('a.'+ext)[0] if mime is not None: record.set('mime', clean(mime)) if 'sourceid' not in record.attrib: record.set('sourceid', '1') if 'id' not in record.attrib: num = self.max_id(record.getroottree().getroot()) record.set('id', str(num+1)) return (gtz_count, ltz_count, use_tz_var)
def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=ascii_filename, replace_whitespace=False, to_lowercase=False, safe_format=True): tsorder = tweaks['save_template_title_series_sorting'] format_args = FORMAT_ARGS.copy() format_args.update(mi.all_non_none_fields()) if mi.title: if tsorder == 'strictly_alphabetic': v = mi.title else: # title_sort might be missing or empty. Check both conditions v = mi.get('title_sort', None) if not v: v = title_sort(mi.title, order=tsorder) format_args['title'] = v if mi.authors: format_args['authors'] = mi.format_authors() format_args['author'] = format_args['authors'] if mi.tags: format_args['tags'] = mi.format_tags() if format_args['tags'].startswith('/'): format_args['tags'] = format_args['tags'][1:] else: format_args['tags'] = '' if mi.series: format_args['series'] = title_sort(mi.series, order=tsorder) if mi.series_index is not None: format_args['series_index'] = mi.format_series_index() else: template = re.sub(r'\{series_index[^}]*?\}', '', template) if mi.rating is not None: format_args['rating'] = mi.format_rating(divide_by=2.0) if mi.identifiers: format_args['identifiers'] = mi.format_field_extended('identifiers')[1] else: format_args['identifiers'] = '' if hasattr(mi.timestamp, 'timetuple'): format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple()) if hasattr(mi.pubdate, 'timetuple'): format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple()) if hasattr(mi, 'last_modified') and hasattr(mi.last_modified, 'timetuple'): format_args['last_modified'] = strftime(timefmt, mi.last_modified.timetuple()) format_args['id'] = str(id) # Now format the custom fields custom_metadata = mi.get_all_user_metadata(make_copy=False) for key in custom_metadata: if key in format_args: cm = custom_metadata[key] if cm['datatype'] == 'series': format_args[key] = title_sort(format_args[key], order=tsorder) if key+'_index' in format_args: format_args[key+'_index'] = fmt_sidx(format_args[key+'_index']) elif cm['datatype'] == 'datetime': format_args[key] = strftime(timefmt, format_args[key].timetuple()) elif cm['datatype'] == 'bool': format_args[key] = _('yes') if format_args[key] else _('no') elif cm['datatype'] == 'rating': format_args[key] = mi.format_rating(format_args[key], divide_by=2.0) elif cm['datatype'] in ['int', 'float']: if format_args[key] != 0: format_args[key] = unicode(format_args[key]) else: format_args[key] = '' if safe_format: components = Formatter().safe_format(template, format_args, 'G_C-EXCEPTION!', mi) else: components = Formatter().unsafe_format(template, format_args, mi) components = [x.strip() for x in components.split('/')] components = [sanitize_func(x) for x in components if x] if not components: components = [str(id)] if to_lowercase: components = [x.lower() for x in components] if replace_whitespace: components = [re.sub(r'\s', '_', x) for x in components] return shorten_components_to(length, components)
def title_sorter(self): '''String to sort the title. If absent, title is returned''' return title_sort(self.title)