diff options
Diffstat (limited to 'src/assistant/help/qhelpsearchindexreader.cpp')
-rw-r--r-- | src/assistant/help/qhelpsearchindexreader.cpp | 288 |
1 files changed, 245 insertions, 43 deletions
diff --git a/src/assistant/help/qhelpsearchindexreader.cpp b/src/assistant/help/qhelpsearchindexreader.cpp index e2172deda..5fd583335 100644 --- a/src/assistant/help/qhelpsearchindexreader.cpp +++ b/src/assistant/help/qhelpsearchindexreader.cpp @@ -1,48 +1,193 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Assistant of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qhelpsearchindexreader_p.h" +#include "qhelpenginecore.h" +#include "qhelpfilterengine.h" + +#include <QtCore/qmap.h> +#include <QtCore/qset.h> +#include <QtSql/qsqldatabase.h> +#include <QtSql/qsqlquery.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace fulltextsearch { +class Reader +{ +public: + void setIndexPath(const QString &path) + { + m_indexPath = path; + m_namespaceAttributes.clear(); + m_filterEngineNamespaceList.clear(); + m_useFilterEngine = false; + } + void addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes) + { + m_namespaceAttributes.insert(namespaceName, attributes); + } + void setFilterEngineNamespaceList(const QStringList &namespaceList) + { + m_useFilterEngine = true; + m_filterEngineNamespaceList = namespaceList; + } + + void searchInDB(const QString &term); + QList<QHelpSearchResult> searchResults() const { return m_searchResults; } + +private: + QList<QHelpSearchResult> queryTable(const QSqlDatabase &db, const QString &tableName, + const QString &searchInput) const; + + QMultiMap<QString, QStringList> m_namespaceAttributes; + QStringList m_filterEngineNamespaceList; + QList<QHelpSearchResult> m_searchResults; + QString m_indexPath; + bool m_useFilterEngine = false; +}; + +static QString namespacePlaceholders(const QMultiMap<QString, QStringList> &namespaces) +{ + QString placeholders; + const auto &namespaceList = namespaces.uniqueKeys(); + bool firstNS = true; + for (const QString &ns : namespaceList) { + if (firstNS) + firstNS = false; + else + placeholders += " OR "_L1; + placeholders += "(namespace = ?"_L1; + + const QList<QStringList> &attributeSets = namespaces.values(ns); + bool firstAS = true; + for (const QStringList &attributeSet : attributeSets) { + if (!attributeSet.isEmpty()) { + if (firstAS) { + firstAS = false; + placeholders += " AND ("_L1; + } else { + placeholders += " OR "_L1; + } + placeholders += "attributes = ?"_L1; + } + } + if (!firstAS) + placeholders += u')'; // close "AND (" + placeholders += u')'; + } + return placeholders; +} + +static void bindNamespacesAndAttributes(QSqlQuery *query, + const QMultiMap<QString, QStringList> &namespaces) +{ + const auto &namespaceList = namespaces.uniqueKeys(); + for (const QString &ns : namespaceList) { + query->addBindValue(ns); + + const QList<QStringList> &attributeSets = namespaces.values(ns); + for (const QStringList &attributeSet : attributeSets) { + if (!attributeSet.isEmpty()) + query->addBindValue(attributeSet.join(u'|')); + } + } +} + +static QString namespacePlaceholders(const QStringList &namespaceList) +{ + QString placeholders; + bool firstNS = true; + for (int i = namespaceList.size(); i; --i) { + if (firstNS) + firstNS = false; + else + placeholders += " OR "_L1; + placeholders += "namespace = ?"_L1; + } + return placeholders; +} + +static void bindNamespacesAndAttributes(QSqlQuery *query, const QStringList &namespaceList) +{ + for (const QString &ns : namespaceList) + query->addBindValue(ns); +} + +QList<QHelpSearchResult> Reader::queryTable(const QSqlDatabase &db, const QString &tableName, + const QString &searchInput) const +{ + const QString nsPlaceholders = m_useFilterEngine + ? namespacePlaceholders(m_filterEngineNamespaceList) + : namespacePlaceholders(m_namespaceAttributes); + QSqlQuery query(db); + query.prepare("SELECT url, title, snippet("_L1 + tableName + + ", -1, '<b>', '</b>', '...', '10') FROM "_L1 + tableName + + " WHERE ("_L1 + nsPlaceholders + + ") AND "_L1 + tableName + + " MATCH ? ORDER BY rank"_L1); + m_useFilterEngine + ? bindNamespacesAndAttributes(&query, m_filterEngineNamespaceList) + : bindNamespacesAndAttributes(&query, m_namespaceAttributes); + query.addBindValue(searchInput); + query.exec(); + + QList<QHelpSearchResult> results; + + while (query.next()) { + const QString &url = query.value("url"_L1).toString(); + const QString &title = query.value("title"_L1).toString(); + const QString &snippet = query.value(2).toString(); + results.append(QHelpSearchResult(url, title, snippet)); + } + return results; +} + +void Reader::searchInDB(const QString &searchInput) +{ + const QString &uniqueId = QHelpGlobal::uniquifyConnectionName("QHelpReader"_L1, this); + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, uniqueId); + db.setConnectOptions("QSQLITE_OPEN_READONLY"_L1); + db.setDatabaseName(m_indexPath + "/fts"_L1); + + if (db.open()) { + const QList<QHelpSearchResult> titleResults = queryTable(db, "titles"_L1, searchInput); + const QList<QHelpSearchResult> contentResults = + queryTable(db, "contents"_L1, searchInput); + + // merge results form title and contents searches + m_searchResults.clear(); + QSet<QUrl> urls; + for (const QHelpSearchResult &result : titleResults) { + const auto size = urls.size(); + urls.insert(result.url()); + if (size != urls.size()) // insertion took place + m_searchResults.append(result); + } + for (const QHelpSearchResult &result : contentResults) { + const auto size = urls.size(); + urls.insert(result.url()); + if (size != urls.size()) // insertion took place + m_searchResults.append(result); + } + } + } + QSqlDatabase::removeDatabase(uniqueId); +} + +static bool attributesMatchFilter(const QStringList &attributes, const QStringList &filter) +{ + for (const QString &attribute : filter) { + if (!attributes.contains(attribute, Qt::CaseInsensitive)) + return false; + } + return true; +} + QHelpSearchIndexReader::~QHelpSearchIndexReader() { cancelSearching(); @@ -56,7 +201,7 @@ void QHelpSearchIndexReader::cancelSearching() } void QHelpSearchIndexReader::search(const QString &collectionFile, const QString &indexFilesFolder, - const QString &searchInput, bool usesFilterEngine) + const QString &searchInput, bool usesFilterEngine) { wait(); @@ -73,17 +218,74 @@ void QHelpSearchIndexReader::search(const QString &collectionFile, const QString int QHelpSearchIndexReader::searchResultCount() const { QMutexLocker lock(&m_mutex); - return m_searchResults.count(); + return m_searchResults.size(); } -QList<QHelpSearchResult> QHelpSearchIndexReader::searchResults(int start, - int end) const +QList<QHelpSearchResult> QHelpSearchIndexReader::searchResults(int start, int end) const { QMutexLocker lock(&m_mutex); return m_searchResults.mid(start, end - start); } +void QHelpSearchIndexReader::run() +{ + QMutexLocker lock(&m_mutex); + + if (m_cancel) + return; + + const QString searchInput = m_searchInput; + const QString collectionFile = m_collectionFile; + const QString indexPath = m_indexFilesFolder; + const bool usesFilterEngine = m_usesFilterEngine; + + lock.unlock(); + + QHelpEngineCore engine(collectionFile, nullptr); + if (!engine.setupData()) + return; + + emit searchingStarted(); + + // setup the reader + Reader reader; + reader.setIndexPath(indexPath); + + if (usesFilterEngine) { + reader.setFilterEngineNamespaceList( + engine.filterEngine()->namespacesForFilter(engine.filterEngine()->activeFilter())); + } else { + const QStringList ®isteredDocs = engine.registeredDocumentations(); + const QStringList ¤tFilter = engine.filterAttributes(engine.currentFilter()); + + for (const QString &namespaceName : registeredDocs) { + const QList<QStringList> &attributeSets = + engine.filterAttributeSets(namespaceName); + + for (const QStringList &attributes : attributeSets) { + if (attributesMatchFilter(attributes, currentFilter)) + reader.addNamespaceAttributes(namespaceName, attributes); + } + } + } + + lock.relock(); + if (m_cancel) { + emit searchingFinished(0); // TODO: check this, speed issue while locking??? + return; + } + lock.unlock(); + + m_searchResults.clear(); + reader.searchInDB(searchInput); // TODO: should this be interruptible as well ??? + + lock.relock(); + m_searchResults = reader.searchResults(); + lock.unlock(); + + emit searchingFinished(m_searchResults.size()); +} -} // namespace fulltextsearch +} // namespace fulltextsearch QT_END_NAMESPACE |