mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
commit
d2a2da5f68
@ -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
|
||||||
|
@ -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
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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]
|
||||||
|
@ -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'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user