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