From 4467f1e54c940f390012803a67d644911e615b30 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Jun 2022 11:16:06 +0530 Subject: [PATCH] Allow removing individual books from the FTS index if they are somehow missing from the main db --- src/calibre/db/backend.py | 3 +++ src/calibre/db/cache.py | 4 ++++ src/calibre/db/fts/connect.py | 7 +++++++ src/calibre/gui2/fts/search.py | 26 ++++++++++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 82e6fb39df..f38add03f6 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -979,6 +979,9 @@ class DB: if self.fts is not None: return self.fts.commit_result(book_id, fmt, fmt_size, fmt_hash, text, err_msg) + def fts_unindex(self, book_id, fmt=None): + self.fts.unindex(book_id, fmt=fmt) + def fts_search(self, fts_engine_query, use_stemming, highlight_start, highlight_end, snippet_size, restrict_to_book_ids, return_text, ): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index f73257e344..929a0801c6 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -475,6 +475,10 @@ class Cache: self.fts_job_queue = Queue() return fts + @write_api + def fts_unindex(self, book_id, fmt=None): + self.backend.fts_unindex(book_id, fmt=fmt) + @staticmethod def dispatch_fts_jobs(queue, dbref): from .fts.text import is_fmt_ok diff --git a/src/calibre/db/fts/connect.py b/src/calibre/db/fts/connect.py index 2912b0b7a1..53d0ceb04b 100644 --- a/src/calibre/db/fts/connect.py +++ b/src/calibre/db/fts/connect.py @@ -87,6 +87,13 @@ class FTS: conn = self.get_connection() conn.execute('DELETE FROM fts_db.dirtied_formats WHERE book=? AND format=?', (book_id, fmt.upper())) + def unindex(self, book_id, fmt=None): + conn = self.get_connection() + if fmt is None: + conn.execute('DELETE FROM books_text WHERE book=?', (book_id,)) + else: + conn.execute('DELETE FROM books_text WHERE book=? AND format=?', (book_id, fmt.upper())) + def add_text(self, book_id, fmt, text, text_hash='', fmt_size=0, fmt_hash='', err_msg=''): conn = self.get_connection() ts = (utcnow() - EPOCH).total_seconds() diff --git a/src/calibre/gui2/fts/search.py b/src/calibre/gui2/fts/search.py index 44e9edacd9..d07bf187c0 100644 --- a/src/calibre/gui2/fts/search.py +++ b/src/calibre/gui2/fts/search.py @@ -237,6 +237,17 @@ class ResultsModel(QAbstractItemModel): self.current_thread.start() return True + def remove_book(self, book_id): + idx = self.result_map.get(book_id) + if idx is not None: + self.beginRemoveRows(ROOT, idx, idx) + del self.results[idx] + del self.result_map[book_id] + self.endRemoveRows() + self.matches_found.emit(len(self.results)) + return True + return False + def search_text_in_thread(self, query_id, abort, *a, **kw): db = get_db() generator = db.fts_search(*a, **kw, result_type=lambda x: x) @@ -506,6 +517,7 @@ class SearchInputPanel(QWidget): class ResultDetails(QWidget): show_in_viewer = pyqtSignal(int, int, str) + remove_book_from_results = pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) @@ -533,6 +545,10 @@ class ResultDetails(QWidget): mark_books(self.current_book_id) elif url.host() == 'jump': jump_to_book(self.current_book_id) + elif url.host() == 'unindex': + db = get_db() + db.fts_unindex(self.current_book_id) + self.remove_book_from_results.emit(self.current_book_id) def results_anchor_clicked(self, url): if self.current_book_id > 0 and url.scheme() == 'book': @@ -601,6 +617,10 @@ class ResultDetails(QWidget): _('Mark'), '

' + _( 'Put a pin on this book in the calibre library, for future reference.' ' You can search for marked books using the search term: {0}').format('

marked:true')) + if not get_db().has_id(results.book_id): + text += '

\xa0{0}'.format( + _('Remove from index'), _('This book has been deleted from the library but is still present in the' + ' full text search index. Remove it.')) self.book_info.setHtml(text) def render_results(self, results, individual_match=None): @@ -643,6 +663,7 @@ class ResultDetails(QWidget): class DetailsPanel(QStackedWidget): show_in_viewer = pyqtSignal(int, int, str) + remove_book_from_results = pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) @@ -683,6 +704,7 @@ p { margin: 0; } self.result_details = rd = ResultDetails(self) rd.show_in_viewer.connect(self.show_in_viewer) + rd.remove_book_from_results.connect(self.remove_book_from_results) self.addWidget(rd) def sizeHint(self): @@ -745,6 +767,7 @@ class ResultsPanel(QWidget): self.details = d = DetailsPanel(self) d.show_in_viewer.connect(self.show_in_viewer) + d.remove_book_from_results.connect(self.remove_book_from_results) rv.current_changed.connect(d.show_result) rv.search_started.connect(d.clear) rv.result_with_context_found.connect(d.result_with_context_found) @@ -753,6 +776,9 @@ class ResultsPanel(QWidget): if st is not None: s.restoreState(st) + def remove_book_from_results(self, book_id): + self.results_view.m.remove_book(book_id) + def show_in_viewer(self, book_id, result_num, fmt): r = self.results_view.m.get_result(book_id, result_num) text = r['text'].strip('…').replace('\x1d', '').replace('\xa0', ' ')