| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Assistant of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qhelpcollectionhandler_p.h" |
| #include "qhelp_global.h" |
| #include "qhelpdbreader_p.h" |
| #include "qhelpfilterdata.h" |
| |
| #include <QtCore/QDataStream> |
| #include <QtCore/QDateTime> |
| #include <QtCore/QDir> |
| #include <QtCore/QFile> |
| #include <QtCore/QFileInfo> |
| #include <QtCore/QTimer> |
| #include <QtCore/QVector> |
| #include <QtCore/QVersionNumber> |
| |
| #include <QtHelp/QHelpLink> |
| |
| #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) |
| { |
| const QFileInfo fi(m_collectionFile); |
| if (!fi.isAbsolute()) |
| m_collectionFile = fi.absoluteFilePath(); |
| } |
| |
| QHelpCollectionHandler::~QHelpCollectionHandler() |
| { |
| closeDB(); |
| } |
| |
| bool QHelpCollectionHandler::isDBOpened() const |
| { |
| if (m_query) |
| return true; |
| emit error(tr("The collection file \"%1\" is not set up yet."). |
| arg(m_collectionFile)); |
| return false; |
| } |
| |
| void QHelpCollectionHandler::closeDB() |
| { |
| if (!m_query) |
| return; |
| |
| delete m_query; |
| m_query = nullptr; |
| QSqlDatabase::removeDatabase(m_connectionName); |
| m_connectionName = QString(); |
| } |
| |
| QString QHelpCollectionHandler::collectionFile() const |
| { |
| return m_collectionFile; |
| } |
| |
| bool QHelpCollectionHandler::openCollectionFile() |
| { |
| if (m_query) |
| return true; |
| |
| m_connectionName = QHelpGlobal::uniquifyConnectionName( |
| QLatin1String("QHelpCollectionHandler"), this); |
| { |
| QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), |
| m_connectionName); |
| if (db.driver() |
| && db.driver()->lastError().type() == QSqlError::ConnectionError) { |
| emit error(tr("Cannot load sqlite database driver.")); |
| return false; |
| } |
| |
| db.setDatabaseName(collectionFile()); |
| if (db.open()) |
| m_query = new QSqlQuery(db); |
| |
| if (!m_query) { |
| QSqlDatabase::removeDatabase(m_connectionName); |
| emit error(tr("Cannot open collection file: %1").arg(collectionFile())); |
| return false; |
| } |
| } |
| |
| if (m_readOnly) |
| return true; |
| |
| m_query->exec(QLatin1String("PRAGMA synchronous=OFF")); |
| m_query->exec(QLatin1String("PRAGMA cache_size=3000")); |
| |
| m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' " |
| "AND Name=\'NamespaceTable\'")); |
| m_query->next(); |
| |
| 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())); |
| return false; |
| } |
| } |
| |
| bool indexAndNamespaceFilterTablesMissing = false; |
| |
| const QStringList newTables = { |
| QLatin1String("IndexTable"), |
| QLatin1String("FileNameTable"), |
| QLatin1String("ContentsTable"), |
| QLatin1String("FileFilterTable"), |
| QLatin1String("IndexFilterTable"), |
| QLatin1String("ContentsFilterTable"), |
| QLatin1String("FileAttributeSetTable"), |
| QLatin1String("OptimizedFilterTable"), |
| QLatin1String("TimeStampTable"), |
| QLatin1String("VersionTable"), |
| QLatin1String("Filter"), |
| QLatin1String("ComponentTable"), |
| QLatin1String("ComponentMapping"), |
| QLatin1String("ComponentFilter"), |
| QLatin1String("VersionFilter") |
| }; |
| |
| QString queryString = QLatin1String("SELECT COUNT(*) " |
| "FROM sqlite_master " |
| "WHERE TYPE=\'table\'"); |
| queryString.append(QLatin1String(" AND (Name=\'")); |
| queryString.append(newTables.join(QLatin1String("\' OR Name=\'"))); |
| queryString.append(QLatin1String("\')")); |
| |
| m_query->exec(queryString); |
| m_query->next(); |
| if (m_query->value(0).toInt() != newTables.count()) { |
| if (!recreateIndexAndNamespaceFilterTables(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, true)) { |
| emit error(tr("Cannot register index tables in file %1.").arg(collectionFile())); |
| 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)) { |
| emit error(tr("Cannot unregister index tables in file %1.").arg(collectionFile())); |
| return false; |
| } |
| } |
| transaction.commit(); |
| |
| for (const QHelpCollectionHandler::FileInfo &info : docList) { |
| if (!hasTimeStampInfo(info.namespaceName) |
| && !registerIndexAndNamespaceFilterTables(info.namespaceName)) { |
| // we may have a doc registered without a timestamp |
| // and the doc may be missing currently |
| unregisterDocumentation(info.namespaceName); |
| } |
| } |
| |
| 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(); |
| m_query->clear(); |
| 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; |
| |
| m_query->clear(); |
| 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) |
| return false; |
| |
| const QFileInfo fi(fileName); |
| if (fi.exists()) { |
| emit error(tr("The collection file \"%1\" already exists."). |
| arg(fileName)); |
| return false; |
| } |
| |
| if (!fi.absoluteDir().exists() && !QDir().mkpath(fi.absolutePath())) { |
| emit error(tr("Cannot create directory: %1").arg(fi.absolutePath())); |
| return false; |
| } |
| |
| const QString &colFile = fi.absoluteFilePath(); |
| const QString &connectionName = QHelpGlobal::uniquifyConnectionName( |
| QLatin1String("QHelpCollectionHandlerCopy"), this); |
| QSqlQuery *copyQuery = nullptr; |
| bool openingOk = true; |
| { |
| QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), connectionName); |
| db.setDatabaseName(colFile); |
| openingOk = db.open(); |
| if (openingOk) |
| copyQuery = new QSqlQuery(db); |
| } |
| |
| if (!openingOk) { |
| emit error(tr("Cannot open collection file: %1").arg(colFile)); |
| return false; |
| } |
| |
| copyQuery->exec(QLatin1String("PRAGMA synchronous=OFF")); |
| copyQuery->exec(QLatin1String("PRAGMA cache_size=3000")); |
| |
| if (!createTables(copyQuery) || !recreateIndexAndNamespaceFilterTables(copyQuery)) { |
| emit error(tr("Cannot copy collection file: %1").arg(colFile)); |
| delete copyQuery; |
| return false; |
| } |
| |
| const QString &oldBaseDir = QFileInfo(collectionFile()).absolutePath(); |
| const QFileInfo newColFi(colFile); |
| m_query->exec(QLatin1String("SELECT Name, FilePath FROM NamespaceTable")); |
| while (m_query->next()) { |
| copyQuery->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toString()); |
| QString oldFilePath = m_query->value(1).toString(); |
| if (!QDir::isAbsolutePath(oldFilePath)) |
| oldFilePath = oldBaseDir + QLatin1Char('/') + oldFilePath; |
| copyQuery->bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath)); |
| copyQuery->exec(); |
| } |
| |
| m_query->exec(QLatin1String("SELECT NamespaceId, Name FROM FolderTable")); |
| while (m_query->next()) { |
| copyQuery->prepare(QLatin1String("INSERT INTO FolderTable VALUES(NULL, ?, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toString()); |
| copyQuery->bindValue(1, m_query->value(1).toString()); |
| copyQuery->exec(); |
| } |
| |
| m_query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable")); |
| while (m_query->next()) { |
| copyQuery->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toString()); |
| copyQuery->exec(); |
| } |
| |
| m_query->exec(QLatin1String("SELECT Name FROM FilterNameTable")); |
| while (m_query->next()) { |
| copyQuery->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toString()); |
| copyQuery->exec(); |
| } |
| |
| m_query->exec(QLatin1String("SELECT NameId, FilterAttributeId FROM FilterTable")); |
| while (m_query->next()) { |
| copyQuery->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toInt()); |
| copyQuery->bindValue(1, m_query->value(1).toInt()); |
| copyQuery->exec(); |
| } |
| |
| m_query->exec(QLatin1String("SELECT Key, Value FROM SettingsTable")); |
| while (m_query->next()) { |
| if (m_query->value(0).toString() == QLatin1String("FTS5IndexedNamespaces")) |
| continue; |
| copyQuery->prepare(QLatin1String("INSERT INTO SettingsTable VALUES(?, ?)")); |
| copyQuery->bindValue(0, m_query->value(0).toString()); |
| copyQuery->bindValue(1, m_query->value(1)); |
| copyQuery->exec(); |
| } |
| |
| copyQuery->clear(); |
| delete copyQuery; |
| QSqlDatabase::removeDatabase(connectionName); |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::createTables(QSqlQuery *query) |
| { |
| const QStringList tables = QStringList() |
| << QLatin1String("CREATE TABLE NamespaceTable (" |
| "Id INTEGER PRIMARY KEY, " |
| "Name TEXT, " |
| "FilePath TEXT )") |
| << QLatin1String("CREATE TABLE FolderTable (" |
| "Id INTEGER PRIMARY KEY, " |
| "NamespaceId INTEGER, " |
| "Name TEXT )") |
| << QLatin1String("CREATE TABLE FilterAttributeTable (" |
| "Id INTEGER PRIMARY KEY, " |
| "Name TEXT )") |
| << QLatin1String("CREATE TABLE FilterNameTable (" |
| "Id INTEGER PRIMARY KEY, " |
| "Name TEXT )") |
| << QLatin1String("CREATE TABLE FilterTable (" |
| "NameId INTEGER, " |
| "FilterAttributeId INTEGER )") |
| << QLatin1String("CREATE TABLE SettingsTable (" |
| "Key TEXT PRIMARY KEY, " |
| "Value BLOB )"); |
| |
| for (const QString &q : tables) { |
| if (!query->exec(q)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::recreateIndexAndNamespaceFilterTables(QSqlQuery *query) |
| { |
| const QStringList tables = QStringList() |
| << QLatin1String("DROP TABLE IF EXISTS FileNameTable") |
| << QLatin1String("DROP TABLE IF EXISTS IndexTable") |
| << QLatin1String("DROP TABLE IF EXISTS ContentsTable") |
| << QLatin1String("DROP TABLE IF EXISTS FileFilterTable") // legacy |
| << QLatin1String("DROP TABLE IF EXISTS IndexFilterTable") // legacy |
| << QLatin1String("DROP TABLE IF EXISTS ContentsFilterTable") // legacy |
| << QLatin1String("DROP TABLE IF EXISTS FileAttributeSetTable") // legacy |
| << QLatin1String("DROP TABLE IF EXISTS OptimizedFilterTable") // legacy |
| << QLatin1String("DROP TABLE IF EXISTS TimeStampTable") |
| << QLatin1String("DROP TABLE IF EXISTS VersionTable") |
| << QLatin1String("DROP TABLE IF EXISTS Filter") |
| << QLatin1String("DROP TABLE IF EXISTS ComponentTable") |
| << QLatin1String("DROP TABLE IF EXISTS ComponentMapping") |
| << QLatin1String("DROP TABLE IF EXISTS ComponentFilter") |
| << QLatin1String("DROP TABLE IF EXISTS VersionFilter") |
| << 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)") |
| << QLatin1String("CREATE TABLE VersionTable (" |
| "NamespaceId INTEGER, " |
| "Version TEXT)") |
| << QLatin1String("CREATE TABLE Filter (" |
| "FilterId INTEGER PRIMARY KEY, " |
| "Name TEXT)") |
| << QLatin1String("CREATE TABLE ComponentTable (" |
| "ComponentId INTEGER PRIMARY KEY, " |
| "Name TEXT)") |
| << QLatin1String("CREATE TABLE ComponentMapping (" |
| "ComponentId INTEGER, " |
| "NamespaceId INTEGER)") |
| << QLatin1String("CREATE TABLE ComponentFilter (" |
| "ComponentName TEXT, " |
| "FilterId INTEGER)") |
| << QLatin1String("CREATE TABLE VersionFilter (" |
| "Version TEXT, " |
| "FilterId INTEGER)"); |
| |
| for (const QString &q : tables) { |
| if (!query->exec(q)) |
| return false; |
| } |
| return true; |
| } |
| |
| QStringList QHelpCollectionHandler::customFilters() const |
| { |
| QStringList list; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT Name FROM FilterNameTable")); |
| while (m_query->next()) |
| list.append(m_query->value(0).toString()); |
| } |
| return list; |
| } |
| |
| |
| QStringList QHelpCollectionHandler::filters() const |
| { |
| QStringList list; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT Name FROM Filter ORDER BY Name")); |
| while (m_query->next()) |
| list.append(m_query->value(0).toString()); |
| } |
| return list; |
| } |
| |
| QStringList QHelpCollectionHandler::availableComponents() const |
| { |
| QStringList list; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT DISTINCT Name FROM ComponentTable ORDER BY Name")); |
| while (m_query->next()) |
| list.append(m_query->value(0).toString()); |
| } |
| return list; |
| } |
| |
| QList<QVersionNumber> QHelpCollectionHandler::availableVersions() const |
| { |
| QList<QVersionNumber> list; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT DISTINCT Version FROM VersionTable ORDER BY Version")); |
| while (m_query->next()) |
| list.append(QVersionNumber::fromString(m_query->value(0).toString())); |
| } |
| return list; |
| } |
| |
| QMap<QString, QString> QHelpCollectionHandler::namespaceToComponent() const |
| { |
| QMap<QString, QString> result; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT " |
| "NamespaceTable.Name, " |
| "ComponentTable.Name " |
| "FROM NamespaceTable, " |
| "ComponentTable, " |
| "ComponentMapping " |
| "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId " |
| "AND ComponentMapping.ComponentId = ComponentTable.ComponentId")); |
| while (m_query->next()) |
| result.insert(m_query->value(0).toString(), m_query->value(1).toString()); |
| } |
| return result; |
| } |
| |
| QMap<QString, QVersionNumber> QHelpCollectionHandler::namespaceToVersion() const |
| { |
| QMap<QString, QVersionNumber> result; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT " |
| "NamespaceTable.Name, " |
| "VersionTable.Version " |
| "FROM NamespaceTable, " |
| "VersionTable " |
| "WHERE NamespaceTable.Id = VersionTable.NamespaceId")); |
| while (m_query->next()) { |
| result.insert(m_query->value(0).toString(), |
| QVersionNumber::fromString(m_query->value(1).toString())); |
| } |
| } |
| return result; |
| } |
| |
| QHelpFilterData QHelpCollectionHandler::filterData(const QString &filterName) const |
| { |
| QStringList components; |
| QList<QVersionNumber> versions; |
| if (m_query) { |
| m_query->prepare(QLatin1String("SELECT ComponentFilter.ComponentName " |
| "FROM ComponentFilter, Filter " |
| "WHERE ComponentFilter.FilterId = Filter.FilterId " |
| "AND Filter.Name = ? " |
| "ORDER BY ComponentFilter.ComponentName")); |
| m_query->bindValue(0, filterName); |
| m_query->exec(); |
| while (m_query->next()) |
| components.append(m_query->value(0).toString()); |
| |
| m_query->prepare(QLatin1String("SELECT VersionFilter.Version " |
| "FROM VersionFilter, Filter " |
| "WHERE VersionFilter.FilterId = Filter.FilterId " |
| "AND Filter.Name = ? " |
| "ORDER BY VersionFilter.Version")); |
| m_query->bindValue(0, filterName); |
| m_query->exec(); |
| while (m_query->next()) |
| versions.append(QVersionNumber::fromString(m_query->value(0).toString())); |
| |
| } |
| QHelpFilterData data; |
| data.setComponents(components); |
| data.setVersions(versions); |
| return data; |
| } |
| |
| bool QHelpCollectionHandler::setFilterData(const QString &filterName, |
| const QHelpFilterData &filterData) |
| { |
| if (!removeFilter(filterName)) |
| return false; |
| |
| m_query->prepare(QLatin1String("INSERT INTO Filter " |
| "VALUES (NULL, ?)")); |
| m_query->bindValue(0, filterName); |
| if (!m_query->exec()) |
| return false; |
| |
| const int filterId = m_query->lastInsertId().toInt(); |
| |
| QVariantList componentList; |
| QVariantList versionList; |
| QVariantList filterIdList; |
| |
| for (const QString &component : filterData.components()) { |
| componentList.append(component); |
| filterIdList.append(filterId); |
| } |
| |
| m_query->prepare(QLatin1String("INSERT INTO ComponentFilter " |
| "VALUES (?, ?)")); |
| m_query->addBindValue(componentList); |
| m_query->addBindValue(filterIdList); |
| if (!m_query->execBatch()) |
| return false; |
| |
| filterIdList.clear(); |
| for (const QVersionNumber &version : filterData.versions()) { |
| versionList.append(version.isNull() ? QString() : version.toString()); |
| filterIdList.append(filterId); |
| } |
| |
| m_query->prepare(QLatin1String("INSERT INTO VersionFilter " |
| "VALUES (?, ?)")); |
| m_query->addBindValue(versionList); |
| m_query->addBindValue(filterIdList); |
| if (!m_query->execBatch()) |
| return false; |
| |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::removeFilter(const QString &filterName) |
| { |
| m_query->prepare(QLatin1String("SELECT FilterId " |
| "FROM Filter " |
| "WHERE Name = ?")); |
| m_query->bindValue(0, filterName); |
| if (!m_query->exec()) |
| return false; |
| |
| if (!m_query->next()) |
| return true; // no filter in DB |
| |
| const int filterId = m_query->value(0).toInt(); |
| |
| m_query->prepare(QLatin1String("DELETE FROM Filter " |
| "WHERE Filter.Name = ?")); |
| m_query->bindValue(0, filterName); |
| if (!m_query->exec()) |
| return false; |
| |
| m_query->prepare(QLatin1String("DELETE FROM ComponentFilter " |
| "WHERE ComponentFilter.FilterId = ?")); |
| m_query->bindValue(0, filterId); |
| if (!m_query->exec()) |
| return false; |
| |
| m_query->prepare(QLatin1String("DELETE FROM VersionFilter " |
| "WHERE VersionFilter.FilterId = ?")); |
| m_query->bindValue(0, filterId); |
| if (!m_query->exec()) |
| return false; |
| |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::removeCustomFilter(const QString &filterName) |
| { |
| if (!isDBOpened() || filterName.isEmpty()) |
| return false; |
| |
| int filterNameId = -1; |
| m_query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?")); |
| m_query->bindValue(0, filterName); |
| m_query->exec(); |
| if (m_query->next()) |
| filterNameId = m_query->value(0).toInt(); |
| |
| if (filterNameId < 0) { |
| emit error(tr("Unknown filter \"%1\".").arg(filterName)); |
| return false; |
| } |
| |
| m_query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?")); |
| m_query->bindValue(0, filterNameId); |
| m_query->exec(); |
| |
| m_query->prepare(QLatin1String("DELETE FROM FilterNameTable WHERE Id=?")); |
| m_query->bindValue(0, filterNameId); |
| m_query->exec(); |
| |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::addCustomFilter(const QString &filterName, |
| const QStringList &attributes) |
| { |
| if (!isDBOpened() || filterName.isEmpty()) |
| return false; |
| |
| int nameId = -1; |
| m_query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?")); |
| m_query->bindValue(0, filterName); |
| m_query->exec(); |
| if (m_query->next()) |
| nameId = m_query->value(0).toInt(); |
| |
| m_query->exec(QLatin1String("SELECT Id, Name FROM FilterAttributeTable")); |
| QStringList idsToInsert = attributes; |
| QMap<QString, int> attributeMap; |
| while (m_query->next()) { |
| // 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)) { |
| m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); |
| m_query->bindValue(0, id); |
| m_query->exec(); |
| attributeMap.insert(id, m_query->lastInsertId().toInt()); |
| } |
| |
| if (nameId < 0) { |
| m_query->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)")); |
| m_query->bindValue(0, filterName); |
| if (m_query->exec()) |
| nameId = m_query->lastInsertId().toInt(); |
| } |
| |
| if (nameId < 0) { |
| emit error(tr("Cannot register filter %1.").arg(filterName)); |
| return false; |
| } |
| |
| m_query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?")); |
| m_query->bindValue(0, nameId); |
| m_query->exec(); |
| |
| for (const QString &att : attributes) { |
| m_query->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)")); |
| m_query->bindValue(0, nameId); |
| m_query->bindValue(1, attributeMap[att]); |
| if (!m_query->exec()) |
| return false; |
| } |
| return true; |
| } |
| |
| QHelpCollectionHandler::FileInfo QHelpCollectionHandler::registeredDocumentation( |
| const QString &namespaceName) const |
| { |
| FileInfo fileInfo; |
| |
| if (!m_query) |
| 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; |
| |
| fileInfo.namespaceName = m_query->value(0).toString(); |
| fileInfo.fileName = m_query->value(1).toString(); |
| fileInfo.folderName = m_query->value(2).toString(); |
| |
| m_query->clear(); |
| |
| 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()) { |
| 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; |
| } |
| |
| bool QHelpCollectionHandler::registerDocumentation(const QString &fileName) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName( |
| QLatin1String("QHelpCollectionHandler"), this), nullptr); |
| if (!reader.init()) { |
| emit error(tr("Cannot open documentation file %1.").arg(fileName)); |
| return false; |
| } |
| |
| const QString &ns = reader.namespaceName(); |
| if (ns.isEmpty()) { |
| emit error(tr("Invalid documentation file \"%1\".").arg(fileName)); |
| return false; |
| } |
| |
| const int nsId = registerNamespace(ns, fileName); |
| if (nsId < 1) |
| return false; |
| |
| const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId); |
| if (vfId < 1) |
| return false; |
| |
| registerVersion(reader.version(), nsId); |
| registerFilterAttributes(reader.filterAttributeSets(), nsId); // qset, what happens when removing documentation? |
| for (const QString &filterName : reader.customFilters()) |
| addCustomFilter(filterName, reader.filterAttributes(filterName)); |
| |
| if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName)) |
| return false; |
| |
| return true; |
| } |
| |
| bool QHelpCollectionHandler::unregisterDocumentation(const QString &namespaceName) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| m_query->prepare(QLatin1String("SELECT Id FROM NamespaceTable WHERE Name = ?")); |
| m_query->bindValue(0, namespaceName); |
| m_query->exec(); |
| |
| if (!m_query->next()) { |
| emit error(tr("The namespace %1 was not registered.").arg(namespaceName)); |
| return false; |
| } |
| |
| const int nsId = 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("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; |
| |
| const int count = m_query->value(0).toInt(); |
| m_query->clear(); |
| |
| return count; |
| } |
| |
| static QString prepareFilterQuery(const QString &filterName) |
| { |
| if (filterName.isEmpty()) |
| return QString(); |
| |
| return QString::fromLatin1(" AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) " |
| "AND (" |
| "(NOT EXISTS(" // 1. filter by component |
| "SELECT * FROM " |
| "ComponentFilter, " |
| "Filter " |
| "WHERE ComponentFilter.FilterId = Filter.FilterId " |
| "AND Filter.Name = ?) " |
| "OR NamespaceTable.Id IN (" |
| "SELECT " |
| "NamespaceTable.Id " |
| "FROM " |
| "NamespaceTable, " |
| "ComponentTable, " |
| "ComponentMapping, " |
| "ComponentFilter, " |
| "Filter " |
| "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id " |
| "AND ComponentTable.ComponentId = ComponentMapping.ComponentId " |
| "AND ((ComponentTable.Name = ComponentFilter.ComponentName) " |
| "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) " |
| "AND ComponentFilter.FilterId = Filter.FilterId " |
| "AND Filter.Name = ?))" |
| " AND " |
| "(NOT EXISTS(" // 2. filter by version |
| "SELECT * FROM " |
| "VersionFilter, " |
| "Filter " |
| "WHERE VersionFilter.FilterId = Filter.FilterId " |
| "AND Filter.Name = ?) " |
| "OR NamespaceTable.Id IN (" |
| "SELECT " |
| "NamespaceTable.Id " |
| "FROM " |
| "NamespaceTable, " |
| "VersionFilter, " |
| "VersionTable, " |
| "Filter " |
| "WHERE VersionFilter.FilterId = Filter.FilterId " |
| "AND ((VersionFilter.Version = VersionTable.Version) " |
| "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) " |
| "AND VersionTable.NamespaceId = NamespaceTable.Id " |
| "AND Filter.Name = ?))" |
| ")"); |
| } |
| |
| static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName) |
| { |
| if (filterName.isEmpty()) |
| return; |
| |
| query->bindValue(bindStart, filterName); |
| query->bindValue(bindStart + 1, filterName); |
| query->bindValue(bindStart + 2, filterName); |
| query->bindValue(bindStart + 3, filterName); |
| query->bindValue(bindStart + 4, filterName); |
| } |
| |
| 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(); |
| |
| if (namespaceList.contains(fileInfo.namespaceName)) |
| return fileInfo.namespaceName; |
| |
| const QString originalVersion = namespaceVersion(fileInfo.namespaceName); |
| |
| for (const QString &ns : namespaceList) { |
| const QString nsVersion = namespaceVersion(ns); |
| if (originalVersion == nsVersion) |
| return ns; |
| } |
| |
| // TODO: still, we may like to return the ns for the highest available version |
| return namespaceList.first(); |
| } |
| |
| QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, |
| const QString &filterName) 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(filterName); |
| |
| m_query->prepare(filterQuery); |
| m_query->bindValue(0, fileInfo.folderName); |
| m_query->bindValue(1, fileInfo.fileName); |
| bindFilterQuery(m_query, 2, filterName); |
| |
| 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(); |
| |
| if (namespaceList.contains(fileInfo.namespaceName)) |
| return fileInfo.namespaceName; |
| |
| const QString originalVersion = namespaceVersion(fileInfo.namespaceName); |
| |
| for (const QString &ns : namespaceList) { |
| const QString nsVersion = namespaceVersion(ns); |
| if (originalVersion == nsVersion) |
| return ns; |
| } |
| |
| // TODO: still, we may like to return the ns for the highest available version |
| return namespaceList.first(); |
| } |
| |
| 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; |
| } |
| |
| QStringList QHelpCollectionHandler::files(const QString &namespaceName, |
| const QString &filterName, |
| 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(filterName); |
| |
| 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, filterName); |
| |
| 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; |
| } |
| |
| QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QString &filterName) const |
| { |
| if (!isDBOpened()) |
| return QUrl(); |
| |
| const QString namespaceName = namespaceForFile(url, filterName); |
| 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, QString()); |
| 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; |
| } |
| |
| |
| QStringList QHelpCollectionHandler::indicesForFilter(const QString &filterName) 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(filterName) |
| + QLatin1String(" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"); |
| |
| m_query->prepare(filterQuery); |
| bindFilterQuery(m_query, 0, filterName); |
| |
| m_query->exec(); |
| |
| while (m_query->next()) |
| indices.append(m_query->value(0).toString()); |
| |
| return indices; |
| } |
| |
| static QString getTitle(const QByteArray &contents) |
| { |
| if (!contents.size()) |
| return QString(); |
| |
| int depth = 0; |
| QString link; |
| QString title; |
| |
| QDataStream s(contents); |
| s >> depth; |
| s >> link; |
| s >> title; |
| |
| return title; |
| } |
| |
| 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, " |
| "VersionTable.Version " |
| "FROM " |
| "FolderTable, " |
| "NamespaceTable, " |
| "ContentsTable, " |
| "VersionTable " |
| "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " |
| "AND NamespaceTable.Id = FolderTable.NamespaceId " |
| "AND ContentsTable.NamespaceId = NamespaceTable.Id " |
| "AND VersionTable.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, QMap<QVersionNumber, ContentsData>> contentsMap; |
| |
| while (m_query->next()) { |
| const QString namespaceName = m_query->value(0).toString(); |
| const QByteArray contents = m_query->value(2).toByteArray(); |
| const QString versionString = m_query->value(3).toString(); |
| |
| const QString title = getTitle(contents); |
| const QVersionNumber version = QVersionNumber::fromString(versionString); |
| // get existing or insert a new one otherwise |
| ContentsData &contentsData = contentsMap[title][version]; |
| contentsData.namespaceName = namespaceName; |
| contentsData.folderName = m_query->value(1).toString(); |
| contentsData.contentsList.append(contents); |
| } |
| |
| QList<QHelpCollectionHandler::ContentsData> result; |
| for (const auto &versionContents : qAsConst(contentsMap)) { |
| // insert items in the reverse order of version number |
| const auto itBegin = versionContents.constBegin(); |
| auto it = versionContents.constEnd(); |
| while (it != itBegin) { |
| --it; |
| result.append(it.value()); |
| } |
| } |
| |
| return result; |
| } |
| |
| QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForFilter(const QString &filterName) const |
| { |
| if (!isDBOpened()) |
| return QList<ContentsData>(); |
| |
| const QString filterlessQuery = QString::fromLatin1( |
| "SELECT DISTINCT " |
| "NamespaceTable.Name, " |
| "FolderTable.Name, " |
| "ContentsTable.Data, " |
| "VersionTable.Version " |
| "FROM " |
| "FolderTable, " |
| "NamespaceTable, " |
| "ContentsTable, " |
| "VersionTable " |
| "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " |
| "AND NamespaceTable.Id = FolderTable.NamespaceId " |
| "AND ContentsTable.NamespaceId = NamespaceTable.Id " |
| "AND VersionTable.NamespaceId = NamespaceTable.Id"); |
| |
| const QString filterQuery = filterlessQuery |
| + prepareFilterQuery(filterName); |
| |
| m_query->prepare(filterQuery); |
| bindFilterQuery(m_query, 0, filterName); |
| |
| m_query->exec(); |
| |
| QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap; |
| |
| while (m_query->next()) { |
| const QString namespaceName = m_query->value(0).toString(); |
| const QByteArray contents = m_query->value(2).toByteArray(); |
| const QString versionString = m_query->value(3).toString(); |
| |
| const QString title = getTitle(contents); |
| const QVersionNumber version = QVersionNumber::fromString(versionString); |
| // get existing or insert a new one otherwise |
| ContentsData &contentsData = contentsMap[title][version]; |
| contentsData.namespaceName = namespaceName; |
| contentsData.folderName = m_query->value(1).toString(); |
| contentsData.contentsList.append(contents); |
| } |
| |
| QList<QHelpCollectionHandler::ContentsData> result; |
| for (const auto &versionContents : qAsConst(contentsMap)) { |
| // insert items in the reverse order of version number |
| const auto itBegin = versionContents.constBegin(); |
| auto it = versionContents.constEnd(); |
| while (it != itBegin) { |
| --it; |
| result.append(it.value()); |
| } |
| } |
| |
| return result; |
| } |
| |
| bool QHelpCollectionHandler::removeCustomValue(const QString &key) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| m_query->prepare(QLatin1String("DELETE FROM SettingsTable WHERE Key=?")); |
| m_query->bindValue(0, key); |
| return m_query->exec(); |
| } |
| |
| QVariant QHelpCollectionHandler::customValue(const QString &key, |
| const QVariant &defaultValue) const |
| { |
| if (!m_query) |
| return defaultValue; |
| |
| m_query->prepare(QLatin1String("SELECT COUNT(Key) FROM SettingsTable WHERE Key=?")); |
| m_query->bindValue(0, key); |
| if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) { |
| m_query->clear(); |
| return defaultValue; |
| } |
| |
| m_query->clear(); |
| m_query->prepare(QLatin1String("SELECT Value FROM SettingsTable WHERE Key=?")); |
| m_query->bindValue(0, key); |
| if (m_query->exec() && m_query->next()) { |
| const QVariant &value = m_query->value(0); |
| m_query->clear(); |
| return value; |
| } |
| |
| return defaultValue; |
| } |
| |
| bool QHelpCollectionHandler::setCustomValue(const QString &key, |
| const QVariant &value) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| m_query->prepare(QLatin1String("SELECT Value FROM SettingsTable WHERE Key=?")); |
| m_query->bindValue(0, key); |
| m_query->exec(); |
| if (m_query->next()) { |
| m_query->prepare(QLatin1String("UPDATE SettingsTable SET Value=? where Key=?")); |
| m_query->bindValue(0, value); |
| m_query->bindValue(1, key); |
| } else { |
| m_query->prepare(QLatin1String("INSERT INTO SettingsTable VALUES(?, ?)")); |
| m_query->bindValue(0, key); |
| m_query->bindValue(1, value); |
| } |
| return m_query->exec(); |
| } |
| |
| bool QHelpCollectionHandler::registerFilterAttributes(const QList<QStringList> &attributeSets, |
| int nsId) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| m_query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable")); |
| QSet<QString> atts; |
| while (m_query->next()) |
| atts.insert(m_query->value(0).toString()); |
| |
| 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 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 |
| { |
| QStringList list; |
| if (m_query) { |
| m_query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable")); |
| while (m_query->next()) |
| list.append(m_query->value(0).toString()); |
| } |
| return list; |
| } |
| |
| QStringList QHelpCollectionHandler::filterAttributes(const QString &filterName) const |
| { |
| QStringList list; |
| if (m_query) { |
| m_query->prepare(QLatin1String( |
| "SELECT " |
| "FilterAttributeTable.Name " |
| "FROM " |
| "FilterAttributeTable, " |
| "FilterTable, " |
| "FilterNameTable " |
| "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId " |
| "AND FilterTable.NameId = FilterNameTable.Id " |
| "AND FilterNameTable.Name=?")); |
| m_query->bindValue(0, filterName); |
| m_query->exec(); |
| while (m_query->next()) |
| list.append(m_query->value(0).toString()); |
| } |
| 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; |
| } |
| |
| QString QHelpCollectionHandler::namespaceVersion(const QString &namespaceName) const |
| { |
| if (!m_query) |
| return QString(); |
| |
| m_query->prepare(QLatin1String("SELECT " |
| "VersionTable.Version " |
| "FROM " |
| "NamespaceTable, " |
| "VersionTable " |
| "WHERE NamespaceTable.Name = ? " |
| "AND NamespaceTable.Id = VersionTable.NamespaceId")); |
| m_query->bindValue(0, namespaceName); |
| if (!m_query->exec() || !m_query->next()) |
| return QString(); |
| |
| const QString ret = m_query->value(0).toString(); |
| m_query->clear(); |
| |
| return ret; |
| } |
| |
| int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QString &fileName) |
| { |
| const int errorValue = -1; |
| if (!m_query) |
| return errorValue; |
| |
| m_query->prepare(QLatin1String("SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?")); |
| m_query->bindValue(0, nspace); |
| m_query->exec(); |
| while (m_query->next()) { |
| if (m_query->value(0).toInt() > 0) { |
| emit error(tr("Namespace %1 already exists.").arg(nspace)); |
| return errorValue; |
| } |
| } |
| |
| QFileInfo fi(m_collectionFile); |
| m_query->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)")); |
| m_query->bindValue(0, nspace); |
| m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName)); |
| int namespaceId = errorValue; |
| if (m_query->exec()) { |
| namespaceId = m_query->lastInsertId().toInt(); |
| m_query->clear(); |
| } |
| if (namespaceId < 1) { |
| emit error(tr("Cannot register namespace \"%1\".").arg(nspace)); |
| return errorValue; |
| } |
| return namespaceId; |
| } |
| |
| int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId) |
| { |
| if (!m_query) |
| return false; |
| |
| m_query->prepare(QLatin1String("INSERT INTO FolderTable VALUES(NULL, ?, ?)")); |
| m_query->bindValue(0, namespaceId); |
| m_query->bindValue(1, folderName); |
| |
| int virtualId = -1; |
| if (m_query->exec()) { |
| virtualId = m_query->lastInsertId().toInt(); |
| m_query->clear(); |
| } |
| if (virtualId < 1) { |
| emit error(tr("Cannot register virtual folder '%1'.").arg(folderName)); |
| return -1; |
| } |
| |
| if (registerComponent(folderName, namespaceId) < 0) |
| return -1; |
| |
| return virtualId; |
| } |
| |
| int QHelpCollectionHandler::registerComponent(const QString &componentName, int namespaceId) |
| { |
| m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentTable WHERE Name = ?")); |
| m_query->bindValue(0, componentName); |
| if (!m_query->exec()) |
| return -1; |
| |
| if (!m_query->next()) { |
| m_query->prepare(QLatin1String("INSERT INTO ComponentTable VALUES(NULL, ?)")); |
| m_query->bindValue(0, componentName); |
| if (!m_query->exec()) |
| return -1; |
| |
| m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentTable WHERE Name = ?")); |
| m_query->bindValue(0, componentName); |
| if (!m_query->exec() || !m_query->next()) |
| return -1; |
| } |
| |
| const int componentId = m_query->value(0).toInt(); |
| |
| m_query->prepare(QLatin1String("INSERT INTO ComponentMapping VALUES(?, ?)")); |
| m_query->bindValue(0, componentId); |
| m_query->bindValue(1, namespaceId); |
| if (!m_query->exec()) |
| return -1; |
| |
| return componentId; |
| } |
| |
| bool QHelpCollectionHandler::registerVersion(const QString &version, int namespaceId) |
| { |
| if (!m_query) |
| return false; |
| |
| m_query->prepare(QLatin1String("INSERT INTO VersionTable " |
| "(NamespaceId, Version) " |
| "VALUES(?, ?)")); |
| m_query->addBindValue(namespaceId); |
| m_query->addBindValue(version); |
| return m_query->exec(); |
| } |
| |
| bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables( |
| const QString &nameSpace, bool createDefaultVersionFilter) |
| { |
| if (!isDBOpened()) |
| return false; |
| |
| 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, Name 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 vfName = m_query->value(1).toString(); |
| |
| const QString absFileName = absoluteDocPath(fileName); |
| QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( |
| fileName, this), this); |
| if (!reader.init()) |
| return false; |
| |
| registerComponent(vfName, nsId); |
| registerVersion(reader.version(), nsId); |
| if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId)) |
| return false; |
| |
| if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName)) |
| return false; |
| |
| if (createDefaultVersionFilter) |
| createVersionFilter(reader.version()); |
| |
| return true; |
| } |
| |
| void QHelpCollectionHandler::createVersionFilter(const QString &version) |
| { |
| if (version.isEmpty()) |
| return; |
| |
| const QVersionNumber versionNumber = QVersionNumber::fromString(version); |
| if (versionNumber.isNull()) |
| return; |
| |
| const QString filterName = tr("Version %1").arg(version); |
| if (filters().contains(filterName)) |
| return; |
| |
| QHelpFilterData filterData; |
| filterData.setVersions(QList<QVersionNumber>() << versionNumber); |
| setFilterData(filterName, filterData); |
| } |
| |
| 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; |
| } |
| |
| m_query->prepare(QLatin1String("INSERT INTO ContentsTable VALUES(NULL, ?, ?)")); |
| m_query->addBindValue(contentsNsIds); |
| m_query->addBindValue(contentsData); |
| if (!m_query->execBatch()) |
| return false; |
| |
| 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()); |
| } |
| |
| 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; |
| |
| m_query->prepare(QLatin1String("DELETE FROM VersionTable WHERE NamespaceId = ?")); |
| m_query->bindValue(0, nsId); |
| if (!m_query->exec()) |
| return false; |
| |
| m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?")); |
| m_query->bindValue(0, nsId); |
| if (!m_query->exec()) |
| return false; |
| |
| if (!m_query->next()) |
| return false; |
| |
| const int componentId = m_query->value(0).toInt(); |
| |
| m_query->prepare(QLatin1String("DELETE FROM ComponentMapping WHERE NamespaceId = ?")); |
| m_query->bindValue(0, nsId); |
| if (!m_query->exec()) |
| return false; |
| |
| m_query->prepare(QLatin1String("SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?")); |
| m_query->bindValue(0, componentId); |
| if (!m_query->exec()) |
| return false; |
| |
| if (!m_query->next()) { // no more namespaces refer to the componentId |
| m_query->prepare(QLatin1String("DELETE FROM ComponentTable WHERE ComponentId = ?")); |
| m_query->bindValue(0, componentId); |
| 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(); |
| |
| static_cast<QMultiMap<QString, QUrl> &>(linkMap).insert(title, buildQUrl( |
| m_query->value(1).toString(), |
| m_query->value(2).toString(), |
| m_query->value(3).toString(), |
| m_query->value(4).toString())); |
| } |
| return linkMap; |
| } |
| |
| QMap<QString, QUrl> QHelpCollectionHandler::linksForIdentifier(const QString &id, |
| const QString &filterName) const |
| { |
| return linksForField(QLatin1String("Identifier"), id, filterName); |
| } |
| |
| QMap<QString, QUrl> QHelpCollectionHandler::linksForKeyword(const QString &keyword, |
| const QString &filterName) const |
| { |
| return linksForField(QLatin1String("Name"), keyword, filterName); |
| } |
| |
| QList<QHelpLink> QHelpCollectionHandler::documentsForIdentifier(const QString &id, |
| const QString &filterName) const |
| { |
| return documentsForField(QLatin1String("Identifier"), id, filterName); |
| } |
| |
| QList<QHelpLink> QHelpCollectionHandler::documentsForKeyword(const QString &keyword, |
| const QString &filterName) const |
| { |
| return documentsForField(QLatin1String("Name"), keyword, filterName); |
| } |
| |
| QMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldName, |
| const QString &fieldValue, |
| const QString &filterName) const |
| { |
| QMap<QString, QUrl> linkMap; |
| const auto documents = documentsForField(fieldName, fieldValue, filterName); |
| for (const auto &document : documents) |
| static_cast<QMultiMap<QString, QUrl> &>(linkMap).insert(document.title, document.url); |
| |
| return linkMap; |
| } |
| |
| QList<QHelpLink> QHelpCollectionHandler::documentsForField(const QString &fieldName, |
| const QString &fieldValue, |
| const QString &filterName) const |
| { |
| QList<QHelpLink> docList; |
| |
| if (!isDBOpened()) |
| return docList; |
| |
| 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(filterName) |
| + QLatin1String(" ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"); |
| |
| m_query->prepare(filterQuery); |
| m_query->bindValue(0, fieldValue); |
| bindFilterQuery(m_query, 1, filterName); |
| |
| 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(); |
| |
| const QUrl url = buildQUrl(m_query->value(1).toString(), |
| m_query->value(2).toString(), |
| m_query->value(3).toString(), |
| m_query->value(4).toString()); |
| docList.append(QHelpLink {url, title}); |
| } |
| return docList; |
| } |
| |
| QStringList QHelpCollectionHandler::namespacesForFilter(const QString &filterName) const |
| { |
| QStringList namespaceList; |
| |
| if (!isDBOpened()) |
| return namespaceList; |
| |
| const QString filterlessQuery = QString::fromLatin1( |
| "SELECT " |
| "NamespaceTable.Name " |
| "FROM " |
| "NamespaceTable " |
| "WHERE TRUE"); |
| |
| const QString filterQuery = filterlessQuery |
| + prepareFilterQuery(filterName); |
| |
| m_query->prepare(filterQuery); |
| bindFilterQuery(m_query, 0, filterName); |
| |
| m_query->exec(); |
| |
| while (m_query->next()) |
| namespaceList.append(m_query->value(0).toString()); |
| |
| return namespaceList; |
| } |
| |
| void QHelpCollectionHandler::setReadOnly(bool readOnly) |
| { |
| m_readOnly = readOnly; |
| } |
| |
| QT_END_NAMESPACE |