1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Assistant of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qhelpenginecore.h"
41#include "qhelpfilterengine.h"
42#include "qhelpsearchindexreader_default_p.h"
43
44#include <QtCore/QSet>
45#include <QtSql/QSqlDatabase>
46#include <QtSql/QSqlQuery>
47
48QT_BEGIN_NAMESPACE
49
50namespace fulltextsearch {
51namespace qt {
52
53void Reader::setIndexPath(const QString &path)
54{
55 m_indexPath = path;
56 m_namespaceAttributes.clear();
57 m_filterEngineNamespaceList.clear();
58 m_useFilterEngine = false;
59}
60
61void Reader::addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes)
62{
63 m_namespaceAttributes.insert(akey: namespaceName, avalue: attributes);
64}
65
66void Reader::setFilterEngineNamespaceList(const QStringList &namespaceList)
67{
68 m_useFilterEngine = true;
69 m_filterEngineNamespaceList = namespaceList;
70}
71
72static QString namespacePlaceholders(const QMultiMap<QString, QStringList> &namespaces)
73{
74 QString placeholders;
75 const auto &namespaceList = namespaces.uniqueKeys();
76 bool firstNS = true;
77 for (const QString &ns : namespaceList) {
78 if (firstNS)
79 firstNS = false;
80 else
81 placeholders += QLatin1String(" OR ");
82 placeholders += QLatin1String("(namespace = ?");
83
84 const QList<QStringList> &attributeSets = namespaces.values(akey: ns);
85 bool firstAS = true;
86 for (const QStringList &attributeSet : attributeSets) {
87 if (!attributeSet.isEmpty()) {
88 if (firstAS) {
89 firstAS = false;
90 placeholders += QLatin1String(" AND (");
91 } else {
92 placeholders += QLatin1String(" OR ");
93 }
94 placeholders += QLatin1String("attributes = ?");
95 }
96 }
97 if (!firstAS)
98 placeholders += QLatin1Char(')'); // close "AND ("
99 placeholders += QLatin1Char(')');
100 }
101 return placeholders;
102}
103
104static void bindNamespacesAndAttributes(QSqlQuery *query, const QMultiMap<QString, QStringList> &namespaces)
105{
106 const auto &namespaceList = namespaces.uniqueKeys();
107 for (const QString &ns : namespaceList) {
108 query->addBindValue(val: ns);
109
110 const QList<QStringList> &attributeSets = namespaces.values(akey: ns);
111 for (const QStringList &attributeSet : attributeSets) {
112 if (!attributeSet.isEmpty())
113 query->addBindValue(val: attributeSet.join(sep: QLatin1Char('|')));
114 }
115 }
116}
117
118static QString namespacePlaceholders(const QStringList &namespaceList)
119{
120 QString placeholders;
121 bool firstNS = true;
122 for (int i = namespaceList.count(); i; --i) {
123 if (firstNS)
124 firstNS = false;
125 else
126 placeholders += QLatin1String(" OR ");
127 placeholders += QLatin1String("namespace = ?");
128 }
129 return placeholders;
130}
131
132static void bindNamespacesAndAttributes(QSqlQuery *query, const QStringList &namespaceList)
133{
134 for (const QString &ns : namespaceList)
135 query->addBindValue(val: ns);
136}
137
138QVector<QHelpSearchResult> Reader::queryTable(const QSqlDatabase &db,
139 const QString &tableName,
140 const QString &searchInput) const
141{
142 const QString nsPlaceholders = m_useFilterEngine
143 ? namespacePlaceholders(namespaceList: m_filterEngineNamespaceList)
144 : namespacePlaceholders(namespaces: m_namespaceAttributes);
145 QSqlQuery query(db);
146 query.prepare(query: QLatin1String("SELECT url, title, snippet(") + tableName +
147 QLatin1String(", -1, '<b>', '</b>', '...', '10') FROM ") + tableName +
148 QLatin1String(" WHERE (") + nsPlaceholders +
149 QLatin1String(") AND ") + tableName +
150 QLatin1String(" MATCH ? ORDER BY rank"));
151 m_useFilterEngine
152 ? bindNamespacesAndAttributes(query: &query, namespaceList: m_filterEngineNamespaceList)
153 : bindNamespacesAndAttributes(query: &query, namespaces: m_namespaceAttributes);
154 query.addBindValue(val: searchInput);
155 query.exec();
156
157 QVector<QHelpSearchResult> results;
158
159 while (query.next()) {
160 const QString &url = query.value(name: QLatin1String("url")).toString();
161 const QString &title = query.value(name: QLatin1String("title")).toString();
162 const QString &snippet = query.value(i: 2).toString();
163 results.append(t: QHelpSearchResult(url, title, snippet));
164 }
165
166 return results;
167}
168
169void Reader::searchInDB(const QString &searchInput)
170{
171 const QString &uniqueId = QHelpGlobal::uniquifyConnectionName(name: QLatin1String("QHelpReader"), pointer: this);
172 {
173 QSqlDatabase db = QSqlDatabase::addDatabase(type: QLatin1String("QSQLITE"), connectionName: uniqueId);
174 db.setConnectOptions(QLatin1String("QSQLITE_OPEN_READONLY"));
175 db.setDatabaseName(m_indexPath + QLatin1String("/fts"));
176
177 if (db.open()) {
178 const QVector<QHelpSearchResult> titleResults = queryTable(db,
179 tableName: QLatin1String("titles"), searchInput);
180 const QVector<QHelpSearchResult> contentResults = queryTable(db,
181 tableName: QLatin1String("contents"), searchInput);
182
183 // merge results form title and contents searches
184 m_searchResults = QVector<QHelpSearchResult>();
185
186 QSet<QUrl> urls;
187
188 for (const QHelpSearchResult &result : titleResults) {
189 const QUrl &url = result.url();
190 if (!urls.contains(value: url)) {
191 urls.insert(value: url);
192 m_searchResults.append(t: result);
193 }
194 }
195
196 for (const QHelpSearchResult &result : contentResults) {
197 const QUrl &url = result.url();
198 if (!urls.contains(value: url)) {
199 urls.insert(value: url);
200 m_searchResults.append(t: result);
201 }
202 }
203 }
204 }
205 QSqlDatabase::removeDatabase(connectionName: uniqueId);
206}
207
208QVector<QHelpSearchResult> Reader::searchResults() const
209{
210 return m_searchResults;
211}
212
213static bool attributesMatchFilter(const QStringList &attributes,
214 const QStringList &filter)
215{
216 for (const QString &attribute : filter) {
217 if (!attributes.contains(str: attribute, cs: Qt::CaseInsensitive))
218 return false;
219 }
220
221 return true;
222}
223
224void QHelpSearchIndexReaderDefault::run()
225{
226 QMutexLocker lock(&m_mutex);
227
228 if (m_cancel)
229 return;
230
231 const QString searchInput = m_searchInput;
232 const QString collectionFile = m_collectionFile;
233 const QString indexPath = m_indexFilesFolder;
234 const bool usesFilterEngine = m_usesFilterEngine;
235
236 lock.unlock();
237
238 QHelpEngineCore engine(collectionFile, nullptr);
239 if (!engine.setupData())
240 return;
241
242 emit searchingStarted();
243
244 // setup the reader
245 m_reader.setIndexPath(indexPath);
246
247 if (usesFilterEngine) {
248 m_reader.setFilterEngineNamespaceList(
249 engine.filterEngine()->namespacesForFilter(
250 filterName: engine.filterEngine()->activeFilter()));
251 } else {
252 const QStringList &registeredDocs = engine.registeredDocumentations();
253 const QStringList &currentFilter = engine.filterAttributes(filterName: engine.currentFilter());
254
255 for (const QString &namespaceName : registeredDocs) {
256 const QList<QStringList> &attributeSets =
257 engine.filterAttributeSets(namespaceName);
258
259 for (const QStringList &attributes : attributeSets) {
260 if (attributesMatchFilter(attributes, filter: currentFilter)) {
261 m_reader.addNamespaceAttributes(namespaceName, attributes);
262 }
263 }
264 }
265 }
266
267 lock.relock();
268 if (m_cancel) {
269 emit searchingFinished(searchResultCount: 0); // TODO: check this, speed issue while locking???
270 return;
271 }
272 lock.unlock();
273
274 m_searchResults.clear();
275 m_reader.searchInDB(searchInput); // TODO: should this be interruptible as well ???
276
277 lock.relock();
278 m_searchResults = m_reader.searchResults();
279 lock.unlock();
280
281 emit searchingFinished(searchResultCount: m_searchResults.count());
282}
283
284} // namespace std
285} // namespace fulltextsearch
286
287QT_END_NAMESPACE
288

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