Allow searching for books in a virtual library using a new 'vl:' prefix

Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
Kovid Goyal 2017-08-20 16:46:45 +05:30
commit d2a2da5f68
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 54 additions and 9 deletions

View File

@ -359,7 +359,7 @@ You can build advanced search queries easily using the :guilabel:`Advanced searc
clicking the button |sbi|. clicking the button |sbi|.
Available fields for searching are: ``tag, title, author, publisher, series, series_index, rating, cover, Available fields for searching are: ``tag, title, author, publisher, series, series_index, rating, cover,
comments, format, identifiers, date, pubdate, search, size`` and custom columns. If a device is plugged in, the ``ondevice`` field becomes available, when searching the calibre library view. To find the search name (actually called the `lookup name`) for a custom column, hover your mouse over the column header in the library view. comments, format, identifiers, date, pubdate, search, size, vl`` and custom columns. If a device is plugged in, the ``ondevice`` field becomes available, when searching the calibre library view. To find the search name (actually called the `lookup name`) for a custom column, hover your mouse over the column header in the library view.
The syntax for searching for dates is:: The syntax for searching for dates is::
@ -400,6 +400,12 @@ The special field ``search`` is used for saved searches. So if you save a search
"My spouse's books" you can enter ``search:"My spouse's books"`` in the Search bar to reuse the saved "My spouse's books" you can enter ``search:"My spouse's books"`` in the Search bar to reuse the saved
search. More about saving searches below. search. More about saving searches below.
The special field ``vl`` is used to search for books in a virtual library. For
example, ``vl:Read`` will find all the books in the *Read* virtual library. The search
``vl:Read and vl:"Science Fiction"`` will find all the books that are in both the *Read* and
*Science Fiction* virtual libraries. The value following ``vl:`` must be the name of a
virtual library. If the virtual library name contains spaces then surround it with quotes.
You can search for the absence or presence of a field using the special "true" and "false" values. For example:: You can search for the absence or presence of a field using the special "true" and "false" values. For example::
cover:false will give you all books without a cover cover:false will give you all books without a cover

View File

@ -95,6 +95,23 @@ virtual libraries as tabs`. You can re-arrange the tabs by drag and drop and
close ones you do not want to see. Closed tabs can be restored by close ones you do not want to see. Closed tabs can be restored by
right-clicking on the tab bar. right-clicking on the tab bar.
Using Virtual libraries in searches
-------------------------------------
You can search for books that are in a virtual library using the ``vl:`` prefix. For
example, ``vl:Read`` will find all the books in the *Read* virtual library. The search
``vl:Read and vl:"Science Fiction"`` will find all the books that are in both the *Read* and
*Science Fiction* virtual libraries.
The value following ``vl:`` must be the name of a virtual library. If the virtual library name
contains spaces then surround it with quotes.
One use for a virtual library search is in the content server. In
:guilabel:`Preferences->Sharing over the net->Require username/password` you
can limit the calibre libraries visible to a user. For each visible library you
can specify a search expression to further limit which books are seen. Use
``vl:"Virtual library name"`` to limit the books to those in a virtual library.
Using additional restrictions Using additional restrictions
------------------------------- -------------------------------

View File

@ -498,6 +498,15 @@ class Parser(SearchQueryParser): # {{{
if location not in self.all_search_locations: if location not in self.all_search_locations:
return matches return matches
if location == 'vl':
vl = self.dbcache._pref('virtual_libraries', {}).get(query) if query else None
if not vl:
raise ParseException(_('No such virtual library: {}').format(query))
try:
return candidates & self.dbcache.books_in_virtual_library(query)
except RuntimeError:
raise ParseException(_('Virtual library search is recursive: {}').format(query))
if (len(location) > 2 and location.startswith('@') and if (len(location) > 2 and location.startswith('@') and
location[1:] in self.grouped_search_terms): location[1:] in self.grouped_search_terms):
location = location[1:] location = location[1:]
@ -915,4 +924,3 @@ class Search(object):
self.cache.add(query, result) self.cache.add(query, result)
return result return result

View File

@ -380,7 +380,7 @@ class FieldMetadata(object):
'int', 'float', 'bool', 'series', 'composite', 'enumeration']) 'int', 'float', 'bool', 'series', 'composite', 'enumeration'])
# search labels that are not db columns # search labels that are not db columns
search_items = ['all', 'search'] search_items = ['all', 'search', 'vl']
__calibre_serializable__ = True __calibre_serializable__ = True
def __init__(self): def __init__(self):

View File

@ -58,6 +58,12 @@ class HTTPForbidden(HTTPSimpleResponse):
HTTPSimpleResponse.__init__(self, httplib.FORBIDDEN, http_message, close_connection, log=log) HTTPSimpleResponse.__init__(self, httplib.FORBIDDEN, http_message, close_connection, log=log)
class HTTPInternalServerError(HTTPSimpleResponse):
def __init__(self, http_message='', close_connection=True, log=None):
HTTPSimpleResponse.__init__(self, httplib.INTERNAL_SERVER_ERROR, http_message, close_connection, log=log)
class BookNotFound(HTTPNotFound): class BookNotFound(HTTPNotFound):
def __init__(self, book_id, db): def __init__(self, book_id, db):

View File

@ -118,8 +118,10 @@ class Context(object):
raise raise
return frozenset() return frozenset()
def get_categories(self, request_data, db, sort='name', first_letter_sort=True, vl=''): def get_categories(self, request_data, db, sort='name', first_letter_sort=True,
restrict_to_ids = self.get_effective_book_ids(db, request_data, vl) vl='', report_parse_errors=False):
restrict_to_ids = self.get_effective_book_ids(db, request_data, vl,
report_parse_errors=report_parse_errors)
key = restrict_to_ids, sort, first_letter_sort key = restrict_to_ids, sort, first_letter_sort
with self.lock: with self.lock:
cache = self.library_broker.category_caches[db.server_library_id] cache = self.library_broker.category_caches[db.server_library_id]

View File

@ -20,8 +20,9 @@ from calibre.library.comments import comments_to_html
from calibre import guess_type, prepare_string_for_xml as xml from calibre import guess_type, prepare_string_for_xml as xml
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.date import as_utc, timestampfromdt, is_date_undefined from calibre.utils.date import as_utc, timestampfromdt, is_date_undefined
from calibre.utils.search_query_parser import ParseException
from calibre.srv.errors import HTTPNotFound from calibre.srv.errors import HTTPNotFound, HTTPInternalServerError
from calibre.srv.routes import endpoint from calibre.srv.routes import endpoint
from calibre.srv.utils import get_library_data, http_date, Offsets from calibre.srv.utils import get_library_data, http_date, Offsets
@ -381,8 +382,9 @@ class RequestContext(object):
def last_modified(self): def last_modified(self):
return self.db.last_modified() return self.db.last_modified()
def get_categories(self): def get_categories(self, report_parse_errors=False):
return self.ctx.get_categories(self.rd, self.db) return self.ctx.get_categories(self.rd, self.db,
report_parse_errors=report_parse_errors)
def search(self, query): def search(self, query):
return self.ctx.search(self.rd, self.db, query) return self.ctx.search(self.rd, self.db, query)
@ -470,7 +472,11 @@ def get_navcatalog(request_context, which, page_url, up_url, offset=0):
def opds(ctx, rd): def opds(ctx, rd):
rc = RequestContext(ctx, rd) rc = RequestContext(ctx, rd)
db = rc.db db = rc.db
categories = rc.get_categories() try:
categories = rc.get_categories(report_parse_errors=True)
except ParseException as p:
raise HTTPInternalServerError(p.msg)
category_meta = db.field_metadata category_meta = db.field_metadata
cats = [ cats = [
(_('Newest'), _('Date'), 'Onewest'), (_('Newest'), _('Date'), 'Onewest'),