def opds(self, version=0): version = int(version) if version not in BASE_HREFS: raise cherrypy.HTTPError(404, "Not found") categories = self.categories_cache(self.get_opds_allowed_ids_for_version(version)) category_meta = self.db.field_metadata cats = [(_("Newest"), _("Date"), "Onewest"), (_("Title"), _("Title"), "Otitle")] def getter(x): try: return category_meta[x]["name"].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and category not in custom_fields_to_display(self.db): continue cats.append((meta["name"], meta["name"], "N" + category)) updated = self.db.last_modified() cherrypy.response.headers["Last-Modified"] = self.last_modified(updated) cherrypy.response.headers["Content-Type"] = "application/atom+xml" feed = TopLevel(updated, cats, version) return str(feed)
def __init__(self, updated, id_, items, offsets, page_url, up_url, version, db, prefix, title=None): NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url, title=title) CFM = db.field_metadata CKEYS = [ key for key in sorted(custom_fields_to_display(db), key=lambda x: sort_key(CFM[x]['name'])) ] for item in items: self.root.append( ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix))
def browse_toplevel(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), ] def getter(x): return category_meta[x]['name'].lower() displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if meta['is_custom'] and category not in displayed_custom_fields: continue # get the icon files if category in self.icon_map: icon = '_'+quote(self.icon_map[category]) elif category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] elif meta['kind'] == 'user': icon = category_icon_map['user:'******'blank.png' if meta['kind'] == 'user': dot = category.find('.') if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta['name'][:dot-1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta['name'], category, icon)) uc_displayed.add(category) else: cats.append((meta['name'], category, icon)) cats = [(u'<li><a title="{2} {0}" href="{3}/browse/category/{1}"> </a>' u'<img src="{3}{src}" alt="{0}" />' u'<span class="label">{0}</span>' u'</li>') .format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), self.opts.url_prefix, src='/browse/icon/'+z) for x, y, z in cats] main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\ .format(_('Choose a category to browse by:'), u'\n\n'.join(cats)) return self.browse_template('name').format(title='', script='toplevel();', main=main)
def opds(self, version=0): version = int(version) if version not in BASE_HREFS: raise cherrypy.HTTPError(404, 'Not found') categories = self.categories_cache( self.get_opds_allowed_ids_for_version(version)) category_meta = self.db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): return category_meta[x]['name'].lower() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in custom_fields_to_display(self.db): continue cats.append((meta['name'], meta['name'], 'N'+category)) updated = self.db.last_modified() cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Content-Type'] = 'application/atom+xml' feed = TopLevel(updated, cats, version) return str(feed)
def opds(self): categories = self.db.get_categories() category_meta = self.db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in custom_fields_to_display(self.db): continue cats.append((meta['name'], meta['name'], 'N' + category)) updated = self.db.last_modified() self.set_header('Last-Modified', self.last_modified(updated)) self.set_header('Content-Type', 'application/atom+xml; charset=UTF-8') feed = TopLevel(updated, cats) return str(feed)
def __init__(self, updated, id_, items, offsets, page_url, up_url, version, db, prefix): NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url) CFM = db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(db), key=lambda x: sort_key(CFM[x]['name']))] for item in items: self.root.append(ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS, prefix))
def browse_template(self, sort, category=True, initial_search=''): if not hasattr(self, '__browse_template__') or \ self.opts.develop: self.__browse_template__ = \ P('content_server/browse/browse.html', data=True).decode('utf-8') ans = self.__browse_template__ scn = 'calibre_browse_server_sort_' if category: sort_opts = [('rating', _('Average rating')), ('name', _('Name')), ('popularity', _('Popularity'))] scn += 'category' else: scn += 'list' fm = self.db.field_metadata sort_opts, added = [], set([]) displayed_custom_fields = custom_fields_to_display(self.db) for x in fm.sortable_field_keys(): if x in ('ondevice', 'formats', 'sort'): continue if fm.is_ignorable_field( x) and x not in displayed_custom_fields: continue if x == 'comments' or fm[x]['datatype'] == 'comments': continue n = fm[x]['name'] if n not in added: added.add(n) sort_opts.append((x, n)) ans = ans.replace('{sort_select_label}', xml(_('Sort by') + ':')) ans = ans.replace('{sort_cookie_name}', scn) ans = ans.replace('{prefix}', self.opts.url_prefix) ans = ans.replace('{library}', _('library')) ans = ans.replace('{home}', _('home')) ans = ans.replace('{Search}', _('Search')) opts = [ '<option %svalue="%s">%s</option>' % ( 'selected="selected" ' if k == sort else '', xml(k), xml(nl), ) for k, nl in sorted( sort_opts, key=lambda x: sort_key(operator.itemgetter(1)(x))) if k and nl ] ans = ans.replace('{sort_select_options}', ('\n' + ' ' * 20).join(opts)) lp = self.db.library_path if isbytestring(lp): lp = force_unicode(lp, filesystem_encoding) ans = ans.replace('{library_name}', xml(os.path.basename(lp))) ans = ans.replace('{library_path}', xml(lp, True)) ans = ans.replace('{initial_search}', xml(initial_search, attribute=True)) return ans
def ajax_categories(self): ''' Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } ''' ans = {} categories = self.categories_cache() category_meta = self.db.field_metadata def getter(x): return category_meta[x]['name'] displayed_custom_fields = custom_fields_to_display(self.db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue display_name = meta['name'] if category.startswith('@'): category = category.partition('.')[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{'url':k, 'name':v[0], 'icon':v[1], 'is_category':True} for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x['name'])) for name, url, icon in [ (_('All books'), 'allbooks', 'book.png'), (_('Newest'), 'newest', 'forward.png'), ]: ans.insert(0, {'name':name, 'url':url, 'icon':icon, 'is_category':False}) for c in ans: c['url'] = category_url(self.opts.url_prefix, c['url']) c['icon'] = icon_url(self.opts.url_prefix, c['icon']) return ans
def browse_render_details(self, id_): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(fmt, fname, id_, fmt.upper(), self.opts.url_prefix) for fmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if m['is_custom'] and field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and \ m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [u'<div class="field">{0}</div>'.format(f[1]) for f in fields] fields = u'<div class="fields">%s</div>'%('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [(u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments] comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments)) return self.browse_details_template.format(id=id_, title=xml(mi.title, True), fields=fields, formats=args['formats'], comments=comments)
def browse_template(self, sort, category=True, initial_search=''): if not hasattr(self, '__browse_template__') or \ self.opts.develop: self.__browse_template__ = \ P('content_server/browse/browse.html', data=True).decode('utf-8') ans = self.__browse_template__ scn = 'calibre_browse_server_sort_' if category: sort_opts = [('rating', _('Average rating')), ('name', _('Name')), ('popularity', _('Popularity'))] scn += 'category' else: scn += 'list' fm = self.db.field_metadata sort_opts, added = [], set([]) displayed_custom_fields = custom_fields_to_display(self.db) for x in fm.sortable_field_keys(): if x in ('ondevice', 'formats', 'sort'): continue if fm.is_ignorable_field(x) and x not in displayed_custom_fields: continue if x == 'comments' or fm[x]['datatype'] == 'comments': continue n = fm[x]['name'] if n not in added: added.add(n) sort_opts.append((x, n)) ans = ans.replace('{sort_select_label}', xml(_('Sort by')+':')) ans = ans.replace('{sort_cookie_name}', scn) ans = ans.replace('{prefix}', self.opts.url_prefix) ans = ans.replace('{library}', _('library')) ans = ans.replace('{home}', _('home')) ans = ans.replace('{Search}', _('Search')) ans = ans.replace('{nav}', self.nav) opts = ['<option %svalue="%s">%s</option>' % ( 'selected="selected" ' if k==sort else '', xml(k), xml(nl), ) for k, nl in sorted(sort_opts, key=lambda x: sort_key(operator.itemgetter(1)(x))) if k and nl] ans = ans.replace('{sort_select_options}', ('\n'+' '*20).join(opts)) lp = self.db.library_path if isbytestring(lp): lp = force_unicode(lp, filesystem_encoding) ans = ans.replace('{library_name}', xml(os.path.basename(lp))) ans = ans.replace('{library_path}', xml(lp, True)) ans = ans.replace('{initial_search}', xml(initial_search, attribute=True)) return ans
def browse_template(self, sort, category=True, initial_search=""): if not hasattr(self, "__browse_template__") or self.opts.develop: self.__browse_template__ = P("content_server/browse/browse.html", data=True).decode("utf-8") ans = self.__browse_template__ scn = "calibre_browse_server_sort_" if category: sort_opts = [("rating", _("Average rating")), ("name", _("Name")), ("popularity", _("Popularity"))] scn += "category" else: scn += "list" fm = self.db.field_metadata sort_opts, added = [], set([]) displayed_custom_fields = custom_fields_to_display(self.db) for x in fm.sortable_field_keys(): if x in ("ondevice", "formats", "sort"): continue if fm.is_ignorable_field(x) and x not in displayed_custom_fields: continue if x == "comments" or fm[x]["datatype"] == "comments": continue n = fm[x]["name"] if n not in added: added.add(n) sort_opts.append((x, n)) ans = ans.replace("{sort_select_label}", xml(_("Sort by") + ":")) ans = ans.replace("{sort_cookie_name}", scn) ans = ans.replace("{prefix}", self.opts.url_prefix) ans = ans.replace("{library}", _("library")) ans = ans.replace("{home}", _("home")) ans = ans.replace("{Search}", _("Search")) opts = [ '<option %svalue="%s">%s</option>' % ('selected="selected" ' if k == sort else "", xml(k), xml(nl)) for k, nl in sorted(sort_opts, key=lambda x: sort_key(operator.itemgetter(1)(x))) if k and nl ] ans = ans.replace("{sort_select_options}", ("\n" + " " * 20).join(opts)) lp = self.db.library_path if isbytestring(lp): lp = force_unicode(lp, filesystem_encoding) ans = ans.replace("{library_name}", xml(os.path.basename(lp))) ans = ans.replace("{library_path}", xml(lp, True)) ans = ans.replace("{initial_search}", xml(initial_search, attribute=True)) 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.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 browse_toplevel(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), (_('Random book'), 'randombook', 'random.png'), ] virt_libs = self.db.prefs.get('virtual_libraries', {}) if virt_libs: cats.append((_('Virtual Libs.'), 'virt_libs', 'lt.png')) def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if self.db.field_metadata.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue # get the icon files main_cat = (category.partition('.')[0]) if hasattr( category, 'partition') else category if main_cat in self.icon_map: icon = '_' + quote(self.icon_map[main_cat]) elif category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] elif meta['kind'] == 'user': icon = category_icon_map['user:'******'blank.png' if meta['kind'] == 'user': dot = category.find('.') if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta['name'][:dot - 1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta['name'], category, icon)) uc_displayed.add(category) else: cats.append((meta['name'], category, icon)) cats = [( u'<li><a title="{2} {0}" href="{3}/browse/category/{1}"> </a>' u'<img src="{3}{src}" alt="{0}" />' u'<span class="label">{0}</span>' u'</li>').format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), self.opts.url_prefix, src='/browse/icon/' + z) for x, y, z in cats] main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\ .format(_('Choose a category to browse by:'), u'\n\n'.join(cats)) return self.browse_template('name').format(title='', script='toplevel();', main=main)
def browse_render_details(self, id_, add_random_button=False, add_title=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args( mi, id_, add_category_links=True) args['fmt'] = fmt if fmt: args['get_url'] = xml( self.opts.url_prefix + '/get/%s/%s_%d.%s' % (fmt, fname, id_, fmt), True) else: args['get_url'] = 'javascript:alert(\'%s\')' % xml( _('This book has no available formats to view'), True) args['formats'] = '' if fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'. format(xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix) for xfmt in fmts ] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if self.db.field_metadata.is_ignorable_field(field) and \ field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if field == 'identifiers': urls = urls_from_identifiers(mi.get(field, {})) links = [ u'<a class="details_category_link" target="_new" 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: fields.append( (field, m['name'], u'<strong>%s: </strong>%s' % (_('Ids'), links))) continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((field, m['name'], r)) def fsort(x): num = {'authors': 0, 'series': 1, 'tags': 2}.get(x[0], 100) return (num, sort_key(x[-1])) fields.sort(key=fsort) if add_title: fields.insert(0, ('title', 'Title', u'<strong>%s: </strong>%s' % (xml(_('Title')), xml(mi.title)))) fields = [ u'<div class="field">{0}</div>'.format(f[-1]) for f in fields ] fields = u'<div class="fields">%s</div>' % ('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ( '\n\n'.join(comments)) random = '' if add_random_button: href = '%s/browse/random?v=%s' % (self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_('Choose another random book'), True), xml(_('Another random book'))) return self.browse_details_template.format(id=id_, title=xml( mi.title, True), fields=fields, get_url=args['get_url'], fmt=args['fmt'], formats=args['formats'], comments=comments, random=random)
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 browse_nav(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_('Newest'), 'newest', 'whatshot'), (_('All books'), 'allbooks', 'library_books'), (_('Random book'), 'randombook', 'shuffle'), ] virt_libs = self.db.prefs.get('virtual_libraries', {}) if virt_libs: cats.append((_('Virtual Libs.'), 'virt_libs', 'graphic_eq')) def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if self.db.field_metadata.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue # get the icon files main_cat = (category.partition('.')[0]) if hasattr(category, 'partition') else category if main_cat in self.icon_map: icon = self.icon_map[main_cat] elif category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] elif meta['kind'] == 'user': icon = category_icon_map['user:'******'check_box_outline_blank' if meta['kind'] == 'user': dot = category.find('.') if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta['name'][:dot-1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta['name'], category, icon)) uc_displayed.add(category) else: cats.append((meta['name'], category, icon)) cats = [(u'<li><a title="{2} {0}" href="{3}/browse/category/{1}">' u'<i class="material-icons">{icon}</i>' u'<span>{0}</span></a>' u'</li>') .format(xml(name, True), xml(quote(cat)), xml(_('Browse books by')), self.opts.url_prefix, icon=icon) for name, cat, icon in cats] return '\n'.join(cats)
def browse_render_details(self, id_, add_random_button=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args['fmt'] = fmt if fmt: args['get_url'] = xml(self.opts.url_prefix + '/get/%s/%s_%d.%s'%( fmt, fname, id_, fmt), True) else: args['get_url'] = '' args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>' .format(xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix) for xfmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if self.db.field_metadata.is_ignorable_field(field) and \ field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if field == 'identifiers': urls = urls_from_identifiers(mi.get(field, {})) links = [u'<a class="details_category_link" target="_new" 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: fields.append((m['name'], u'<strong>%s: </strong>%s'%( _('Ids'), links))) continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [u'<div class="field">{0}</div>'.format(f[1]) for f in fields] fields = u'<div class="fields">%s</div>'%('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [(u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments] comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments)) random = '' if add_random_button: href = '%s/browse/random?v=%s'%( self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_('Choose another random book'), True), xml(_('Another random book'))) return self.browse_details_template.format( id=id_, title=xml(mi.title, True), fields=fields, get_url=args['get_url'], fmt=args['fmt'], formats=args['formats'], comments=comments, random=random)
def browse_render_details(self, id_): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args( mi, id_, add_category_links=True) args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(fmt, fname, id_, fmt.upper(), self.opts.url_prefix) for fmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if m['is_custom'] and field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and \ m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [ u'<div class="field">{0}</div>'.format(f[1]) for f in fields ] fields = u'<div class="fields">%s</div>' % ('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ( '\n\n'.join(comments)) return self.browse_details_template.format(id=id_, title=xml( mi.title, True), fields=fields, formats=args['formats'], comments=comments)
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 browse_toplevel(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_("Newest"), "newest", "forward.png"), (_("All books"), "allbooks", "book.png"), (_("Random book"), "randombook", "random.png"), ] virt_libs = self.db.prefs.get("virtual_libraries", {}) if virt_libs: cats.append((_("Virtual Libs."), "virt_libs", "lt.png")) def getter(x): try: return category_meta[x]["name"].lower() except KeyError: return x displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue if self.db.field_metadata.is_ignorable_field(category) and category not in displayed_custom_fields: continue # get the icon files main_cat = (category.partition(".")[0]) if hasattr(category, "partition") else category if main_cat in self.icon_map: icon = "_" + quote(self.icon_map[main_cat]) elif category in category_icon_map: icon = category_icon_map[category] elif meta["is_custom"]: icon = category_icon_map["custom:"] elif meta["kind"] == "user": icon = category_icon_map["user:"******"blank.png" if meta["kind"] == "user": dot = category.find(".") if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta["name"][: dot - 1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta["name"], category, icon)) uc_displayed.add(category) else: cats.append((meta["name"], category, icon)) cats = [ ( u'<li><a title="{2} {0}" href="{3}/browse/category/{1}"> </a>' u'<img src="{3}{src}" alt="{0}" />' u'<span class="label">{0}</span>' u"</li>" ).format( xml(x, True), xml(quote(y)), xml(_("Browse books by")), self.opts.url_prefix, src="/browse/icon/" + z ) for x, y, z in cats ] main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'.format( _("Choose a category to browse by:"), u"\n\n".join(cats) ) return self.browse_template("name").format(title="", script="toplevel();", main=main)
def browse_render_details(self, id_, add_random_button=False, add_title=False): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _("This book has been deleted") else: args, fmt, fmts, fname = self.browse_get_book_args(mi, id_, add_category_links=True) args["fmt"] = fmt if fmt: args["get_url"] = xml(self.opts.url_prefix + "/get/%s/%s_%d.%s" % (fmt, fname, id_, fmt), True) else: args["get_url"] = "javascript:alert('%s')" % xml(_("This book has no available formats to view"), True) args["formats"] = "" if fmts: ofmts = [ u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'.format( xfmt, fname, id_, xfmt.upper(), self.opts.url_prefix ) for xfmt in fmts ] ofmts = ", ".join(ofmts) args["formats"] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + list( mi.get_all_user_metadata(False).items() ): if self.db.field_metadata.is_ignorable_field(field) and field not in displayed_custom_fields: continue if ( m["datatype"] == "comments" or field == "comments" or (m["datatype"] == "composite" and m["display"].get("contains_html", False)) ): val = mi.get(field, "") if val and val.strip(): comments.append((m["name"], comments_to_html(val))) continue if field in ("title", "formats") or not args.get(field, False) or not m["name"]: continue if field == "identifiers": urls = urls_from_identifiers(mi.get(field, {})) links = [ u'<a class="details_category_link" target="_new" 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: fields.append((field, m["name"], u"<strong>%s: </strong>%s" % (_("Ids"), links))) continue if m["datatype"] == "rating": r = ( u"<strong>%s: </strong>" % xml(m["name"]) + render_rating(mi.get(field) / 2.0, self.opts.url_prefix, prefix=m["name"])[0] ) else: r = u"<strong>%s: </strong>" % xml(m["name"]) + args[field] fields.append((field, m["name"], r)) def fsort(x): num = {"authors": 0, "series": 1, "tags": 2}.get(x[0], 100) return (num, sort_key(x[-1])) fields.sort(key=fsort) if add_title: fields.insert(0, ("title", "Title", u"<strong>%s: </strong>%s" % (xml(_("Title")), xml(mi.title)))) fields = [u'<div class="field">{0}</div>'.format(f[-1]) for f in fields] fields = u'<div class="fields">%s</div>' % ("\n\n".join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ("\n\n".join(comments)) random = "" if add_random_button: href = "%s/browse/random?v=%s" % (self.opts.url_prefix, time.time()) random = '<a href="%s" id="random_button" title="%s">%s</a>' % ( xml(href, True), xml(_("Choose another random book"), True), xml(_("Another random book")), ) return self.browse_details_template.format( id=id_, title=xml(mi.title, True), fields=fields, get_url=args["get_url"], fmt=args["fmt"], formats=args["formats"], comments=comments, random=random, )
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)