diff options
author | Jarek Kobus <jaroslaw.kobus@qt.io> | 2017-08-25 17:02:29 +0200 |
---|---|---|
committer | Jarek Kobus <jaroslaw.kobus@qt.io> | 2018-04-23 14:14:34 +0000 |
commit | 829f9dec6bff57e8b3f1afdc982778a9d573174f (patch) | |
tree | 908d7930a11e24b621074ace5b3509a63a61d4f6 | |
parent | 58bf8789242b0775d491c8969abae8f42f2bc32d (diff) | |
download | qttools-829f9dec6bff57e8b3f1afdc982778a9d573174f.tar.gz |
Store the copy of the index inside the help collection file
This avoids having to open all .qch files at startup, which is slow
and can lead to contention on open file handles.
This change is backward compatible. The old help lib
won't use the new tables. The new help lib will
generate the new tables on first run or regenerate them
when it detects they are not anymore in sync.
Store also the timestamp info about the qch files and compare
it upon every startup. If it doesn't match then new tables' data
is regenerated. The case when running new Creator,
then running old Creator and reconfiguring doc set and then
running new Creator again should work properly.
Task-number: QTCREATORBUG-18242
Change-Id: I7f656935ae2383b866b27e256eec23dab588efc5
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
-rw-r--r-- | src/assistant/assistant/helpenginewrapper.cpp | 2 | ||||
-rw-r--r-- | src/assistant/help/qhelpcollectionhandler.cpp | 1144 | ||||
-rw-r--r-- | src/assistant/help/qhelpcollectionhandler_p.h | 70 | ||||
-rw-r--r-- | src/assistant/help/qhelpcontentwidget.cpp | 83 | ||||
-rw-r--r-- | src/assistant/help/qhelpcontentwidget.h | 5 | ||||
-rw-r--r-- | src/assistant/help/qhelpdbreader.cpp | 575 | ||||
-rw-r--r-- | src/assistant/help/qhelpdbreader_p.h | 63 | ||||
-rw-r--r-- | src/assistant/help/qhelpengine.cpp | 18 | ||||
-rw-r--r-- | src/assistant/help/qhelpengine_p.h | 12 | ||||
-rw-r--r-- | src/assistant/help/qhelpenginecore.cpp | 200 | ||||
-rw-r--r-- | src/assistant/help/qhelpgenerator.cpp | 2 | ||||
-rw-r--r-- | src/assistant/help/qhelpindexwidget.cpp | 85 | ||||
-rw-r--r-- | src/assistant/help/qhelpsearchindexwriter_default.cpp | 74 | ||||
-rw-r--r-- | src/assistant/qcollectiongenerator/main.cpp | 7 | ||||
-rw-r--r-- | tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp | 4 | ||||
-rw-r--r-- | tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp | 6 |
16 files changed, 1615 insertions, 735 deletions
diff --git a/src/assistant/assistant/helpenginewrapper.cpp b/src/assistant/assistant/helpenginewrapper.cpp index 20e6ff7ff..73f3ace32 100644 --- a/src/assistant/assistant/helpenginewrapper.cpp +++ b/src/assistant/assistant/helpenginewrapper.cpp @@ -146,6 +146,7 @@ HelpEngineWrapper::HelpEngineWrapper(const QString &collectionFile) * This call is reverted by initialDocSetupDone(), which must be * called after the new docs have been installed. */ +// TODO: probably remove it disconnect(d->m_helpEngine, &QHelpEngineCore::setupFinished, searchEngine(), &QHelpSearchEngine::scheduleIndexDocumentation); @@ -175,6 +176,7 @@ HelpEngineWrapper::~HelpEngineWrapper() void HelpEngineWrapper::initialDocSetupDone() { TRACE_OBJ +// TODO: probably remove it connect(d->m_helpEngine, &QHelpEngineCore::setupFinished, searchEngine(), &QHelpSearchEngine::scheduleIndexDocumentation); setupData(); diff --git a/src/assistant/help/qhelpcollectionhandler.cpp b/src/assistant/help/qhelpcollectionhandler.cpp index bf0eb9e91..ff2ac48e7 100644 --- a/src/assistant/help/qhelpcollectionhandler.cpp +++ b/src/assistant/help/qhelpcollectionhandler.cpp @@ -41,16 +41,49 @@ #include "qhelp_global.h" #include "qhelpdbreader_p.h" -#include <QtCore/QFile> +#include <QtCore/QDateTime> #include <QtCore/QDir> +#include <QtCore/QFile> #include <QtCore/QFileInfo> -#include <QtCore/QDebug> +#include <QtCore/QTimer> +#include <QtCore/QVector> #include <QtSql/QSqlError> #include <QtSql/QSqlDriver> QT_BEGIN_NAMESPACE +class Transaction +{ +public: + Transaction(const QString &connectionName) + : m_db(QSqlDatabase::database(connectionName)), + m_inTransaction(m_db.driver()->hasFeature(QSqlDriver::Transactions)) + { + if (m_inTransaction) + m_inTransaction = m_db.transaction(); + } + + ~Transaction() + { + if (m_inTransaction) + m_db.rollback(); + } + + void commit() + { + if (!m_inTransaction) + return; + + m_db.commit(); + m_inTransaction = false; + } + +private: + QSqlDatabase m_db; + bool m_inTransaction; +}; + QHelpCollectionHandler::QHelpCollectionHandler(const QString &collectionFile, QObject *parent) : QObject(parent) , m_collectionFile(collectionFile) @@ -65,7 +98,7 @@ QHelpCollectionHandler::~QHelpCollectionHandler() closeDB(); } -bool QHelpCollectionHandler::isDBOpened() +bool QHelpCollectionHandler::isDBOpened() const { if (m_query) return true; @@ -123,7 +156,9 @@ bool QHelpCollectionHandler::openCollectionFile() m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' " "AND Name=\'NamespaceTable\'")); m_query->next(); - if (m_query->value(0).toInt() < 1) { + + const bool tablesExist = m_query->value(0).toInt() > 0; + if (!tablesExist) { if (!createTables(m_query)) { closeDB(); emit error(tr("Cannot create tables in file %1.").arg(collectionFile())); @@ -131,9 +166,149 @@ bool QHelpCollectionHandler::openCollectionFile() } } + bool indexAndNamespaceFilterTablesMissing = false; + + m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' " + "AND (Name=\'IndexTable\' " + "OR Name=\'FileNameTable\' " + "OR Name=\'ContentsTable\' " + "OR Name=\'FileFilterTable\' " + "OR Name=\'IndexFilterTable\' " + "OR Name=\'ContentsFilterTable\' " + "OR Name=\'FileAttributeSetTable\' " + "OR Name=\'OptimizedFilterTable\' " + "OR Name=\'TimeStampTable\')")); + m_query->next(); + if (m_query->value(0).toInt() != 9) { + if (!createIndexAndNamespaceFilterTables(m_query)) { + emit error(tr("Cannot create index tables in file %1.").arg(collectionFile())); + return false; + } + + // Old tables exist, index tables didn't, recreate index tables only in this case + indexAndNamespaceFilterTablesMissing = tablesExist; + } + + const FileInfoList &docList = registeredDocumentations(); + if (indexAndNamespaceFilterTablesMissing) { + for (const QHelpCollectionHandler::FileInfo &info : docList) { + if (!registerIndexAndNamespaceFilterTables(info.namespaceName)) + return false; + } + return true; + } + + QList<TimeStamp> timeStamps; + m_query->exec(QLatin1String("SELECT NamespaceId, FolderId, FilePath, Size, TimeStamp " + "FROM TimeStampTable")); + while (m_query->next()) { + TimeStamp timeStamp; + timeStamp.namespaceId = m_query->value(0).toInt(); + timeStamp.folderId = m_query->value(1).toInt(); + timeStamp.fileName = m_query->value(2).toString(); + timeStamp.size = m_query->value(3).toInt(); + timeStamp.timeStamp = m_query->value(4).toString(); + timeStamps.append(timeStamp); + } + + QVector<TimeStamp> toRemove; + for (const TimeStamp &timeStamp : timeStamps) { + if (!isTimeStampCorrect(timeStamp)) + toRemove.append(timeStamp); + } + + // TODO: we may optimize when toRemove.size() == timeStamps.size(). + // In this case we remove all records from tables. + Transaction transaction(m_connectionName); + for (const TimeStamp &timeStamp : toRemove) { + if (!unregisterIndexTable(timeStamp.namespaceId, timeStamp.folderId)) + return false; + } + transaction.commit(); + + for (const QHelpCollectionHandler::FileInfo &info : docList) { + if (!hasTimeStampInfo(info.namespaceName) + && !registerIndexAndNamespaceFilterTables(info.namespaceName)) { + return false; + } + } + + return true; +} + +QString QHelpCollectionHandler::absoluteDocPath(const QString &fileName) const +{ + const QFileInfo fi(collectionFile()); + return QDir::isAbsolutePath(fileName) + ? fileName + : QFileInfo(fi.absolutePath() + QLatin1Char('/') + fileName) + .absoluteFilePath(); +} + +bool QHelpCollectionHandler::isTimeStampCorrect(const TimeStamp &timeStamp) const +{ + const QFileInfo fi(absoluteDocPath(timeStamp.fileName)); + + if (!fi.exists()) + return false; + + if (fi.size() != timeStamp.size) + return false; + + if (fi.lastModified().toString(Qt::ISODate) != timeStamp.timeStamp) + return false; + + m_query->prepare(QLatin1String("SELECT FilePath " + "FROM NamespaceTable " + "WHERE Id = ?")); + m_query->bindValue(0, timeStamp.namespaceId); + if (!m_query->exec() || !m_query->next()) + return false; + + const QString oldFileName = m_query->value(0).toString(); + if (oldFileName != timeStamp.fileName) + return false; + return true; } +bool QHelpCollectionHandler::hasTimeStampInfo(const QString &nameSpace) const +{ + m_query->prepare(QLatin1String("SELECT " + "TimeStampTable.NamespaceId " + "FROM " + "NamespaceTable, " + "TimeStampTable " + "WHERE NamespaceTable.Id = TimeStampTable.NamespaceId " + "AND NamespaceTable.Name = ? LIMIT 1")); + m_query->bindValue(0, nameSpace); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return false; + + return true; +} + +void QHelpCollectionHandler::scheduleVacuum() +{ + if (m_vacuumScheduled) + return; + + m_vacuumScheduled = true; + QTimer::singleShot(0, this, &QHelpCollectionHandler::execVacuum); +} + +void QHelpCollectionHandler::execVacuum() +{ + if (!m_query) + return; + + m_query->exec(QLatin1String("VACUUM")); + m_vacuumScheduled = false; +} + bool QHelpCollectionHandler::copyCollectionFile(const QString &fileName) { if (!m_query) @@ -172,7 +347,7 @@ bool QHelpCollectionHandler::copyCollectionFile(const QString &fileName) copyQuery->exec(QLatin1String("PRAGMA synchronous=OFF")); copyQuery->exec(QLatin1String("PRAGMA cache_size=3000")); - if (!createTables(copyQuery)) { + if (!createTables(copyQuery) || !createIndexAndNamespaceFilterTables(copyQuery)) { emit error(tr("Cannot copy collection file: %1").arg(colFile)); return false; } @@ -185,7 +360,7 @@ bool QHelpCollectionHandler::copyCollectionFile(const QString &fileName) copyQuery->bindValue(0, m_query->value(0).toString()); QString oldFilePath = m_query->value(1).toString(); if (!QDir::isAbsolutePath(oldFilePath)) - oldFilePath = oldBaseDir + QDir::separator() + oldFilePath; + oldFilePath = oldBaseDir + QLatin1Char('/') + oldFilePath; copyQuery->bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath)); copyQuery->exec(); } @@ -267,6 +442,55 @@ bool QHelpCollectionHandler::createTables(QSqlQuery *query) return true; } +bool QHelpCollectionHandler::createIndexAndNamespaceFilterTables(QSqlQuery *query) +{ + const QStringList tables = QStringList() + << QLatin1String("CREATE TABLE FileNameTable (" + "FolderId INTEGER, " + "Name TEXT, " + "FileId INTEGER PRIMARY KEY, " + "Title TEXT)") + << QLatin1String("CREATE TABLE IndexTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT, " + "Identifier TEXT, " + "NamespaceId INTEGER, " + "FileId INTEGER, " + "Anchor TEXT)") + << QLatin1String("CREATE TABLE ContentsTable (" + "Id INTEGER PRIMARY KEY, " + "NamespaceId INTEGER, " + "Data BLOB)") + << QLatin1String("CREATE TABLE FileFilterTable (" + "FilterAttributeId INTEGER, " + "FileId INTEGER)") + << QLatin1String("CREATE TABLE IndexFilterTable (" + "FilterAttributeId INTEGER, " + "IndexId INTEGER)") + << QLatin1String("CREATE TABLE ContentsFilterTable (" + "FilterAttributeId INTEGER, " + "ContentsId INTEGER )") + << QLatin1String("CREATE TABLE FileAttributeSetTable (" + "NamespaceId INTEGER, " + "FilterAttributeSetId INTEGER, " + "FilterAttributeId INTEGER)") + << QLatin1String("CREATE TABLE OptimizedFilterTable (" + "NamespaceId INTEGER, " + "FilterAttributeId INTEGER)") + << QLatin1String("CREATE TABLE TimeStampTable (" + "NamespaceId INTEGER, " + "FolderId INTEGER, " + "FilePath TEXT, " + "Size INTEGER, " + "TimeStamp TEXT)"); + + for (const QString &q : tables) { + if (!query->exec(q)) + return false; + } + return true; +} + QStringList QHelpCollectionHandler::customFilters() const { QStringList list; @@ -323,10 +547,11 @@ bool QHelpCollectionHandler::addCustomFilter(const QString &filterName, QStringList idsToInsert = attributes; QMap<QString, int> attributeMap; while (m_query->next()) { - attributeMap.insert(m_query->value(1).toString(), - m_query->value(0).toInt()); - if (idsToInsert.contains(m_query->value(1).toString())) - idsToInsert.removeAll(m_query->value(1).toString()); + // all old attributes + const QString attributeName = m_query->value(1).toString(); + attributeMap.insert(attributeName, m_query->value(0).toInt()); + if (idsToInsert.contains(attributeName)) + idsToInsert.removeAll(attributeName); } for (const QString &id : qAsConst(idsToInsert)) { @@ -362,33 +587,57 @@ bool QHelpCollectionHandler::addCustomFilter(const QString &filterName, return true; } -QHelpCollectionHandler::DocInfoList QHelpCollectionHandler::registeredDocumentations( +QHelpCollectionHandler::FileInfo QHelpCollectionHandler::registeredDocumentation( const QString &namespaceName) const { - DocInfoList list; + FileInfo fileInfo; if (!m_query) - return list; + return fileInfo; + + m_query->prepare(QLatin1String("SELECT " + "NamespaceTable.Name, " + "NamespaceTable.FilePath, " + "FolderTable.Name " + "FROM " + "NamespaceTable, " + "FolderTable " + "WHERE NamespaceTable.Id = FolderTable.NamespaceId " + "AND NamespaceTable.Name = ? LIMIT 1")); + m_query->bindValue(0, namespaceName); + if (!m_query->exec() || !m_query->next()) + return fileInfo; - static const QLatin1String baseQuery("SELECT a.Name, a.FilePath, b.Name " - "FROM NamespaceTable a, FolderTable b " - "WHERE a.Id=b.NamespaceId"); + fileInfo.namespaceName = m_query->value(0).toString(); + fileInfo.fileName = m_query->value(1).toString(); + fileInfo.folderName = m_query->value(2).toString(); - if (namespaceName.isEmpty()) { - m_query->prepare(baseQuery); - } else { - m_query->prepare(baseQuery + QLatin1String(" AND a.Name=? LIMIT 1")); - m_query->bindValue(0, namespaceName); - } + m_query->clear(); - m_query->exec(); + return fileInfo; +} + +QHelpCollectionHandler::FileInfoList QHelpCollectionHandler::registeredDocumentations() const +{ + FileInfoList list; + if (!m_query) + return list; + + m_query->exec(QLatin1String("SELECT " + "NamespaceTable.Name, " + "NamespaceTable.FilePath, " + "FolderTable.Name " + "FROM " + "NamespaceTable, " + "FolderTable " + "WHERE NamespaceTable.Id = FolderTable.NamespaceId")); while (m_query->next()) { - DocInfo info; - info.namespaceName = m_query->value(0).toString(); - info.fileName = m_query->value(1).toString(); - info.folderName = m_query->value(2).toString(); - list.append(info); + FileInfo fileInfo; + fileInfo.namespaceName = m_query->value(0).toString(); + fileInfo.fileName = m_query->value(1).toString(); + fileInfo.folderName = m_query->value(2).toString(); + list.append(fileInfo); } return list; @@ -416,14 +665,16 @@ bool QHelpCollectionHandler::registerDocumentation(const QString &fileName) if (nsId < 1) return false; - if (!registerVirtualFolder(reader.virtualFolder(), nsId)) + const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId); + if (vfId < 1) return false; - addFilterAttributes(reader.filterAttributes()); + registerFilterAttributes(reader.filterAttributeSets(), nsId); // qset, what happens when removing documentation? for (const QString &filterName : reader.customFilters()) addCustomFilter(filterName, reader.filterAttributes(filterName)); - optimizeDatabase(fileName); + if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName)) + return false; return true; } @@ -449,14 +700,352 @@ bool QHelpCollectionHandler::unregisterDocumentation(const QString &namespaceNam if (!m_query->exec()) return false; + m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + m_query->exec(); + + if (!m_query->next()) { + emit error(tr("The namespace %1 was not registered.").arg(namespaceName)); + return false; + } + + const int vfId = m_query->value(0).toInt(); + + m_query->prepare(QLatin1String("DELETE FROM NamespaceTable WHERE Id = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + m_query->prepare(QLatin1String("DELETE FROM FolderTable WHERE NamespaceId = ?")); m_query->bindValue(0, nsId); if (!m_query->exec()) return false; + if (!unregisterIndexTable(nsId, vfId)) + return false; + + scheduleVacuum(); + return true; } +static QHelpCollectionHandler::FileInfo extractFileInfo(const QUrl &url) +{ + QHelpCollectionHandler::FileInfo fileInfo; + + if (!url.isValid() || url.toString().count(QLatin1Char('/')) < 4 + || url.scheme() != QLatin1String("qthelp")) { + return fileInfo; + } + + fileInfo.namespaceName = url.authority(); + fileInfo.fileName = url.path(); + if (fileInfo.fileName.startsWith(QLatin1Char('/'))) + fileInfo.fileName = fileInfo.fileName.mid(1); + fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(QLatin1Char('/'), 1)); + fileInfo.fileName.remove(0, fileInfo.folderName.length() + 1); + + return fileInfo; +} + +bool QHelpCollectionHandler::fileExists(const QUrl &url) const +{ + if (!isDBOpened()) + return false; + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return false; + + m_query->prepare(QLatin1String("SELECT COUNT (DISTINCT NamespaceTable.Id)" + "FROM" + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id")); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + if (!m_query->exec() || !m_query->next()) + return false; + + return m_query->value(0).toInt(); +} + +static QString prepareFilterQuery(int attributesCount, + const QString &idTableName, + const QString &idColumnName, + const QString &filterTableName, + const QString &filterColumnName) +{ + if (!attributesCount) + return QString(); + + QString filterQuery = QString::fromLatin1(" AND (%1.%2 IN (").arg(idTableName, idColumnName); + + const QString filterQueryTemplate = QString::fromLatin1( + "SELECT %1.%2 " + "FROM %1, FilterAttributeTable " + "WHERE %1.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = ?") + .arg(filterTableName, filterColumnName); + + for (int i = 0; i < attributesCount; ++i) { + if (i > 0) + filterQuery.append(QLatin1String(" INTERSECT ")); + filterQuery.append(filterQueryTemplate); + } + + filterQuery.append(QLatin1String(") OR NamespaceTable.Id IN (")); + + const QString optimizedFilterQueryTemplate = QLatin1String( + "SELECT OptimizedFilterTable.NamespaceId " + "FROM OptimizedFilterTable, FilterAttributeTable " + "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = ?"); + + for (int i = 0; i < attributesCount; ++i) { + if (i > 0) + filterQuery.append(QLatin1String(" INTERSECT ")); + filterQuery.append(optimizedFilterQueryTemplate); + } + + filterQuery.append(QLatin1String("))")); + + return filterQuery; +} + +void bindFilterQuery(QSqlQuery *query, int startingBindPos, const QStringList &filterAttributes) +{ + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < filterAttributes.count(); j++) { + query->bindValue(i * filterAttributes.count() + j + startingBindPos, + filterAttributes.at(j)); + } + } +} + +QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, + const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return QString(); + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return QString(); + + const QString filterlessQuery = QLatin1String( + "SELECT DISTINCT " + "NamespaceTable.Name " + "FROM " + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.count(), + QLatin1String("FileNameTable"), + QLatin1String("FileId"), + QLatin1String("FileFilterTable"), + QLatin1String("FileId")); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + bindFilterQuery(m_query, 2, filterAttributes); + + if (!m_query->exec()) + return QString(); + + QVector<QString> namespaceList; + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + + if (namespaceList.isEmpty()) + return QString(); + + return namespaceList.contains(fileInfo.namespaceName) + ? fileInfo.namespaceName + : namespaceList.first(); // TODO: version match heuristics +} + +QStringList QHelpCollectionHandler::files(const QString &namespaceName, + const QStringList &filterAttributes, + const QString &extensionFilter) const +{ + if (!isDBOpened()) + return QStringList(); + + const QString extensionQuery = extensionFilter.isEmpty() + ? QString() : QLatin1String(" AND FileNameTable.Name LIKE ?"); + const QString filterlessQuery = QLatin1String( + "SELECT " + "FolderTable.Name, " + "FileNameTable.Name " + "FROM " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ?") + extensionQuery; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.count(), + QLatin1String("FileNameTable"), + QLatin1String("FileId"), + QLatin1String("FileFilterTable"), + QLatin1String("FileId")); + + m_query->prepare(filterQuery); + m_query->bindValue(0, namespaceName); + int bindCount = 1; + if (!extensionFilter.isEmpty()) { + m_query->bindValue(bindCount, QString::fromLatin1("%.%1").arg(extensionFilter)); + ++bindCount; + } + bindFilterQuery(m_query, bindCount, filterAttributes); + + if (!m_query->exec()) + return QStringList(); + + QStringList fileNames; + while (m_query->next()) { + fileNames.append(m_query->value(0).toString() + + QLatin1Char('/') + + m_query->value(1).toString()); + } + + return fileNames; +} + +QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return QUrl(); + + const QString namespaceName = namespaceForFile(url, filterAttributes); + if (namespaceName.isEmpty()) + return QUrl(); + + QUrl result = url; + result.setAuthority(namespaceName); + return result; +} + +QByteArray QHelpCollectionHandler::fileData(const QUrl &url) const +{ + if (!isDBOpened()) + return QByteArray(); + + const QString namespaceName = namespaceForFile(url); + if (namespaceName.isEmpty()) + return QByteArray(); + + const FileInfo fileInfo = extractFileInfo(url); + + const FileInfo docInfo = registeredDocumentation(namespaceName); + const QString absFileName = absoluteDocPath(docInfo.fileName); + + QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( + docInfo.fileName, const_cast<QHelpCollectionHandler *>(this)), nullptr); + if (!reader.init()) + return QByteArray(); + + return reader.fileData(fileInfo.folderName, fileInfo.fileName); +} + +QStringList QHelpCollectionHandler::indicesForFilter(const QStringList &filterAttributes) const +{ + QStringList indices; + + if (!isDBOpened()) + return indices; + + const QString filterlessQuery = QString::fromLatin1( + "SELECT DISTINCT " + "IndexTable.Name " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.count(), + QLatin1String("IndexTable"), + QLatin1String("Id"), + QLatin1String("IndexFilterTable"), + QLatin1String("IndexId")) + + QLatin1String(" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"); + // this doesn't work: ASC COLLATE NOCASE + + m_query->prepare(filterQuery); + bindFilterQuery(m_query, 0, filterAttributes); + + m_query->exec(); + + while (m_query->next()) + indices.append(m_query->value(0).toString()); + + return indices; +} + +QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForFilter( + const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return QList<ContentsData>(); + + const QString filterlessQuery = QString::fromLatin1( + "SELECT DISTINCT " + "NamespaceTable.Name, " + "FolderTable.Name, " + "ContentsTable.Data " + "FROM " + "FolderTable, " + "NamespaceTable, " + "ContentsTable " + "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Id = FolderTable.NamespaceId " + "AND ContentsTable.NamespaceId = NamespaceTable.Id"); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.count(), + QLatin1String("ContentsTable"), + QLatin1String("Id"), + QLatin1String("ContentsFilterTable"), + QLatin1String("ContentsId")); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query, 0, filterAttributes); + + m_query->exec(); + + QMap<QString, ContentsData> contentsMap; + + while (m_query->next()) { + const QString namespaceName = m_query->value(0).toString(); + // get existing or insert a new one otherwise + ContentsData &contentsData = contentsMap[namespaceName]; + contentsData.namespaceName = namespaceName; + contentsData.folderName = m_query->value(1).toString(); + contentsData.contentsList.append(m_query->value(2).toByteArray()); + } + + return contentsMap.values(); +} + bool QHelpCollectionHandler::removeCustomValue(const QString &key) { if (!isDBOpened()) @@ -513,7 +1102,8 @@ bool QHelpCollectionHandler::setCustomValue(const QString &key, return m_query->exec(); } -bool QHelpCollectionHandler::addFilterAttributes(const QStringList &attributes) +bool QHelpCollectionHandler::registerFilterAttributes(const QList<QStringList> &attributeSets, + int nsId) { if (!isDBOpened()) return false; @@ -523,14 +1113,62 @@ bool QHelpCollectionHandler::addFilterAttributes(const QStringList &attributes) while (m_query->next()) atts.insert(m_query->value(0).toString()); - for (const QString &s : attributes) { - if (!atts.contains(s)) { - m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); - m_query->bindValue(0, s); - m_query->exec(); + for (const QStringList &attributeSet : attributeSets) { + for (const QString &attribute : attributeSet) { + if (!atts.contains(attribute)) { + m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); + m_query->bindValue(0, attribute); + m_query->exec(); + } } } - return true; + return registerFileAttributeSets(attributeSets, nsId); +} + +bool QHelpCollectionHandler::registerFileAttributeSets(const QList<QStringList> &attributeSets, + int nsId) +{ + if (!isDBOpened()) + return false; + + if (attributeSets.isEmpty()) + return true; + + QVariantList nsIds; + QVariantList attributeSetIds; + QVariantList filterAttributeIds; + + if (!m_query->exec(QLatin1String("SELECT MAX(FilterAttributeSetId) FROM FileAttributeSetTable")) + || !m_query->next()) { + return false; + } + + int attributeSetId = m_query->value(0).toInt(); + + for (const QStringList &attributeSet : attributeSets) { + ++attributeSetId; + + for (const QString &attribute : attributeSet) { + + m_query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable WHERE Name=?")); + m_query->bindValue(0, attribute); + + if (!m_query->exec() || !m_query->next()) + return false; + + nsIds.append(nsId); + attributeSetIds.append(attributeSetId); + filterAttributeIds.append(m_query->value(0).toInt()); + } + } + + m_query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable " + "(NamespaceId, FilterAttributeSetId, FilterAttributeId) " + "VALUES(?, ?, ?)")); + m_query->addBindValue(nsIds); + m_query->addBindValue(attributeSetIds); + m_query->addBindValue(filterAttributeIds); + return m_query->execBatch(); } QStringList QHelpCollectionHandler::filterAttributes() const @@ -559,6 +1197,42 @@ QStringList QHelpCollectionHandler::filterAttributes(const QString &filterName) return list; } +QList<QStringList> QHelpCollectionHandler::filterAttributeSets(const QString &namespaceName) const +{ + QList<QStringList> result; + if (!isDBOpened()) + return result; + + m_query->prepare(QLatin1String( + "SELECT " + "FileAttributeSetTable.FilterAttributeSetId, " + "FilterAttributeTable.Name " + "FROM " + "FileAttributeSetTable, " + "FilterAttributeTable, " + "NamespaceTable " + "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ? " + "ORDER BY FileAttributeSetTable.FilterAttributeSetId")); + m_query->bindValue(0, namespaceName); + m_query->exec(); + int oldId = -1; + while (m_query->next()) { + const int id = m_query->value(0).toInt(); + if (id != oldId) { + result.append(QStringList()); + oldId = id; + } + result.last().append(m_query->value(1).toString()); + } + + if (result.isEmpty()) + result.append(QStringList()); + + return result; +} + int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QString &fileName) { const int errorValue = -1; @@ -589,7 +1263,7 @@ int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QStri return namespaceId; } -bool QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId) +int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId) { if (!m_query) return false; @@ -597,33 +1271,389 @@ bool QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, in m_query->prepare(QLatin1String("INSERT INTO FolderTable VALUES(NULL, ?, ?)")); m_query->bindValue(0, namespaceId); m_query->bindValue(1, folderName); - return m_query->exec(); + + int virtualId = -1; + if (m_query->exec()) + virtualId = m_query->lastInsertId().toInt(); + if (virtualId < 1) { + emit error(tr("Cannot register virtual folder '%1'.").arg(folderName)); + return -1; + } + return virtualId; } -void QHelpCollectionHandler::optimizeDatabase(const QString &fileName) +bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(const QString &nameSpace) { - if (!QFile::exists(fileName)) - return; + if (!isDBOpened()) + return false; - { // according to removeDatabase() documentation - QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("optimize")); - db.setDatabaseName(fileName); - if (!db.open()) { - QSqlDatabase::removeDatabase(QLatin1String("optimize")); - emit error(tr("Cannot open database \"%1\" to optimize.").arg(fileName)); - return; + m_query->prepare(QLatin1String("SELECT Id, FilePath FROM NamespaceTable WHERE Name=?")); + m_query->bindValue(0, nameSpace); + m_query->exec(); + if (!m_query->next()) + return false; + + const int nsId = m_query->value(0).toInt(); + const QString fileName = m_query->value(1).toString(); + + m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE NamespaceId=?")); + m_query->bindValue(0, nsId); + m_query->exec(); + if (!m_query->next()) + return false; + + const int vfId = m_query->value(0).toInt(); + + const QString absFileName = absoluteDocPath(fileName); + QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( + fileName, this), this); + if (!reader.init()) + return false; + + if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId)) + return false; + + if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName)) + return false; + + return true; +} + +bool QHelpCollectionHandler::registerIndexTable(const QHelpDBReader::IndexTable &indexTable, + int nsId, int vfId, const QString &fileName) +{ + Transaction transaction(m_connectionName); + + QMap<QString, QVariantList> filterAttributeToNewFileId; + + QVariantList fileFolderIds; + QVariantList fileNames; + QVariantList fileTitles; + const int fileSize = indexTable.fileItems.size(); + fileFolderIds.reserve(fileSize); + fileNames.reserve(fileSize); + fileTitles.reserve(fileSize); + + if (!m_query->exec(QLatin1String("SELECT MAX(FileId) FROM FileNameTable")) || !m_query->next()) + return false; + + const int maxFileId = m_query->value(0).toInt(); + + int newFileId = 0; + for (const QHelpDBReader::FileItem &item : indexTable.fileItems) { + fileFolderIds.append(vfId); + fileNames.append(item.name); + fileTitles.append(item.title); + + for (const QString &filterAttribute : item.filterAttributes) + filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1); + ++newFileId; + } + + m_query->prepare(QLatin1String("INSERT INTO FileNameTable VALUES(?, ?, NULL, ?)")); + m_query->addBindValue(fileFolderIds); + m_query->addBindValue(fileNames); + m_query->addBindValue(fileTitles); + if (!m_query->execBatch()) + return false; + + for (auto it = filterAttributeToNewFileId.cbegin(), + end = filterAttributeToNewFileId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare(QLatin1String("SELECT Id From FilterAttributeTable WHERE Name = ?")); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().count(); i++) + attributeIds.append(attributeId); + + m_query->prepare(QLatin1String("INSERT INTO FileFilterTable VALUES(?, ?)")); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QMap<QString, QVariantList> filterAttributeToNewIndexId; + + if (!m_query->exec(QLatin1String("SELECT MAX(Id) FROM IndexTable")) || !m_query->next()) + return false; + + const int maxIndexId = m_query->value(0).toInt(); + int newIndexId = 0; + + QVariantList indexNames; + QVariantList indexIdentifiers; + QVariantList indexNamespaceIds; + QVariantList indexFileIds; + QVariantList indexAnchors; + const int indexSize = indexTable.indexItems.size(); + indexNames.reserve(indexSize); + indexIdentifiers.reserve(indexSize); + indexNamespaceIds.reserve(indexSize); + indexFileIds.reserve(indexSize); + indexAnchors.reserve(indexSize); + + for (const QHelpDBReader::IndexItem &item : indexTable.indexItems) { + indexNames.append(item.name); + indexIdentifiers.append(item.identifier); + indexNamespaceIds.append(nsId); + indexFileIds.append(maxFileId + item.fileId + 1); + indexAnchors.append(item.anchor); + + for (const QString &filterAttribute : item.filterAttributes) + filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1); + ++newIndexId; + } + + m_query->prepare(QLatin1String("INSERT INTO IndexTable VALUES(NULL, ?, ?, ?, ?, ?)")); + m_query->addBindValue(indexNames); + m_query->addBindValue(indexIdentifiers); + m_query->addBindValue(indexNamespaceIds); + m_query->addBindValue(indexFileIds); + m_query->addBindValue(indexAnchors); + if (!m_query->execBatch()) + return false; + + for (auto it = filterAttributeToNewIndexId.cbegin(), + end = filterAttributeToNewIndexId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare(QLatin1String("SELECT Id From FilterAttributeTable WHERE Name = ?")); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().count(); i++) + attributeIds.append(attributeId); + + m_query->prepare(QLatin1String("INSERT INTO IndexFilterTable VALUES(?, ?)")); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QMap<QString, QVariantList> filterAttributeToNewContentsId; + + QVariantList contentsNsIds; + QVariantList contentsData; + const int contentsSize = indexTable.contentsItems.size(); + contentsNsIds.reserve(contentsSize); + contentsData.reserve(contentsSize); + + if (!m_query->exec(QLatin1String("SELECT MAX(Id) FROM ContentsTable")) || !m_query->next()) + return false; + + const int maxContentsId = m_query->value(0).toInt(); + + int newContentsId = 0; + for (const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) { + contentsNsIds.append(nsId); + contentsData.append(item.data); + + for (const QString &filterAttribute : item.filterAttributes) { + filterAttributeToNewContentsId[filterAttribute] + .append(maxContentsId + newContentsId + 1); } + ++newContentsId; + } - db.exec(QLatin1String("PRAGMA synchronous=OFF")); - db.exec(QLatin1String("PRAGMA cache_size=3000")); - db.exec(QLatin1String("CREATE INDEX IF NOT EXISTS NameIndex ON IndexTable(Name)")); - db.exec(QLatin1String("CREATE INDEX IF NOT EXISTS FileNameIndex ON FileNameTable(Name)")); - db.exec(QLatin1String("CREATE INDEX IF NOT EXISTS FileIdIndex ON FileNameTable(FileId)")); + m_query->prepare(QLatin1String("INSERT INTO ContentsTable VALUES(NULL, ?, ?)")); + m_query->addBindValue(contentsNsIds); + m_query->addBindValue(contentsData); + if (!m_query->execBatch()) + return false; - db.close(); + for (auto it = filterAttributeToNewContentsId.cbegin(), + end = filterAttributeToNewContentsId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare(QLatin1String("SELECT Id From FilterAttributeTable WHERE Name = ?")); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().count(); i++) + attributeIds.append(attributeId); + + m_query->prepare(QLatin1String("INSERT INTO ContentsFilterTable VALUES(?, ?)")); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QVariantList filterNsIds; + QVariantList filterAttributeIds; + for (const QString &filterAttribute : indexTable.usedFilterAttributes) { + filterNsIds.append(nsId); + + m_query->prepare(QLatin1String("SELECT Id From FilterAttributeTable WHERE Name = ?")); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + filterAttributeIds.append(m_query->value(0).toInt()); } - QSqlDatabase::removeDatabase(QLatin1String("optimize")); + m_query->prepare(QLatin1String("INSERT INTO OptimizedFilterTable " + "(NamespaceId, FilterAttributeId) VALUES(?, ?)")); + m_query->addBindValue(filterNsIds); + m_query->addBindValue(filterAttributeIds); + if (!m_query->execBatch()) + return false; + + m_query->prepare(QLatin1String("INSERT INTO TimeStampTable " + "(NamespaceId, FolderId, FilePath, Size, TimeStamp) " + "VALUES(?, ?, ?, ?, ?)")); + m_query->addBindValue(nsId); + m_query->addBindValue(vfId); + m_query->addBindValue(fileName); + const QFileInfo fi(absoluteDocPath(fileName)); + m_query->addBindValue(fi.size()); + m_query->addBindValue(fi.lastModified().toString(Qt::ISODate)); + if (!m_query->exec()) + return false; + + transaction.commit(); + return true; +} + +bool QHelpCollectionHandler::unregisterIndexTable(int nsId, int vfId) +{ + m_query->prepare(QLatin1String("DELETE FROM IndexFilterTable WHERE IndexId IN " + "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM IndexTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM FileFilterTable WHERE FileId IN " + "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)")); + m_query->bindValue(0, vfId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM FileNameTable WHERE FolderId = ?")); + m_query->bindValue(0, vfId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM ContentsFilterTable WHERE ContentsId IN " + "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM ContentsTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare(QLatin1String("DELETE FROM TimeStampTable WHERE NamespaceId = ?")); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + return true; +} + +static QUrl buildQUrl(const QString &ns, const QString &folder, + const QString &relFileName, const QString &anchor) +{ + QUrl url; + url.setScheme(QLatin1String("qthelp")); + url.setAuthority(ns); + url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName); + url.setFragment(anchor); + return url; +} + +QMap<QString, QUrl> QHelpCollectionHandler::linksForIdentifier(const QString &id, + const QStringList &filterAttributes) const +{ + return linksForField(QLatin1String("Identifier"), id, filterAttributes); +} + +QMap<QString, QUrl> QHelpCollectionHandler::linksForKeyword(const QString &keyword, + const QStringList &filterAttributes) const +{ + return linksForField(QLatin1String("Name"), keyword, filterAttributes); +} + +QMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldName, + const QString &fieldValue, + const QStringList &filterAttributes) const +{ + QMap<QString, QUrl> linkMap; + + if (!isDBOpened()) + return linkMap; + + const QString filterlessQuery = QString::fromLatin1( + "SELECT " + "FileNameTable.Title, " + "NamespaceTable.Name, " + "FolderTable.Name, " + "FileNameTable.Name, " + "IndexTable.Anchor " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id " + "AND IndexTable.%1 = ?").arg(fieldName); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.count(), + QLatin1String("IndexTable"), + QLatin1String("Id"), + QLatin1String("IndexFilterTable"), + QLatin1String("IndexId")); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fieldValue); + bindFilterQuery(m_query, 1, filterAttributes); + + m_query->exec(); + + while (m_query->next()) { + QString title = m_query->value(0).toString(); + if (title.isEmpty()) // generate a title + corresponding path + title = fieldValue + QLatin1String(" : ") + m_query->value(3).toString(); + + linkMap.insertMulti(title, buildQUrl(m_query->value(1).toString(), + m_query->value(2).toString(), + m_query->value(3).toString(), + m_query->value(4).toString())); + } + return linkMap; } QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpcollectionhandler_p.h b/src/assistant/help/qhelpcollectionhandler_p.h index d804b3f04..d349a49f2 100644 --- a/src/assistant/help/qhelpcollectionhandler_p.h +++ b/src/assistant/help/qhelpcollectionhandler_p.h @@ -59,6 +59,8 @@ #include <QtSql/QSqlQuery> +#include "qhelpdbreader_p.h" + QT_BEGIN_NAMESPACE class QHelpCollectionHandler : public QObject @@ -66,13 +68,29 @@ class QHelpCollectionHandler : public QObject Q_OBJECT public: - struct DocInfo + struct FileInfo { QString fileName; QString folderName; QString namespaceName; }; - typedef QList<DocInfo> DocInfoList; + typedef QList<FileInfo> FileInfoList; + + struct TimeStamp + { + int namespaceId = -1; + int folderId = -1; + QString fileName; + int size = 0; + QString timeStamp; + }; + + struct ContentsData + { + QString namespaceName; + QString folderName; + QList<QByteArray> contentsList; + }; explicit QHelpCollectionHandler(const QString &collectionFile, QObject *parent = 0); @@ -88,33 +106,67 @@ public: bool addCustomFilter(const QString &filterName, const QStringList &attributes); - DocInfoList registeredDocumentations(const QString &namespaceName = QString()) const; + FileInfo registeredDocumentation(const QString &namespaceName) const; + FileInfoList registeredDocumentations() const; bool registerDocumentation(const QString &fileName); bool unregisterDocumentation(const QString &namespaceName); + bool fileExists(const QUrl &url) const; + QStringList files(const QString &namespaceName, + const QStringList &filterAttributes = QStringList(), + const QString &extensionFilter = QString()) const; + QString namespaceForFile(const QUrl &url, + const QStringList &filterAttributes = QStringList()) const; + QUrl findFile(const QUrl &url, + const QStringList &filterAttributes = QStringList()) const; + QByteArray fileData(const QUrl &url) const; + + QStringList indicesForFilter(const QStringList &filterAttributes) const; + QList<ContentsData> contentsForFilter(const QStringList &filterAttributes) const; + bool removeCustomValue(const QString &key); QVariant customValue(const QString &key, const QVariant &defaultValue) const; bool setCustomValue(const QString &key, const QVariant &value); - bool addFilterAttributes(const QStringList &attributes); QStringList filterAttributes() const; QStringList filterAttributes(const QString &filterName) const; + QList<QStringList> filterAttributeSets(const QString &namespaceName) const; int registerNamespace(const QString &nspace, const QString &fileName); - bool registerVirtualFolder(const QString &folderName, int namespaceId); - void optimizeDatabase(const QString &fileName); + int registerVirtualFolder(const QString &folderName, int namespaceId); + + QMap<QString, QUrl> linksForIdentifier(const QString &id, + const QStringList &filterAttributes) const; + QMap<QString, QUrl> linksForKeyword(const QString &keyword, + const QStringList &filterAttributes) const; signals: - void error(const QString &msg); + void error(const QString &msg) const; private: - bool isDBOpened(); - void closeDB(); + QMap<QString, QUrl> linksForField(const QString &fieldName, + const QString &fieldValue, + const QStringList &filterAttributes) const; + bool isDBOpened() const; bool createTables(QSqlQuery *query); + void closeDB(); + bool createIndexAndNamespaceFilterTables(QSqlQuery *query); + bool registerIndexAndNamespaceFilterTables(const QString &nameSpace); + bool registerFilterAttributes(const QList<QStringList> &attributeSets, int nsId); + bool registerFileAttributeSets(const QList<QStringList> &attributeSets, int nsId); + bool registerIndexTable(const QHelpDBReader::IndexTable &indexTable, + int nsId, int vfId, const QString &fileName); + bool unregisterIndexTable(int nsId, int vfId); + QString absoluteDocPath(const QString &fileName) const; + bool isTimeStampCorrect(const TimeStamp &timeStamp) const; + bool hasTimeStampInfo(const QString &nameSpace) const; + void scheduleVacuum(); + void execVacuum(); QString m_collectionFile; QString m_connectionName; QSqlQuery *m_query = nullptr; + bool m_vacuumScheduled = false; }; QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpcontentwidget.cpp b/src/assistant/help/qhelpcontentwidget.cpp index ee7f974aa..101da8818 100644 --- a/src/assistant/help/qhelpcontentwidget.cpp +++ b/src/assistant/help/qhelpcontentwidget.cpp @@ -41,6 +41,7 @@ #include "qhelpenginecore.h" #include "qhelpengine_p.h" #include "qhelpdbreader_p.h" +#include "qhelpcollectionhandler_p.h" #include <QDir> #include <QtCore/QStack> @@ -53,12 +54,10 @@ QT_BEGIN_NAMESPACE class QHelpContentItemPrivate { public: - QHelpContentItemPrivate(const QString &t, const QString &l, - QHelpDBReader *r, QHelpContentItem *p) + QHelpContentItemPrivate(const QString &t, const QUrl &l, QHelpContentItem *p) : parent(p), title(t), - link(l), - helpDBReader(r) + link(l) { } @@ -67,8 +66,7 @@ public: QList<QHelpContentItem*> childItems; QHelpContentItem *parent; QString title; - QString link; - QHelpDBReader *helpDBReader; + QUrl link; }; class QHelpContentProvider : public QThread @@ -91,7 +89,7 @@ private: QStringList m_filterAttributes; QQueue<QHelpContentItem*> m_rootItems; QMutex m_mutex; - bool m_abort; + bool m_abort = false; }; class QHelpContentModelPrivate @@ -110,10 +108,9 @@ public: \since 4.4 */ -QHelpContentItem::QHelpContentItem(const QString &name, const QString &link, - QHelpDBReader *reader, QHelpContentItem *parent) +QHelpContentItem::QHelpContentItem(const QString &name, const QUrl &link, QHelpContentItem *parent) { - d = new QHelpContentItemPrivate(name, link, reader, parent); + d = new QHelpContentItemPrivate(name, link, parent); } /*! @@ -166,7 +163,7 @@ QString QHelpContentItem::title() const */ QUrl QHelpContentItem::url() const { - return d->helpDBReader->urlOfPath(d->link); + return d->link; } /*! @@ -191,7 +188,6 @@ QHelpContentProvider::QHelpContentProvider(QHelpEnginePrivate *helpEngine) : QThread(helpEngine) { m_helpEngine = helpEngine; - m_abort = false; } QHelpContentProvider::~QHelpContentProvider() @@ -237,6 +233,28 @@ QHelpContentItem *QHelpContentProvider::rootItem() return m_rootItems.dequeue(); } +// TODO: this is a copy from helpcollectionhandler, make it common +static QUrl buildQUrl(const QString &ns, const QString &folder, + const QString &relFileName, const QString &anchor) +{ + QUrl url; + url.setScheme(QLatin1String("qthelp")); + url.setAuthority(ns); + url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName); + url.setFragment(anchor); + return url; +} + +static QUrl constructUrl(const QString &namespaceName, + const QString &folderName, + const QString &relativePath) +{ + const int idx = relativePath.indexOf(QLatin1Char('#')); + const QString &rp = idx < 0 ? relativePath : relativePath.left(idx); + const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1); + return buildQUrl(namespaceName, folderName, rp, anchor); +} + void QHelpContentProvider::run() { QString title; @@ -246,11 +264,21 @@ void QHelpContentProvider::run() m_mutex.lock(); QHelpContentItem * const rootItem = new QHelpContentItem(QString(), QString(), 0); - QStringList atts = m_filterAttributes; - const QStringList fileNames = m_helpEngine->orderedFileNameList; + const QStringList attributes = m_filterAttributes; + const QString collectionFile = m_helpEngine->collectionHandler->collectionFile(); m_mutex.unlock(); - for (const QString &dbFileName : fileNames) { + if (collectionFile.isEmpty()) + return; + + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return; + + const QList<QHelpCollectionHandler::ContentsData> result + = collectionHandler.contentsForFilter(attributes); + + for (const auto &contentsData : result) { m_mutex.lock(); if (m_abort) { delete rootItem; @@ -259,32 +287,29 @@ void QHelpContentProvider::run() return; } m_mutex.unlock(); - QHelpDBReader reader(dbFileName, - QHelpGlobal::uniquifyConnectionName(dbFileName + - QLatin1String("FromQHelpContentProvider"), - QThread::currentThread()), 0); - if (!reader.init()) - continue; - for (const QByteArray &ba : reader.contentsForFilter(atts)) { - if (ba.size() < 1) + + const QString namespaceName = contentsData.namespaceName; + const QString folderName = contentsData.folderName; + for (const QByteArray &contents : contentsData.contentsList) { + if (contents.size() < 1) continue; int _depth = 0; bool _root = false; QStack<QHelpContentItem*> stack; - QDataStream s(ba); + QDataStream s(contents); for (;;) { s >> depth; s >> link; s >> title; if (title.isEmpty()) break; + const QUrl url = constructUrl(namespaceName, folderName, link); CHECK_DEPTH: if (depth == 0) { m_mutex.lock(); - item = new QHelpContentItem(title, link, - m_helpEngine->fileNameReaderMap.value(dbFileName), rootItem); + item = new QHelpContentItem(title, url, rootItem); rootItem->d->appendChild(item); m_mutex.unlock(); stack.push(item); @@ -296,8 +321,7 @@ CHECK_DEPTH: stack.push(item); } if (depth == _depth) { - item = new QHelpContentItem(title, link, - m_helpEngine->fileNameReaderMap.value(dbFileName), stack.top()); + item = new QHelpContentItem(title, url, stack.top()); stack.top()->d->appendChild(item); } else if (depth < _depth) { stack.pop(); @@ -308,6 +332,7 @@ CHECK_DEPTH: } } } + m_mutex.lock(); m_rootItems.enqueue(rootItem); m_abort = false; @@ -315,8 +340,6 @@ CHECK_DEPTH: emit finishedSuccessFully(); } - - /*! \class QHelpContentModel \inmodule QtHelp diff --git a/src/assistant/help/qhelpcontentwidget.h b/src/assistant/help/qhelpcontentwidget.h index 45d9a5f28..efe311164 100644 --- a/src/assistant/help/qhelpcontentwidget.h +++ b/src/assistant/help/qhelpcontentwidget.h @@ -50,7 +50,6 @@ QT_BEGIN_NAMESPACE class QHelpEnginePrivate; -class QHelpDBReader; class QHelpContentItemPrivate; class QHelpContentModelPrivate; class QHelpEngine; @@ -70,8 +69,8 @@ public: int childPosition(QHelpContentItem *child) const; private: - QHelpContentItem(const QString &name, const QString &link, - QHelpDBReader *reader, QHelpContentItem *parent = nullptr); + QHelpContentItem(const QString &name, const QUrl &link, + QHelpContentItem *parent = nullptr); QHelpContentItemPrivate *d; friend class QHelpContentProvider; diff --git a/src/assistant/help/qhelpdbreader.cpp b/src/assistant/help/qhelpdbreader.cpp index 0caf4ffd3..ae687ebed 100644 --- a/src/assistant/help/qhelpdbreader.cpp +++ b/src/assistant/help/qhelpdbreader.cpp @@ -41,6 +41,7 @@ #include "qhelp_global.h" #include <QtCore/QVariant> +#include <QtCore/QVector> #include <QtCore/QFile> #include <QtSql/QSqlError> #include <QtSql/QSqlQuery> @@ -105,16 +106,6 @@ bool QHelpDBReader::initDB() return true; } -QString QHelpDBReader::databaseName() const -{ - return m_dbName; -} - -QString QHelpDBReader::errorMessage() const -{ - return m_error; -} - QString QHelpDBReader::namespaceName() const { if (!m_namespace.isEmpty()) @@ -137,6 +128,202 @@ QString QHelpDBReader::virtualFolder() const return QString(); } +static bool isAttributeUsed(QSqlQuery *query, const QString &tableName, int attributeId) +{ + query->prepare(QString::fromLatin1("SELECT FilterAttributeId " + "FROM %1 " + "WHERE FilterAttributeId = ? " + "LIMIT 1").arg(tableName)); + query->bindValue(0, attributeId); + query->exec(); + return query->next(); // if we got a result it means it was used +} + +static int filterDataCount(QSqlQuery *query, const QString &tableName) +{ + query->exec(QString::fromLatin1("SELECT COUNT(*) FROM" + "(SELECT DISTINCT * FROM %1)").arg(tableName)); + query->next(); + return query->value(0).toInt(); +} + +QHelpDBReader::IndexTable QHelpDBReader::indexTable() const +{ + IndexTable table; + if (!m_query) + return table; + + QMap<int, QString> attributeIds; + m_query->exec(QLatin1String("SELECT DISTINCT Id, Name FROM FilterAttributeTable ORDER BY Id")); + while (m_query->next()) + attributeIds.insert(m_query->value(0).toInt(), m_query->value(1).toString()); + + // Maybe some are unused and specified erroneously in the named filter only, + // like it was in case of qtlocation.qch <= qt 5.9 + QVector<int> usedAttributeIds; + for (auto it = attributeIds.cbegin(), end = attributeIds.cend(); it != end; ++it) { + const int attributeId = it.key(); + if (isAttributeUsed(m_query, QLatin1String("IndexFilterTable"), attributeId) + || isAttributeUsed(m_query, QLatin1String("ContentsFilterTable"), attributeId) + || isAttributeUsed(m_query, QLatin1String("FileFilterTable"), attributeId)) { + usedAttributeIds.append(attributeId); + } + } + + bool legacy = false; + m_query->exec(QLatin1String("SELECT * FROM pragma_table_info('IndexTable') " + "WHERE name='ContextName'")); + if (m_query->next()) + legacy = true; + + const QString identifierColumnName = legacy + ? QLatin1String("ContextName") + : QLatin1String("Identifier"); + + const int usedAttributeCount = usedAttributeIds.count(); + + QMap<int, IndexItem> idToIndexItem; + + m_query->exec(QString::fromLatin1("SELECT Name, %1, FileId, Anchor, Id " + "FROM IndexTable " + "ORDER BY Id").arg(identifierColumnName)); + while (m_query->next()) { + IndexItem indexItem; + indexItem.name = m_query->value(0).toString(); + indexItem.identifier = m_query->value(1).toString(); + indexItem.fileId = m_query->value(2).toInt(); + indexItem.anchor = m_query->value(3).toString(); + const int indexId = m_query->value(4).toInt(); + + idToIndexItem.insert(indexId, indexItem); + } + + QMap<int, FileItem> idToFileItem; + QMap<int, int> originalFileIdToNewFileId; + + int filesCount = 0; + m_query->exec(QLatin1String("SELECT " + "FileNameTable.FileId, " + "FileNameTable.Name, " + "FileNameTable.Title " + "FROM FileNameTable, FolderTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "ORDER BY FileId")); + while (m_query->next()) { + const int fileId = m_query->value(0).toInt(); + FileItem fileItem; + fileItem.name = m_query->value(1).toString(); + fileItem.title = m_query->value(2).toString(); + + idToFileItem.insert(fileId, fileItem); + originalFileIdToNewFileId.insert(fileId, filesCount); + ++filesCount; + } + + QMap<int, ContentsItem> idToContentsItem; + + m_query->exec(QLatin1String("SELECT Data, Id " + "FROM ContentsTable " + "ORDER BY Id")); + while (m_query->next()) { + ContentsItem contentsItem; + contentsItem.data = m_query->value(0).toByteArray(); + const int contentsId = m_query->value(1).toInt(); + + idToContentsItem.insert(contentsId, contentsItem); + } + + bool optimized = true; + + if (usedAttributeCount) { + // May optimize only when all usedAttributes are attached to every + // index and file. It means the number of rows in the + // IndexTable multiplied by number of used attributes + // must equal the number of rows inside IndexFilterTable + // (yes, we have a combinatorial explosion of data in IndexFilterTable, + // which we want to optimize). The same with FileNameTable and + // FileFilterTable. + + const bool mayOptimizeIndexTable + = filterDataCount(m_query, QLatin1String("IndexFilterTable")) + == idToIndexItem.count() * usedAttributeCount; + const bool mayOptimizeFileTable + = filterDataCount(m_query, QLatin1String("FileFilterTable")) + == idToFileItem.count() * usedAttributeCount; + const bool mayOptimizeContentsTable + = filterDataCount(m_query, QLatin1String("ContentsFilterTable")) + == idToContentsItem.count() * usedAttributeCount; + optimized = mayOptimizeIndexTable && mayOptimizeFileTable && mayOptimizeContentsTable; + + if (!optimized) { + m_query->exec(QLatin1String( + "SELECT " + "IndexFilterTable.IndexId, " + "FilterAttributeTable.Name " + "FROM " + "IndexFilterTable, " + "FilterAttributeTable " + "WHERE " + "IndexFilterTable.FilterAttributeId = FilterAttributeTable.Id")); + while (m_query->next()) { + const int indexId = m_query->value(0).toInt(); + auto it = idToIndexItem.find(indexId); + if (it != idToIndexItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + + m_query->exec(QLatin1String( + "SELECT " + "FileFilterTable.FileId, " + "FilterAttributeTable.Name " + "FROM " + "FileFilterTable, " + "FilterAttributeTable " + "WHERE " + "FileFilterTable.FilterAttributeId = FilterAttributeTable.Id")); + while (m_query->next()) { + const int fileId = m_query->value(0).toInt(); + auto it = idToFileItem.find(fileId); + if (it != idToFileItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + + m_query->exec(QLatin1String( + "SELECT " + "ContentsFilterTable.ContentsId, " + "FilterAttributeTable.Name " + "FROM " + "ContentsFilterTable, " + "FilterAttributeTable " + "WHERE " + "ContentsFilterTable.FilterAttributeId = FilterAttributeTable.Id")); + while (m_query->next()) { + const int contentsId = m_query->value(0).toInt(); + auto it = idToContentsItem.find(contentsId); + if (it != idToContentsItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + } + } + + // reindex fileId references + for (auto it = idToIndexItem.cbegin(), end = idToIndexItem.cend(); it != end; ++it) { + IndexItem item = it.value(); + item.fileId = originalFileIdToNewFileId.value(item.fileId); + table.indexItems.append(item); + } + + table.fileItems = idToFileItem.values(); + table.contentsItems = idToContentsItem.values(); + + if (optimized) { + for (int attributeId : usedAttributeIds) + table.usedFilterAttributes.append(attributeIds.value(attributeId)); + } + + return table; +} + QList<QStringList> QHelpDBReader::filterAttributeSets() const { QList<QStringList> result; @@ -156,42 +343,6 @@ QList<QStringList> QHelpDBReader::filterAttributeSets() const return result; } -bool QHelpDBReader::fileExists(const QString &virtualFolder, - const QString &filePath, - const QStringList &filterAttributes) const -{ - if (virtualFolder.isEmpty() || filePath.isEmpty() || !m_query) - return false; - -//SELECT COUNT(a.Name) FROM FileNameTable a, FolderTable b, FileFilterTable c, FilterAttributeTable d WHERE a.FolderId=b.Id AND b.Name='qtdoc' AND a.Name='qstring.html' AND a.FileId=c.FileId AND c.FilterAttributeId=d.Id AND d.Name='qtrefdoc' - - QString query; - namespaceName(); - if (filterAttributes.isEmpty()) { - query = QString(QLatin1String("SELECT COUNT(a.Name) FROM FileNameTable a, FolderTable b " - "WHERE a.FolderId=b.Id AND b.Name=\'%1\' AND a.Name=\'%2\'")).arg(quote(virtualFolder)).arg(quote(filePath)); - } else { - query = QString(QLatin1String("SELECT COUNT(a.Name) FROM FileNameTable a, FolderTable b, " - "FileFilterTable c, FilterAttributeTable d WHERE a.FolderId=b.Id " - "AND b.Name=\'%1\' AND a.Name=\'%2\' AND a.FileId=c.FileId AND " - "c.FilterAttributeId=d.Id AND d.Name=\'%3\'")) - .arg(quote(virtualFolder)).arg(quote(filePath)) - .arg(quote(filterAttributes.first())); - for (int i = 1; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT COUNT(a.Name) FROM FileNameTable a, " - "FolderTable b, FileFilterTable c, FilterAttributeTable d WHERE a.FolderId=b.Id " - "AND b.Name=\'%1\' AND a.Name=\'%2\' AND a.FileId=c.FileId AND " - "c.FilterAttributeId=d.Id AND d.Name=\'%3\'")) - .arg(quote(virtualFolder)).arg(quote(filePath)) - .arg(quote(filterAttributes.at(i)))); - } - } - m_query->exec(query); - if (m_query->next() && m_query->isValid() && m_query->value(0).toInt()) - return true; - return false; -} - QByteArray QHelpDBReader::fileData(const QString &virtualFolder, const QString &filePath) const { @@ -243,252 +394,59 @@ QStringList QHelpDBReader::filterAttributes(const QString &filterName) const return lst; } -QStringList QHelpDBReader::indicesForFilter(const QStringList &filterAttributes) const -{ - QStringList indices; - if (!m_query) - return indices; - - //SELECT DISTINCT a.Name FROM IndexTable a, IndexFilterTable b, FilterAttributeTable c WHERE a.Id=b.IndexId AND b.FilterAttributeId=c.Id AND c.Name in ('4.2.3', 'qt') - - QString query; - if (filterAttributes.isEmpty()) { - query = QLatin1String("SELECT DISTINCT Name FROM IndexTable"); - } else { - query = QString(QLatin1String("SELECT DISTINCT a.Name FROM IndexTable a, " - "IndexFilterTable b, FilterAttributeTable c WHERE a.Id=b.IndexId " - "AND b.FilterAttributeId=c.Id AND c.Name='%1'")).arg(quote(filterAttributes.first())); - for (int i = 1; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT DISTINCT a.Name FROM IndexTable a, " - "IndexFilterTable b, FilterAttributeTable c WHERE a.Id=b.IndexId " - "AND b.FilterAttributeId=c.Id AND c.Name='%1'")) - .arg(quote(filterAttributes.at(i)))); - } - } - - m_query->exec(query); - while (m_query->next()) { - if (!m_query->value(0).toString().isEmpty()) - indices.append(m_query->value(0).toString()); - } - return indices; -} - -void QHelpDBReader::linksForKeyword(const QString &keyword, - const QStringList &filterAttributes, - QMap<QString, QUrl> *linkMap) const -{ - if (!m_query) - return; - - QString query; - if (filterAttributes.isEmpty()) { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor " - "FROM IndexTable a, FileNameTable d, " - "FolderTable e, NamespaceTable f WHERE " - "a.FileId=d.FileId AND d.FolderId=e.Id AND a.NamespaceId=f.Id " - "AND a.Name='%1'")).arg(quote(keyword)); - } else if (m_useAttributesCache) { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor, a.Id " - "FROM IndexTable a, " - "FileNameTable d, FolderTable e, NamespaceTable f WHERE " - "a.FileId=d.FileId AND d.FolderId=e.Id " - "AND a.NamespaceId=f.Id AND a.Name='%1'")) - .arg(quote(keyword)); - m_query->exec(query); - while (m_query->next()) { - if (m_indicesCache.contains(m_query->value(5).toInt())) { - linkMap->insertMulti(m_query->value(0).toString(), buildQUrl(m_query->value(1).toString(), - m_query->value(2).toString(), m_query->value(3).toString(), - m_query->value(4).toString())); - } - } - return; - } else { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor " - "FROM IndexTable a, IndexFilterTable b, FilterAttributeTable c, " - "FileNameTable d, FolderTable e, NamespaceTable f " - "WHERE a.FileId=d.FileId AND d.FolderId=e.Id " - "AND a.NamespaceId=f.Id AND b.IndexId=a.Id AND b.FilterAttributeId=c.Id " - "AND a.Name='%1' AND c.Name='%2'")).arg(quote(keyword)) - .arg(quote(filterAttributes.first())); - for (int i = 1; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor " - "FROM IndexTable a, IndexFilterTable b, FilterAttributeTable c, " - "FileNameTable d, FolderTable e, NamespaceTable f " - "WHERE a.FileId=d.FileId AND d.FolderId=e.Id " - "AND a.NamespaceId=f.Id AND b.IndexId=a.Id AND b.FilterAttributeId=c.Id " - "AND a.Name='%1' AND c.Name='%2'")).arg(quote(keyword)) - .arg(quote(filterAttributes.at(i)))); - } - } - - QString title; - m_query->exec(query); - while (m_query->next()) { - title = m_query->value(0).toString(); - if (title.isEmpty()) // generate a title + corresponding path - title = keyword + QLatin1String(" : ") + m_query->value(3).toString(); - linkMap->insertMulti(title, buildQUrl(m_query->value(1).toString(), - m_query->value(2).toString(), m_query->value(3).toString(), - m_query->value(4).toString())); - } -} - -void QHelpDBReader::linksForIdentifier(const QString &id, - const QStringList &filterAttributes, - QMap<QString, QUrl> *linkMap) const -{ - if (!m_query) - return; - - QString query; - if (filterAttributes.isEmpty()) { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor " - "FROM IndexTable a, FileNameTable d, FolderTable e, " - "NamespaceTable f WHERE a.FileId=d.FileId AND " - "d.FolderId=e.Id AND a.NamespaceId=f.Id AND a.Identifier='%1'")) - .arg(quote(id)); - } else if (m_useAttributesCache) { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor, a.Id " - "FROM IndexTable a," - "FileNameTable d, FolderTable e, NamespaceTable f WHERE " - "a.FileId=d.FileId AND d.FolderId=e.Id " - "AND a.NamespaceId=f.Id AND a.Identifier='%1'")) - .arg(quote(id)); - m_query->exec(query); - while (m_query->next()) { - if (m_indicesCache.contains(m_query->value(5).toInt())) { - linkMap->insertMulti(m_query->value(0).toString(), buildQUrl(m_query->value(1).toString(), - m_query->value(2).toString(), m_query->value(3).toString(), - m_query->value(4).toString())); - } - } - return; - } else { - query = QString(QLatin1String("SELECT d.Title, f.Name, e.Name, d.Name, a.Anchor " - "FROM IndexTable a, IndexFilterTable b, FilterAttributeTable c, " - "FileNameTable d, FolderTable e, NamespaceTable f " - "WHERE a.FileId=d.FileId AND d.FolderId=e.Id " - "AND a.NamespaceId=f.Id AND b.IndexId=a.Id AND b.FilterAttributeId=c.Id " - "AND a.Identifier='%1' AND c.Name='%2'")).arg(quote(id)) - .arg(quote(filterAttributes.first())); - for (int i = 0; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT d.Title, f.Name, e.Name, " - "d.Name, a.Anchor FROM IndexTable a, IndexFilterTable b, " - "FilterAttributeTable c, FileNameTable d, " - "FolderTable e, NamespaceTable f WHERE " - "a.FileId=d.FileId AND d.FolderId=e.Id AND a.NamespaceId=f.Id " - "AND b.IndexId=a.Id AND b.FilterAttributeId=c.Id AND " - "a.Identifier='%1' AND c.Name='%2'")).arg(quote(id)) - .arg(quote(filterAttributes.at(i)))); - } - } - - m_query->exec(query); - while (m_query->next()) { - linkMap->insertMulti(m_query->value(0).toString(), buildQUrl(m_query->value(1).toString(), - m_query->value(2).toString(), m_query->value(3).toString(), - m_query->value(4).toString())); - } -} - -QUrl QHelpDBReader::buildQUrl(const QString &ns, const QString &folder, - const QString &relFileName, const QString &anchor) const -{ - QUrl url; - url.setScheme(QLatin1String("qthelp")); - url.setAuthority(ns); - url.setPath(QLatin1Char('/') + folder + QLatin1Char('/') + relFileName); - url.setFragment(anchor); - return url; -} - -QList<QByteArray> QHelpDBReader::contentsForFilter(const QStringList &filterAttributes) const -{ - QList<QByteArray> contents; - if (!m_query) - return contents; - - //SELECT DISTINCT a.Data FROM ContentsTable a, ContentsFilterTable b, FilterAttributeTable c WHERE a.Id=b.ContentsId AND b.FilterAttributeId=c.Id AND c.Name='qt' INTERSECT SELECT DISTINCT a.Data FROM ContentsTable a, ContentsFilterTable b, FilterAttributeTable c WHERE a.Id=b.ContentsId AND b.FilterAttributeId=c.Id AND c.Name='3.3.8'; - - QString query; - if (filterAttributes.isEmpty()) { - query = QLatin1String("SELECT Data from ContentsTable"); - } else { - query = QString(QLatin1String("SELECT a.Data FROM ContentsTable a, " - "ContentsFilterTable b, FilterAttributeTable c " - "WHERE a.Id=b.ContentsId AND b.FilterAttributeId=c.Id " - "AND c.Name='%1'")).arg(quote(filterAttributes.first())); - for (int i = 1; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT a.Data FROM ContentsTable a, " - "ContentsFilterTable b, FilterAttributeTable c " - "WHERE a.Id=b.ContentsId AND b.FilterAttributeId=c.Id " - "AND c.Name='%1'")).arg(quote(filterAttributes.at(i)))); - } - } - - m_query->exec(query); - while (m_query->next()) - contents.append(m_query->value(0).toByteArray()); - return contents; -} - -QUrl QHelpDBReader::urlOfPath(const QString &relativePath) const +QMap<QString, QByteArray> QHelpDBReader::filesData( + const QStringList &filterAttributes, + const QString &extensionFilter) const { + QMap<QString, QByteArray> result; if (!m_query) - return QUrl(); - - m_query->exec(QLatin1String("SELECT a.Name, b.Name FROM NamespaceTable a, " - "FolderTable b WHERE a.id=b.NamespaceId and a.Id=1")); - if (!m_query->next()) - return QUrl(); - - const int idx = relativePath.indexOf(QLatin1Char('#')); - const QString &rp = idx < 0 ? relativePath : relativePath.left(idx); - const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1); - return buildQUrl(m_query->value(0).toString(), - m_query->value(1).toString(), rp, anchor); -} - -QStringList QHelpDBReader::files(const QStringList &filterAttributes, - const QString &extensionFilter) const -{ - QStringList lst; - if (!m_query) - return lst; + return result; QString query; QString extension; if (!extensionFilter.isEmpty()) - extension = QString(QLatin1String("AND b.Name like \'%.%1\'")).arg(extensionFilter); + extension = QString(QLatin1String("AND FileNameTable.Name " + "LIKE \'%.%1\'")).arg(extensionFilter); if (filterAttributes.isEmpty()) { - query = QString(QLatin1String("SELECT a.Name, b.Name FROM FolderTable a, " - "FileNameTable b WHERE b.FolderId=a.Id %1")) + query = QString(QLatin1String("SELECT " + "FileNameTable.Name, " + "FileDataTable.Data " + "FROM " + "FolderTable, " + "FileNameTable, " + "FileDataTable " + "WHERE FileDataTable.Id = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id %1")) .arg(extension); } else { - query = QString(QLatin1String("SELECT a.Name, b.Name FROM FolderTable a, " - "FileNameTable b, FileFilterTable c, FilterAttributeTable d " - "WHERE b.FolderId=a.Id AND b.FileId=c.FileId " - "AND c.FilterAttributeId=d.Id AND d.Name=\'%1\' %2")) - .arg(quote(filterAttributes.first())).arg(extension); - for (int i = 1; i < filterAttributes.count(); ++i) { - query.append(QString(QLatin1String(" INTERSECT SELECT a.Name, b.Name FROM " - "FolderTable a, FileNameTable b, FileFilterTable c, " - "FilterAttributeTable d WHERE b.FolderId=a.Id AND " - "b.FileId=c.FileId AND c.FilterAttributeId=d.Id AND " - "d.Name=\'%1\' %2")).arg(quote(filterAttributes.at(i))) - .arg(extension)); + for (int i = 0; i < filterAttributes.count(); ++i) { + if (i > 0) + query.append(QLatin1String(" INTERSECT ")); + query.append(QString(QLatin1String( + "SELECT " + "FileNameTable.Name, " + "FileDataTable.Data " + "FROM " + "FolderTable, " + "FileNameTable, " + "FileDataTable, " + "FileFilterTable, " + "FilterAttributeTable " + "WHERE FileDataTable.Id = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FileNameTable.FileId = FileFilterTable.FileId " + "AND FileFilterTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = \'%1\' %2")) + .arg(quote(filterAttributes.at(i))) + .arg(extension)); } } m_query->exec(query); - while (m_query->next()) { - lst.append(m_query->value(0).toString() + QLatin1Char('/') - + m_query->value(1).toString()); - } + while (m_query->next()) + result.insert(m_query->value(0).toString(), qUncompress(m_query->value(1).toByteArray())); - return lst; + return result; } QVariant QHelpDBReader::metaData(const QString &name) const @@ -506,16 +464,6 @@ QVariant QHelpDBReader::metaData(const QString &name) const return v; } -QString QHelpDBReader::mergeList(const QStringList &list) const -{ - QString str; - for (const QString &s : list) - str.append(QLatin1Char('\'') + quote(s) + QLatin1String("\', ")); - if (str.endsWith(QLatin1String(", "))) - str.chop(2); - return str; -} - QString QHelpDBReader::quote(const QString &string) const { QString s = string; @@ -523,55 +471,4 @@ QString QHelpDBReader::quote(const QString &string) const return s; } -QSet<int> QHelpDBReader::indexIds(const QStringList &attributes) const -{ - QSet<int> ids; - - if (attributes.isEmpty()) - return ids; - - QString query = QString(QLatin1String("SELECT a.IndexId FROM IndexFilterTable a, " - "FilterAttributeTable b WHERE a.FilterAttributeId=b.Id " - "AND b.Name='%1'")).arg(attributes.first()); - for (const QString &attribute : attributes) { - query.append(QString(QLatin1String(" INTERSECT SELECT a.IndexId FROM " - "IndexFilterTable a, FilterAttributeTable b WHERE " - "a.FilterAttributeId=b.Id AND b.Name='%1'")) - .arg(attribute)); - } - - if (!m_query->exec(query)) - return ids; - - while (m_query->next()) - ids.insert(m_query->value(0).toInt()); - - return ids; -} - -bool QHelpDBReader::createAttributesCache(const QStringList &attributes, - const QSet<int> &indexIds) -{ - m_useAttributesCache = false; - - if (attributes.count() < 2) { - m_viewAttributes.clear(); - return true; - } - - const bool needUpdate = !m_viewAttributes.count(); - - for (const QString &s : attributes) - m_viewAttributes.remove(s); - - if (m_viewAttributes.count() || needUpdate) { - m_viewAttributes.clear(); - m_indicesCache = indexIds; - } - for (const QString &s : attributes) - m_viewAttributes.insert(s); - m_useAttributesCache = true; - return true; -} - QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpdbreader_p.h b/src/assistant/help/qhelpdbreader_p.h index aca4c8afa..c01715493 100644 --- a/src/assistant/help/qhelpdbreader_p.h +++ b/src/assistant/help/qhelpdbreader_p.h @@ -66,6 +66,43 @@ class QHelpDBReader : public QObject Q_OBJECT public: + class IndexItem + { + public: + IndexItem() = default; + QString name; + QString identifier; + int fileId = 0; + QString anchor; + QStringList filterAttributes; + }; + + class FileItem + { + public: + FileItem() = default; + QString name; + QString title; + QStringList filterAttributes; + }; + + class ContentsItem + { + public: + ContentsItem() = default; + QByteArray data; + QStringList filterAttributes; + }; + + class IndexTable + { + public: + QList<IndexItem> indexItems; + QList<FileItem> fileItems; + QList<ContentsItem> contentsItems; + QStringList usedFilterAttributes; + }; + QHelpDBReader(const QString &dbName); QHelpDBReader(const QString &dbName, const QString &uniqueId, QObject *parent); @@ -73,40 +110,21 @@ public: bool init(); - QString errorMessage() const; - - QString databaseName() const; QString namespaceName() const; QString virtualFolder() const; + IndexTable indexTable() const; QList<QStringList> filterAttributeSets() const; - QStringList files(const QStringList &filterAttributes, + QMap<QString, QByteArray> filesData(const QStringList &filterAttributes, const QString &extensionFilter = QString()) const; - bool fileExists(const QString &virtualFolder, const QString &filePath, - const QStringList &filterAttributes = QStringList()) const; QByteArray fileData(const QString &virtualFolder, const QString &filePath) const; QStringList customFilters() const; QStringList filterAttributes(const QString &filterName = QString()) const; - QStringList indicesForFilter(const QStringList &filterAttributes) const; - void linksForKeyword(const QString &keyword, const QStringList &filterAttributes, - QMap<QString, QUrl> *linkMap) const; - - void linksForIdentifier(const QString &id, const QStringList &filterAttributes, - QMap<QString, QUrl> *linkMap) const; - - QList<QByteArray> contentsForFilter(const QStringList &filterAttributes) const; - QUrl urlOfPath(const QString &relativePath) const; - QSet<int> indexIds(const QStringList &attributes) const; - bool createAttributesCache(const QStringList &attributes, - const QSet<int> &indexIds); QVariant metaData(const QString &name) const; private: - QUrl buildQUrl(const QString &ns, const QString &folder, - const QString &relFileName, const QString &anchor) const; - QString mergeList(const QStringList &list) const; QString quote(const QString &string) const; bool initDB(); @@ -116,9 +134,6 @@ private: QString m_error; QSqlQuery *m_query = nullptr; mutable QString m_namespace; - QSet<QString> m_viewAttributes; - bool m_useAttributesCache = false; - QSet<int> m_indicesCache; }; QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpengine.cpp b/src/assistant/help/qhelpengine.cpp index 41ded85ee..966c33354 100644 --- a/src/assistant/help/qhelpengine.cpp +++ b/src/assistant/help/qhelpengine.cpp @@ -48,6 +48,7 @@ #include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QPluginLoader> +#include <QtCore/QTimer> #include <QtWidgets/QApplication> #include <QtSql/QSqlQuery> @@ -64,15 +65,26 @@ void QHelpEnginePrivate::init(const QString &collectionFile, indexModel = new QHelpIndexModel(this); connect(helpEngineCore, &QHelpEngineCore::setupFinished, - this, &QHelpEnginePrivate::applyCurrentFilter); + this, &QHelpEnginePrivate::scheduleApplyCurrentFilter); connect(helpEngineCore, &QHelpEngineCore::currentFilterChanged, - this, &QHelpEnginePrivate::applyCurrentFilter); + this, &QHelpEnginePrivate::scheduleApplyCurrentFilter); } -void QHelpEnginePrivate::applyCurrentFilter() +void QHelpEnginePrivate::scheduleApplyCurrentFilter() { if (!error.isEmpty()) return; + + if (m_isApplyCurrentFilterScheduled) + return; + + m_isApplyCurrentFilterScheduled = true; + QTimer::singleShot(0, this, &QHelpEnginePrivate::applyCurrentFilter); +} + +void QHelpEnginePrivate::applyCurrentFilter() +{ + m_isApplyCurrentFilterScheduled = false; contentModel->createContents(currentFilter); indexModel->createIndex(currentFilter); } diff --git a/src/assistant/help/qhelpengine_p.h b/src/assistant/help/qhelpengine_p.h index d56b51178..b1f986d20 100644 --- a/src/assistant/help/qhelpengine_p.h +++ b/src/assistant/help/qhelpengine_p.h @@ -79,14 +79,9 @@ public: virtual void init(const QString &collectionFile, QHelpEngineCore *helpEngineCore); - void clearMaps(); + void emitReadersAboutToBeInvalidated(); bool setup(); - QMap<QString, QHelpDBReader*> readerMap; - QMap<QString, QHelpDBReader*> fileNameReaderMap; - QMultiMap<QString, QHelpDBReader*> virtualFolderMap; - QStringList orderedFileNameList; - QHelpCollectionHandler *collectionHandler = nullptr; QString currentFilter; QString error; @@ -131,7 +126,12 @@ public slots: void unsetIndexWidgetBusy(); private slots: + void scheduleApplyCurrentFilter(); void applyCurrentFilter(); + +private: + bool m_isApplyCurrentFilterScheduled = false; + }; QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpenginecore.cpp b/src/assistant/help/qhelpenginecore.cpp index e351c07d1..de5981a06 100644 --- a/src/assistant/help/qhelpenginecore.cpp +++ b/src/assistant/help/qhelpenginecore.cpp @@ -65,20 +65,12 @@ void QHelpEngineCorePrivate::init(const QString &collectionFile, QHelpEngineCorePrivate::~QHelpEngineCorePrivate() { delete collectionHandler; - clearMaps(); + emitReadersAboutToBeInvalidated(); } -void QHelpEngineCorePrivate::clearMaps() +void QHelpEngineCorePrivate::emitReadersAboutToBeInvalidated() { emit q->readersAboutToBeInvalidated(); - - for (const QHelpDBReader *reader : qAsConst(readerMap)) - delete reader; - - readerMap.clear(); - fileNameReaderMap.clear(); - virtualFolderMap.clear(); - orderedFileNameList.clear(); } bool QHelpEngineCorePrivate::setup() @@ -89,39 +81,15 @@ bool QHelpEngineCorePrivate::setup() needsSetup = false; emit q->setupStarted(); - clearMaps(); + emitReadersAboutToBeInvalidated(); - if (!collectionHandler->openCollectionFile()) { - emit q->setupFinished(); - return false; - } + const bool opened = collectionHandler->openCollectionFile(); + if (opened) + q->currentFilter(); - const QHelpCollectionHandler::DocInfoList &docList = - collectionHandler->registeredDocumentations(); - const QFileInfo fi(collectionHandler->collectionFile()); - - for (const QHelpCollectionHandler::DocInfo &info : docList) { - const QString &absFileName = QDir::isAbsolutePath(info.fileName) - ? info.fileName - : QFileInfo(fi.absolutePath() + QDir::separator() + info.fileName) - .absoluteFilePath(); - - QHelpDBReader *reader = new QHelpDBReader(absFileName, - QHelpGlobal::uniquifyConnectionName(info.fileName, this), this); - if (!reader->init()) { - emit q->warning(QHelpEngineCore::tr("Cannot open documentation file %1: %2.") - .arg(absFileName, reader->errorMessage())); - continue; - } - - readerMap.insert(info.namespaceName, reader); - fileNameReaderMap.insert(absFileName, reader); - virtualFolderMap.insert(info.folderName, reader); - orderedFileNameList.append(absFileName); - } - q->currentFilter(); emit q->setupFinished(); - return true; + + return opened; } void QHelpEngineCorePrivate::errorReceived(const QString &msg) @@ -129,8 +97,6 @@ void QHelpEngineCorePrivate::errorReceived(const QString &msg) error = msg; } - - /*! \class QHelpEngineCore \since 4.4 @@ -252,7 +218,7 @@ void QHelpEngineCore::setCollectionFile(const QString &fileName) if (d->collectionHandler) { delete d->collectionHandler; d->collectionHandler = 0; - d->clearMaps(); + d->emitReadersAboutToBeInvalidated(); } d->init(fileName, this); d->needsSetup = true; @@ -349,20 +315,20 @@ bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName) */ QString QHelpEngineCore::documentationFileName(const QString &namespaceName) { - if (d->setup()) { - const QHelpCollectionHandler::DocInfoList &docList = - d->collectionHandler->registeredDocumentations(namespaceName); - for (const QHelpCollectionHandler::DocInfo &info : docList) { - if (info.namespaceName == namespaceName) { - if (QDir::isAbsolutePath(info.fileName)) - return info.fileName; - - return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath() - + QDir::separator() + info.fileName).absoluteFilePath(); - } - } - } - return QString(); + if (!d->setup()) + return QString(); + + const QHelpCollectionHandler::FileInfo fileInfo = + d->collectionHandler->registeredDocumentation(namespaceName); + + if (fileInfo.namespaceName.isEmpty()) + return QString(); + + if (QDir::isAbsolutePath(fileInfo.fileName)) + return fileInfo.fileName; + + return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath() + + QLatin1Char('/') + fileInfo.fileName).absoluteFilePath(); } /*! @@ -374,8 +340,9 @@ QStringList QHelpEngineCore::registeredDocumentations() const QStringList list; if (!d->setup()) return list; - const QHelpCollectionHandler::DocInfoList &docList = d->collectionHandler->registeredDocumentations(); - for (const QHelpCollectionHandler::DocInfo &info : docList) + const QHelpCollectionHandler::FileInfoList &docList + = d->collectionHandler->registeredDocumentations(); + for (const QHelpCollectionHandler::FileInfo &info : docList) list.append(info.namespaceName); return list; } @@ -488,15 +455,10 @@ void QHelpEngineCore::setCurrentFilter(const QString &filterName) */ QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const { - QList<QStringList> ret; - if (d->setup()) { - QHelpDBReader *reader = d->readerMap.value(namespaceName); - if (reader) - ret = reader->filterAttributeSets(); - } - if (ret.isEmpty()) - ret.append(QStringList()); - return ret; + if (!d->setup()) + return QList<QStringList>(); + + return d->collectionHandler->filterAttributeSets(namespaceName); } /*! @@ -511,17 +473,13 @@ QList<QUrl> QHelpEngineCore::files(const QString namespaceName, QList<QUrl> res; if (!d->setup()) return res; - QHelpDBReader *reader = d->readerMap.value(namespaceName); - if (!reader) { - d->error = tr("The specified namespace does not exist."); - return res; - } QUrl url; url.setScheme(QLatin1String("qthelp")); url.setAuthority(namespaceName); - const QStringList &files = reader->files(filterAttributes, extensionFilter); + const QStringList &files = d->collectionHandler->files( + namespaceName, filterAttributes, extensionFilter); for (const QString &file : files) { url.setPath(QLatin1String("/") + file); res.append(url); @@ -537,48 +495,19 @@ QList<QUrl> QHelpEngineCore::files(const QString namespaceName, */ QUrl QHelpEngineCore::findFile(const QUrl &url) const { - QUrl res; - if (!d->setup() || !url.isValid() || url.toString().count(QLatin1Char('/')) < 4 - || url.scheme() != QLatin1String("qthelp")) { - return res; - } - - const QString &ns = url.authority(); - QString filePath = url.path(); - if (filePath.startsWith(QLatin1Char('/'))) - filePath = filePath.mid(1); - const QString &virtualFolder = filePath.mid(0, filePath.indexOf(QLatin1Char('/'), 1)); - filePath.remove(0, virtualFolder.length() + 1); - - QHelpDBReader *defaultReader = 0; - if (d->readerMap.contains(ns)) { - defaultReader = d->readerMap.value(ns); - if (defaultReader->fileExists(virtualFolder, filePath)) - return url; - } + if (!d->setup()) + return url; const QStringList &attributes = filterAttributes(currentFilter()); - for (const QHelpDBReader *reader : d->virtualFolderMap.values(virtualFolder)) { - if (reader == defaultReader) - continue; - if (reader->fileExists(virtualFolder, filePath, attributes)) { - res = url; - res.setAuthority(reader->namespaceName()); - return res; - } - } + QUrl result = d->collectionHandler->findFile(url, attributes); + if (!result.isEmpty()) + return result; - for (const QHelpDBReader *reader : d->virtualFolderMap.values(virtualFolder)) { - if (reader == defaultReader) - continue; - if (reader->fileExists(virtualFolder, filePath)) { - res = url; - res.setAuthority(reader->namespaceName()); - break; - } - } + result = d->collectionHandler->findFile(url); + if (!result.isEmpty()) + return result; - return res; + return url; } /*! @@ -589,35 +518,10 @@ QUrl QHelpEngineCore::findFile(const QUrl &url) const */ QByteArray QHelpEngineCore::fileData(const QUrl &url) const { - if (!d->setup() || !url.isValid() || url.toString().count(QLatin1Char('/')) < 4 - || url.scheme() != QLatin1String("qthelp")) { + if (!d->setup()) return QByteArray(); - } - - const QString &ns = url.authority(); - QString filePath = url.path(); - if (filePath.startsWith(QLatin1Char('/'))) - filePath = filePath.mid(1); - const QString &virtualFolder = filePath.mid(0, filePath.indexOf(QLatin1Char('/'), 1)); - filePath.remove(0, virtualFolder.length() + 1); - - QByteArray ba; - QHelpDBReader *defaultReader = 0; - if (d->readerMap.contains(ns)) { - defaultReader = d->readerMap.value(ns); - ba = defaultReader->fileData(virtualFolder, filePath); - } - if (ba.isEmpty()) { - for (const QHelpDBReader *reader : d->virtualFolderMap.values(virtualFolder)) { - if (reader == defaultReader) - continue; - ba = reader->fileData(virtualFolder, filePath); - if (!ba.isEmpty()) - return ba; - } - } - return ba; + return d->collectionHandler->fileData(url); } /*! @@ -628,15 +532,10 @@ QByteArray QHelpEngineCore::fileData(const QUrl &url) const */ QMap<QString, QUrl> QHelpEngineCore::linksForIdentifier(const QString &id) const { - QMap<QString, QUrl> linkMap; if (!d->setup()) - return linkMap; - - const QStringList &attributes = filterAttributes(d->currentFilter); - for (const QHelpDBReader *reader : qAsConst(d->readerMap)) - reader->linksForIdentifier(id, attributes, &linkMap); + return QMap<QString, QUrl>(); - return linkMap; + return d->collectionHandler->linksForIdentifier(id, filterAttributes(d->currentFilter)); } /*! @@ -647,11 +546,10 @@ QMap<QString, QUrl> QHelpEngineCore::linksForIdentifier(const QString &id) const */ QMap<QString, QUrl> QHelpEngineCore::linksForKeyword(const QString &keyword) const { - QMap<QString, QUrl> linkMap; - const QStringList &attributes = filterAttributes(d->currentFilter); - for (const QHelpDBReader *reader : qAsConst(d->readerMap)) - reader->linksForKeyword(keyword, attributes, &linkMap); - return linkMap; + if (!d->setup()) + return QMap<QString, QUrl>(); + + return d->collectionHandler->linksForKeyword(keyword, filterAttributes(d->currentFilter)); } /*! diff --git a/src/assistant/help/qhelpgenerator.cpp b/src/assistant/help/qhelpgenerator.cpp index ac034421a..690a983d0 100644 --- a/src/assistant/help/qhelpgenerator.cpp +++ b/src/assistant/help/qhelpgenerator.cpp @@ -470,7 +470,6 @@ bool QHelpGenerator::insertFiles(const QStringList &files, const QString &rootPa QString title; QString charSet; - FileNameTableData fileNameData; QList<QByteArray> fileDataList; QMap<int, QSet<int> > tmpFileFilterMap; QList<FileNameTableData> fileNameDataList; @@ -508,6 +507,7 @@ bool QHelpGenerator::insertFiles(const QStringList &files, const QString &rootPa if (it == d->fileMap.cend()) { fileDataList.append(qCompress(data)); + FileNameTableData fileNameData; fileNameData.name = fileName; fileNameData.fileId = tableFileId; fileNameData.title = title; diff --git a/src/assistant/help/qhelpindexwidget.cpp b/src/assistant/help/qhelpindexwidget.cpp index fb65aceb5..bc7ebe80e 100644 --- a/src/assistant/help/qhelpindexwidget.cpp +++ b/src/assistant/help/qhelpindexwidget.cpp @@ -41,6 +41,7 @@ #include "qhelpenginecore.h" #include "qhelpengine_p.h" #include "qhelpdbreader_p.h" +#include "qhelpcollectionhandler_p.h" #include <QtCore/QThread> #include <QtCore/QMutex> @@ -59,16 +60,12 @@ public: void collectIndices(const QString &customFilterName); void stopCollecting(); QStringList indices() const; - QList<QHelpDBReader*> activeReaders() const; - QSet<int> indexIds(QHelpDBReader *reader) const; private: void run() override; QHelpEnginePrivate *m_helpEngine; QStringList m_indices; - QList<QHelpDBReader*> m_activeReaders; - QMap<QHelpDBReader*, QSet<int> > m_indexIds; QStringList m_filterAttributes; mutable QMutex m_mutex; bool m_abort = false; @@ -86,9 +83,6 @@ public: QHelpEnginePrivate *helpEngine; QHelpIndexProvider *indexProvider; QStringList indices; - int insertedRows = 0; - QString currentFilter; - QList<QHelpDBReader*> activeReaders; }; QHelpIndexProvider::QHelpIndexProvider(QHelpEnginePrivate *helpEngine) @@ -132,63 +126,28 @@ QStringList QHelpIndexProvider::indices() const return m_indices; } -QList<QHelpDBReader*> QHelpIndexProvider::activeReaders() const -{ - QMutexLocker lck(&m_mutex); - return m_activeReaders; -} - -QSet<int> QHelpIndexProvider::indexIds(QHelpDBReader *reader) const -{ - QMutexLocker lck(&m_mutex); - return m_indexIds.value(reader); -} - void QHelpIndexProvider::run() { m_mutex.lock(); - QStringList atts = m_filterAttributes; m_indices.clear(); - m_activeReaders.clear(); - QSet<QString> indicesSet; + const QStringList attributes = m_filterAttributes; + const QString collectionFile = m_helpEngine->collectionHandler->collectionFile(); m_mutex.unlock(); - for (const QString &dbFileName : m_helpEngine->fileNameReaderMap.keys()) { - m_mutex.lock(); - if (m_abort) { - m_mutex.unlock(); - return; - } - m_mutex.unlock(); - QHelpDBReader reader(dbFileName, - QHelpGlobal::uniquifyConnectionName(dbFileName + - QLatin1String("FromIndexProvider"), - QThread::currentThread()), 0); - if (!reader.init()) - continue; - const QStringList &list = reader.indicesForFilter(atts); - if (!list.isEmpty()) { - m_mutex.lock(); - for (const QString &s : list) - indicesSet.insert(s); - if (m_abort) { - m_mutex.unlock(); - return; - } - QHelpDBReader *orgReader = m_helpEngine->fileNameReaderMap.value(dbFileName); - m_indexIds.insert(orgReader, reader.indexIds(atts)); - m_activeReaders.append(orgReader); - m_mutex.unlock(); - } - } + if (collectionFile.isEmpty()) + return; + + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return; + + const QStringList result = collectionHandler.indicesForFilter(attributes); + m_mutex.lock(); - m_indices = indicesSet.values(); - m_indices.sort(Qt::CaseInsensitive); + m_indices = result; m_mutex.unlock(); } - - /*! \class QHelpIndexModel \since 4.4 @@ -222,8 +181,6 @@ QHelpIndexModel::QHelpIndexModel(QHelpEnginePrivate *helpEngine) connect(d->indexProvider, &QThread::finished, this, &QHelpIndexModel::insertIndices); - connect(helpEngine->q, &QHelpEngineCore::readersAboutToBeInvalidated, - this, [this]() { invalidateIndex(); }); } QHelpIndexModel::~QHelpIndexModel() @@ -233,14 +190,7 @@ QHelpIndexModel::~QHelpIndexModel() void QHelpIndexModel::invalidateIndex(bool onShutDown) { - if (onShutDown) { - disconnect(d->indexProvider, &QThread::finished, - this, &QHelpIndexModel::insertIndices); - } - d->indexProvider->stopCollecting(); - d->indices.clear(); - if (!onShutDown) - filter(QString()); + Q_UNUSED(onShutDown) } /*! @@ -249,7 +199,6 @@ void QHelpIndexModel::invalidateIndex(bool onShutDown) */ void QHelpIndexModel::createIndex(const QString &customFilterName) { - d->currentFilter = customFilterName; d->indexProvider->collectIndices(customFilterName); emit indexCreationStarted(); } @@ -257,12 +206,6 @@ void QHelpIndexModel::createIndex(const QString &customFilterName) void QHelpIndexModel::insertIndices() { d->indices = d->indexProvider->indices(); - d->activeReaders = d->indexProvider->activeReaders(); - const QStringList &attributes = d->helpEngine->q->filterAttributes(d->currentFilter); - if (attributes.count() > 1) { - for (QHelpDBReader *r : qAsConst(d->activeReaders)) - r->createAttributesCache(attributes, d->indexProvider->indexIds(r)); - } filter(QString()); emit indexCreated(); } diff --git a/src/assistant/help/qhelpsearchindexwriter_default.cpp b/src/assistant/help/qhelpsearchindexwriter_default.cpp index 72e92ecab..11d92ab5d 100644 --- a/src/assistant/help/qhelpsearchindexwriter_default.cpp +++ b/src/assistant/help/qhelpsearchindexwriter_default.cpp @@ -40,6 +40,7 @@ #include "qhelpsearchindexwriter_default_p.h" #include "qhelp_global.h" #include "qhelpenginecore.h" +#include "qhelpdbreader_p.h" #include <QtCore/QDataStream> #include <QtCore/QDateTime> @@ -362,14 +363,6 @@ static bool clearIndexMap(QHelpEngineCore *engine) return engine->removeCustomValue(QLatin1String(IndexedNamespacesKey)); } -static QList<QUrl> indexableFiles(QHelpEngineCore *helpEngine, - const QString &namespaceName, const QStringList &attributes) -{ - return helpEngine->files(namespaceName, attributes, QLatin1String("html")) - + helpEngine->files(namespaceName, attributes, QLatin1String("htm")) - + helpEngine->files(namespaceName, attributes, QLatin1String("txt")); -} - void QHelpSearchIndexWriter::run() { QMutexLocker lock(&m_mutex); @@ -387,13 +380,6 @@ void QHelpSearchIndexWriter::run() if (!engine.setupData()) return; -// QFileInfo fInfo(indexPath); -// if (fInfo.exists() && !fInfo.isWritable()) { -// qWarning("Full Text Search, could not create index (missing permissions for '%s').", -// qPrintable(indexPath)); -// return; -// } - if (reindex) clearIndexMap(&engine); @@ -458,27 +444,32 @@ void QHelpSearchIndexWriter::run() if (indexMap.contains(namespaceName)) continue; + const QString fileName = engine.documentationFileName(namespaceName); + QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName( + fileName, this), nullptr); + if (!reader.init()) + continue; + + const QString virtualFolder = reader.virtualFolder(); + const QList<QStringList> &attributeSets = engine.filterAttributeSets(namespaceName); for (const QStringList &attributes : attributeSets) { const QString &attributesString = attributes.join(QLatin1Char('|')); - QSet<QString> documentsSet; - const QList<QUrl> &docFiles = indexableFiles(&engine, namespaceName, attributes); - for (QUrl url : docFiles) { - // get rid of duplicated files - if (url.hasFragment()) - url.setFragment(QString()); - const QString &s = url.toString(); - if (s.endsWith(QLatin1String(".html")) - || s.endsWith(QLatin1String(".htm")) - || s.endsWith(QLatin1String(".txt"))) - documentsSet.insert(s); - } + const QMap<QString, QByteArray> htmlFiles + = reader.filesData(attributes, QLatin1String("html")); + const QMap<QString, QByteArray> htmFiles + = reader.filesData(attributes, QLatin1String("htm")); + const QMap<QString, QByteArray> txtFiles + = reader.filesData(attributes, QLatin1String("txt")); - const QStringList documentsList(documentsSet.toList()); - for (const QString &url : documentsList) { + QMap<QString, QByteArray> files = htmlFiles; + files.unite(htmFiles); + files.unite(txtFiles); + + for (auto it = files.cbegin(), end = files.cend(); it != end ; ++it) { lock.relock(); if (m_cancel) { // store what we have done so far @@ -489,10 +480,27 @@ void QHelpSearchIndexWriter::run() } lock.unlock(); - const QByteArray data(engine.fileData(url)); + const QString &file = it.key(); + const QByteArray &data = it.value(); + if (data.isEmpty()) continue; + QUrl url; + url.setScheme(QLatin1String("qthelp")); + url.setAuthority(namespaceName); + url.setPath(QLatin1Char('/') + virtualFolder + QLatin1Char('/') + file); + + if (url.hasFragment()) + url.setFragment(QString()); + + const QString &fullFileName = url.toString(); + if (!fullFileName.endsWith(QLatin1String(".html")) + && !fullFileName.endsWith(QLatin1String(".htm")) + && !fullFileName.endsWith(QLatin1String(".txt"))) { + continue; + } + QTextStream s(data); const QString &en = QHelpGlobal::codecFromData(data); s.setCodec(QTextCodec::codecForName(en.toLatin1().constData())); @@ -503,8 +511,8 @@ void QHelpSearchIndexWriter::run() QString title; QString contents; - if (url.endsWith(QLatin1String(".txt"))) { - title = url.mid(url.lastIndexOf(QLatin1Char('/')) + 1); + if (fullFileName.endsWith(QLatin1String(".txt"))) { + title = fullFileName.mid(fullFileName.lastIndexOf(QLatin1Char('/')) + 1); contents = text.toHtmlEscaped(); } else { QTextDocument doc; @@ -514,7 +522,7 @@ void QHelpSearchIndexWriter::run() contents = doc.toPlainText().toHtmlEscaped(); } - writer.insertDoc(namespaceName, attributesString, url, title, contents); + writer.insertDoc(namespaceName, attributesString, fullFileName, title, contents); } } writer.flush(); diff --git a/src/assistant/qcollectiongenerator/main.cpp b/src/assistant/qcollectiongenerator/main.cpp index 57aaac0f0..a2968ecd0 100644 --- a/src/assistant/qcollectiongenerator/main.cpp +++ b/src/assistant/qcollectiongenerator/main.cpp @@ -476,14 +476,15 @@ int main(int argc, char *argv[]) return -1; } } - if (!config.filesToRegister().isEmpty()) + if (!config.filesToRegister().isEmpty()) { if (Q_UNLIKELY(qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH"))) { QDateTime dt; dt.setSecsSinceEpoch(qEnvironmentVariableIntValue("SOURCE_DATE_EPOCH")); CollectionConfiguration::updateLastRegisterTime(helpEngine, dt); - } - else + } else { CollectionConfiguration::updateLastRegisterTime(helpEngine); + } + } if (!config.title().isEmpty()) CollectionConfiguration::setWindowTitle(helpEngine, config.title()); diff --git a/tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp b/tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp index 63ee55cbc..5a39cc923 100644 --- a/tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp +++ b/tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp @@ -135,7 +135,7 @@ void tst_QHelpContentModel::contentItemAt() QCOMPARE(h.currentFilter(), QString("unfiltered")); - QModelIndex root = m->index(0, 0); + QModelIndex root = m->index(2, 0); if (!root.isValid()) QFAIL("Cannot retrieve root item!"); QHelpContentItem *item = m->contentItemAt(root); @@ -146,7 +146,7 @@ void tst_QHelpContentModel::contentItemAt() item = m->contentItemAt(m->index(4, 0, root)); QCOMPARE(item->title(), QString("qmake Concepts")); - item = m->contentItemAt(m->index(3, 0)); + item = m->contentItemAt(m->index(1, 0)); QCOMPARE(item->title(), QString("Fancy Manual")); w.start(); diff --git a/tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp b/tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp index 494355c5d..713c229ce 100644 --- a/tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp +++ b/tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp @@ -170,11 +170,11 @@ void tst_QHelpIndexModel::linksForIndex() QCOMPARE(map.count(), 2); QCOMPARE(map.contains("Test Manual"), true); QCOMPARE(map.value("Test Manual"), - QUrl("qthelp://trolltech.com.1.0.0.test/testFolder/test.html#foo")); + QUrl("qthelp://trolltech.com.1-0-0.test/testFolder/test.html#foo")); QCOMPARE(map.contains("Fancy"), true); QCOMPARE(map.value("Fancy"), - QUrl("qthelp://trolltech.com.1.0.0.test/testFolder/fancy.html#foo")); + QUrl("qthelp://trolltech.com.1-0-0.test/testFolder/fancy.html#foo")); map = m->linksForKeyword("foobar"); QCOMPARE(map.count(), 1); @@ -193,7 +193,7 @@ void tst_QHelpIndexModel::linksForIndex() QCOMPARE(map.count(), 1); QCOMPARE(map.contains("Test Manual"), true); QCOMPARE(map.value("Test Manual"), - QUrl("qthelp://trolltech.com.1.0.0.test/testFolder/test.html#foo")); + QUrl("qthelp://trolltech.com.1-0-0.test/testFolder/test.html#foo")); } QTEST_MAIN(tst_QHelpIndexModel) |