forked from qt-creator/qt-creator
Help index filter optimizations
Move retrieval away from prepare search, and do some caching. Also split the keyword search into individual chunks per help database that are executed on the UI thread individually. Change-Id: I0b8fe4abfc3cba46620985752d3d90638e10512f Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
This commit is contained in:
@@ -111,7 +111,7 @@ HelpManager::~HelpManager()
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject *HelpManager::instance()
|
HelpManager *HelpManager::instance()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_instance);
|
Q_ASSERT(m_instance);
|
||||||
return m_instance;
|
return m_instance;
|
||||||
@@ -248,51 +248,6 @@ QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &id)
|
|||||||
return d->m_helpEngine->linksForIdentifier(id);
|
return d->m_helpEngine->linksForIdentifier(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should go into Qt 4.8 once we start using it for Qt Creator
|
|
||||||
QStringList HelpManager::findKeywords(const QString &key, Qt::CaseSensitivity caseSensitivity,
|
|
||||||
int maxHits)
|
|
||||||
{
|
|
||||||
QTC_ASSERT(!d->m_needsSetup, return QStringList());
|
|
||||||
|
|
||||||
const QLatin1String sqlite("QSQLITE");
|
|
||||||
const QLatin1String name("HelpManager::findKeywords");
|
|
||||||
|
|
||||||
QSet<QString> keywords;
|
|
||||||
QSet<QString> keywordsToSort;
|
|
||||||
|
|
||||||
DbCleaner cleaner(name);
|
|
||||||
QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name);
|
|
||||||
if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) {
|
|
||||||
QHelpEngineCore core(collectionFilePath());
|
|
||||||
core.setAutoSaveFilter(false);
|
|
||||||
core.setCurrentFilter(tr("Unfiltered"));
|
|
||||||
core.setupData();
|
|
||||||
const QStringList ®isteredDocs = core.registeredDocumentations();
|
|
||||||
foreach (const QString &nameSpace, registeredDocs) {
|
|
||||||
db.setDatabaseName(core.documentationFileName(nameSpace));
|
|
||||||
if (db.open()) {
|
|
||||||
QSqlQuery query = QSqlQuery(db);
|
|
||||||
query.setForwardOnly(true);
|
|
||||||
query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE "
|
|
||||||
"'%%1%' LIMIT %2").arg(key, QString::number(maxHits)));
|
|
||||||
while (query.next()) {
|
|
||||||
const QString &keyValue = query.value(0).toString();
|
|
||||||
if (!keyValue.isEmpty()) {
|
|
||||||
if (keyValue.startsWith(key, caseSensitivity))
|
|
||||||
keywordsToSort.insert(keyValue);
|
|
||||||
else
|
|
||||||
keywords.insert(keyValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList keywordsSorted = keywordsToSort.toList();
|
|
||||||
Utils::sort(keywordsSorted);
|
|
||||||
return keywordsSorted + keywords.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl HelpManager::findFile(const QUrl &url)
|
QUrl HelpManager::findFile(const QUrl &url)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(!d->m_needsSetup, return QUrl());
|
QTC_ASSERT(!d->m_needsSetup, return QUrl());
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
|
|
||||||
typedef QHash<QString, QStringList> Filters;
|
typedef QHash<QString, QStringList> Filters;
|
||||||
|
|
||||||
static QObject *instance();
|
static HelpManager *instance();
|
||||||
static QString collectionFilePath();
|
static QString collectionFilePath();
|
||||||
|
|
||||||
static void registerDocumentation(const QStringList &fileNames);
|
static void registerDocumentation(const QStringList &fileNames);
|
||||||
@@ -74,9 +74,6 @@ public:
|
|||||||
|
|
||||||
static QMap<QString, QUrl> linksForKeyword(const QString &key);
|
static QMap<QString, QUrl> linksForKeyword(const QString &key);
|
||||||
static QMap<QString, QUrl> linksForIdentifier(const QString &id);
|
static QMap<QString, QUrl> linksForIdentifier(const QString &id);
|
||||||
static QStringList findKeywords(const QString &key,
|
|
||||||
Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive,
|
|
||||||
int maxHits = INT_MAX);
|
|
||||||
|
|
||||||
static QUrl findFile(const QUrl &url);
|
static QUrl findFile(const QUrl &url);
|
||||||
static QByteArray fileData(const QUrl &url);
|
static QByteArray fileData(const QUrl &url);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
QT += help network printsupport
|
QT += help network printsupport sql
|
||||||
!isEmpty(QT.webkitwidgets.name): QT += webkitwidgets webkit
|
!isEmpty(QT.webkitwidgets.name): QT += webkitwidgets webkit
|
||||||
else: DEFINES += QT_NO_WEBKIT
|
else: DEFINES += QT_NO_WEBKIT
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,14 @@
|
|||||||
#include <extensionsystem/pluginmanager.h>
|
#include <extensionsystem/pluginmanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/helpmanager.h>
|
#include <coreplugin/helpmanager.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlDriver>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace Help;
|
using namespace Help;
|
||||||
@@ -47,6 +53,7 @@ using namespace Help::Internal;
|
|||||||
Q_DECLARE_METATYPE(ILocatorFilter*)
|
Q_DECLARE_METATYPE(ILocatorFilter*)
|
||||||
|
|
||||||
HelpIndexFilter::HelpIndexFilter()
|
HelpIndexFilter::HelpIndexFilter()
|
||||||
|
: m_needsUpdate(true)
|
||||||
{
|
{
|
||||||
setId("HelpIndexFilter");
|
setId("HelpIndexFilter");
|
||||||
setDisplayName(tr("Help Index"));
|
setDisplayName(tr("Help Index"));
|
||||||
@@ -54,6 +61,12 @@ HelpIndexFilter::HelpIndexFilter()
|
|||||||
setShortcutString(QString(QLatin1Char('?')));
|
setShortcutString(QString(QLatin1Char('?')));
|
||||||
|
|
||||||
m_icon = QIcon(QLatin1String(":/help/images/bookmark.png"));
|
m_icon = QIcon(QLatin1String(":/help/images/bookmark.png"));
|
||||||
|
connect(HelpManager::instance(), &HelpManager::setupFinished,
|
||||||
|
this, &HelpIndexFilter::invalidateCache);
|
||||||
|
connect(HelpManager::instance(), &HelpManager::documentationChanged,
|
||||||
|
this, &HelpIndexFilter::invalidateCache);
|
||||||
|
connect(HelpManager::instance(), &HelpManager::collectionFileChanged,
|
||||||
|
this, &HelpIndexFilter::invalidateCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpIndexFilter::~HelpIndexFilter()
|
HelpIndexFilter::~HelpIndexFilter()
|
||||||
@@ -62,21 +75,62 @@ HelpIndexFilter::~HelpIndexFilter()
|
|||||||
|
|
||||||
void HelpIndexFilter::prepareSearch(const QString &entry)
|
void HelpIndexFilter::prepareSearch(const QString &entry)
|
||||||
{
|
{
|
||||||
if (entry.length() < 2)
|
Q_UNUSED(entry)
|
||||||
m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry), 200);
|
QStringList namespaces = HelpManager::registeredNamespaces();
|
||||||
else
|
m_helpDatabases = Utils::transform(namespaces, [](const QString &ns) {
|
||||||
m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry));
|
return HelpManager::fileFromNamespace(ns);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
|
QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
|
||||||
{
|
{
|
||||||
Q_UNUSED(entry) // search is already done in the GUI thread in prepareSearch
|
m_mutex.lock(); // guard m_needsUpdate
|
||||||
|
bool forceUpdate = m_needsUpdate;
|
||||||
|
m_mutex.unlock();
|
||||||
|
|
||||||
|
if (forceUpdate || m_searchTermCache.size() < 2 || m_searchTermCache.isEmpty()
|
||||||
|
|| !entry.contains(m_searchTermCache)) {
|
||||||
|
int limit = entry.size() < 2 ? 200 : INT_MAX;
|
||||||
|
QSet<QString> results;
|
||||||
|
foreach (const QString &filePath, m_helpDatabases) {
|
||||||
|
QSet<QString> result;
|
||||||
|
QMetaObject::invokeMethod(this, "searchMatches", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QSet<QString>, result),
|
||||||
|
Q_ARG(QString, filePath),
|
||||||
|
Q_ARG(QString, entry),
|
||||||
|
Q_ARG(int, limit));
|
||||||
|
results.unite(result);
|
||||||
|
}
|
||||||
|
m_mutex.lock(); // guard m_needsUpdate
|
||||||
|
m_needsUpdate = false;
|
||||||
|
m_mutex.unlock();
|
||||||
|
m_keywordCache = results;
|
||||||
|
m_searchTermCache = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::CaseSensitivity cs = caseSensitivity(entry);
|
||||||
QList<LocatorFilterEntry> entries;
|
QList<LocatorFilterEntry> entries;
|
||||||
foreach (const QString &keyword, m_keywords) {
|
QStringList keywords;
|
||||||
|
QStringList unsortedKeywords;
|
||||||
|
keywords.reserve(m_keywordCache.size());
|
||||||
|
unsortedKeywords.reserve(m_keywordCache.size());
|
||||||
|
QSet<QString> allresults;
|
||||||
|
foreach (const QString &keyword, m_keywordCache) {
|
||||||
if (future.isCanceled())
|
if (future.isCanceled())
|
||||||
break;
|
break;
|
||||||
entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon));
|
if (keyword.startsWith(entry, cs)) {
|
||||||
|
keywords.append(keyword);
|
||||||
|
allresults.insert(keyword);
|
||||||
|
} else if (keyword.contains(entry, cs)) {
|
||||||
|
unsortedKeywords.append(keyword);
|
||||||
|
allresults.insert(keyword);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Utils::sort(keywords);
|
||||||
|
keywords << unsortedKeywords;
|
||||||
|
m_keywordCache = allresults;
|
||||||
|
foreach (const QString &keyword, keywords)
|
||||||
|
entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon));
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
@@ -95,5 +149,42 @@ void HelpIndexFilter::accept(LocatorFilterEntry selection) const
|
|||||||
void HelpIndexFilter::refresh(QFutureInterface<void> &future)
|
void HelpIndexFilter::refresh(QFutureInterface<void> &future)
|
||||||
{
|
{
|
||||||
Q_UNUSED(future)
|
Q_UNUSED(future)
|
||||||
// Nothing to refresh
|
invalidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> HelpIndexFilter::searchMatches(const QString &databaseFilePath,
|
||||||
|
const QString &term, int limit)
|
||||||
|
{
|
||||||
|
static const QLatin1String sqlite("QSQLITE");
|
||||||
|
static const QLatin1String name("HelpManager::findKeywords");
|
||||||
|
|
||||||
|
QSet<QString> keywords;
|
||||||
|
|
||||||
|
{ // make sure db is destroyed before removeDatabase call
|
||||||
|
QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name);
|
||||||
|
if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) {
|
||||||
|
db.setDatabaseName(databaseFilePath);
|
||||||
|
if (db.open()) {
|
||||||
|
QSqlQuery query = QSqlQuery(db);
|
||||||
|
query.setForwardOnly(true);
|
||||||
|
query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE "
|
||||||
|
"'%%1%' LIMIT %2").arg(term, QString::number(limit)));
|
||||||
|
while (query.next()) {
|
||||||
|
const QString &keyValue = query.value(0).toString();
|
||||||
|
if (!keyValue.isEmpty())
|
||||||
|
keywords.insert(keyValue);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QSqlDatabase::removeDatabase(name);
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelpIndexFilter::invalidateCache()
|
||||||
|
{
|
||||||
|
m_mutex.lock();
|
||||||
|
m_needsUpdate = true;
|
||||||
|
m_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <coreplugin/locator/ilocatorfilter.h>
|
#include <coreplugin/locator/ilocatorfilter.h>
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
namespace Help {
|
namespace Help {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
@@ -48,17 +49,26 @@ public:
|
|||||||
|
|
||||||
// ILocatorFilter
|
// ILocatorFilter
|
||||||
void prepareSearch(const QString &entry);
|
void prepareSearch(const QString &entry);
|
||||||
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry);
|
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
|
||||||
|
const QString &entry);
|
||||||
void accept(Core::LocatorFilterEntry selection) const;
|
void accept(Core::LocatorFilterEntry selection) const;
|
||||||
void refresh(QFutureInterface<void> &future);
|
void refresh(QFutureInterface<void> &future);
|
||||||
|
|
||||||
|
Q_INVOKABLE QSet<QString> searchMatches(const QString &databaseFilePath,
|
||||||
|
const QString &term, int limit);
|
||||||
signals:
|
signals:
|
||||||
void linkActivated(const QUrl &link) const;
|
void linkActivated(const QUrl &link) const;
|
||||||
void linksActivated(const QMap<QString, QUrl> &links, const QString &key) const;
|
void linksActivated(const QMap<QString, QUrl> &links, const QString &key) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void invalidateCache();
|
||||||
|
|
||||||
|
QStringList m_helpDatabases;
|
||||||
|
QSet<QString> m_keywordCache;
|
||||||
|
QString m_searchTermCache;
|
||||||
|
bool m_needsUpdate;
|
||||||
|
QMutex m_mutex;
|
||||||
QIcon m_icon;
|
QIcon m_icon;
|
||||||
QStringList m_keywords;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|||||||
Reference in New Issue
Block a user