diff options
-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) |