Beispiel #1
0
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, "invalid category id: %r" % cid)
        categories = self.categories_cache()

        if category not in categories and category not in ("newest", "allbooks", "virt_libs"):
            raise cherrypy.HTTPError(404, "category not found")
        fm = self.db.field_metadata
        try:
            category_name = fm[category]["name"]
            dt = fm[category]["datatype"]
        except:
            if category not in ("newest", "allbooks", "virt_libs"):
                raise
            category_name = {"newest": _("Newest"), "allbooks": _("All books"), "virt_libs": _("Virtual Libraries")}[
                category
            ]
            dt = None

        hide_sort = "true" if dt == "series" else "false"
        if category == "search":
            which = unhexlify(cid).decode("utf-8")
            try:
                ids = self.search_cache('search:"%s"' % which)
            except:
                raise cherrypy.HTTPError(404, "Search: %r not understood" % which)
        else:
            all_ids = self.search_cache("")
            if category == "newest":
                ids = all_ids
                hide_sort = "true"
            elif category == "allbooks":
                ids = all_ids
            elif category == "virt_libs":
                which = unhexlify(cid).decode("utf-8")
                vls = self.db.prefs.get("virtual_libraries", {})
                ids = self.search_cache(vls[which])
            else:
                if fm.get(category, {"datatype": None})["datatype"] == "composite":
                    cid = cid.decode("utf-8")
                q = category
                if q == "news":
                    q = "tags"
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data.tablerow_for_id(x) for x in ids]
        if category == "newest":
            list_sort = "timestamp"
        if dt == "series":
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids, self.opts.url_prefix, suffix=_("in") + " " + category_name)

        return self.browse_template(sort, category=False).format(
            title=_("Books in") + " " + category_name, script="booklist(%s);" % hide_sort, main=html
        )
Beispiel #2
0
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, 'invalid category id: %r'%cid)
        categories = self.categories_cache()

        if category not in categories and \
                category not in ('newest', 'allbooks'):
            raise cherrypy.HTTPError(404, 'category not found')
        fm = self.db.field_metadata
        try:
            category_name = fm[category]['name']
            dt = fm[category]['datatype']
        except:
            if category not in ('newest', 'allbooks'):
                raise
            category_name = {
                    'newest' : _('Newest'),
                    'allbooks' : _('All books'),
            }[category]
            dt = None

        hide_sort = 'true' if dt == 'series' else 'false'
        if category == 'search':
            which = unhexlify(cid).decode('utf-8')
            try:
                ids = self.search_cache('search:"%s"'%which)
            except:
                raise cherrypy.HTTPError(404, 'Search: %r not understood'%which)
        else:
            all_ids = self.search_cache('')
            if category == 'newest':
                ids = all_ids
                hide_sort = 'true'
            elif category == 'allbooks':
                ids = all_ids
            else:
                if fm.get(category, {'datatype':None})['datatype'] == 'composite':
                    cid = cid.decode('utf-8')
                q = category
                if q == 'news':
                    q = 'tags'
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data._data[x] for x in ids]
        if category == 'newest':
            list_sort = 'timestamp'
        if dt == 'series':
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids, self.opts.url_prefix,
                suffix=_('in') + ' ' + category_name)

        return self.browse_template(sort, category=False).format(
                title=_('Books in') + " " +category_name,
                script='booklist(%s);'%hide_sort, main=html)
Beispiel #3
0
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, "invalid category id: %r" % cid)
        categories = self.categories_cache()

        if category not in categories and category not in ("newest", "allbooks", "virt_libs"):
            raise cherrypy.HTTPError(404, "category not found")
        fm = self.db.field_metadata
        try:
            category_name = fm[category]["name"]
            dt = fm[category]["datatype"]
        except:
            if category not in ("newest", "allbooks", "virt_libs"):
                raise
            category_name = {"newest": _("Newest"), "allbooks": _("All books"), "virt_libs": _("Virtual Libraries")}[
                category
            ]
            dt = None

        hide_sort = "true" if dt == "series" else "false"
        if category == "search":
            which = unhexlify(cid).decode("utf-8")
            try:
                ids = self.search_cache('search:"%s"' % which)
            except:
                raise cherrypy.HTTPError(404, "Search: %r not understood" % which)
        else:
            all_ids = self.search_cache("")
            if category == "newest":
                ids = all_ids
                hide_sort = "true"
            elif category == "allbooks":
                ids = all_ids
            elif category == "virt_libs":
                which = unhexlify(cid).decode("utf-8")
                vls = self.db.prefs.get("virtual_libraries", {})
                ids = self.search_cache(vls[which])
                category_name = _("virtual library: ") + xml(which)
                if not ids:
                    msg = _("The virtual library <b>%s</b> has no books.") % prepare_string_for_xml(which)
                    if self.search_restriction:
                        msg += " " + _(
                            "This is probably because you have applied a virtual library"
                            " to the content server in Preferences->Sharing over the net."
                            " This virtual library is applied globally and combined with"
                            " the current virtual library."
                        )
                    return self.browse_template("name").format(title="", script="", main="<p>%s</p>" % msg)
            else:
                if fm.get(category, {"datatype": None})["datatype"] == "composite":
                    cid = cid.decode("utf-8")
                q = category
                if q == "news":
                    q = "tags"
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data.tablerow_for_id(x) for x in ids]
        if category == "newest":
            list_sort = "timestamp"
        if dt == "series":
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids, self.opts.url_prefix, suffix=_("in") + " " + category_name)

        return self.browse_template(sort, category=False).format(
            title=_("Books in") + " " + category_name, script="booklist(%s);" % hide_sort, main=html
        )
Beispiel #4
0
    def browse_category(self, category, sort):
        categories = self.categories_cache()
        categories["virt_libs"] = {}
        if category not in categories:
            raise cherrypy.HTTPError(404, "category not found")
        category_meta = self.db.field_metadata
        category_name = _("Virtual Libraries") if category == "virt_libs" else category_meta[category]["name"]
        datatype = "text" if category == "virt_libs" else category_meta[category]["datatype"]

        # See if we have any sub-categories to display. As we find them, add
        # them to the displayed set to avoid showing the same item twice
        uc_displayed = set()
        cats = []
        for ucat in sorted(categories.keys(), key=sort_key):
            if len(categories[ucat]) == 0:
                continue
            if category == "formats":
                continue
            meta = category_meta.get(ucat, None)
            if meta is None:
                continue
            if meta["kind"] != "user":
                continue
            cat_len = len(category)
            if not (len(ucat) > cat_len and ucat.startswith(category + ".")):
                continue

            if ucat in self.icon_map:
                icon = "_" + quote(self.icon_map[ucat])
            else:
                icon = category_icon_map["user:"******".")
            if dot > 0:
                # More subcats
                cat = cat[:dot]
                if cat not in uc_displayed:
                    cats.append((cat, ucat[: cat_len + dot], icon))
                    uc_displayed.add(cat)
            else:
                # This is the end of the chain
                cats.append((cat, ucat, icon))
                uc_displayed.add(cat)

        cats = u"\n\n".join(
            [
                (
                    u'<li><a title="{2} {0}" href="{3}/browse/category/{1}">&nbsp;</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
            ]
        )
        if cats:
            cats = (u'\n<div class="toplevel">\n' "{0}</div>").format(cats)
            script = "toplevel();"
        else:
            script = "true"

        # Now do the category items
        vls = self.db.prefs.get("virtual_libraries", {})
        categories["virt_libs"] = sorted([Tag(k) for k, v in vls.iteritems()], key=lambda x: sort_key(x.name))
        items = categories[category]

        sort = self.browse_sort_categories(items, sort)

        if not cats and len(items) == 1:
            # Only one item in category, go directly to book list
            html = get_category_items(category, items, datatype, self.opts.url_prefix)
            href = re.search(r'<a href="([^"]+)"', html)
            if href is not None:
                # cherrypy does not auto unquote params when using
                # InternalRedirect
                raise cherrypy.InternalRedirect(unquote(href.group(1)))

        if len(items) <= self.opts.max_opds_ungrouped_items:
            script = "false"
            items = get_category_items(category, items, datatype, self.opts.url_prefix)
        else:
            getter = lambda x: unicode(getattr(x, "sort", x.name))
            starts = set([])
            for x in items:
                val = getter(x)
                if not val:
                    val = u"A"
                starts.add(val[0].upper())
            category_groups = OrderedDict()
            for x in sorted(starts):
                category_groups[x] = len([y for y in items if getter(y).upper().startswith(x)])
            items = [
                (
                    u'<h3 title="{0}"><a class="load_href" title="{0}"'
                    u' href="{4}{3}"><strong>{0}</strong> [{2}]</a></h3><div>'
                    u'<div class="loaded" style="display:none"></div>'
                    u'<div class="loading"><img alt="{1}" src="{4}/static/loading.gif" /><em>{1}</em></div>'
                    u"</div>"
                ).format(
                    xml(s, True),
                    xml(_("Loading, please wait")) + "&hellip;",
                    unicode(c),
                    xml(
                        u"/browse/category_group/%s/%s"
                        % (hexlify(category.encode("utf-8")), hexlify(s.encode("utf-8"))),
                        True,
                    ),
                    self.opts.url_prefix,
                )
                for s, c in category_groups.items()
            ]
            items = "\n\n".join(items)
            items = u'<div id="groups">\n{0}</div>'.format(items)

        if cats:
            script = "toplevel();category(%s);" % script
        else:
            script = "category(%s);" % script

        main = u"""
            <div class="category">
                <h3>{0}</h3>
                    <a class="navlink" href="{3}/browse"
                        title="{2}">{2}&nbsp;&uarr;</a>
                {1}
            </div>
        """.format(
            xml(_("Browsing by") + ": " + category_name), cats + items, xml(_("Up"), True), self.opts.url_prefix
        )

        return self.browse_template(sort).format(title=category_name, script=script, main=main)
Beispiel #5
0
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, 'invalid category id: %r' % cid)
        categories = self.categories_cache()

        if category not in categories and \
                category not in ('newest', 'allbooks', 'virt_libs'):
            raise cherrypy.HTTPError(404, 'category not found')
        fm = self.db.field_metadata
        try:
            category_name = fm[category]['name']
            dt = fm[category]['datatype']
        except:
            if category not in ('newest', 'allbooks', 'virt_libs'):
                raise
            category_name = {
                'newest': _('Newest'),
                'allbooks': _('All books'),
                'virt_libs': _('Virtual Libraries'),
            }[category]
            dt = None

        hide_sort = 'true' if dt == 'series' else 'false'
        if category == 'search':
            which = unhexlify(cid).decode('utf-8')
            try:
                ids = self.search_cache('search:"%s"' % which)
            except:
                raise cherrypy.HTTPError(404,
                                         'Search: %r not understood' % which)
        else:
            all_ids = self.search_cache('')
            if category == 'newest':
                ids = all_ids
                hide_sort = 'true'
            elif category == 'allbooks':
                ids = all_ids
            elif category == 'virt_libs':
                which = unhexlify(cid).decode('utf-8')
                vls = self.db.prefs.get('virtual_libraries', {})
                ids = self.search_cache(vls[which])
                category_name = _('virtual library: ') + xml(which)
                if not ids:
                    msg = _('The virtual library <b>%s</b> has no books.'
                            ) % prepare_string_for_xml(which)
                    if self.search_restriction:
                        msg += ' ' + _(
                            'This is probably because you have applied a virtual library'
                            ' to the content server in Preferences->Sharing over the net.'
                            ' This virtual library is applied globally and combined with'
                            ' the current virtual library.')
                    return self.browse_template('name').format(
                        title='', script='', main='<p>%s</p>' % msg)
            else:
                if fm.get(category,
                          {'datatype': None})['datatype'] == 'composite':
                    cid = cid.decode('utf-8')
                q = category
                if q == 'news':
                    q = 'tags'
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data.tablerow_for_id(x) for x in ids]
        if category == 'newest':
            list_sort = 'timestamp'
        if dt == 'series':
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids,
                                self.opts.url_prefix,
                                suffix=_('in') + ' ' + category_name)

        return self.browse_template(sort, category=False).format(
            title=_('Books in') + " " + category_name,
            script='booklist(%s);' % hide_sort,
            main=html)
Beispiel #6
0
    def browse_category(self, category, sort):
        categories = self.categories_cache()
        categories['virt_libs'] = {}
        if category not in categories:
            raise cherrypy.HTTPError(404, 'category not found')
        category_meta = self.db.field_metadata
        category_name = _(
            'Virtual Libraries'
        ) if category == 'virt_libs' else category_meta[category]['name']
        datatype = 'text' if category == 'virt_libs' else category_meta[
            category]['datatype']

        # See if we have any sub-categories to display. As we find them, add
        # them to the displayed set to avoid showing the same item twice
        uc_displayed = set()
        cats = []
        for ucat in sorted(categories.keys(), key=sort_key):
            if len(categories[ucat]) == 0:
                continue
            if category == 'formats':
                continue
            meta = category_meta.get(ucat, None)
            if meta is None:
                continue
            if meta['kind'] != 'user':
                continue
            cat_len = len(category)
            if not (len(ucat) > cat_len and ucat.startswith(category + '.')):
                continue

            if ucat in self.icon_map:
                icon = '_' + quote(self.icon_map[ucat])
            else:
                icon = category_icon_map['user:'******'.')
            if dot > 0:
                # More subcats
                cat = cat[:dot]
                if cat not in uc_displayed:
                    cats.append((cat, ucat[:cat_len + dot], icon))
                    uc_displayed.add(cat)
            else:
                # This is the end of the chain
                cats.append((cat, ucat, icon))
                uc_displayed.add(cat)

        cats = u'\n\n'.join([(
            u'<li><a title="{2} {0}" href="{3}/browse/category/{1}">&nbsp;</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])
        if cats:
            cats = (u'\n<div class="toplevel">\n' '{0}</div>').format(cats)
            script = 'toplevel();'
        else:
            script = 'true'

        # Now do the category items
        vls = self.db.prefs.get('virtual_libraries', {})
        categories['virt_libs'] = sorted([Tag(k) for k, v in vls.iteritems()],
                                         key=lambda x: sort_key(x.name))
        items = categories[category]

        sort = self.browse_sort_categories(items, sort)

        if not cats and len(items) == 1:
            # Only one item in category, go directly to book list
            html = get_category_items(category, items, datatype,
                                      self.opts.url_prefix)
            href = re.search(r'<a href="([^"]+)"', html)
            if href is not None:
                # cherrypy does not auto unquote params when using
                # InternalRedirect
                raise cherrypy.InternalRedirect(unquote(href.group(1)))

        if len(items) <= self.opts.max_opds_ungrouped_items:
            script = 'false'
            items = get_category_items(category, items, datatype,
                                       self.opts.url_prefix)
        else:
            getter = lambda x: unicode(getattr(x, 'sort', x.name))
            starts = set([])
            for x in items:
                val = getter(x)
                if not val:
                    val = u'A'
                starts.add(val[0].upper())
            category_groups = OrderedDict()
            for x in sorted(starts):
                category_groups[x] = len(
                    [y for y in items if getter(y).upper().startswith(x)])
            items = [(
                u'<h3 title="{0}"><a class="load_href" title="{0}"'
                u' href="{4}{3}"><strong>{0}</strong> [{2}]</a></h3><div>'
                u'<div class="loaded" style="display:none"></div>'
                u'<div class="loading"><img alt="{1}" src="{4}/static/loading.gif" /><em>{1}</em></div>'
                u'</div>').format(
                    xml(s, True),
                    xml(_('Loading, please wait')) + '&hellip;', unicode(c),
                    xml(
                        u'/browse/category_group/%s/%s' %
                        (hexlify(category.encode('utf-8')),
                         hexlify(s.encode('utf-8'))), True),
                    self.opts.url_prefix) for s, c in category_groups.items()]
            items = '\n\n'.join(items)
            items = u'<div id="groups">\n{0}</div>'.format(items)

        if cats:
            script = 'toplevel();category(%s);' % script
        else:
            script = 'category(%s);' % script

        main = u'''
            <div class="category">
                <h3>{0}</h3>
                    <a class="navlink" href="{3}/browse"
                        title="{2}">{2}&nbsp;&uarr;</a>
                {1}
            </div>
        '''.format(xml(_('Browsing by') + ': ' + category_name), cats + items,
                   xml(_('Up'), True), self.opts.url_prefix)

        return self.browse_template(sort).format(title=category_name,
                                                 script=script,
                                                 main=main)
Beispiel #7
0
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, 'invalid category id: %r'%cid)
        categories = self.categories_cache()

        if category not in categories and \
                category not in ('newest', 'allbooks', 'virt_libs'):
            raise cherrypy.HTTPError(404, 'category not found')
        fm = self.db.field_metadata
        try:
            category_name = fm[category]['name']
            dt = fm[category]['datatype']
        except:
            if category not in ('newest', 'allbooks', 'virt_libs'):
                raise
            category_name = {
                    'newest' : _('Newest'),
                    'allbooks' : _('All books'),
                    'virt_libs': _('Virtual Libraries'),
            }[category]
            dt = None

        hide_sort = 'true' if dt == 'series' else 'false'
        if category == 'search':
            which = unhexlify(cid).decode('utf-8')
            try:
                ids = self.search_cache('search:"%s"'%which)
            except:
                raise cherrypy.HTTPError(404, 'Search: %r not understood'%which)
        else:
            all_ids = self.search_cache('')
            if category == 'newest':
                ids = all_ids
                hide_sort = 'true'
            elif category == 'allbooks':
                ids = all_ids
            elif category == 'virt_libs':
                which = unhexlify(cid).decode('utf-8')
                vls = self.db.prefs.get('virtual_libraries', {})
                ids = self.search_cache(vls[which])
                category_name = _('virtual library: ') + xml(which)
                if not ids:
                    msg = _('The virtual library <b>%s</b> has no books.') % prepare_string_for_xml(which)
                    if self.search_restriction:
                        msg += ' ' + _(
                            'This is probably because you have applied a virtual library'
                            ' to the content server in Preferences->Sharing over the net.'
                            ' This virtual library is applied globally and combined with'
                            ' the current virtual library.')
                    return self.browse_template('name').format(title='',
                        script='', main='<p>%s</p>'%msg)
            else:
                if fm.get(category, {'datatype':None})['datatype'] == 'composite':
                    cid = cid.decode('utf-8')
                q = category
                if q == 'news':
                    q = 'tags'
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data.tablerow_for_id(x) for x in ids]
        if category == 'newest':
            list_sort = 'timestamp'
        if dt == 'series':
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids, self.opts.url_prefix,
                suffix=_('in') + ' ' + category_name)

        return self.browse_template(sort, category=False).format(
                title=_('Books in') + " " +category_name,
                script='booklist(%s);'%hide_sort, main=html)
    def browse_matches(self, category=None, cid=None, list_sort=None):
        if list_sort:
            list_sort = unquote(list_sort)
        if not cid:
            raise cherrypy.HTTPError(404, 'invalid category id: %r' % cid)
        categories = self.categories_cache()

        if category not in categories and \
                category not in ('newest', 'allbooks'):
            raise cherrypy.HTTPError(404, 'category not found')
        fm = self.db.field_metadata
        try:
            category_name = fm[category]['name']
            dt = fm[category]['datatype']
        except:
            if category not in ('newest', 'allbooks'):
                raise
            category_name = {
                'newest': _('Newest'),
                'allbooks': _('All books'),
            }[category]
            dt = None

        hide_sort = 'true' if dt == 'series' else 'false'
        if category == 'search':
            which = unhexlify(cid).decode('utf-8')
            try:
                ids = self.search_cache('search:"%s"' % which)
            except:
                raise cherrypy.HTTPError(404,
                                         'Search: %r not understood' % which)
        else:
            all_ids = self.search_cache('')
            if category == 'newest':
                ids = all_ids
                hide_sort = 'true'
            elif category == 'allbooks':
                ids = all_ids
            else:
                if fm.get(category,
                          {'datatype': None})['datatype'] == 'composite':
                    cid = cid.decode('utf-8')
                q = category
                if q == 'news':
                    q = 'tags'
                ids = self.db.get_books_for_category(q, cid)
                ids = [x for x in ids if x in all_ids]

        items = [self.db.data._data[x] for x in ids]
        if category == 'newest':
            list_sort = 'timestamp'
        if dt == 'series':
            list_sort = category
        sort = self.browse_sort_book_list(items, list_sort)
        ids = [x[0] for x in items]
        html = render_book_list(ids,
                                self.opts.url_prefix,
                                suffix=_('in') + ' ' + category_name)

        return self.browse_template(sort, category=False).format(
            title=_('Books in') + " " + category_name,
            script='booklist(%s);' % hide_sort,
            main=html)
Beispiel #9
0
    def browse_category(self, category, sort):
        categories = self.categories_cache()
        categories['virt_libs'] = {}
        if category not in categories:
            raise cherrypy.HTTPError(404, 'category not found')
        category_meta = self.db.field_metadata
        category_name = _('Virtual Libraries') if category == 'virt_libs' else category_meta[category]['name']
        datatype = 'text' if category == 'virt_libs' else category_meta[category]['datatype']

        # See if we have any sub-categories to display. As we find them, add
        # them to the displayed set to avoid showing the same item twice
        uc_displayed = set()
        cats = []
        for ucat in sorted(categories.keys(), key=sort_key):
            if len(categories[ucat]) == 0:
                continue
            if category == 'formats':
                continue
            meta = category_meta.get(ucat, None)
            if meta is None:
                continue
            if meta['kind'] != 'user':
                continue
            cat_len = len(category)
            if not (len(ucat) > cat_len and ucat.startswith(category+'.')):
                continue

            if ucat in self.icon_map:
                icon = '_'+quote(self.icon_map[ucat])
            else:
                icon = category_icon_map['user:'******'.')
            if dot > 0:
                # More subcats
                cat = cat[:dot]
                if cat not in uc_displayed:
                    cats.append((cat, ucat[:cat_len+dot], icon))
                    uc_displayed.add(cat)
            else:
                # This is the end of the chain
                cats.append((cat, ucat, icon))
                uc_displayed.add(cat)

        cats = u'\n\n'.join(
                [(u'<li><a title="{2} {0}" href="{3}/browse/category/{1}">&nbsp;</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])
        if cats:
            cats = (u'\n<div class="toplevel">\n'
                     '{0}</div>').format(cats)

        # Now do the category items
        vls = self.db.prefs.get('virtual_libraries', {})
        categories['virt_libs'] = sorted([Tag(k) for k, v in vls.iteritems()], key=lambda x:sort_key(x.name))
        items = categories[category]
        print(items)

        sort = self.browse_sort_categories(items, sort)

        if not cats and len(items) == 1:
            # Only one item in category, go directly to book list
            html = get_category_items(category, items,
                    datatype, self.opts.url_prefix)
            href = re.search(r'<a href="([^"]+)"', html)
            if href is not None:
                # cherrypy does not auto unquote params when using
                # InternalRedirect
                raise cherrypy.InternalRedirect(unquote(href.group(1)))

        if len(items) <= self.opts.max_opds_ungrouped_items:
            items = get_category_items(category, items,
                    datatype, self.opts.url_prefix)
        else:
            getter = lambda x: unicode(getattr(x, 'sort', None) or x.name)
            starts = set([])
            for x in items:
                val = getter(x)
                if not val:
                    val = u'A'
                starts.add(val[0].upper())
            category_groups = OrderedDict()
            for x in sorted(starts):
                category_groups[x] = len([y for y in items if
                    getter(y).upper().startswith(x)])
            items = [(u'<h3 title="{0}"><a class="load_href" title="{0}"'
                      u' href="{4}{3}"><strong>{0}</strong> [{2}]</a></h3><div>'
                      u'<div class="loaded" style="display:none"></div>'
                      u'<div class="loading"><img alt="{1}" src="{4}/static/loading.gif" /><em>{1}</em></div>'
                      u'</div>').format(
                        xml(s, True),
                        xml(_('Loading, please wait'))+'&hellip;',
                        unicode(c),
                        xml(u'/browse/category_group/%s/%s'%(
                            hexlify(category.encode('utf-8')),
                            hexlify(s.encode('utf-8'))), True),
                        self.opts.url_prefix)
                    for s, c in category_groups.items()]
            items = '\n\n'.join(items)
            items = u'<div id="groups">\n{0}</div>'.format(items)

        main = u'''
            <div class="category">
                <h3>{0}</h3>
                    <a class="navlink" href="{3}/browse"
                        title="{2}">{2}&nbsp;&uarr;</a>
                {1}
            </div>
        '''.format(
                xml(_('Browsing by')+': ' + category_name), cats + items,
                xml(_('Up'), True), self.opts.url_prefix)

        return self.browse_template(sort).format(title=category_name,
                main=main)