From ebee2f795e9240e4ea43847fdf41b7ec3fcc85aa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Apr 2010 10:07:11 +0530 Subject: [PATCH] Fix #5244 (Add date: and pubdate: searches) --- src/calibre/library/database2.py | 75 ++++++++++++++++++++++-- src/calibre/manual/gui.rst | 19 ++++++ src/calibre/utils/search_query_parser.py | 2 + 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 6c886f0e5d..90fe28952b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -10,7 +10,6 @@ import os, re, sys, shutil, cStringIO, glob, collections, textwrap, \ itertools, functools, traceback from itertools import repeat from math import floor - from PyQt4.QtCore import QThread, QReadWriteLock try: from PIL import Image as PILImage @@ -33,7 +32,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import from calibre.utils.filenames import ascii_filename -from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp +from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp, parse_date from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format if iswindows: @@ -196,10 +195,58 @@ class ResultCache(SearchQueryParser): Stores sorted and filtered metadata in memory. ''' + def build_relop_dict(self): + ''' + Because the database dates have time in them, we can't use direct + comparisons even when field_count == 3. The query has time = 0, but + the database object has time == something. As such, a complete compare + will almost never be correct. + ''' + def relop_eq(db, query, field_count): + if db.year == query.year: + if field_count == 1: + return True + if db.month == query.month: + if field_count == 2: + return True + return db.day == query.day + return False + + def relop_gt(db, query, field_count): + if db.year > query.year: + return True + if field_count > 1 and db.year == query.year: + if db.month > query.month: + return True + return field_count == 3 and db.month == query.month and db.day > query.day + return False + + def relop_lt(db, query, field_count): + if db.year < query.year: + return True + if field_count > 1 and db.year == query.year: + if db.month < query.month: + return True + return field_count == 3 and db.month == query.month and db.day < query.day + return False + + def relop_ne(db, query, field_count): + return not relop_eq(db, query, field_count) + + def relop_ge(db, query, field_count): + return not relop_lt(db, query, field_count) + + def relop_le(db, query, field_count): + return not relop_gt(db, query, field_count) + + self.search_relops = {'=':[1, relop_eq], '>':[1, relop_gt], '<':[1, relop_lt], \ + '!=':[2, relop_ne], '>=':[2, relop_ge], '<=':[2, relop_le]} + def __init__(self): self._map = self._map_filtered = self._data = [] self.first_sort = True SearchQueryParser.__init__(self) + self.build_relop_dict() def __getitem__(self, row): return self._data[self._map_filtered[row]] @@ -219,6 +266,27 @@ class ResultCache(SearchQueryParser): if query and query.strip(): location = location.lower().strip() + ### take care of dates special case + if location in ('pubdate', 'date'): + if len(query) < 2: + return matches + relop = None + for k in self.search_relops.keys(): + if query.startswith(k): + (p, relop) = self.search_relops[k] + query = query[p:] + if relop is None: + return matches + loc = FIELD_MAP[{'date':'timestamp', 'pubdate':'pubdate'}[location]] + qd = parse_date(query) + field_count = query.count('-') + 1 + for item in self._data: + if item is None: continue + if relop(item[loc], qd, field_count): + matches.add(item[0]) + return matches + + ### everything else matchkind = CONTAINS_MATCH if (len(query) > 1): if query.startswith('\\'): @@ -1994,6 +2062,3 @@ books_series_link feeds self.refresh_ids(list(bad.keys())) return bad - - - diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index d37b5eda15..631f738c94 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -207,6 +207,19 @@ Should you need to search for a string with a leading equals or tilde, prefix th You can build advanced search queries easily using the :guilabel:`Advanced Search Dialog`, accessed by clicking the button |sbi|. +Available fields for searching are: ``tag, title, author, publisher, series, rating cover, comments, format, +isbn, date, pubdate, search``. + +The syntax for searching for dates and publication dates is:: + + pubdate:>2000-1 Will find all books published after Jan, 2000 + date:<=2000-1-3 Will find all books added to calibre beforre 3 Jan, 2000 + pubdate:=2009 Will find all books published in 2009 + +The special field ``search`` is used for saved searches. So if you save a search with the name +"My spouse's books" you can enter ``search:"My spouses' books"`` in the search bar to reuse the saved +search. More about saving searches, below. + .. |sbi| image:: images/search_button.png :align: middle @@ -214,6 +227,12 @@ clicking the button |sbi|. :guilabel:`Advanced Search Dialog` +Saving searches +----------------- + +|app| has a useful feature, it allows you to save a search you use frequently under a special name and then re-use that search with a single click. To do this, create your search, either by typing it in the search bar, or using the Tag Browser. Then, type the name you would like to give to the search in the Saved Searches box next to the search bar and click the plus icon next to the saved searches box to save the search. + +Now, you can access your saved search in the Tga Browser under "Saved searches". A single click will allow you to re-use any arbitrarily comple search easily without needing to re-create it. .. _configuration: diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 787afec7bd..2acb4708e9 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -98,6 +98,8 @@ class SearchQueryParser(object): 'format', 'isbn', 'search', + 'date', + 'pubdate', 'all', ]