Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhelpsearchindexreader.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
7
8#include <QtCore/qmap.h>
9#include <QtCore/qset.h>
10#include <QtSql/qsqldatabase.h>
11#include <QtSql/qsqlquery.h>
12
14
15using namespace Qt::StringLiterals;
16
17namespace fulltextsearch {
18
19class Reader
20{
21public:
22 void setIndexPath(const QString &path)
23 {
24 m_indexPath = path;
25 m_namespaceAttributes.clear();
26 m_filterEngineNamespaceList.clear();
27 m_useFilterEngine = false;
28 }
29 void addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes)
30 {
31 m_namespaceAttributes.insert(namespaceName, attributes);
32 }
33 void setFilterEngineNamespaceList(const QStringList &namespaceList)
34 {
35 m_useFilterEngine = true;
36 m_filterEngineNamespaceList = namespaceList;
37 }
38
39 void searchInDB(const QString &term);
40 QList<QHelpSearchResult> searchResults() const { return m_searchResults; }
41
42private:
43 QList<QHelpSearchResult> queryTable(const QSqlDatabase &db, const QString &tableName,
44 const QString &searchInput) const;
45
46 QMultiMap<QString, QStringList> m_namespaceAttributes;
47 QStringList m_filterEngineNamespaceList;
48 QList<QHelpSearchResult> m_searchResults;
49 QString m_indexPath;
50 bool m_useFilterEngine = false;
51};
52
53static QString namespacePlaceholders(const QMultiMap<QString, QStringList> &namespaces)
54{
55 QString placeholders;
56 const auto &namespaceList = namespaces.uniqueKeys();
57 bool firstNS = true;
58 for (const QString &ns : namespaceList) {
59 if (firstNS)
60 firstNS = false;
61 else
62 placeholders += " OR "_L1;
63 placeholders += "(namespace = ?"_L1;
64
65 const QList<QStringList> &attributeSets = namespaces.values(ns);
66 bool firstAS = true;
67 for (const QStringList &attributeSet : attributeSets) {
68 if (!attributeSet.isEmpty()) {
69 if (firstAS) {
70 firstAS = false;
71 placeholders += " AND ("_L1;
72 } else {
73 placeholders += " OR "_L1;
74 }
75 placeholders += "attributes = ?"_L1;
76 }
77 }
78 if (!firstAS)
79 placeholders += u')'; // close "AND ("
80 placeholders += u')';
81 }
82 return placeholders;
83}
84
85static void bindNamespacesAndAttributes(QSqlQuery *query,
86 const QMultiMap<QString, QStringList> &namespaces)
87{
88 const auto &namespaceList = namespaces.uniqueKeys();
89 for (const QString &ns : namespaceList) {
90 query->addBindValue(ns);
91
92 const QList<QStringList> &attributeSets = namespaces.values(ns);
93 for (const QStringList &attributeSet : attributeSets) {
94 if (!attributeSet.isEmpty())
95 query->addBindValue(attributeSet.join(u'|'));
96 }
97 }
98}
99
100static QString namespacePlaceholders(const QStringList &namespaceList)
101{
102 QString placeholders;
103 bool firstNS = true;
104 for (int i = namespaceList.size(); i; --i) {
105 if (firstNS)
106 firstNS = false;
107 else
108 placeholders += " OR "_L1;
109 placeholders += "namespace = ?"_L1;
110 }
111 return placeholders;
112}
113
114static void bindNamespacesAndAttributes(QSqlQuery *query, const QStringList &namespaceList)
115{
116 for (const QString &ns : namespaceList)
117 query->addBindValue(ns);
118}
119
120QList<QHelpSearchResult> Reader::queryTable(const QSqlDatabase &db, const QString &tableName,
121 const QString &searchInput) const
122{
123 const QString nsPlaceholders = m_useFilterEngine
124 ? namespacePlaceholders(m_filterEngineNamespaceList)
125 : namespacePlaceholders(m_namespaceAttributes);
126 QSqlQuery query(db);
127 query.prepare("SELECT url, title, snippet("_L1 + tableName +
128 ", -1, '<b>', '</b>', '...', '10') FROM "_L1 + tableName +
129 " WHERE ("_L1 + nsPlaceholders +
130 ") AND "_L1 + tableName +
131 " MATCH ? ORDER BY rank"_L1);
132 m_useFilterEngine
133 ? bindNamespacesAndAttributes(&query, m_filterEngineNamespaceList)
134 : bindNamespacesAndAttributes(&query, m_namespaceAttributes);
135 query.addBindValue(searchInput);
136 query.exec();
137
138 QList<QHelpSearchResult> results;
139
140 while (query.next()) {
141 const QString &url = query.value("url"_L1).toString();
142 const QString &title = query.value("title"_L1).toString();
143 const QString &snippet = query.value(2).toString();
144 results.append(QHelpSearchResult(url, title, snippet));
145 }
146 return results;
147}
148
149void Reader::searchInDB(const QString &searchInput)
150{
151 const QString &uniqueId = QHelpGlobal::uniquifyConnectionName("QHelpReader"_L1, this);
152 {
153 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, uniqueId);
154 db.setConnectOptions("QSQLITE_OPEN_READONLY"_L1);
155 db.setDatabaseName(m_indexPath + "/fts"_L1);
156
157 if (db.open()) {
158 const QList<QHelpSearchResult> titleResults = queryTable(db, "titles"_L1, searchInput);
159 const QList<QHelpSearchResult> contentResults =
160 queryTable(db, "contents"_L1, searchInput);
161
162 // merge results form title and contents searches
163 m_searchResults.clear();
164 QSet<QUrl> urls;
165 for (const QHelpSearchResult &result : titleResults) {
166 const auto size = urls.size();
167 urls.insert(result.url());
168 if (size != urls.size()) // insertion took place
169 m_searchResults.append(result);
170 }
171 for (const QHelpSearchResult &result : contentResults) {
172 const auto size = urls.size();
173 urls.insert(result.url());
174 if (size != urls.size()) // insertion took place
175 m_searchResults.append(result);
176 }
177 }
178 }
179 QSqlDatabase::removeDatabase(uniqueId);
180}
181
182static bool attributesMatchFilter(const QStringList &attributes, const QStringList &filter)
183{
184 for (const QString &attribute : filter) {
185 if (!attributes.contains(attribute, Qt::CaseInsensitive))
186 return false;
187 }
188 return true;
189}
190
191QHelpSearchIndexReader::~QHelpSearchIndexReader()
192{
194 wait();
195}
196
198{
199 QMutexLocker lock(&m_mutex);
200 m_cancel = true;
201}
202
203void QHelpSearchIndexReader::search(const QString &collectionFile, const QString &indexFilesFolder,
204 const QString &searchInput, bool usesFilterEngine)
205{
206 wait();
207
208 m_searchResults.clear();
209 m_cancel = false;
210 m_searchInput = searchInput;
211 m_collectionFile = collectionFile;
212 m_indexFilesFolder = indexFilesFolder;
213 m_usesFilterEngine = usesFilterEngine;
214
215 start(QThread::NormalPriority);
216}
217
219{
220 QMutexLocker lock(&m_mutex);
221 return m_searchResults.size();
222}
223
225{
226 QMutexLocker lock(&m_mutex);
227 return m_searchResults.mid(start, end - start);
228}
229
231{
232 QMutexLocker lock(&m_mutex);
233
234 if (m_cancel)
235 return;
236
237 const QString searchInput = m_searchInput;
238 const QString collectionFile = m_collectionFile;
239 const QString indexPath = m_indexFilesFolder;
240 const bool usesFilterEngine = m_usesFilterEngine;
241
242 lock.unlock();
243
244 QHelpEngineCore engine(collectionFile, nullptr);
245 if (!engine.setupData())
246 return;
247
248 emit searchingStarted();
249
250 // setup the reader
251 Reader reader;
252 reader.setIndexPath(indexPath);
253
254 if (usesFilterEngine) {
255 reader.setFilterEngineNamespaceList(
256 engine.filterEngine()->namespacesForFilter(engine.filterEngine()->activeFilter()));
257 } else {
258 const QStringList &registeredDocs = engine.registeredDocumentations();
259 const QStringList &currentFilter = engine.filterAttributes(engine.currentFilter());
260
261 for (const QString &namespaceName : registeredDocs) {
262 const QList<QStringList> &attributeSets =
263 engine.filterAttributeSets(namespaceName);
264
265 for (const QStringList &attributes : attributeSets) {
266 if (attributesMatchFilter(attributes, currentFilter))
267 reader.addNamespaceAttributes(namespaceName, attributes);
268 }
269 }
270 }
271
272 lock.relock();
273 if (m_cancel) {
274 lock.unlock();
275 emit searchingFinished();
276 return;
277 }
278 m_searchResults.clear();
279 lock.unlock();
280
281 reader.searchInDB(searchInput); // TODO: should this be interruptible as well ???
282
283 lock.relock();
284 m_searchResults = reader.searchResults();
285 lock.unlock();
286
287 emit searchingFinished();
288}
289
290} // namespace fulltextsearch
291
292QT_END_NAMESPACE
void search(const QString &collectionFile, const QString &indexFilesFolder, const QString &searchInput, bool usesFilterEngine=false)
QList< QHelpSearchResult > searchResults(int start, int end) const
QList< QHelpSearchResult > searchResults() const
void searchInDB(const QString &term)
void setIndexPath(const QString &path)
void addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes)
void setFilterEngineNamespaceList(const QStringList &namespaceList)
Combined button and popup list for selecting options.
static bool attributesMatchFilter(const QStringList &attributes, const QStringList &filter)
static void bindNamespacesAndAttributes(QSqlQuery *query, const QMultiMap< QString, QStringList > &namespaces)
static QString namespacePlaceholders(const QMultiMap< QString, QStringList > &namespaces)