def __new__(self, series, series_index): series = roman = escape(series or u'') if series and series_index is not None: roman = _('Number {1} of <em>{0}</em>').format( escape(series), escape(fmt_sidx(series_index, use_roman=True))) series = escape(series + ' [%s]'%fmt_sidx(series_index, use_roman=False)) s = unicode.__new__(self, series) s.roman = roman return s
def __new__(self, series, series_index): if series and series_index is not None: roman = _('{1} of <em>{0}</em>').format( escape(series), escape(fmt_sidx(series_index, use_roman=True))) combined = _('{1} of <em>{0}</em>').format( escape(series), escape(fmt_sidx(series_index, use_roman=False))) else: combined = roman = escape(series or u'') s = unicode_type.__new__(self, combined) s.roman = roman s.name = escape(series or u'') s.number = escape(fmt_sidx(series_index or 1.0, use_roman=False)) s.roman_number = escape(fmt_sidx(series_index or 1.0, use_roman=True)) return s
def helpEvent(self, event, view, option, index): if event is not None and view is not None and event.type() == QEvent.ToolTip: try: db = index.model().db except AttributeError: return False try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return False db = db.new_api device_connected = self.parent().gui.device_connected on_device = device_connected is not None and db.field_for('ondevice', book_id) p = prepare_string_for_xml title = db.field_for('title', book_id) authors = db.field_for('authors', book_id) if title and authors: title = '<b>%s</b>' % ('<br>'.join(wrap(p(title), 120))) authors = '<br>'.join(wrap(p(' & '.join(authors)), 120)) tt = '%s<br><br>%s' % (title, authors) series = db.field_for('series', book_id) if series: use_roman_numbers=config['use_roman_numerals_for_series_number'] val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(db.field_for('series_index', book_id), use_roman=use_roman_numbers), series=p(series)) tt += '<br><br>' + val if on_device: val = _('This book is on the device in %s') % on_device tt += '<br><br>' + val QToolTip.showText(event.globalPos(), tt, view) return True return False
def orig_series_index_string(self): # debug_print("SeriesBook:orig_series_index - self._orig_series_index=", self._orig_series_index) # debug_print("SeriesBook:orig_series_index - self._orig_series_index.__class__=", self._orig_series_index.__class__) if self._orig_series_index_string is not None: return self._orig_series_index_string return fmt_sidx(self._orig_series_index)
def insert_series_index(self, series): db = self.dbref() if db is None or not series: return num = db.get_next_series_num_for(series) sidx = fmt_sidx(num) self.setText(self.text() + ' [%s]' % sidx)
def format_text(mi, prefs): with preserve_fields(mi, 'authors formatted_series_index'): mi.authors = [a for a in mi.authors if a != _('Unknown')] mi.formatted_series_index = fmt_sidx( mi.series_index or 0, use_roman=config['use_roman_numerals_for_series_number']) return tuple(format_fields(mi, prefs))
def default_cover(self): ''' Create a generic cover for books that dont have a cover ''' from calibre.ebooks.metadata import authors_to_string, fmt_sidx if self.no_default_cover: return None self.log('Generating default cover') m = self.oeb.metadata title = unicode(m.title[0]) authors = [unicode(x) for x in m.creator if x.role == 'aut'] series_string = None if m.series and m.series_index: series_string = _('Book %(sidx)s of %(series)s') % dict( sidx=fmt_sidx(m.series_index[0], use_roman=True), series=unicode(m.series[0])) try: from calibre.ebooks import calibre_cover img_data = calibre_cover(title, authors_to_string(authors), series_string=series_string) id, href = self.oeb.manifest.generate('cover', u'cover_image.jpg') item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0], data=img_data) m.clear('cover') m.add('cover', item.id) return item.href except: self.log.exception('Failed to generate default cover') return None
def default_cover(self): ''' Create a generic cover for books that dont have a cover ''' from calibre.ebooks.metadata import authors_to_string, fmt_sidx if self.no_default_cover: return None self.log('Generating default cover') m = self.oeb.metadata title = unicode(m.title[0]) authors = [unicode(x) for x in m.creator if x.role == 'aut'] series_string = None if m.series and m.series_index: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(m.series_index[0], use_roman=True), series=unicode(m.series[0])) try: from calibre.ebooks import calibre_cover img_data = calibre_cover(title, authors_to_string(authors), series_string=series_string) id, href = self.oeb.manifest.generate('cover', u'cover_image.jpg') item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0], data=img_data) m.clear('cover') m.add('cover', item.id) return item.href except: self.log.exception('Failed to generate default cover') return None
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if (mi.rating or 0) > 0: rating = rating_to_stars(mi.rating) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(str(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(str(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(mi.last_modified), E.published(mi.timestamp.isoformat())) if mi.pubdate and not is_date_undefined(mi.pubdate): ans.append(ans.makeelement('{%s}date' % DC_NS)) ans[-1].text = mi.pubdate.isoformat() if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: fm = mi.format_metadata for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: link = E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition") ffm = fm.get(fmt.upper()) if ffm: link.set('length', str(ffm['size'])) link.set('mtime', ffm['mtime'].isoformat()) ans.append(link) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/image")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/image/thumbnail")) return ans
def __init__(self, series_name, series_index): if series_name: text = '%s [%s]' % (series_name, fmt_sidx(series_index)) else: text = '' ReadOnlyTableWidgetItem.__init__(self, text) self.series_name = series_name self.series_index = series_index
def format_series_index(self, val=None): from calibre.ebooks.metadata import fmt_sidx v = self.series_index if val is None else val try: x = float(v) except (ValueError, TypeError): x = 1 return fmt_sidx(x)
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u"".join(repeat(u"\u2605", int(mi.rating / 2.0))) extra.append(_("RATING: %s<br />") % rating) if mi.tags: extra.append(_("TAGS: %s<br />") % xml(format_tag_string(mi.tags, None))) if mi.series: extra.append( _("SERIES: %(series)s [%(sidx)s]<br />") % dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index))) ) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm["datatype"] if datatype == "text" and fm["is_multiple"]: extra.append( "%s: %s<br />" % ( xml(name), xml( format_tag_string( val, fm["is_multiple"]["ui_to_list"], joinval=fm["is_multiple"]["list_to_ui"] ) ), ) ) elif datatype == "comments" or ( fm["datatype"] == "composite" and fm["display"].get("contains_html", False) ): extra.append("%s: %s<br />" % (xml(name), comments_to_html(unicode(val)))) else: extra.append("%s: %s<br />" % (xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml("\n".join(extra)) ans = E.entry( TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID("urn:uuid:" + mi.uuid), UPDATED(updated) ) if len(extra): ans.append(E.content(extra, type="xhtml")) get = partial(request_context.ctx.url_for, "/get", book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type("a." + fmt)[0] if mt: ans.append(E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append(E.link(type="image/jpeg", href=get(what="cover"), rel="http://opds-spec.org/cover")) ans.append(E.link(type="image/jpeg", href=get(what="thumb"), rel="http://opds-spec.org/thumbnail")) return ans
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = rating_to_stars(mi.rating) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in filter(request_context.ctx.is_field_displayable, field_metadata.ignorable_field_keys()): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(mi.last_modified), E.published(mi.timestamp.isoformat())) if mi.pubdate and not is_date_undefined(mi.pubdate): ans.append(ans.makeelement('{%s}date' % DC_NS)) ans[-1].text = mi.pubdate.isoformat() if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: fm = mi.format_metadata for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: link = E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition") ffm = fm.get(fmt.upper()) if ffm: link.set('length', str(ffm['size'])) link.set('mtime', ffm['mtime'].isoformat()) ans.append(link) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def __init__(self, series, series_index=None): display = '' if series: if series_index: from calibre.ebooks.metadata import fmt_sidx display = '%s [%s]' % (series, fmt_sidx(series_index)) self.sortKey = '%s%04d' % (series, series_index) else: display = series self.sortKey = series ReadOnlyTableWidgetItem.__init__(self, display)
def set_series(root, prefixes, refines, series, series_index): for meta in XPath( './opf:metadata/opf:meta[@name="calibre:series" or @name="calibre:series_index"]' )(root): remove_element(meta, refines) for meta in XPath( './opf:metadata/opf:meta[@property="belongs-to-collection"]')( root): remove_element(meta, refines) if series: create_series(root, refines, series, fmt_sidx(series_index))
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u''.join(repeat(u'\u2605', int(mi.rating/2.))) extra.append(_('RATING: %s<br />')%rating) if mi.tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(mi.tags, None, no_tag_count=True))) if mi.series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')% dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in field_metadata.ignorable_field_keys(): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, fm['is_multiple']['ui_to_list'], no_tag_count=True, joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] if mt: ans.append(E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append(E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def __init__(self, series_name, series_index, is_original=False, assigned_index=None): if series_name: text = '%s [%s]' % (series_name, fmt_sidx(series_index)) else: text = '' ReadOnlyTableWidgetItem.__init__(self, text) if assigned_index is not None: self.setIcon(get_icon('images/lock.png')) self.setToolTip('Value assigned by user') if is_original: #self.foreground().setColor(Qt.darkGray) self.setForeground(Qt.darkGray)
def generate_cover(self, *args): from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config title = self.dialog.title.current_val author = authors_to_string(self.dialog.authors.current_val) if not title or not author: return error_dialog(self, _('Specify title and author'), _('You must specify a title and author before generating ' 'a cover'), show=True) series = self.dialog.series.current_val series_string = None if series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(self.dialog.series_index.current_val, use_roman=config['use_roman_numerals_for_series_number']), series=series) self.current_val = calibre_cover(title, author, series_string=series_string)
def generate_cover(self, *args): from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config title = self.dialog.title.current_val author = authors_to_string(self.dialog.authors.current_val) if not title or not author: return error_dialog( self, _('Specify title and author'), _('You must specify a title and author before generating ' 'a cover'), show=True) series = self.dialog.series.current_val series_string = None if series: series_string = _('Book %(sidx)s of %(series)s') % dict( sidx=fmt_sidx( self.dialog.series_index.current_val, use_roman=config['use_roman_numerals_for_series_number']), series=series) self.current_val = calibre_cover(title, author, series_string=series_string)
def format_text(mi, prefs): with preserve_fields(mi, 'authors formatted_series_index'): mi.authors = [a for a in mi.authors if a != _('Unknown')] mi.formatted_series_index = fmt_sidx(mi.series_index or 0, use_roman=get_use_roman()) return tuple(format_fields(mi, prefs))
def series_index_string(self, column=None): if self._series_index_format is not None: return self._series_index_format % self._mi.series_index return fmt_sidx(self._mi.series_index)
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] comment_fields = [] isdevice = not hasattr(mi, "id") fm = getattr(mi, "field_metadata", field_metadata) row = u'<td class="title">%s</td><td class="value">%s</td>' for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == "sort": field = "title_sort" if all_fields: display = True if metadata["datatype"] == "bool": isnull = mi.get(field) is None else: isnull = mi.is_null(field) if not display or not metadata or isnull: continue name = metadata["name"] if not name: name = field name += ":" if metadata["datatype"] == "comments" or field == "comments": val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata["datatype"] == "rating": val = getattr(mi, field) if val: val = val / 2.0 ans.append( ( field, u'<td class="title">%s</td><td class="rating value" ' "style='font-family:\"%s\"'>%s</td>" % (name, rating_font(), u"\u2605" * int(val)), ) ) elif metadata["datatype"] == "composite" and metadata["display"].get("contains_html", False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == "path": if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u"devpath" if isdevice else u"path" url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _("Click to open") extra = "" if isdevice: durl = url if durl.startswith("mtp:::"): durl = ":::".join((durl.split(":::"))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % (prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra, ) ans.append((field, row % (name, link))) elif field == "formats": if isdevice: continue fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats] ans.append((field, row % (name, u", ".join(fmts)))) elif field == "identifiers": urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls ] links = u", ".join(links) if links: ans.append((field, row % (_("Ids") + ":", links))) elif field == "authors" and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = "" if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get("default_author_link"): vals = {"author": aut.replace(" ", "+")} try: vals["author_sort"] = mi.author_sort_map[aut].replace(" ", "+") except: vals["author_sort"] = aut.replace(" ", "+") link = formatter.safe_format(gprefs.get("default_author_link"), vals, "", vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a calibre-data="authors" href="%s">%s</a>' % (link, aut)) else: authors.append(aut) ans.append((field, row % (name, u" & ".join(authors)))) elif field == "languages": if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u", ".join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata["datatype"] == "series": sidx = mi.get(field + "_index") if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field)) ) elif metadata["datatype"] == "datetime": aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, "device_collections", []) if dc: dc = u", ".join(sorted(dc, key=sort_key)) ans.append(("device_collections", row % (_("Collections") + ":", dc))) def classname(field): try: dt = fm[field]["datatype"] except: dt = "text" return "datatype_%s" % dt ans = [u'<tr id="%s" class="%s">%s</tr>' % (field.replace("#", "_"), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % (u"\n".join(ans)), comment_fields
def do_one(self, id): remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \ do_autonumber, do_remove_format, remove_format, do_swap_ta, \ do_remove_conv, do_auto_author, series, do_series_restart, \ series_start_value, do_title_case, cover_action, clear_series, \ pubdate, adddate, do_title_sort, languages, clear_languages, \ restore_original = self.args # first loop: All changes that modify the filesystem and commit # immediately. We want to # try hard to keep the DB and the file system in sync, even in the face # of exceptions or forced exits. if self.current_phase == 1: title_set = False if do_swap_ta: title = self.db.title(id, index_is_id=True) aum = self.db.authors(id, index_is_id=True) if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')] new_title = authors_to_string(aum) if do_title_case: new_title = titlecase(new_title) self.db.set_title(id, new_title, notify=False) title_set = True if title: new_authors = string_to_authors(title) self.db.set_authors(id, new_authors, notify=False) if do_title_case and not title_set: title = self.db.title(id, index_is_id=True) self.db.set_title(id, titlecase(title), notify=False) if do_title_sort: title = self.db.title(id, index_is_id=True) if languages: lang = languages[0] else: lang = self.db.languages(id, index_is_id=True) if lang: lang = lang.partition(',')[0] self.db.set_title_sort(id, title_sort(title, lang=lang), notify=False) if au: self.db.set_authors(id, string_to_authors(au), notify=False) if cover_action == 'remove': self.db.remove_cover(id) elif cover_action == 'generate': from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config mi = self.db.get_metadata(id, index_is_id=True) series_string = None if mi.series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(mi.series_index, use_roman=config['use_roman_numerals_for_series_number']), series=mi.series) cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) self.db.set_cover(id, cdata) elif cover_action == 'fromfmt': fmts = self.db.formats(id, index_is_id=True, verify_formats=False) if fmts: covers = [] for fmt in fmts.split(','): fmtf = self.db.format(id, fmt, index_is_id=True, as_file=True) if fmtf is None: continue cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) if covers: self.db.set_cover(id, covers[-1][0]) covers = [] if do_remove_format: self.db.remove_format(id, remove_format, index_is_id=True, notify=False, commit=True) if restore_original: formats = self.db.formats(id, index_is_id=True) formats = formats.split(',') if formats else [] originals = [x.upper() for x in formats if x.upper().startswith('ORIGINAL_')] for ofmt in originals: fmt = ofmt.replace('ORIGINAL_', '') with SpooledTemporaryFile(SPOOL_SIZE) as stream: self.db.copy_format_to(id, ofmt, stream, index_is_id=True) stream.seek(0) self.db.add_format(id, fmt, stream, index_is_id=True, notify=False) self.db.remove_format(id, ofmt, index_is_id=True, notify=False, commit=True) elif self.current_phase == 2: # All of these just affect the DB, so we can tolerate a total rollback if do_auto_author: x = self.db.author_sort_from_book(id, index_is_id=True) if x: self.db.set_author_sort(id, x, notify=False, commit=False) if aus and do_aus: self.db.set_author_sort(id, aus, notify=False, commit=False) if rating != -1: self.db.set_rating(id, 2*rating, notify=False, commit=False) if pub: self.db.set_publisher(id, pub, notify=False, commit=False) if clear_series: self.db.set_series(id, '', notify=False, commit=False) if pubdate is not None: self.db.set_pubdate(id, pubdate, notify=False, commit=False) if adddate is not None: self.db.set_timestamp(id, adddate, notify=False, commit=False) if do_series: if do_series_restart: if self.series_start_value is None: self.series_start_value = series_start_value next = self.series_start_value self.series_start_value += 1 else: next = self.db.get_next_series_num_for(series) self.db.set_series(id, series, notify=False, commit=False) if not series: self.db.set_series_index(id, 1.0, notify=False, commit=False) elif do_autonumber: # is True if do_series_restart is True self.db.set_series_index(id, next, notify=False, commit=False) elif tweaks['series_index_auto_increment'] != 'no_change': self.db.set_series_index(id, 1.0, notify=False, commit=False) if do_remove_conv: self.db.delete_conversion_options(id, 'PIPE', commit=False) if clear_languages: self.db.set_languages(id, [], notify=False, commit=False) elif languages: self.db.set_languages(id, languages, notify=False, commit=False) elif self.current_phase == 3: # both of these are fast enough to just do them all for w in self.cc_widgets: w.commit(self.ids) if remove_all: self.db.remove_all_tags(self.ids) self.db.bulk_modify_tags(self.ids, add=add, remove=remove, notify=False) self.current_index = len(self.ids) elif self.current_phase == 4: self.s_r_func(id) # do the next one self.current_index += 1 self.do_one_signal.emit()
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM['title']] if not title: title = _('Unknown') authors = item[FM['authors']] if not authors: authors = _('Unknown') authors = ' & '.join([i.replace('|', ',') for i in authors.split(',')]) extra = [] rating = item[FM['rating']] if rating > 0: rating = u''.join(repeat(u'\u2605', int(rating / 2.))) extra.append(_('RATING: %s<br />') % rating) tags = item[FM['tags']] if tags: extra.append( _('TAGS: %s<br />') % xml( format_tag_string( tags, ',', ignore_max=True, no_tag_count=True))) series = item[FM['series']] if series: extra.append( _('SERIES: %(series)s [%(sidx)s]<br />') % dict(series=xml(series), sidx=fmt_sidx(float(item[FM['series_index']])))) for key in CKEYS: mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if val: datatype = CFM[key]['datatype'] if datatype == 'text' and CFM[key]['is_multiple']: extra.append( '%s: %s<br />' % (xml(name), xml( format_tag_string( val, CFM[key]['is_multiple']['ui_to_list'], ignore_max=True, no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (CFM[key]['datatype'] == 'composite' and CFM[key]['display'].get( 'contains_html', False)): extra.append('%s: %s<br />' % (xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />' % (xml(name), xml(unicode(val)))) comments = item[FM['comments']] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) idm = 'calibre' if version == 0 else 'uuid' id_ = 'urn:%s:%s' % (idm, item[FM['uuid']]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) formats = item[FM['formats']] if formats: for fmt in formats.split(','): fmt = fmt.lower() mt = guess_type('a.' + fmt)[0] href = prefix + '/get/%s/%s' % (fmt, item[FM['id']]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set('rel', "http://opds-spec.org/acquisition") ans.append(link) ans.append( E.link(type='image/jpeg', href=prefix + '/get/cover/%s' % item[FM['id']], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover")) ans.append( E.link(type='image/jpeg', href=prefix + '/get/thumb/%s' % item[FM['id']], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail")) return ans
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val / 2.0 ans.append( (field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font, u'\u2605' * int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml( path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = '' if mi.path: h, t = os.path.split(mi.path) path = '/'.join((os.path.basename(h), t)) data = ({ 'fmt': x, 'path': a(path or ''), 'fname': a(mi.format_files.get(x, '')), 'ext': x.lower(), 'id': mi.id } for x in mi.formats) fmts = [ u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>' .format(**x) for x in data ] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, row % (_('Ids') + ':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif default_author_link: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace( ' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format(default_author_link, vals, '', vals) aut = p(aut) if link: authors.append( u'<a calibre-data="authors" title="%s" href="%s">%s</a>' % (a(link), a(link), aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 val = _( 'Book %(sidx)s of <span class="series_name">%(series)s</span>' ) % dict(sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=p(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections') + ':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans ] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % ( u'\n'.join(ans)), comment_fields
def show_result(self, result_or_none): self.current_result = r = result_or_none if r is None: self.set_controls_visibility(False) return self.set_controls_visibility(True) db = current_db() book_id = r['book_id'] title, authors = db.field_for('title', book_id), db.field_for( 'authors', book_id) authors = authors_to_string(authors) series, sidx = db.field_for('series', book_id), db.field_for( 'series_index', book_id) series_text = '' if series: use_roman_numbers = config['use_roman_numerals_for_series_number'] series_text = '{0} of {1}'.format( fmt_sidx(sidx, use_roman=use_roman_numbers), series) annot = r['annotation'] atype = annotation_title(annot['type'], singular=True) book_format = r['format'] annot_text = '' a = prepare_string_for_xml paras = [] def p(text, tag='p'): paras.append('<{0}>{1}</{0}>'.format(tag, a(text))) if annot['type'] == 'bookmark': p(annot['title']) elif annot['type'] == 'highlight': p(annot['highlighted_text']) notes = annot.get('notes') if notes: paras.append( '<h4>{} (<a title="{}" href="calibre://edit_result">{}</a>)</h4>' .format(_('Notes'), _('Edit the notes of this highlight'), _('Edit'))) paras.extend(render_notes(notes)) else: paras.append( '<p><a title="{}" href="calibre://edit_result">{}</a></p>'. format(_('Add notes to this highlight'), _('Add notes'))) annot_text += '\n'.join(paras) date = QDateTime.fromString(annot['timestamp'], Qt.ISODate).toLocalTime().toString( Qt.SystemLocaleShortDate) text = ''' <style>a {{ text-decoration: none }}</style> <h2 style="text-align: center">{title} [{book_format}]</h2> <div style="text-align: center">{authors}</div> <div style="text-align: center">{series}</div> <div> </div> <div> </div> <div>{dt}: {date}</div> <div>{ut}: {user}</div> <div> <a href="calibre://open_result" title="{ovtt}" style="margin-right: 20px">{ov}</a> <span>\xa0\xa0\xa0</span> <a title="{sictt}" href="calibre://show_in_library">{sic}</a> </div> <h2 style="text-align: left">{atype}</h2> {text} '''.format( title=a(title), authors=a(authors), series=a(series_text), book_format=a(book_format), atype=a(atype), text=annot_text, dt=_('Date'), date=a(date), ut=a(_('User')), user=a(friendly_username(r['user_type'], r['user'])), ov=a(_('Open in viewer')), sic=a(_('Show in calibre')), ovtt=a( _('Open the book at this annotation in the calibre viewer')), sictt=(_('Show this book in the main calibre book list')), ) self.text_browser.setHtml(text)
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font, u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, row % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = '' if mi.path: h, t = os.path.split(mi.path) path = '/'.join((os.path.basename(h), t)) data = ({ 'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')), 'ext':x.lower(), 'id':mi.id } for x in mi.formats) fmts = [u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(name)) for name, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, row % (_('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif default_author_link: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( default_author_link, vals, '', vals) aut = p(aut) if link: authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(a(link), aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=p(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections')+':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>'%(u'\n'.join(ans)), comment_fields
def do_all(self): cache = self.db.new_api args = self.args # Title and authors if args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) authors_map = cache.all_field_for('authors', self.ids) def new_title(authors): ans = authors_to_string(authors) return titlecase(ans) if args.do_title_case else ans new_title_map = {bid:new_title(authors) for bid, authors in authors_map.iteritems()} new_authors_map = {bid:string_to_authors(title) for bid, title in title_map.iteritems()} cache.set_field('authors', new_authors_map) cache.set_field('title', new_title_map) if args.do_title_case and not args.do_swap_ta: title_map = cache.all_field_for('title', self.ids) cache.set_field('title', {bid:titlecase(title) for bid, title in title_map.iteritems()}) if args.do_title_sort: lang_map = cache.all_field_for('languages', self.ids) title_map = cache.all_field_for('title', self.ids) 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) cache.set_field('sort', {bid:get_sort(bid) for bid in self.ids}) if args.au: authors = string_to_authors(args.au) cache.set_field('authors', {bid:authors for bid in self.ids}) if args.do_auto_author: aus_map = cache.author_sort_strings_for_books(self.ids) cache.set_field('author_sort', {book_id:' & '.join(aus_map[book_id]) for book_id in aus_map}) if args.aus and args.do_aus: cache.set_field('author_sort', {bid:args.aus for bid in self.ids}) # Covers if args.cover_action == 'remove': cache.set_cover({bid:None for bid in self.ids}) elif args.cover_action == 'generate': from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx from calibre.gui2 import config for book_id in self.ids: mi = self.db.get_metadata(book_id, index_is_id=True) series_string = None if mi.series: series_string = _('Book %(sidx)s of %(series)s')%dict( sidx=fmt_sidx(mi.series_index, use_roman=config['use_roman_numerals_for_series_number']), series=mi.series) cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) cache.set_cover({book_id:cdata}) elif args.cover_action == 'fromfmt': for book_id in self.ids: fmts = cache.formats(book_id, verify_formats=False) if fmts: covers = [] for fmt in fmts: fmtf = cache.format(book_id, fmt, as_file=True) if fmtf is None: continue cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) if covers: cache.set_cover({book_id:covers[-1][0]}) elif args.cover_action == 'trim': from calibre.utils.magick import Image for book_id in self.ids: cdata = cache.cover(book_id) if cdata: im = Image() im.load(cdata) im.trim(tweaks['cover_trim_fuzz_value']) cdata = im.export('jpg') cache.set_cover({book_id:cdata}) elif args.cover_action == 'clone': cdata = None for book_id in self.ids: cdata = cache.cover(book_id) if cdata: break if cdata: cache.set_cover({bid:cdata for bid in self.ids if bid != book_id}) # Formats if args.do_remove_format: cache.remove_formats({bid:(args.remove_format,) for bid in self.ids}) if args.restore_original: for book_id in self.ids: formats = cache.formats(book_id) originals = tuple(x.upper() for x in formats if x.upper().startswith('ORIGINAL_')) for ofmt in originals: cache.restore_original_format(book_id, ofmt) # Various fields if args.rating != -1: cache.set_field('rating', {bid:args.rating*2 for bid in self.ids}) if args.clear_pub: cache.set_field('publisher', {bid:'' for bid in self.ids}) if args.pub: cache.set_field('publisher', {bid:args.pub for bid in self.ids}) if args.clear_series: cache.set_field('series', {bid:'' for bid in self.ids}) if args.pubdate is not None: cache.set_field('pubdate', {bid:args.pubdate for bid in self.ids}) if args.adddate is not None: cache.set_field('timestamp', {bid:args.adddate for bid in self.ids}) if args.do_series: sval = args.series_start_value if args.do_series_restart else cache.get_next_series_num_for(args.series, current_indices=True) cache.set_field('series', {bid:args.series for bid in self.ids}) if not args.series: cache.set_field('series_index', {bid:1.0 for bid in self.ids}) else: def next_series_num(bid, i): if args.do_series_restart: return sval + i next_num = _get_next_series_num_for_list(sorted(sval.itervalues()), unwrap=False) sval[bid] = next_num return next_num smap = {bid:next_series_num(bid, i) for i, bid in enumerate(self.ids)} if args.do_autonumber: cache.set_field('series_index', smap) elif tweaks['series_index_auto_increment'] != 'no_change': cache.set_field('series_index', {bid:1.0 for bid in self.ids}) if args.comments is not null: cache.set_field('comments', {bid:args.comments for bid in self.ids}) if args.do_remove_conv: cache.delete_conversion_options(self.ids) if args.clear_languages: cache.set_field('languages', {bid:() for bid in self.ids}) elif args.languages: cache.set_field('languages', {bid:args.languages for bid in self.ids}) if args.remove_all: cache.set_field('tags', {bid:() for bid in self.ids}) if args.add or args.remove: self.db.bulk_modify_tags(self.ids, add=args.add, remove=args.remove) if self.do_sr: for book_id in self.ids: self.s_r_func(book_id) if self.sr_calls: for field, book_id_val_map in self.sr_calls.iteritems(): self.refresh_books.update(self.db.new_api.set_field(field, book_id_val_map))
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] isdevice = not hasattr(mi, 'id') fm = getattr(mi, 'field_metadata', field_metadata) for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == 'sort': field = 'title_sort' if all_fields: display = True if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if (not display or not metadata or isnull or field == 'comments'): continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) ans.append( (field, u'<td class="comments" colspan="2">%s</td>' % comments_to_html(val))) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val / 2.0 ans.append( (field, u'<td class="title">%s</td><td class="rating" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font(), u'\u2605' * int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml( path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % ( scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append( (field, u'<td class="title">%s</td><td>%s</td>' % (name, link))) elif field == 'formats': if isdevice: continue fmts = [ u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats ] ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (_('Ids') + ':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get('default_author_link'): vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace( ' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( gprefs.get('default_author_link'), vals, '', vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a href="%s">%s</a>' % (link, aut)) else: authors.append(aut) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, u'<td class="title">%s</td><td>%s</td>' % (name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 val = _( 'Book %(sidx)s of <span class="series_name">%(series)s</span>' ) % dict(sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append( (field, u'<td class="title">%s</td><td>%s</td>' % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append( ('device_collections', u'<td class="title">%s</td><td>%s</td>' % (_('Collections') + ':', dc))) def classname(field): try: dt = fm[field]['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (field.replace('#', '_'), classname(field), html) for field, html in ans ] # print '\n'.join(ans) return u'<table class="fields">%s</table>' % (u'\n'.join(ans))
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) book_id = getattr(mi, 'id', 0) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['is_custom'] and metadata['datatype'] in { 'bool', 'int', 'float' }: isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' disp = metadata['display'] if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: ctype = disp.get('interpret_as') or 'html' val = force_unicode(val) if ctype == 'long-text': val = '<pre style="white-space:pre-wrap">%s</pre>' % p(val) elif ctype == 'short-text': val = '<span>%s</span>' % p(val) elif ctype == 'markdown': val = markdown(val) else: val = comments_to_html(val) if disp.get('heading_position', 'hide') == 'side': ans.append((field, row % (name, val))) else: if disp.get('heading_position', 'hide') == 'above': val = '<h3 class="comments-heading">%s</h3>%s' % ( p(name), val) comment_fields.append( '<div id="%s" class="comments">%s</div>' % (field.replace('#', '_'), val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: star_string = rating_to_stars( val, disp.get('allow_half_stars', False)) ans.append( (field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>' % (name, rating_font, star_string))) elif metadata['datatype'] == 'composite': val = getattr(mi, field) if val: val = force_unicode(val) if disp.get('contains_html', False): ans.append((field, row % (name, comments_to_html(val)))) else: if not metadata['is_multiple']: val = '<a href="%s" title="%s">%s</a>' % ( search_action(field, val), _('Click to see books with {0}: {1}').format( metadata['name'], a(val)), p(val)) else: all_vals = [ v.strip() for v in val.split( metadata['is_multiple']['list_to_ui']) if v.strip() ] links = [ '<a href="%s" title="%s">%s</a>' % (search_action(field, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals ] val = metadata['is_multiple']['list_to_ui'].join(links) ans.append((field, row % (name, val))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' loc = path if isdevice else book_id pathstr = _('Click to open') extra = '' if isdevice: durl = path if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>' % ( prepare_string_for_xml(durl)) link = '<a href="%s" title="%s">%s</a>%s' % (action( scheme, loc=loc), prepare_string_for_xml( path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = mi.path or '' bpath = '' if path: h, t = os.path.split(path) bpath = os.sep.join((os.path.basename(h), t)) data = ({ 'fmt': x, 'path': a(path or ''), 'fname': a(mi.format_files.get(x, '')), 'ext': x.lower(), 'id': book_id, 'bpath': bpath, 'sep': os.sep, 'action': action('format', book_id=book_id, fmt=x, path=path or '', fname=mi.format_files.get(x, '')) } for x in mi.formats) fmts = [ '<a title="{bpath}{sep}{fname}.{ext}" href="{action}">{fmt}</a>' .format(**x) for x in data ] ans.append((field, row % (name, ', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [ '<a href="%s" title="%s:%s">%s</a>' % (action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls ] links = u', '.join(links) if links: ans.append((field, row % (_('Ids') + ':', links))) elif field == 'authors': authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map.get(aut): link = lt = mi.author_link_map[aut] elif default_author_link: if isdevice and default_author_link == 'search-calibre': default_author_link = DEFAULT_AUTHOR_LINK if default_author_link.startswith('search-'): which_src = default_author_link.partition('-')[2] link, lt = author_search_href(which_src, title=mi.title, author=aut) else: vals = { 'author': qquote(aut), 'title': qquote(mi.title) } try: vals['author_sort'] = qquote( mi.author_sort_map[aut]) except KeyError: vals['author_sort'] = qquote(aut) link = lt = formatter.safe_format( default_author_link, vals, '', vals) aut = p(aut) if link: authors.append( '<a title="%s" href="%s">%s</a>' % (a(lt), action('author', url=link, name=aut, title=lt), aut)) else: authors.append(aut) ans.append((field, row % (name, ' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) names = [ '<a href="%s" title="%s">%s</a>' % (search_action('languages', n), _('Search calibre for books with the language: {}').format(n), n) for n in names ] ans.append((field, row % (name, u', '.join(names)))) elif field == 'publisher': if not mi.publisher: continue val = '<a href="%s" title="%s">%s</a>' % (search_action_with_data( 'publisher', mi.publisher, book_id), _('Click to see books with {0}: {1}').format( metadata['name'], a(mi.publisher)), p(mi.publisher)) ans.append((field, row % (name, val))) elif field == 'title': # otherwise title gets metadata['datatype'] == 'text' # treatment below with a click to search link (which isn't # too bad), and a right-click 'Delete' option to delete # the title (which is bad). val = mi.format_field(field)[-1] ans.append((field, row % (name, val))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field + '_index') if sidx is None: sidx = 1.0 try: st = metadata['search_terms'][0] except Exception: st = field series = getattr(mi, field) val = _('%(sidx)s of <a href="%(href)s" title="%(tt)s">' '<span class="%(cls)s">%(series)s</span></a>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", series=p(series), href=search_action_with_data( st, series, book_id, field), tt=p(_('Click to see books in this series'))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue elif metadata['datatype'] == 'text' and metadata['is_multiple']: try: st = metadata['search_terms'][0] except Exception: st = field all_vals = mi.get(field) if not metadata.get('display', {}).get('is_names', False): all_vals = sorted(all_vals, key=sort_key) links = [ '<a href="%s" title="%s">%s</a>' % (search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals ] val = metadata['is_multiple']['list_to_ui'].join(links) elif metadata['datatype'] == 'text' or metadata[ 'datatype'] == 'enumeration': # text/is_multiple handled above so no need to add the test to the if try: st = metadata['search_terms'][0] except Exception: st = field val = '<a href="%s" title="%s">%s</a>' % ( search_action_with_data(st, val, book_id, field), a( _('Click to see books with {0}: {1}').format( metadata['name'], val)), p(val)) ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections') + ':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s' % dt ans = [ u'<tr id="%s" class="%s">%s</tr>' % (fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans ] # print '\n'.join(ans) direction = 'rtl' if rtl else 'ltr' margin = 'left' if rtl else 'right' return u'<style>table.fields td { vertical-align:top}</style>' + \ u'<table class="fields" style="direction: %s; margin-%s:auto">%s</table>'%( direction, margin, u'\n'.join(ans)), comment_fields
def mobile(self, start='1', num='25', sort='date', search='', _=None, order='descending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer' % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer' % num) if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) CFM = self.db.field_metadata CKEYS = [ key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]['name'])) ] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start - 1):(start - 1) + num]: book = { 'formats': record[FM['formats']], 'size': record[FM['size']] } if not book['formats']: book['formats'] = '' if not book['size']: book['size'] = 0 book['size'] = human_readable(book['size']) aus = record[FM['authors']] if record[ FM['authors']] else __builtin__._('Unknown') aut_is = CFM['authors']['is_multiple'] authors = aut_is['list_to_ui'].join( [i.replace('|', ',') for i in aus.split(',')]) book['authors'] = authors book['series_index'] = fmt_sidx(float(record[FM['series_index']])) book['series'] = record[FM['series']] book['tags'] = format_tag_string(record[FM['tags']], ',', no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): book[x] = strftime('%d %b, %Y', record[FM[x]]) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: def concat(name, val): return '%s:#:%s' % (name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue if datatype == 'text' and CFM[key]['is_multiple']: book[key] = concat( name, format_tag_string( val, CFM[key]['is_multiple']['ui_to_list'], no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Last-Modified'] = self.last_modified( updated) url_base = "/mobile?search=" + search + ";order=" + order + ";sort=" + sort + ";num=" + str( num) raw = html.tostring(build_index(books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix), encoding='utf-8', pretty_print=True) # tostring's include_meta_content_type is broken raw = raw.replace( '<head>', '<head>\n' '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' ) return raw
def xml(self, start='0', num='50', sort=None, search=None, _=None, order='ascending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer'%start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) order = order.lower().strip() == 'ascending' if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.search_for_books(search) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, order) books = [] def serialize(x): if isinstance(x, unicode): return x if isbytestring(x): return x.decode(preferred_encoding, 'replace') return unicode(x) # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. for record in items[start:start+num]: kwargs = {} aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) kwargs['authors'] = authors kwargs['series_index'] = \ fmt_sidx(float(record[FM['series_index']])) for x in ('timestamp', 'pubdate'): kwargs[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]]) for x in ('id', 'title', 'sort', 'author_sort', 'rating', 'size'): kwargs[x] = serialize(record[FM[x]]) for x in ('formats', 'series', 'tags', 'publisher', 'comments', 'identifiers'): y = record[FM[x]] if x == 'tags': y = format_tag_string(y, ',', ignore_max=True) kwargs[x] = serialize(y) if y else '' isbn = self.db.isbn(record[FM['id']], index_is_id=True) kwargs['isbn'] = serialize(isbn if isbn else '') kwargs['safe_title'] = ascii_filename(kwargs['title']) c = kwargs.pop('comments') CFM = self.db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]['name']))] custcols = [] for key in CKEYS: def concat(name, val): return '%s:#:%s'%(name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue k = str('CF_'+key[1:]) name = CFM[key]['name'] custcols.append(k) if datatype == 'text' and CFM[key]['is_multiple']: kwargs[k] = \ concat('#T#'+name, format_tag_string(val, CFM[key]['is_multiple']['ui_to_list'], ignore_max=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: kwargs[k] = concat(name, val) kwargs['custcols'] = ','.join(custcols) books.append(E.book(c, **kwargs)) updated = self.db.last_modified() kwargs = dict( start=str(start), updated=updated.strftime('%Y-%m-%dT%H:%M:%S+00:00'), total=str(len(ids)), num=str(len(books))) ans = E.library(*books, **kwargs) cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) return etree.tostring(ans, encoding='utf-8', pretty_print=True, xml_declaration=True)
def xml(self, start='0', num='50', sort=None, search=None, _=None, order='ascending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer' % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer' % num) order = order.lower().strip() == 'ascending' if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.search_for_books(search) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, order) books = [] def serialize(x): if isinstance(x, unicode): return x if isbytestring(x): return x.decode(preferred_encoding, 'replace') return unicode(x) # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. for record in items[start:start + num]: kwargs = {} aus = record[FM['authors']] if record[ FM['authors']] else __builtin__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) kwargs['authors'] = authors kwargs['series_index'] = \ fmt_sidx(float(record[FM['series_index']])) for x in ('timestamp', 'pubdate'): kwargs[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]]) for x in ('id', 'title', 'sort', 'author_sort', 'rating', 'size'): kwargs[x] = serialize(record[FM[x]]) for x in ('formats', 'series', 'tags', 'publisher', 'comments', 'identifiers'): y = record[FM[x]] if x == 'tags': y = format_tag_string(y, ',', ignore_max=True) kwargs[x] = serialize(y) if y else '' isbn = self.db.isbn(record[FM['id']], index_is_id=True) kwargs['isbn'] = serialize(isbn if isbn else '') kwargs['safe_title'] = ascii_filename(kwargs['title']) c = kwargs.pop('comments') CFM = self.db.field_metadata CKEYS = [ key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]['name'])) ] custcols = [] for key in CKEYS: def concat(name, val): return '%s:#:%s' % (name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue k = str('CF_' + key[1:]) name = CFM[key]['name'] custcols.append(k) if datatype == 'text' and CFM[key]['is_multiple']: kwargs[k] = \ concat('#T#'+name, format_tag_string(val, CFM[key]['is_multiple']['ui_to_list'], ignore_max=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: kwargs[k] = concat(name, val) kwargs['custcols'] = ','.join(custcols) books.append(E.book(c, **kwargs)) updated = self.db.last_modified() kwargs = dict(start=str(start), updated=updated.strftime('%Y-%m-%dT%H:%M:%S+00:00'), total=str(len(ids)), num=str(len(books))) ans = E.library(*books, **kwargs) cherrypy.response.headers['Content-Type'] = 'text/xml' cherrypy.response.headers['Last-Modified'] = self.last_modified( updated) return etree.tostring(ans, encoding='utf-8', pretty_print=True, xml_declaration=True)
def render_data(mi, use_roman_numbers=True, all_fields=False): ans = [] isdevice = not hasattr(mi, 'id') fm = getattr(mi, 'field_metadata', field_metadata) for field, display in get_field_list(fm): metadata = fm.get(field, None) if field == 'sort': field = 'title_sort' if all_fields: display = True if metadata['datatype'] == 'bool': isnull = mi.get(field) is None else: isnull = mi.is_null(field) if (not display or not metadata or isnull or field == 'comments'): continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="comments" colspan="2">%s</td>'%comments_to_html(val))) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font(), u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite' and \ metadata['display'].get('contains_html', False): val = getattr(mi, field) if val: val = force_unicode(val) ans.append((field, u'<td class="title">%s</td><td>%s</td>'% (name, comments_to_html(val)))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(mi.id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link))) elif field == 'formats': if isdevice: continue fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x in mi.formats] ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, u'<td class="title">%s</td><td>%s</td>'%( _('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = mi.author_link_map[aut] elif gprefs.get('default_author_link'): vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = formatter.safe_format( gprefs.get('default_author_link'), vals, '', vals) if link: link = prepare_string_for_xml(link) authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(link, aut)) else: authors.append(aut) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, u', '.join(names)))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), series=prepare_string_for_xml(getattr(mi, field))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', u'<td class="title">%s</td><td>%s</td>'%( _('Collections')+':', dc))) def classname(field): try: dt = fm[field]['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'), classname(field), html) for field, html in ans] # print '\n'.join(ans) return u'<table class="fields">%s</table>'%(u'\n'.join(ans))
def ACQUISITION_ENTRY(book_id, updated, request_context): field_metadata = request_context.db.field_metadata mi = request_context.db.get_metadata(book_id) extra = [] if mi.rating > 0: rating = u''.join(repeat(u'\u2605', int(mi.rating / 2.))) extra.append(_('RATING: %s<br />') % rating) if mi.tags: extra.append( _('TAGS: %s<br />') % xml(format_tag_string(mi.tags, None, no_tag_count=True))) if mi.series: extra.append( _('SERIES: %(series)s [%(sidx)s]<br />') % dict(series=xml(mi.series), sidx=fmt_sidx(float(mi.series_index)))) for key in field_metadata.ignorable_field_keys(): name, val = mi.format_field(key) if val: fm = field_metadata[key] datatype = fm['datatype'] if datatype == 'text' and fm['is_multiple']: extra.append( '%s: %s<br />' % (xml(name), xml( format_tag_string( val, fm['is_multiple']['ui_to_list'], no_tag_count=True, joinval=fm['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (fm['datatype'] == 'composite' and fm['display'].get( 'contains_html', False)): extra.append('%s: %s<br />' % (xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />' % (xml(name), xml(unicode(val)))) if mi.comments: comments = comments_to_html(mi.comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) ans = E.entry(TITLE(mi.title), E.author(E.name(authors_to_string(mi.authors))), ID('urn:uuid:' + mi.uuid), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) get = partial(request_context.ctx.url_for, '/get', book_id=book_id, library_id=request_context.library_id) if mi.formats: for fmt in mi.formats: fmt = fmt.lower() mt = guess_type('a.' + fmt)[0] if mt: ans.append( E.link(type=mt, href=get(what=fmt), rel="http://opds-spec.org/acquisition")) ans.append( E.link(type='image/jpeg', href=get(what='cover'), rel="http://opds-spec.org/cover")) ans.append( E.link(type='image/jpeg', href=get(what='thumb'), rel="http://opds-spec.org/thumbnail")) return ans
def format_text(mi, prefs): with preserve_fields(mi, 'authors formatted_series_index'): mi.authors = [a for a in mi.authors if a != _('Unknown')] mi.formatted_series_index = fmt_sidx(mi.series_index or 0, use_roman=config['use_roman_numerals_for_series_number']) return tuple(format_fields(mi, prefs))
def __init__(self, series, series_index): display = '' if series: display = '%s [%s]' % (series, fmt_sidx(series_index)) ReadOnlyTableWidgetItem.__init__(self, display) self.sortKey = '%s%04d' % (series, series_index)
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 mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False): if field_list is None: field_list = get_field_list(mi) ans = [] comment_fields = [] isdevice = not hasattr(mi, 'id') row = u'<td class="title">%s</td><td class="value">%s</td>' p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True) book_id = getattr(mi, 'id', 0) for field in (field for field, display in field_list if display): try: metadata = mi.metadata_for_field(field) except: continue if not metadata: continue if field == 'sort': field = 'title_sort' if metadata['is_custom'] and metadata['datatype'] in {'bool', 'int', 'float'}: isnull = mi.get(field) is None else: isnull = mi.is_null(field) if isnull: continue name = metadata['name'] if not name: name = field name += ':' if metadata['datatype'] == 'comments' or field == 'comments': val = getattr(mi, field) if val: val = force_unicode(val) comment_fields.append(comments_to_html(val)) elif metadata['datatype'] == 'rating': val = getattr(mi, field) if val: val = val/2.0 ans.append((field, u'<td class="title">%s</td><td class="rating value" ' 'style=\'font-family:"%s"\'>%s</td>'%( name, rating_font, u'\u2605'*int(val)))) elif metadata['datatype'] == 'composite': val = getattr(mi, field) if val: val = force_unicode(val) if metadata['display'].get('contains_html', False): ans.append((field, row % (name, comments_to_html(val)))) else: if not metadata['is_multiple']: val = '<a href="%s" title="%s">%s</a>' % ( search_href(field, val), _('Click to see books with {0}: {1}').format(metadata['name'], a(val)), p(val)) else: all_vals = [v.strip() for v in val.split(metadata['is_multiple']['list_to_ui']) if v.strip()] links = ['<a href="%s" title="%s">%s</a>' % ( search_href(field, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals] val = metadata['is_multiple']['list_to_ui'].join(links) ans.append((field, row % (name, val))) elif field == 'path': if mi.path: path = force_unicode(mi.path, filesystem_encoding) scheme = u'devpath' if isdevice else u'path' url = prepare_string_for_xml(path if isdevice else unicode(book_id), True) pathstr = _('Click to open') extra = '' if isdevice: durl = url if durl.startswith('mtp:::'): durl = ':::'.join((durl.split(':::'))[2:]) extra = '<br><span style="font-size:smaller">%s</span>'%( prepare_string_for_xml(durl)) link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url, prepare_string_for_xml(path, True), pathstr, extra) ans.append((field, row % (name, link))) elif field == 'formats': if isdevice: continue path = mi.path or '' bpath = '' if path: h, t = os.path.split(path) bpath = os.sep.join((os.path.basename(h), t)) data = ({ 'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')), 'ext':x.lower(), 'id':book_id, 'bpath':bpath, 'sep':os.sep } for x in mi.formats) fmts = [u'<a data-full-path="{path}{sep}{fname}.{ext}" title="{bpath}{sep}{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data] ans.append((field, row % (name, u', '.join(fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers) links = [u'<a href="%s" title="%s:%s" data-item="%s">%s</a>' % (a(url), a(id_typ), a(id_val), a(item_data(field, id_typ, book_id)), p(namel)) for namel, id_typ, id_val, url in urls] links = u', '.join(links) if links: ans.append((field, row % (_('Ids')+':', links))) elif field == 'authors' and not isdevice: authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' if mi.author_link_map[aut]: link = lt = mi.author_link_map[aut] elif default_author_link: if default_author_link == 'search-calibre': link = search_href('authors', aut) lt = a(_('Search the calibre library for books by %s') % aut) else: vals = {'author': aut.replace(' ', '+')} try: vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') except: vals['author_sort'] = aut.replace(' ', '+') link = lt = a(formatter.safe_format(default_author_link, vals, '', vals)) aut = p(aut) if link: authors.append(u'<a calibre-data="authors" title="%s" href="%s">%s</a>'%(lt, link, aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) ans.append((field, row % (name, u', '.join(names)))) elif field == 'publisher': if not mi.publisher: continue val = '<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href('publisher', mi.publisher), _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)), a(item_data('publisher', mi.publisher, book_id)), p(mi.publisher)) ans.append((field, row % (name, val))) elif field == 'title': # otherwise title gets metadata['datatype'] == 'text' # treatment below with a click to search link (which isn't # too bad), and a right-click 'Delete' option to delete # the title (which is bad). val = mi.format_field(field)[-1] ans.append((field, row % (name, val))) else: val = mi.format_field(field)[-1] if val is None: continue val = p(val) if metadata['datatype'] == 'series': sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 try: st = metadata['search_terms'][0] except Exception: st = field series = getattr(mi, field) val = _( '%(sidx)s of <a href="%(href)s" title="%(tt)s" data-item="%(data)s">' '<span class="%(cls)s">%(series)s</span></a>') % dict( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", series=p(series), href=search_href(st, series), data=a(item_data(field, series, book_id)), tt=p(_('Click to see books in this series'))) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): continue elif metadata['datatype'] == 'text' and metadata['is_multiple']: try: st = metadata['search_terms'][0] except Exception: st = field all_vals = mi.get(field) if field == 'tags': all_vals = sorted(all_vals, key=sort_key) links = ['<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href(st, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), a(item_data(field, x, book_id)), p(x)) for x in all_vals] val = metadata['is_multiple']['list_to_ui'].join(links) elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration': # text/is_multiple handled above so no need to add the test to the if try: st = metadata['search_terms'][0] except Exception: st = field val = '<a href="%s" title="%s" data-item="%s">%s</a>' % ( search_href(st, val), a(_('Click to see books with {0}: {1}').format(metadata['name'], val)), a(item_data(field, val, book_id)), p(val)) ans.append((field, row % (name, val))) dc = getattr(mi, 'device_collections', []) if dc: dc = u', '.join(sorted(dc, key=sort_key)) ans.append(('device_collections', row % (_('Collections')+':', dc))) def classname(field): try: dt = mi.metadata_for_field(field)['datatype'] except: dt = 'text' return 'datatype_%s'%dt ans = [u'<tr id="%s" class="%s">%s</tr>'%(fieldl.replace('#', '_'), classname(fieldl), html) for fieldl, html in ans] # print '\n'.join(ans) direction = 'rtl' if rtl else 'ltr' margin = 'left' if rtl else 'right' return u'<table class="fields" style="direction: %s; margin-%s:auto">%s</table>'%(direction, margin, u'\n'.join(ans)), comment_fields
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM["title"]] if not title: title = _("Unknown") authors = item[FM["authors"]] if not authors: authors = _("Unknown") authors = " & ".join([i.replace("|", ",") for i in authors.split(",")]) extra = [] rating = item[FM["rating"]] if rating > 0: rating = u"".join(repeat(u"\u2605", int(rating / 2.0))) extra.append(_("RATING: %s<br />") % rating) tags = item[FM["tags"]] if tags: extra.append(_("TAGS: %s<br />") % xml(format_tag_string(tags, ",", ignore_max=True, no_tag_count=True))) series = item[FM["series"]] if series: extra.append( _("SERIES: %(series)s [%(sidx)s]<br />") % dict(series=xml(series), sidx=fmt_sidx(float(item[FM["series_index"]]))) ) for key in CKEYS: mi = db.get_metadata(item[CFM["id"]["rec_index"]], index_is_id=True) name, val = mi.format_field(key) if val: datatype = CFM[key]["datatype"] if datatype == "text" and CFM[key]["is_multiple"]: extra.append( "%s: %s<br />" % ( xml(name), xml( format_tag_string( val, CFM[key]["is_multiple"]["ui_to_list"], ignore_max=True, no_tag_count=True, joinval=CFM[key]["is_multiple"]["list_to_ui"], ) ), ) ) elif datatype == "comments" or ( CFM[key]["datatype"] == "composite" and CFM[key]["display"].get("contains_html", False) ): extra.append("%s: %s<br />" % (xml(name), comments_to_html(unicode(val)))) else: extra.append("%s: %s<br />" % (xml(name), xml(unicode(val)))) comments = item[FM["comments"]] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml("\n".join(extra)) idm = "calibre" if version == 0 else "uuid" id_ = "urn:%s:%s" % (idm, item[FM["uuid"]]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type="xhtml")) formats = item[FM["formats"]] if formats: for fmt in formats.split(","): fmt = fmt.lower() mt = guess_type("a." + fmt)[0] href = prefix + "/get/%s/%s" % (fmt, item[FM["id"]]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set("rel", "http://opds-spec.org/acquisition") ans.append(link) ans.append( E.link( type="image/jpeg", href=prefix + "/get/cover/%s" % item[FM["id"]], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover", ) ) ans.append( E.link( type="image/jpeg", href=prefix + "/get/thumb/%s" % item[FM["id"]], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail", ) ) return ans
def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix): FM = db.FIELD_MAP title = item[FM['title']] if not title: title = _('Unknown') authors = item[FM['authors']] if not authors: authors = _('Unknown') authors = ' & '.join([i.replace('|', ',') for i in authors.split(',')]) extra = [] rating = item[FM['rating']] if rating > 0: rating = u''.join(repeat(u'\u2605', int(rating/2.))) extra.append(_('RATING: %s<br />')%rating) tags = item[FM['tags']] if tags: extra.append(_('TAGS: %s<br />')%xml(format_tag_string(tags, ',', ignore_max=True, no_tag_count=True))) series = item[FM['series']] if series: extra.append(_('SERIES: %(series)s [%(sidx)s]<br />')%\ dict(series=xml(series), sidx=fmt_sidx(float(item[FM['series_index']])))) for key in CKEYS: mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if val: datatype = CFM[key]['datatype'] if datatype == 'text' and CFM[key]['is_multiple']: extra.append('%s: %s<br />'% (xml(name), xml(format_tag_string(val, CFM[key]['is_multiple']['ui_to_list'], ignore_max=True, no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])))) elif datatype == 'comments' or (CFM[key]['datatype'] == 'composite' and CFM[key]['display'].get('contains_html', False)): extra.append('%s: %s<br />'%(xml(name), comments_to_html(unicode(val)))) else: extra.append('%s: %s<br />'%(xml(name), xml(unicode(val)))) comments = item[FM['comments']] if comments: comments = comments_to_html(comments) extra.append(comments) if extra: extra = html_to_lxml('\n'.join(extra)) idm = 'calibre' if version == 0 else 'uuid' id_ = 'urn:%s:%s'%(idm, item[FM['uuid']]) ans = E.entry(TITLE(title), E.author(E.name(authors)), ID(id_), UPDATED(updated)) if len(extra): ans.append(E.content(extra, type='xhtml')) formats = item[FM['formats']] if formats: for fmt in formats.split(','): fmt = fmt.lower() mt = guess_type('a.'+fmt)[0] href = prefix + '/get/%s/%s'%(fmt, item[FM['id']]) if mt: link = E.link(type=mt, href=href) if version > 0: link.set('rel', "http://opds-spec.org/acquisition") ans.append(link) ans.append(E.link(type='image/jpeg', href=prefix+'/get/cover/%s'%item[FM['id']], rel="x-stanza-cover-image" if version == 0 else "http://opds-spec.org/cover")) ans.append(E.link(type='image/jpeg', href=prefix+'/get/thumb/%s'%item[FM['id']], rel="x-stanza-cover-image-thumbnail" if version == 0 else "http://opds-spec.org/thumbnail")) return ans
def mobile(self, start="1", num="25", sort="date", search="", _=None, order="descending"): """ Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching """ try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, "start: %s is not an integer" % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, "num: %s is not an integer" % num) if not search: search = "" if isbytestring(search): search = search.decode("UTF-8") ids = self.search_for_books(search) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM["id"]] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == "ascending")) CFM = self.db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]["name"]))] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start - 1) : (start - 1) + num]: book = {"formats": record[FM["formats"]], "size": record[FM["size"]]} if not book["formats"]: book["formats"] = "" if not book["size"]: book["size"] = 0 book["size"] = human_readable(book["size"]) aus = record[FM["authors"]] if record[FM["authors"]] else __builtin__._("Unknown") aut_is = CFM["authors"]["is_multiple"] authors = aut_is["list_to_ui"].join([i.replace("|", ",") for i in aus.split(",")]) book["authors"] = authors book["series_index"] = fmt_sidx(float(record[FM["series_index"]])) book["series"] = record[FM["series"]] book["tags"] = format_tag_string(record[FM["tags"]], ",", no_tag_count=True) book["title"] = record[FM["title"]] for x in ("timestamp", "pubdate"): book[x] = strftime("%d %b, %Y", as_local_time(record[FM[x]])) book["id"] = record[FM["id"]] books.append(book) for key in CKEYS: def concat(name, val): return "%s:#:%s" % (name, unicode(val)) mi = self.db.get_metadata(record[CFM["id"]["rec_index"]], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]["datatype"] if datatype in ["comments"]: continue if datatype == "text" and CFM[key]["is_multiple"]: book[key] = concat( name, format_tag_string( val, CFM[key]["is_multiple"]["ui_to_list"], no_tag_count=True, joinval=CFM[key]["is_multiple"]["list_to_ui"], ), ) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers["Content-Type"] = "text/html; charset=utf-8" cherrypy.response.headers["Last-Modified"] = self.last_modified(updated) q = { b"search": search.encode("utf-8"), b"order": order.encode("utf-8"), b"sort": sort.encode("utf-8"), b"num": str(num).encode("utf-8"), } url_base = "/mobile?" + urlencode(q) ua = cherrypy.request.headers.get("User-Agent", "").strip() have_kobo_browser = self.is_kobo_browser(ua) raw = html.tostring( build_index( books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix, have_kobo_browser=have_kobo_browser, ), encoding="utf-8", pretty_print=True, ) # tostring's include_meta_content_type is broken raw = raw.replace("<head>", "<head>\n" '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">') return raw
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 mobile(self, start='1', num='25', sort='date', search='', _=None, order='descending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer'%start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) CFM = self.db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(self.db), key=lambda x:sort_key(CFM[x]['name']))] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start-1):(start-1)+num]: book = {'formats':record[FM['formats']], 'size':record[FM['size']]} if not book['formats']: book['formats'] = '' if not book['size']: book['size'] = 0 book['size'] = human_readable(book['size']) aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown') aut_is = CFM['authors']['is_multiple'] authors = aut_is['list_to_ui'].join([i.replace('|', ',') for i in aus.split(',')]) book['authors'] = authors book['series_index'] = fmt_sidx(float(record[FM['series_index']])) book['series'] = record[FM['series']] book['tags'] = format_tag_string(record[FM['tags']], ',', no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): book[x] = strftime('%d %b, %Y', record[FM[x]]) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: def concat(name, val): return '%s:#:%s'%(name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue if datatype == 'text' and CFM[key]['is_multiple']: book[key] = concat(name, format_tag_string(val, CFM[key]['is_multiple']['ui_to_list'], no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num) raw = html.tostring(build_index(books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix), encoding='utf-8', pretty_print=True) # tostring's include_meta_content_type is broken raw = raw.replace('<head>', '<head>\n' '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">') return raw