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
4#include "qhelpsearchindexreader_p.h"
5#include "qhelpenginecore.h"
6#include "qhelpfilterengine.h"
7
8#include <QtCore/qmap.h>
9#include <QtCore/qset.h>
10#include <QtSql/qsqldatabase.h>
11#include <QtSql/qsqlquery.h>
12
13QT_BEGIN_NAMESPACE
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(key: namespaceName, value: 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(key: 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(val: ns);
91
92 const QList<QStringList> &attributeSets = namespaces.values(key: ns);
93 for (const QStringList &attributeSet : attributeSets) {
94 if (!attributeSet.isEmpty())
95 query->addBindValue(val: attributeSet.join(sep: 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(val: 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(namespaceList: m_filterEngineNamespaceList)
125 : namespacePlaceholders(namespaces: m_namespaceAttributes);
126 QSqlQuery query(db);
127 query.prepare(query: "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: &query, namespaceList: m_filterEngineNamespaceList)
134 : bindNamespacesAndAttributes(query: &query, namespaces: m_namespaceAttributes);
135 query.addBindValue(val: searchInput);
136 query.exec();
137
138 QList<QHelpSearchResult> results;
139
140 while (query.next()) {
141 const QString &url = query.value(name: "url"_L1).toString();
142 const QString &title = query.value(name: "title"_L1).toString();
143 const QString &snippet = query.value(i: 2).toString();
144 results.append(t: QHelpSearchResult(url, title, snippet));
145 }
146 return results;
147}
148
149void Reader::searchInDB(const QString &searchInput)
150{
151 const QString &uniqueId = QHelpGlobal::uniquifyConnectionName(name: "QHelpReader"_L1, pointer: this);
152 {
153 QSqlDatabase db = QSqlDatabase::addDatabase(type: "QSQLITE"_L1, connectionName: 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, tableName: "titles"_L1, searchInput);
159 const QList<QHelpSearchResult> contentResults =
160 queryTable(db, tableName: "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(value: result.url());
168 if (size != urls.size()) // insertion took place
169 m_searchResults.append(t: result);
170 }
171 for (const QHelpSearchResult &result : contentResults) {
172 const auto size = urls.size();
173 urls.insert(value: result.url());
174 if (size != urls.size()) // insertion took place
175 m_searchResults.append(t: result);
176 }
177 }
178 }
179 QSqlDatabase::removeDatabase(connectionName: uniqueId);
180}
181
182static bool attributesMatchFilter(const QStringList &attributes, const QStringList &filter)
183{
184 for (const QString &attribute : filter) {
185 if (!attributes.contains(str: attribute, cs: Qt::CaseInsensitive))
186 return false;
187 }
188 return true;
189}
190
191QHelpSearchIndexReader::~QHelpSearchIndexReader()
192{
193 cancelSearching();
194 wait();
195}
196
197void QHelpSearchIndexReader::cancelSearching()
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
218int QHelpSearchIndexReader::searchResultCount() const
219{
220 QMutexLocker lock(&m_mutex);
221 return m_searchResults.size();
222}
223
224QList<QHelpSearchResult> QHelpSearchIndexReader::searchResults(int start, int end) const
225{
226 QMutexLocker lock(&m_mutex);
227 return m_searchResults.mid(pos: start, len: end - start);
228}
229
230void QHelpSearchIndexReader::run()
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(filterName: engine.filterEngine()->activeFilter()));
257 } else {
258 const QStringList &registeredDocs = engine.registeredDocumentations();
259 const QStringList &currentFilter = engine.filterAttributes(filterName: 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, filter: 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
293

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qttools/src/assistant/help/qhelpsearchindexreader.cpp