11#include <QtCore/qdatastream.h>
12#include <QtCore/qdatetime.h>
13#include <QtCore/qdir.h>
14#include <QtCore/qfile.h>
15#include <QtCore/qfileinfo.h>
16#include <QtCore/qset.h>
17#include <QtCore/qtimer.h>
18#include <QtCore/qversionnumber.h>
19#include <QtSql/qsqldriver.h>
20#include <QtSql/qsqlerror.h>
21#include <QtSql/qsqlquery.h>
30using namespace Qt::StringLiterals;
39 return "PRAGMA user_version = "_L1 + QString::number(CollectionSchemaVersion);
52 m_inTransaction = m_db.transaction();
70 m_inTransaction =
false;
83 const QString &columnName,
int count)
85 if (!query->exec(
"SELECT MAX(%1) FROM %2"_L1.arg(columnName, tableName)) || !query->next())
88 const qint64 maxId = query->value(0).toLongLong();
89 if (maxId > std::numeric_limits<qint64>::max() - count)
96 , m_collectionFile(collectionFile)
98 const QFileInfo fi(m_collectionFile);
100 m_collectionFile = fi.absoluteFilePath();
113 emit that->error(tr(
"The collection file \"%1\" is not set up yet.").arg(m_collectionFile));
123 emit error(tr(
"The collection file \"%1\" is opened in read-only mode.").arg(m_collectionFile));
133 QSqlDatabase::removeDatabase(m_connectionName);
134 m_connectionName.clear();
146 const QFileInfo fi(m_collectionFile);
148 emit error(tr(
"The collection file \"%1\" does not exist.").arg(m_collectionFile));
151 if (fi.size() == 0) {
152 emit error(tr(
"The collection file \"%1\" is empty.").arg(m_collectionFile));
157 m_connectionName = QHelpGlobal::uniquifyConnectionName(
"QHelpCollectionHandler"_L1,
this);
159 QSqlDatabase db = QSqlDatabase::addDatabase(
"QSQLITE"_L1, m_connectionName);
161 && db.driver()->lastError().type() == QSqlError::ConnectionError) {
162 emit error(tr(
"Cannot load sqlite database driver."));
167 db.setConnectOptions(
"QSQLITE_OPEN_READONLY"_L1);
168 db.setDatabaseName(collectionFile());
170 m_query.reset(
new QSqlQuery(db));
173 QSqlDatabase::removeDatabase(m_connectionName);
174 emit error(tr(
"Cannot open collection file: %1").arg(collectionFile()));
179 if (!m_query->exec(
"PRAGMA user_version"_L1) || !m_query->next()) {
181 emit error(tr(
"Cannot read the version of collection file %1.").arg(collectionFile()));
185 const int schemaVersion = m_query->value(0).toInt();
188 emit error(tr(
"The collection file \"%1\" was created by a newer version of Qt Help.")
189 .arg(m_collectionFile));
197 const QStringList tables = {
200 "FilterAttributeTable"_L1,
201 "FilterNameTable"_L1,
205 const QString queryString =
"SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' "
206 "AND (Name=\'"_L1 + tables.join(
"\' OR Name=\'"_L1) +
"\')"_L1;
207 if (!m_query->exec(queryString) || !m_query->next()
208 || m_query->value(0).toInt() != tables.size()) {
210 emit error(tr(
"The collection file \"%1\" is not a valid help collection file.")
211 .arg(m_collectionFile));
217 m_query->exec(
"PRAGMA synchronous=OFF"_L1);
218 m_query->exec(
"PRAGMA cache_size=3000"_L1);
220 m_query->exec(
"SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' "
221 "AND Name=\'NamespaceTable\'"_L1);
224 const bool tablesExist = m_query->value(0).toInt() > 0;
226 if (!createTables(m_query.get())) {
228 emit error(tr(
"Cannot create tables in file %1.").arg(collectionFile()));
233 bool indexAndNamespaceFilterTablesMissing =
false;
235 const QStringList newTables = {
239 "FileFilterTable"_L1,
240 "IndexFilterTable"_L1,
241 "ContentsFilterTable"_L1,
242 "FileAttributeSetTable"_L1,
243 "OptimizedFilterTable"_L1,
248 "ComponentMapping"_L1,
249 "ComponentFilter"_L1,
253 QString queryString =
"SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"_L1;
254 queryString.append(
" AND (Name=\'"_L1);
255 queryString.append(newTables.join(
"\' OR Name=\'"_L1));
256 queryString.append(
"\')"_L1);
258 m_query->exec(queryString);
260 if (m_query->value(0).toInt() != newTables.size()) {
261 if (!recreateIndexAndNamespaceFilterTables(m_query.get())) {
262 emit error(tr(
"Cannot create index tables in file %1.").arg(collectionFile()));
267 indexAndNamespaceFilterTablesMissing = tablesExist;
270 if (schemaVersion < CollectionSchemaVersion
271 && !m_query->exec(writeSchemaVersionStatement())) {
272 emit error(tr(
"Cannot write the version of collection file %1.").arg(collectionFile()));
277 if (indexAndNamespaceFilterTablesMissing) {
278 for (
const QHelpCollectionHandler::FileInfo &info : docList) {
279 if (!registerIndexAndNamespaceFilterTables(info.namespaceName,
true)) {
280 emit error(tr(
"Cannot register index tables in file %1.").arg(collectionFile()));
287 QList<TimeStamp> timeStamps;
288 m_query->exec(
"SELECT NamespaceId, FolderId, FilePath, Size, TimeStamp FROM TimeStampTable"_L1);
289 while (m_query->next()) {
291 timeStamp.namespaceId = m_query->value(0).toLongLong();
292 timeStamp.folderId = m_query->value(1).toLongLong();
293 timeStamp.fileName = m_query->value(2).toString();
294 timeStamp.size = m_query->value(3).toLongLong();
295 timeStamp.timeStamp = m_query->value(4).toDateTime();
296 timeStamps.append(timeStamp);
299 QList<TimeStamp> toRemove;
300 for (
const TimeStamp &timeStamp : timeStamps) {
301 if (!isTimeStampCorrect(timeStamp))
302 toRemove.append(timeStamp);
308 for (
const TimeStamp &timeStamp : toRemove) {
309 if (!unregisterIndexTable(timeStamp.namespaceId, timeStamp.folderId)) {
310 emit error(tr(
"Cannot unregister index tables in file %1.").arg(collectionFile()));
315 emit error(tr(
"Cannot unregister index tables in file %1.").arg(collectionFile()));
319 for (
const QHelpCollectionHandler::FileInfo &info : docList) {
320 if (!hasTimeStampInfo(info.namespaceName)
321 && !registerIndexAndNamespaceFilterTables(info.namespaceName)) {
324 unregisterDocumentation(info.namespaceName);
332 const QFileInfo fi(collectionFile());
333 return QDir::isAbsolutePath(fileName)
335 : QFileInfo(fi.absolutePath() + u'/' + fileName).absoluteFilePath();
340 const QFileInfo fi(absoluteDocPath(timeStamp.fileName));
345 if (fi.size() != timeStamp.size)
348 if (fi.lastModified(QTimeZone::UTC) != timeStamp.timeStamp)
351 m_query->prepare(
"SELECT FilePath FROM NamespaceTable WHERE Id = ?"_L1);
352 m_query->bindValue(0, timeStamp.namespaceId);
353 if (!m_query->exec() || !m_query->next())
356 const QString oldFileName = m_query->value(0).toString();
358 if (oldFileName != timeStamp.fileName)
368 "TimeStampTable.NamespaceId "
372 "WHERE NamespaceTable.Id = TimeStampTable.NamespaceId "
373 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
374 m_query->bindValue(0, nameSpace);
375 if (!m_query->exec())
378 if (!m_query->next())
387 if (m_vacuumScheduled)
390 m_vacuumScheduled =
true;
391 QTimer::singleShot(0,
this, &QHelpCollectionHandler::execVacuum);
399 m_query->exec(
"VACUUM"_L1);
400 m_vacuumScheduled =
false;
408 const QFileInfo fi(fileName);
410 emit error(tr(
"The collection file \"%1\" already exists.").arg(fileName));
414 if (!fi.absoluteDir().exists() && !QDir().mkpath(fi.absolutePath())) {
415 emit error(tr(
"Cannot create directory: %1").arg(fi.absolutePath()));
419 const QString &colFile = fi.absoluteFilePath();
420 const QString &connectionName =
421 QHelpGlobal::uniquifyConnectionName(
"QHelpCollectionHandlerCopy"_L1,
this);
422 QSqlDatabase db = QSqlDatabase::addDatabase(
"QSQLITE"_L1, connectionName);
423 db.setDatabaseName(colFile);
425 emit error(tr(
"Cannot open collection file: %1").arg(colFile));
429 const auto copyTables = [&] {
430 QSqlQuery copyQuery(db);
431 copyQuery.exec(
"PRAGMA synchronous=OFF"_L1);
432 copyQuery.exec(
"PRAGMA cache_size=3000"_L1);
436 if (!createTables(©Query) || !recreateIndexAndNamespaceFilterTables(©Query)
437 || !copyQuery.exec(writeSchemaVersionStatement())) {
441 const QString &oldBaseDir = QFileInfo(collectionFile()).absolutePath();
442 const QFileInfo newColFi(colFile);
443 m_query->exec(
"SELECT Name, FilePath FROM NamespaceTable"_L1);
444 while (m_query->next()) {
445 copyQuery.prepare(
"INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
446 copyQuery.bindValue(0, m_query->value(0).toString());
447 QString oldFilePath = m_query->value(1).toString();
448 if (!QDir::isAbsolutePath(oldFilePath))
449 oldFilePath = oldBaseDir + u'/' + oldFilePath;
450 copyQuery.bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath));
451 if (!copyQuery.exec())
455 m_query->exec(
"SELECT NamespaceId, Name FROM FolderTable"_L1);
456 while (m_query->next()) {
457 copyQuery.prepare(
"INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
458 copyQuery.bindValue(0, m_query->value(0).toString());
459 copyQuery.bindValue(1, m_query->value(1).toString());
460 if (!copyQuery.exec())
464 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
465 while (m_query->next()) {
466 copyQuery.prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
467 copyQuery.bindValue(0, m_query->value(0).toString());
468 if (!copyQuery.exec())
472 m_query->exec(
"SELECT Name FROM FilterNameTable"_L1);
473 while (m_query->next()) {
474 copyQuery.prepare(
"INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
475 copyQuery.bindValue(0, m_query->value(0).toString());
476 if (!copyQuery.exec())
480 m_query->exec(
"SELECT NameId, FilterAttributeId FROM FilterTable"_L1);
481 while (m_query->next()) {
482 copyQuery.prepare(
"INSERT INTO FilterTable VALUES(?, ?)"_L1);
483 copyQuery.bindValue(0, m_query->value(0).toLongLong());
484 copyQuery.bindValue(1, m_query->value(1).toLongLong());
485 if (!copyQuery.exec())
489 m_query->exec(
"SELECT Key, Value FROM SettingsTable"_L1);
490 while (m_query->next()) {
491 if (m_query->value(0).toString() ==
"FTS5IndexedNamespaces"_L1)
493 copyQuery.prepare(
"INSERT INTO SettingsTable VALUES(?, ?)"_L1);
494 copyQuery.bindValue(0, m_query->value(0).toString());
495 copyQuery.bindValue(1, m_query->value(1));
496 if (!copyQuery.exec())
503 const bool copied = copyTables();
506 QSqlDatabase::removeDatabase(connectionName);
508 QFile::remove(colFile);
509 emit error(tr(
"Cannot copy collection file: %1").arg(colFile));
517 const QStringList tables = {
518 "CREATE TABLE NamespaceTable ("
519 "Id INTEGER PRIMARY KEY, "
521 "FilePath TEXT )"_L1,
522 "CREATE TABLE FolderTable ("
523 "Id INTEGER PRIMARY KEY, "
524 "NamespaceId INTEGER, "
526 "CREATE TABLE FilterAttributeTable ("
527 "Id INTEGER PRIMARY KEY, "
529 "CREATE TABLE FilterNameTable ("
530 "Id INTEGER PRIMARY KEY, "
532 "CREATE TABLE FilterTable ("
534 "FilterAttributeId INTEGER )"_L1,
535 "CREATE TABLE SettingsTable ("
536 "Key TEXT PRIMARY KEY, "
540 for (
const QString &q : tables) {
549 const QStringList tables = {
550 "DROP TABLE IF EXISTS FileNameTable"_L1,
551 "DROP TABLE IF EXISTS IndexTable"_L1,
552 "DROP TABLE IF EXISTS ContentsTable"_L1,
553 "DROP TABLE IF EXISTS FileFilterTable"_L1,
554 "DROP TABLE IF EXISTS IndexFilterTable"_L1,
555 "DROP TABLE IF EXISTS ContentsFilterTable"_L1,
556 "DROP TABLE IF EXISTS FileAttributeSetTable"_L1,
557 "DROP TABLE IF EXISTS OptimizedFilterTable"_L1,
558 "DROP TABLE IF EXISTS TimeStampTable"_L1,
559 "DROP TABLE IF EXISTS VersionTable"_L1,
560 "DROP TABLE IF EXISTS Filter"_L1,
561 "DROP TABLE IF EXISTS ComponentTable"_L1,
562 "DROP TABLE IF EXISTS ComponentMapping"_L1,
563 "DROP TABLE IF EXISTS ComponentFilter"_L1,
564 "DROP TABLE IF EXISTS VersionFilter"_L1,
565 "CREATE TABLE FileNameTable ("
568 "FileId INTEGER PRIMARY KEY, "
570 "CREATE TABLE IndexTable ("
571 "Id INTEGER PRIMARY KEY, "
574 "NamespaceId INTEGER, "
577 "CREATE TABLE ContentsTable ("
578 "Id INTEGER PRIMARY KEY, "
579 "NamespaceId INTEGER, "
581 "CREATE TABLE FileFilterTable ("
582 "FilterAttributeId INTEGER, "
583 "FileId INTEGER)"_L1,
584 "CREATE TABLE IndexFilterTable ("
585 "FilterAttributeId INTEGER, "
586 "IndexId INTEGER)"_L1,
587 "CREATE TABLE ContentsFilterTable ("
588 "FilterAttributeId INTEGER, "
589 "ContentsId INTEGER )"_L1,
590 "CREATE TABLE FileAttributeSetTable ("
591 "NamespaceId INTEGER, "
592 "FilterAttributeSetId INTEGER, "
593 "FilterAttributeId INTEGER)"_L1,
594 "CREATE TABLE OptimizedFilterTable ("
595 "NamespaceId INTEGER, "
596 "FilterAttributeId INTEGER)"_L1,
597 "CREATE TABLE TimeStampTable ("
598 "NamespaceId INTEGER, "
602 "TimeStamp TEXT)"_L1,
603 "CREATE TABLE VersionTable ("
604 "NamespaceId INTEGER, "
606 "CREATE TABLE Filter ("
607 "FilterId INTEGER PRIMARY KEY, "
609 "CREATE TABLE ComponentTable ("
610 "ComponentId INTEGER PRIMARY KEY, "
612 "CREATE TABLE ComponentMapping ("
613 "ComponentId INTEGER, "
614 "NamespaceId INTEGER)"_L1,
615 "CREATE TABLE ComponentFilter ("
616 "ComponentName TEXT, "
617 "FilterId INTEGER)"_L1,
618 "CREATE TABLE VersionFilter ("
620 "FilterId INTEGER)"_L1
623 for (
const QString &q : tables) {
634 m_query->exec(
"SELECT Name FROM FilterNameTable"_L1);
635 while (m_query->next())
636 list.append(m_query->value(0).toString());
645 m_query->exec(
"SELECT Name FROM Filter ORDER BY Name"_L1);
646 while (m_query->next())
647 list.append(m_query->value(0).toString());
656 m_query->exec(
"SELECT DISTINCT Name FROM ComponentTable ORDER BY Name"_L1);
657 while (m_query->next())
658 list.append(m_query->value(0).toString());
665 QList<QVersionNumber> list;
667 m_query->exec(
"SELECT DISTINCT Version FROM VersionTable ORDER BY Version"_L1);
668 while (m_query->next())
669 list.append(QVersionNumber::fromString(m_query->value(0).toString()));
676 QMap<QString, QString> result;
680 "NamespaceTable.Name, "
681 "ComponentTable.Name "
682 "FROM NamespaceTable, "
685 "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId "
686 "AND ComponentMapping.ComponentId = ComponentTable.ComponentId"_L1);
687 while (m_query->next())
688 result.insert(m_query->value(0).toString(), m_query->value(1).toString());
695 QMap<QString, QVersionNumber> result;
699 "NamespaceTable.Name, "
700 "VersionTable.Version "
701 "FROM NamespaceTable, "
703 "WHERE NamespaceTable.Id = VersionTable.NamespaceId"_L1);
704 while (m_query->next()) {
705 result.insert(m_query->value(0).toString(),
706 QVersionNumber::fromString(m_query->value(1).toString()));
714 QStringList components;
715 QList<QVersionNumber> versions;
718 "SELECT ComponentFilter.ComponentName "
719 "FROM ComponentFilter, Filter "
720 "WHERE ComponentFilter.FilterId = Filter.FilterId "
721 "AND Filter.Name = ? "
722 "ORDER BY ComponentFilter.ComponentName"_L1);
723 m_query->bindValue(0, filterName);
725 while (m_query->next())
726 components.append(m_query->value(0).toString());
729 "SELECT VersionFilter.Version "
730 "FROM VersionFilter, Filter "
731 "WHERE VersionFilter.FilterId = Filter.FilterId "
732 "AND Filter.Name = ? "
733 "ORDER BY VersionFilter.Version"_L1);
734 m_query->bindValue(0, filterName);
736 while (m_query->next())
737 versions.append(QVersionNumber::fromString(m_query->value(0).toString()));
740 QHelpFilterData data;
741 data.setComponents(components);
742 data.setVersions(versions);
747 const QHelpFilterData &filterData)
749 if (!isDBWritable() || !removeFilter(filterName))
752 m_query->prepare(
"INSERT INTO Filter VALUES (NULL, ?)"_L1);
753 m_query->bindValue(0, filterName);
754 if (!m_query->exec())
757 const qint64 filterId = m_query->lastInsertId().toLongLong();
759 QVariantList componentList;
760 QVariantList versionList;
761 QVariantList filterIdList;
763 const auto &components = filterData.components();
764 for (
const QString &component : components) {
765 componentList.append(component);
766 filterIdList.append(filterId);
769 m_query->prepare(
"INSERT INTO ComponentFilter VALUES (?, ?)"_L1);
770 m_query->addBindValue(componentList);
771 m_query->addBindValue(filterIdList);
772 if (!m_query->execBatch())
775 filterIdList.clear();
776 const auto &versions = filterData.versions();
777 for (
const QVersionNumber &version : versions) {
778 versionList.append(version.isNull() ? QString() : version.toString());
779 filterIdList.append(filterId);
782 m_query->prepare(
"INSERT INTO VersionFilter VALUES (?, ?)"_L1);
783 m_query->addBindValue(versionList);
784 m_query->addBindValue(filterIdList);
785 if (!m_query->execBatch())
796 m_query->prepare(
"SELECT FilterId FROM Filter WHERE Name = ?"_L1);
797 m_query->bindValue(0, filterName);
798 if (!m_query->exec())
801 if (!m_query->next())
804 const qint64 filterId = m_query->value(0).toLongLong();
806 m_query->prepare(
"DELETE FROM Filter WHERE Filter.Name = ?"_L1);
807 m_query->bindValue(0, filterName);
808 if (!m_query->exec())
811 m_query->prepare(
"DELETE FROM ComponentFilter WHERE ComponentFilter.FilterId = ?"_L1);
812 m_query->bindValue(0, filterId);
813 if (!m_query->exec())
816 m_query->prepare(
"DELETE FROM VersionFilter WHERE VersionFilter.FilterId = ?"_L1);
817 m_query->bindValue(0, filterId);
818 if (!m_query->exec())
826 if (!isDBWritable() || filterName.isEmpty())
829 qint64 filterNameId = -1;
830 m_query->prepare(
"SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
831 m_query->bindValue(0, filterName);
834 filterNameId = m_query->value(0).toLongLong();
836 if (filterNameId < 0) {
837 emit error(tr(
"Unknown filter \"%1\".").arg(filterName));
843 m_query->prepare(
"DELETE FROM FilterTable WHERE NameId=?"_L1);
844 m_query->bindValue(0, filterNameId);
845 if (!m_query->exec())
848 m_query->prepare(
"DELETE FROM FilterNameTable WHERE Id=?"_L1);
849 m_query->bindValue(0, filterNameId);
850 if (!m_query->exec())
857 const QStringList &attributes)
859 if (!isDBWritable() || filterName.isEmpty())
863 m_query->prepare(
"SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
864 m_query->bindValue(0, filterName);
867 nameId = m_query->value(0).toLongLong();
869 m_query->exec(
"SELECT Id, Name FROM FilterAttributeTable"_L1);
870 QStringList idsToInsert = attributes;
871 QMap<QString, qint64> attributeMap;
872 while (m_query->next()) {
874 const QString attributeName = m_query->value(1).toString();
875 attributeMap.insert(attributeName, m_query->value(0).toLongLong());
876 idsToInsert.removeAll(attributeName);
881 for (
const QString &id : std::as_const(idsToInsert)) {
882 m_query->prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
883 m_query->bindValue(0, id);
884 if (!m_query->exec())
886 attributeMap.insert(id, m_query->lastInsertId().toLongLong());
890 m_query->prepare(
"INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
891 m_query->bindValue(0, filterName);
893 nameId = m_query->lastInsertId().toLongLong();
897 emit error(tr(
"Cannot register filter %1.").arg(filterName));
901 m_query->prepare(
"DELETE FROM FilterTable WHERE NameId=?"_L1);
902 m_query->bindValue(0, nameId);
903 if (!m_query->exec())
906 for (
const QString &att : attributes) {
907 m_query->prepare(
"INSERT INTO FilterTable VALUES(?, ?)"_L1);
908 m_query->bindValue(0, nameId);
909 m_query->bindValue(1, attributeMap[att]);
910 if (!m_query->exec())
917 const QString &namespaceName)
const
926 "NamespaceTable.Name, "
927 "NamespaceTable.FilePath, "
932 "WHERE NamespaceTable.Id = FolderTable.NamespaceId "
933 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
934 m_query->bindValue(0, namespaceName);
935 if (!m_query->exec() || !m_query->next())
938 fileInfo.namespaceName = m_query->value(0).toString();
939 fileInfo.fileName = m_query->value(1).toString();
940 fileInfo.folderName = m_query->value(2).toString();
955 "NamespaceTable.Name, "
956 "NamespaceTable.FilePath, "
961 "WHERE NamespaceTable.Id = FolderTable.NamespaceId"_L1);
963 while (m_query->next()) {
965 fileInfo.namespaceName = m_query->value(0).toString();
966 fileInfo.fileName = m_query->value(1).toString();
967 fileInfo.folderName = m_query->value(2).toString();
968 list.append(fileInfo);
979 QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName(
980 "QHelpCollectionHandler"_L1,
this),
nullptr);
982 emit error(tr(
"Cannot open documentation file %1.").arg(fileName));
986 const QString &ns = reader.namespaceName();
988 emit error(tr(
"Invalid documentation file \"%1\".").arg(fileName));
995 const QString absFileName = QFileInfo(fileName).absoluteFilePath();
997 for (
const FileInfo &info : registered) {
998 if (info.namespaceName != ns && absoluteDocPath(info.fileName) == absFileName)
999 unregisterDocumentation(info.namespaceName);
1004 const qint64 nsId = registerNamespace(ns, fileName);
1008 const qint64 vfId = registerVirtualFolder(reader.virtualFolder(), nsId);
1012 registerVersion(reader.version(), nsId);
1013 registerFilterAttributes(reader.filterAttributeSets(), nsId);
1014 const auto &customFilters = reader.customFilters();
1015 for (
const QString &filterName : customFilters)
1016 addCustomFilter(filterName, reader.filterAttributes(filterName));
1018 if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName))
1026 if (!isDBWritable())
1029 m_query->prepare(
"SELECT Id FROM NamespaceTable WHERE Name = ?"_L1);
1030 m_query->bindValue(0, namespaceName);
1033 if (!m_query->next()) {
1034 emit error(tr(
"The namespace %1 was not registered.").arg(namespaceName));
1038 const qint64 nsId = m_query->value(0).toLongLong();
1040 m_query->prepare(
"SELECT Id FROM FolderTable WHERE NamespaceId = ?"_L1);
1041 m_query->bindValue(0, nsId);
1044 if (!m_query->next()) {
1045 emit error(tr(
"The namespace %1 was not registered.").arg(namespaceName));
1049 const qint64 vfId = m_query->value(0).toLongLong();
1053 m_query->prepare(
"DELETE FROM NamespaceTable WHERE Id = ?"_L1);
1054 m_query->bindValue(0, nsId);
1055 if (!m_query->exec())
1058 m_query->prepare(
"DELETE FROM FolderTable WHERE NamespaceId = ?"_L1);
1059 m_query->bindValue(0, nsId);
1060 if (!m_query->exec())
1063 if (!unregisterIndexTable(nsId, vfId) || !transaction.commit())
1073 QHelpCollectionHandler::FileInfo fileInfo;
1075 if (!url.isValid() || url.toString().count(u'/') < 4
1076 || url.scheme() !=
"qthelp"_L1) {
1080 fileInfo.namespaceName = url.authority();
1081 fileInfo.fileName = url.path();
1082 if (fileInfo.fileName.startsWith(u'/'))
1083 fileInfo.fileName = fileInfo.fileName.mid(1);
1084 fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(u'/', 1));
1085 fileInfo.fileName.remove(0, fileInfo.folderName.size() + 1);
1095 const FileInfo fileInfo = extractFileInfo(url);
1096 if (fileInfo.namespaceName.isEmpty())
1100 "SELECT COUNT (DISTINCT NamespaceTable.Id) "
1105 "WHERE FolderTable.Name = ? "
1106 "AND FileNameTable.Name = ? "
1107 "AND FileNameTable.FolderId = FolderTable.Id "
1108 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1);
1109 m_query->bindValue(0, fileInfo.folderName);
1110 m_query->bindValue(1, fileInfo.fileName);
1111 if (!m_query->exec() || !m_query->next())
1114 const int count = m_query->value(0).toInt();
1122 if (filterName.isEmpty())
1125 return " AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) "
1131 "WHERE ComponentFilter.FilterId = Filter.FilterId "
1132 "AND Filter.Name = ?) "
1133 "OR NamespaceTable.Id IN ("
1135 "NamespaceTable.Id "
1139 "ComponentMapping, "
1142 "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id "
1143 "AND ComponentTable.ComponentId = ComponentMapping.ComponentId "
1144 "AND ((ComponentTable.Name = ComponentFilter.ComponentName) "
1145 "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) "
1146 "AND ComponentFilter.FilterId = Filter.FilterId "
1147 "AND Filter.Name = ?))"
1153 "WHERE VersionFilter.FilterId = Filter.FilterId "
1154 "AND Filter.Name = ?) "
1155 "OR NamespaceTable.Id IN ("
1157 "NamespaceTable.Id "
1163 "WHERE VersionFilter.FilterId = Filter.FilterId "
1164 "AND ((VersionFilter.Version = VersionTable.Version) "
1165 "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) "
1166 "AND VersionTable.NamespaceId = NamespaceTable.Id "
1167 "AND Filter.Name = ?))"
1173 if (filterName.isEmpty())
1176 query->bindValue(bindStart, filterName);
1177 query->bindValue(bindStart + 1, filterName);
1178 query->bindValue(bindStart + 2, filterName);
1179 query->bindValue(bindStart + 3, filterName);
1180 query->bindValue(bindStart + 4, filterName);
1184 const QString &idTableName,
1185 const QString &idColumnName,
1186 const QString &filterTableName,
1187 const QString &filterColumnName)
1189 if (!attributesCount)
1192 QString filterQuery =
" AND (%1.%2 IN ("_L1.arg(idTableName, idColumnName);
1194 const QString filterQueryTemplate =
1196 "FROM %1, FilterAttributeTable "
1197 "WHERE %1.FilterAttributeId = FilterAttributeTable.Id "
1198 "AND FilterAttributeTable.Name = ?"_L1.arg(filterTableName, filterColumnName);
1200 for (
int i = 0; i < attributesCount; ++i) {
1202 filterQuery.append(
" INTERSECT "_L1);
1203 filterQuery.append(filterQueryTemplate);
1206 filterQuery.append(
") OR NamespaceTable.Id IN ("_L1);
1208 const QString optimizedFilterQueryTemplate =
1209 "SELECT OptimizedFilterTable.NamespaceId "
1210 "FROM OptimizedFilterTable, FilterAttributeTable "
1211 "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id "
1212 "AND FilterAttributeTable.Name = ?"_L1;
1214 for (
int i = 0; i < attributesCount; ++i) {
1216 filterQuery.append(
" INTERSECT "_L1);
1217 filterQuery.append(optimizedFilterQueryTemplate);
1220 filterQuery.append(
"))"_L1);
1225static void bindFilterQuery(QSqlQuery *query,
int startingBindPos,
const QStringList &filterAttributes)
1227 for (
int i = 0; i < 2; ++i) {
1228 for (
int j = 0; j < filterAttributes.size(); j++) {
1229 query->bindValue(i * filterAttributes.size() + j + startingBindPos,
1230 filterAttributes.at(j));
1236 const QStringList &filterAttributes)
const
1241 const FileInfo fileInfo = extractFileInfo(url);
1242 if (fileInfo.namespaceName.isEmpty())
1245 const QString filterlessQuery =
1247 "NamespaceTable.Name "
1252 "WHERE FolderTable.Name = ? "
1253 "AND FileNameTable.Name = ? "
1254 "AND FileNameTable.FolderId = FolderTable.Id "
1255 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1257 const QString filterQuery = filterlessQuery
1258 + prepareFilterQuery(filterAttributes.size(),
"FileNameTable"_L1,
"FileId"_L1,
1259 "FileFilterTable"_L1,
"FileId"_L1);
1261 m_query->prepare(filterQuery);
1262 m_query->bindValue(0, fileInfo.folderName);
1263 m_query->bindValue(1, fileInfo.fileName);
1264 bindFilterQuery(m_query.get(), 2, filterAttributes);
1266 if (!m_query->exec())
1269 QStringList namespaceList;
1270 while (m_query->next())
1271 namespaceList.append(m_query->value(0).toString());
1273 if (namespaceList.isEmpty())
1276 if (namespaceList.contains(fileInfo.namespaceName))
1277 return fileInfo.namespaceName;
1279 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1283 QString fallbackNamespace;
1285 for (
const QString &ns : std::as_const(namespaceList)) {
1286 const QString nsVersion = namespaceVersion(ns);
1287 if (originalVersion == nsVersion)
1290 const QVersionNumber version = QVersionNumber::fromString(nsVersion);
1291 const int compare = QVersionNumber::compare(version, fallbackVersion);
1292 if (fallbackNamespace.isEmpty() || compare > 0
1293 || (compare == 0 && ns < fallbackNamespace)) {
1294 fallbackNamespace = ns;
1295 fallbackVersion = version;
1298 return fallbackNamespace;
1302 const QString &filterName)
const
1307 const FileInfo fileInfo = extractFileInfo(url);
1308 if (fileInfo.namespaceName.isEmpty())
1311 const QString filterlessQuery =
1313 "NamespaceTable.Name "
1318 "WHERE FolderTable.Name = ? "
1319 "AND FileNameTable.Name = ? "
1320 "AND FileNameTable.FolderId = FolderTable.Id "
1321 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1323 const QString filterQuery = filterlessQuery
1324 + prepareFilterQuery(filterName);
1326 m_query->prepare(filterQuery);
1327 m_query->bindValue(0, fileInfo.folderName);
1328 m_query->bindValue(1, fileInfo.fileName);
1329 bindFilterQuery(m_query.get(), 2, filterName);
1331 if (!m_query->exec())
1334 QStringList namespaceList;
1335 while (m_query->next())
1336 namespaceList.append(m_query->value(0).toString());
1338 if (namespaceList.isEmpty())
1341 if (namespaceList.contains(fileInfo.namespaceName))
1342 return fileInfo.namespaceName;
1344 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1348 QString fallbackNamespace;
1350 for (
const QString &ns : std::as_const(namespaceList)) {
1351 const QString nsVersion = namespaceVersion(ns);
1352 if (originalVersion == nsVersion)
1355 const QVersionNumber version = QVersionNumber::fromString(nsVersion);
1356 const int compare = QVersionNumber::compare(version, fallbackVersion);
1357 if (fallbackNamespace.isEmpty() || compare > 0
1358 || (compare == 0 && ns < fallbackNamespace)) {
1359 fallbackNamespace = ns;
1360 fallbackVersion = version;
1363 return fallbackNamespace;
1367 const QStringList &filterAttributes,
1368 const QString &extensionFilter)
const
1373 const QString extensionQuery = extensionFilter.isEmpty()
1374 ? QString() :
" AND FileNameTable.Name LIKE ?"_L1;
1375 const QString filterlessQuery =
1377 "FolderTable.Name, "
1378 "FileNameTable.Name "
1383 "WHERE FileNameTable.FolderId = FolderTable.Id "
1384 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1385 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1387 const QString filterQuery = filterlessQuery
1388 + prepareFilterQuery(filterAttributes.size(),
"FileNameTable"_L1,
"FileId"_L1,
1389 "FileFilterTable"_L1,
"FileId"_L1);
1391 m_query->prepare(filterQuery);
1392 m_query->bindValue(0, namespaceName);
1394 if (!extensionFilter.isEmpty()) {
1395 m_query->bindValue(bindCount,
"%.%1"_L1.arg(extensionFilter));
1398 bindFilterQuery(m_query.get(), bindCount, filterAttributes);
1400 if (!m_query->exec())
1403 QStringList fileNames;
1404 while (m_query->next())
1405 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1410 const QString &filterName,
1411 const QString &extensionFilter)
const
1416 const QString extensionQuery = extensionFilter.isEmpty()
1417 ? QString() :
" AND FileNameTable.Name LIKE ?"_L1;
1418 const QString filterlessQuery =
1420 "FolderTable.Name, "
1421 "FileNameTable.Name "
1426 "WHERE FileNameTable.FolderId = FolderTable.Id "
1427 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1428 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1430 const QString filterQuery = filterlessQuery
1431 + prepareFilterQuery(filterName);
1433 m_query->prepare(filterQuery);
1434 m_query->bindValue(0, namespaceName);
1436 if (!extensionFilter.isEmpty()) {
1437 m_query->bindValue(bindCount,
"%.%1"_L1.arg(extensionFilter));
1441 bindFilterQuery(m_query.get(), bindCount, filterName);
1443 if (!m_query->exec())
1446 QStringList fileNames;
1447 while (m_query->next())
1448 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1457 const QString namespaceName = namespaceForFile(url, filterAttributes);
1458 if (namespaceName.isEmpty())
1462 result.setAuthority(namespaceName);
1471 const QString namespaceName = namespaceForFile(url, filterName);
1472 if (namespaceName.isEmpty())
1476 result.setAuthority(namespaceName);
1485 const QString namespaceName = namespaceForFile(url, QString());
1486 if (namespaceName.isEmpty())
1489 const FileInfo fileInfo = extractFileInfo(url);
1491 const FileInfo docInfo = registeredDocumentation(namespaceName);
1492 const QString absFileName = absoluteDocPath(docInfo.fileName);
1494 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1495 docInfo.fileName,
const_cast<QHelpCollectionHandler *>(
this)),
nullptr);
1499 return reader.fileData(fileInfo.folderName, fileInfo.fileName);
1504 QStringList indices;
1509 const QString filterlessQuery =
1517 "WHERE IndexTable.FileId = FileNameTable.FileId "
1518 "AND FileNameTable.FolderId = FolderTable.Id "
1519 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1521 const QString filterQuery = filterlessQuery
1522 + prepareFilterQuery(filterAttributes.size(),
"IndexTable"_L1,
"Id"_L1,
1523 "IndexFilterTable"_L1,
"IndexId"_L1)
1524 +
" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1527 m_query->prepare(filterQuery);
1528 bindFilterQuery(m_query.get(), 0, filterAttributes);
1532 while (m_query->next())
1533 indices.append(m_query->value(0).toString());
1540 QStringList indices;
1545 const QString filterlessQuery =
1553 "WHERE IndexTable.FileId = FileNameTable.FileId "
1554 "AND FileNameTable.FolderId = FolderTable.Id "
1555 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1557 const QString filterQuery = filterlessQuery
1558 + prepareFilterQuery(filterName)
1559 +
" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1561 m_query->prepare(filterQuery);
1562 bindFilterQuery(m_query.get(), 0, filterName);
1566 while (m_query->next())
1567 indices.append(m_query->value(0).toString());
1574 if (!contents.size())
1581 QDataStream s(contents);
1590 const QStringList &filterAttributes)
const
1595 const QString filterlessQuery =
1597 "NamespaceTable.Name, "
1598 "FolderTable.Name, "
1599 "ContentsTable.Data, "
1600 "VersionTable.Version "
1606 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1607 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1608 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1609 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1611 const QString filterQuery = filterlessQuery
1612 + prepareFilterQuery(filterAttributes.size(),
"ContentsTable"_L1,
"Id"_L1,
1613 "ContentsFilterTable"_L1,
"ContentsId"_L1);
1615 m_query->prepare(filterQuery);
1616 bindFilterQuery(m_query.get(), 0, filterAttributes);
1620 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1622 while (m_query->next()) {
1623 const QString namespaceName = m_query->value(0).toString();
1624 const QByteArray contents = m_query->value(2).toByteArray();
1625 const QString versionString = m_query->value(3).toString();
1627 const QString title = getTitle(contents);
1628 const QVersionNumber version = QVersionNumber::fromString(versionString);
1630 ContentsData &contentsData = contentsMap[title][version];
1631 contentsData.namespaceName = namespaceName;
1632 contentsData.folderName = m_query->value(1).toString();
1633 contentsData.contentsList.append(contents);
1636 QList<QHelpCollectionHandler::ContentsData> result;
1637 for (
const auto &versionContents : std::as_const(contentsMap)) {
1639 const auto itBegin = versionContents.constBegin();
1640 auto it = versionContents.constEnd();
1641 while (it != itBegin) {
1643 result.append(it.value());
1654 const QString filterlessQuery =
1656 "NamespaceTable.Name, "
1657 "FolderTable.Name, "
1658 "ContentsTable.Data, "
1659 "VersionTable.Version "
1665 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1666 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1667 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1668 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1670 const QString filterQuery = filterlessQuery + prepareFilterQuery(filterName);
1672 m_query->prepare(filterQuery);
1673 bindFilterQuery(m_query.get(), 0, filterName);
1677 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1679 while (m_query->next()) {
1680 const QString namespaceName = m_query->value(0).toString();
1681 const QByteArray contents = m_query->value(2).toByteArray();
1682 const QString versionString = m_query->value(3).toString();
1684 const QString title = getTitle(contents);
1685 const QVersionNumber version = QVersionNumber::fromString(versionString);
1687 ContentsData &contentsData = contentsMap[title][version];
1688 contentsData.namespaceName = namespaceName;
1689 contentsData.folderName = m_query->value(1).toString();
1690 contentsData.contentsList.append(contents);
1693 QList<QHelpCollectionHandler::ContentsData> result;
1694 for (
const auto &versionContents : std::as_const(contentsMap)) {
1696 const auto itBegin = versionContents.constBegin();
1697 auto it = versionContents.constEnd();
1698 while (it != itBegin) {
1700 result.append(it.value());
1708 if (!isDBWritable())
1711 m_query->prepare(
"DELETE FROM SettingsTable WHERE Key=?"_L1);
1712 m_query->bindValue(0, key);
1713 return m_query->exec();
1717 const QVariant &defaultValue)
const
1720 return defaultValue;
1722 m_query->prepare(
"SELECT COUNT(Key) FROM SettingsTable WHERE Key=?"_L1);
1723 m_query->bindValue(0, key);
1724 if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) {
1726 return defaultValue;
1730 m_query->prepare(
"SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1731 m_query->bindValue(0, key);
1732 if (m_query->exec() && m_query->next()) {
1733 const QVariant &value = m_query->value(0);
1737 return defaultValue;
1741 const QVariant &value)
1743 if (!isDBWritable())
1746 m_query->prepare(
"SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1747 m_query->bindValue(0, key);
1749 if (m_query->next()) {
1750 m_query->prepare(
"UPDATE SettingsTable SET Value=? where Key=?"_L1);
1751 m_query->bindValue(0, value);
1752 m_query->bindValue(1, key);
1754 m_query->prepare(
"INSERT INTO SettingsTable VALUES(?, ?)"_L1);
1755 m_query->bindValue(0, key);
1756 m_query->bindValue(1, value);
1758 return m_query->exec();
1767 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
1769 while (m_query->next())
1770 atts.insert(m_query->value(0).toString());
1772 for (
const QStringList &attributeSet : attributeSets) {
1773 for (
const QString &attribute : attributeSet) {
1774 if (!atts.contains(attribute)) {
1775 m_query->prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
1776 m_query->bindValue(0, attribute);
1781 return registerFileAttributeSets(attributeSets, nsId);
1790 if (attributeSets.isEmpty())
1794 QVariantList attributeSetIds;
1795 QVariantList filterAttributeIds;
1797 const std::optional<qint64> maxAttributeSetId
1798 = readMaxId(m_query.get(),
"FileAttributeSetTable"_L1,
"FilterAttributeSetId"_L1,
1799 attributeSets.size());
1800 if (!maxAttributeSetId)
1803 qint64 attributeSetId = *maxAttributeSetId;
1805 for (
const QStringList &attributeSet : attributeSets) {
1808 for (
const QString &attribute : attributeSet) {
1809 m_query->prepare(
"SELECT Id FROM FilterAttributeTable WHERE Name=?"_L1);
1810 m_query->bindValue(0, attribute);
1812 if (!m_query->exec() || !m_query->next())
1816 attributeSetIds.append(attributeSetId);
1817 filterAttributeIds.append(m_query->value(0).toLongLong());
1821 m_query->prepare(
"INSERT INTO FileAttributeSetTable "
1822 "(NamespaceId, FilterAttributeSetId, FilterAttributeId) "
1823 "VALUES(?, ?, ?)"_L1);
1824 m_query->addBindValue(nsIds);
1825 m_query->addBindValue(attributeSetIds);
1826 m_query->addBindValue(filterAttributeIds);
1827 return m_query->execBatch();
1834 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
1835 while (m_query->next())
1836 list.append(m_query->value(0).toString());
1847 "FilterAttributeTable.Name "
1849 "FilterAttributeTable, "
1852 "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId "
1853 "AND FilterTable.NameId = FilterNameTable.Id "
1854 "AND FilterNameTable.Name=?"_L1);
1855 m_query->bindValue(0, filterName);
1857 while (m_query->next())
1858 list.append(m_query->value(0).toString());
1870 "FileAttributeSetTable.FilterAttributeSetId, "
1871 "FilterAttributeTable.Name "
1873 "FileAttributeSetTable, "
1874 "FilterAttributeTable, "
1876 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
1877 "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id "
1878 "AND NamespaceTable.Name = ? "
1879 "ORDER BY FileAttributeSetTable.FilterAttributeSetId"_L1);
1880 m_query->bindValue(0, namespaceName);
1883 QList<QStringList> result;
1884 while (m_query->next()) {
1885 const qint64 id = m_query->value(0).toLongLong();
1887 result.append(QStringList());
1890 result.last().append(m_query->value(1).toString());
1893 if (result.isEmpty())
1894 result.append(QStringList());
1905 "VersionTable.Version "
1909 "WHERE NamespaceTable.Name = ? "
1910 "AND NamespaceTable.Id = VersionTable.NamespaceId"_L1);
1911 m_query->bindValue(0, namespaceName);
1912 if (!m_query->exec() || !m_query->next())
1915 const QString ret = m_query->value(0).toString();
1922 const int errorValue = -1;
1926 m_query->prepare(
"SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?"_L1);
1927 m_query->bindValue(0, nspace);
1929 while (m_query->next()) {
1930 if (m_query->value(0).toInt() > 0) {
1931 emit error(tr(
"Namespace %1 already exists.").arg(nspace));
1936 QFileInfo fi(m_collectionFile);
1937 m_query->prepare(
"INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
1938 m_query->bindValue(0, nspace);
1939 m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName));
1940 qint64 namespaceId = errorValue;
1941 if (m_query->exec()) {
1942 namespaceId = m_query->lastInsertId().toLongLong();
1945 if (namespaceId < 1) {
1946 emit error(tr(
"Cannot register namespace \"%1\".").arg(nspace));
1957 m_query->prepare(
"INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
1958 m_query->bindValue(0, namespaceId);
1959 m_query->bindValue(1, folderName);
1961 qint64 virtualId = -1;
1962 if (m_query->exec()) {
1963 virtualId = m_query->lastInsertId().toLongLong();
1966 if (virtualId < 1) {
1967 emit error(tr(
"Cannot register virtual folder '%1'.").arg(folderName));
1970 if (registerComponent(folderName, namespaceId) < 0) {
1971 emit error(tr(
"Cannot register component '%1'.").arg(folderName));
1979 m_query->prepare(
"SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1980 m_query->bindValue(0, componentName);
1981 if (!m_query->exec())
1984 if (!m_query->next()) {
1985 m_query->prepare(
"INSERT INTO ComponentTable VALUES(NULL, ?)"_L1);
1986 m_query->bindValue(0, componentName);
1987 if (!m_query->exec())
1990 m_query->prepare(
"SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1991 m_query->bindValue(0, componentName);
1992 if (!m_query->exec() || !m_query->next())
1996 const qint64 componentId = m_query->value(0).toLongLong();
1998 m_query->prepare(
"INSERT INTO ComponentMapping VALUES(?, ?)"_L1);
1999 m_query->bindValue(0, componentId);
2000 m_query->bindValue(1, namespaceId);
2001 if (!m_query->exec())
2012 m_query->prepare(
"INSERT INTO VersionTable (NamespaceId, Version) VALUES(?, ?)"_L1);
2013 m_query->addBindValue(namespaceId);
2014 m_query->addBindValue(version);
2015 return m_query->exec();
2019 const QString &nameSpace,
bool createDefaultVersionFilter)
2024 m_query->prepare(
"SELECT Id, FilePath FROM NamespaceTable WHERE Name=?"_L1);
2025 m_query->bindValue(0, nameSpace);
2027 if (!m_query->next())
2030 const qint64 nsId = m_query->value(0).toLongLong();
2031 const QString fileName = m_query->value(1).toString();
2033 m_query->prepare(
"SELECT Id, Name FROM FolderTable WHERE NamespaceId=?"_L1);
2034 m_query->bindValue(0, nsId);
2036 if (!m_query->next())
2039 const qint64 vfId = m_query->value(0).toLongLong();
2040 const QString vfName = m_query->value(1).toString();
2044 if (!unregisterIndexTable(nsId, vfId))
2047 const QString absFileName = absoluteDocPath(fileName);
2048 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
2049 fileName,
this),
this);
2053 registerComponent(vfName, nsId);
2054 registerVersion(reader.version(), nsId);
2055 if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId))
2058 if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName))
2061 if (createDefaultVersionFilter)
2062 createVersionFilter(reader.version());
2068 if (version.isEmpty())
2071 const QVersionNumber versionNumber = QVersionNumber::fromString(version);
2072 if (versionNumber.isNull())
2075 const QString filterName = tr(
"Version %1").arg(version);
2076 if (filters().contains(filterName))
2079 QHelpFilterData filterData;
2080 filterData.setVersions({versionNumber});
2081 setFilterData(filterName, filterData);
2085 qint64 nsId, qint64 vfId,
const QString &fileName)
2087 const auto fail = [
this, &fileName] {
2088 emit error(tr(
"Cannot register index tables of documentation %1.").arg(fileName));
2097 QMap<QString, QVariantList> filterAttributeToNewFileId;
2099 QVariantList fileFolderIds;
2100 QVariantList fileNames;
2101 QVariantList fileTitles;
2102 const int fileSize = indexTable.fileItems.size();
2103 fileFolderIds.reserve(fileSize);
2104 fileNames.reserve(fileSize);
2105 fileTitles.reserve(fileSize);
2107 const std::optional<qint64> maxFileIdOpt
2108 = readMaxId(m_query.get(),
"FileNameTable"_L1,
"FileId"_L1, fileSize);
2112 const qint64 maxFileId = *maxFileIdOpt;
2113 QVariantList fileIds;
2114 fileIds.reserve(fileSize);
2116 for (
const QHelpDBReader::FileItem &item : indexTable.fileItems) {
2117 fileIds.append(maxFileId + newFileId + 1);
2118 fileFolderIds.append(vfId);
2119 fileNames.append(item.name);
2120 fileTitles.append(item.title);
2122 for (
const QString &filterAttribute : item.filterAttributes)
2123 filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1);
2127 m_query->prepare(
"INSERT INTO FileNameTable VALUES(?, ?, ?, ?)"_L1);
2128 m_query->addBindValue(fileFolderIds);
2129 m_query->addBindValue(fileNames);
2130 m_query->addBindValue(fileIds);
2131 m_query->addBindValue(fileTitles);
2132 if (!m_query->execBatch())
2135 for (
auto it = filterAttributeToNewFileId.cbegin(),
2136 end = filterAttributeToNewFileId.cend(); it != end; ++it) {
2137 const QString filterAttribute = it.key();
2138 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2139 m_query->bindValue(0, filterAttribute);
2140 if (!m_query->exec() || !m_query->next())
2143 const qint64 attributeId = m_query->value(0).toLongLong();
2145 QVariantList attributeIds;
2146 for (
int i = 0; i < it.value().size(); i++)
2147 attributeIds.append(attributeId);
2149 m_query->prepare(
"INSERT INTO FileFilterTable VALUES(?, ?)"_L1);
2150 m_query->addBindValue(attributeIds);
2151 m_query->addBindValue(it.value());
2152 if (!m_query->execBatch())
2156 QMap<QString, QVariantList> filterAttributeToNewIndexId;
2158 QVariantList indexIds;
2159 QVariantList indexNames;
2160 QVariantList indexIdentifiers;
2161 QVariantList indexNamespaceIds;
2162 QVariantList indexFileIds;
2163 QVariantList indexAnchors;
2164 const int indexSize = indexTable.indexItems.size();
2165 indexIds.reserve(indexSize);
2166 indexNames.reserve(indexSize);
2167 indexIdentifiers.reserve(indexSize);
2168 indexNamespaceIds.reserve(indexSize);
2169 indexFileIds.reserve(indexSize);
2170 indexAnchors.reserve(indexSize);
2172 const std::optional<qint64> maxIndexIdOpt
2173 = readMaxId(m_query.get(),
"IndexTable"_L1,
"Id"_L1, indexSize);
2177 const qint64 maxIndexId = *maxIndexIdOpt;
2179 for (
const QHelpDBReader::IndexItem &item : indexTable.indexItems) {
2182 if (item.fileId < 0 || item.fileId >= fileSize) {
2183 emit error(tr(
"Invalid file reference for keyword '%1' in '%2'.")
2184 .arg(item.name, fileName));
2187 indexIds.append(maxIndexId + newIndexId + 1);
2188 indexNames.append(item.name);
2189 indexIdentifiers.append(item.identifier);
2190 indexNamespaceIds.append(nsId);
2191 indexFileIds.append(maxFileId + item.fileId + 1);
2192 indexAnchors.append(item.anchor);
2194 for (
const QString &filterAttribute : item.filterAttributes)
2195 filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1);
2199 m_query->prepare(
"INSERT INTO IndexTable VALUES(?, ?, ?, ?, ?, ?)"_L1);
2200 m_query->addBindValue(indexIds);
2201 m_query->addBindValue(indexNames);
2202 m_query->addBindValue(indexIdentifiers);
2203 m_query->addBindValue(indexNamespaceIds);
2204 m_query->addBindValue(indexFileIds);
2205 m_query->addBindValue(indexAnchors);
2206 if (!m_query->execBatch())
2209 for (
auto it = filterAttributeToNewIndexId.cbegin(),
2210 end = filterAttributeToNewIndexId.cend(); it != end; ++it) {
2211 const QString filterAttribute = it.key();
2212 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2213 m_query->bindValue(0, filterAttribute);
2214 if (!m_query->exec() || !m_query->next())
2217 const qint64 attributeId = m_query->value(0).toLongLong();
2219 QVariantList attributeIds;
2220 for (
int i = 0; i < it.value().size(); i++)
2221 attributeIds.append(attributeId);
2223 m_query->prepare(
"INSERT INTO IndexFilterTable VALUES(?, ?)"_L1);
2224 m_query->addBindValue(attributeIds);
2225 m_query->addBindValue(it.value());
2226 if (!m_query->execBatch())
2230 QMap<QString, QVariantList> filterAttributeToNewContentsId;
2232 QVariantList contentsIds;
2233 QVariantList contentsNsIds;
2234 QVariantList contentsData;
2235 const int contentsSize = indexTable.contentsItems.size();
2236 contentsIds.reserve(contentsSize);
2237 contentsNsIds.reserve(contentsSize);
2238 contentsData.reserve(contentsSize);
2240 const std::optional<qint64> maxContentsIdOpt
2241 = readMaxId(m_query.get(),
"ContentsTable"_L1,
"Id"_L1, contentsSize);
2242 if (!maxContentsIdOpt)
2245 const qint64 maxContentsId = *maxContentsIdOpt;
2246 int newContentsId = 0;
2247 for (
const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) {
2248 contentsIds.append(maxContentsId + newContentsId + 1);
2249 contentsNsIds.append(nsId);
2250 contentsData.append(item.data);
2252 for (
const QString &filterAttribute : item.filterAttributes) {
2253 filterAttributeToNewContentsId[filterAttribute]
2254 .append(maxContentsId + newContentsId + 1);
2259 m_query->prepare(
"INSERT INTO ContentsTable VALUES(?, ?, ?)"_L1);
2260 m_query->addBindValue(contentsIds);
2261 m_query->addBindValue(contentsNsIds);
2262 m_query->addBindValue(contentsData);
2263 if (!m_query->execBatch())
2266 for (
auto it = filterAttributeToNewContentsId.cbegin(),
2267 end = filterAttributeToNewContentsId.cend(); it != end; ++it) {
2268 const QString filterAttribute = it.key();
2269 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2270 m_query->bindValue(0, filterAttribute);
2271 if (!m_query->exec() || !m_query->next())
2274 const qint64 attributeId = m_query->value(0).toLongLong();
2276 QVariantList attributeIds;
2277 for (
int i = 0; i < it.value().size(); i++)
2278 attributeIds.append(attributeId);
2280 m_query->prepare(
"INSERT INTO ContentsFilterTable VALUES(?, ?)"_L1);
2281 m_query->addBindValue(attributeIds);
2282 m_query->addBindValue(it.value());
2283 if (!m_query->execBatch())
2287 QVariantList filterNsIds;
2288 QVariantList filterAttributeIds;
2289 for (
const QString &filterAttribute : indexTable.usedFilterAttributes) {
2290 filterNsIds.append(nsId);
2292 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2293 m_query->bindValue(0, filterAttribute);
2294 if (!m_query->exec() || !m_query->next())
2297 filterAttributeIds.append(m_query->value(0).toLongLong());
2300 m_query->prepare(
"INSERT INTO OptimizedFilterTable "
2301 "(NamespaceId, FilterAttributeId) VALUES(?, ?)"_L1);
2302 m_query->addBindValue(filterNsIds);
2303 m_query->addBindValue(filterAttributeIds);
2304 if (!m_query->execBatch())
2307 m_query->prepare(
"INSERT INTO TimeStampTable "
2308 "(NamespaceId, FolderId, FilePath, Size, TimeStamp) "
2309 "VALUES(?, ?, ?, ?, ?)"_L1);
2310 m_query->addBindValue(nsId);
2311 m_query->addBindValue(vfId);
2312 m_query->addBindValue(fileName);
2313 const QFileInfo fi(absoluteDocPath(fileName));
2314 m_query->addBindValue(fi.size());
2315 QDateTime lastModified = fi.lastModified(QTimeZone::UTC);
2316 if (qEnvironmentVariableIsSet(
"SOURCE_DATE_EPOCH")) {
2317 const QString sourceDateEpochStr = qEnvironmentVariable(
"SOURCE_DATE_EPOCH");
2319 const qlonglong sourceDateEpoch = sourceDateEpochStr.toLongLong(&ok);
2320 if (ok && sourceDateEpoch < lastModified.toSecsSinceEpoch())
2321 lastModified.setSecsSinceEpoch(sourceDateEpoch);
2323 m_query->addBindValue(lastModified);
2324 if (!m_query->exec())
2334 m_query->prepare(
"DELETE FROM IndexFilterTable WHERE IndexId IN "
2335 "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)"_L1);
2336 m_query->bindValue(0, nsId);
2337 if (!m_query->exec())
2340 m_query->prepare(
"DELETE FROM IndexTable WHERE NamespaceId = ?"_L1);
2341 m_query->bindValue(0, nsId);
2342 if (!m_query->exec())
2345 m_query->prepare(
"DELETE FROM FileFilterTable WHERE FileId IN "
2346 "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)"_L1);
2347 m_query->bindValue(0, vfId);
2348 if (!m_query->exec())
2351 m_query->prepare(
"DELETE FROM FileNameTable WHERE FolderId = ?"_L1);
2352 m_query->bindValue(0, vfId);
2353 if (!m_query->exec())
2356 m_query->prepare(
"DELETE FROM ContentsFilterTable WHERE ContentsId IN "
2357 "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)"_L1);
2358 m_query->bindValue(0, nsId);
2359 if (!m_query->exec())
2362 m_query->prepare(
"DELETE FROM ContentsTable WHERE NamespaceId = ?"_L1);
2363 m_query->bindValue(0, nsId);
2364 if (!m_query->exec())
2367 m_query->prepare(
"DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?"_L1);
2368 m_query->bindValue(0, nsId);
2369 if (!m_query->exec())
2372 m_query->prepare(
"DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?"_L1);
2373 m_query->bindValue(0, nsId);
2374 if (!m_query->exec())
2377 m_query->prepare(
"DELETE FROM TimeStampTable WHERE NamespaceId = ?"_L1);
2378 m_query->bindValue(0, nsId);
2379 if (!m_query->exec())
2382 m_query->prepare(
"DELETE FROM VersionTable WHERE NamespaceId = ?"_L1);
2383 m_query->bindValue(0, nsId);
2384 if (!m_query->exec())
2387 m_query->prepare(
"SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2388 m_query->bindValue(0, nsId);
2389 if (!m_query->exec())
2392 if (!m_query->next())
2395 const qint64 componentId = m_query->value(0).toLongLong();
2397 m_query->prepare(
"DELETE FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2398 m_query->bindValue(0, nsId);
2399 if (!m_query->exec())
2402 m_query->prepare(
"SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?"_L1);
2403 m_query->bindValue(0, componentId);
2404 if (!m_query->exec())
2407 if (!m_query->next()) {
2408 m_query->prepare(
"DELETE FROM ComponentTable WHERE ComponentId = ?"_L1);
2409 m_query->bindValue(0, componentId);
2410 if (!m_query->exec())
2418 const QString &relFileName,
const QString &anchor)
2421 url.setScheme(
"qthelp"_L1);
2422 url.setAuthority(ns);
2423 url.setPath(u'/' + folder + u'/' + relFileName);
2424 url.setFragment(anchor);
2429 const QString &id,
const QStringList &filterAttributes)
const
2431 return documentsForField(
"Identifier"_L1, id, filterAttributes);
2435 const QString &keyword,
const QStringList &filterAttributes)
const
2437 return documentsForField(
"Name"_L1, keyword, filterAttributes);
2441 const QString &fieldValue,
const QStringList &filterAttributes)
const
2446 const QString filterlessQuery =
2448 "FileNameTable.Title, "
2449 "NamespaceTable.Name, "
2450 "FolderTable.Name, "
2451 "FileNameTable.Name, "
2452 "IndexTable.Anchor "
2458 "WHERE IndexTable.FileId = FileNameTable.FileId "
2459 "AND FileNameTable.FolderId = FolderTable.Id "
2460 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2461 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2463 const QString filterQuery = filterlessQuery
2464 + prepareFilterQuery(filterAttributes.size(),
"IndexTable"_L1,
"Id"_L1,
2465 "IndexFilterTable"_L1,
"IndexId"_L1);
2467 m_query->prepare(filterQuery);
2468 m_query->bindValue(0, fieldValue);
2469 bindFilterQuery(m_query.get(), 1, filterAttributes);
2473 QList<QHelpLink> docList;
2474 while (m_query->next()) {
2475 QString title = m_query->value(0).toString();
2476 if (title.isEmpty())
2477 title = fieldValue +
" : "_L1 + m_query->value(3).toString();
2479 const QUrl url = buildQUrl(m_query->value(1).toString(),
2480 m_query->value(2).toString(),
2481 m_query->value(3).toString(),
2482 m_query->value(4).toString());
2483 docList.append(QHelpLink {url, title});
2489 const QString &id,
const QString &filterName)
const
2491 return documentsForField(
"Identifier"_L1, id, filterName);
2495 const QString &keyword,
const QString &filterName)
const
2497 return documentsForField(
"Name"_L1, keyword, filterName);
2501 const QString &fieldValue,
const QString &filterName)
const
2503 QMultiMap<QString, QUrl> linkMap;
2504 const auto documents = documentsForField(fieldName, fieldValue, filterName);
2505 for (
const auto &document : documents)
2506 linkMap.insert(document.title, document.url);
2519 return lhs.title == rhs.title && lhs.url == rhs.url && lhs.version == rhs.version;
2526 return !e.version.isNull();
2531 return lhs.version < rhs.version;
2536 return { e.url, e.title };
2540 const QString &fieldValue,
const QString &filterName)
const
2545 const QString filterlessQuery =
2547 "FileNameTable.Title, "
2548 "NamespaceTable.Name, "
2549 "FolderTable.Name, "
2550 "FileNameTable.Name, "
2551 "IndexTable.Anchor "
2557 "WHERE IndexTable.FileId = FileNameTable.FileId "
2558 "AND FileNameTable.FolderId = FolderTable.Id "
2559 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2560 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2562 const QString filterQuery = filterlessQuery
2563 + prepareFilterQuery(filterName)
2564 +
" ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"_L1;
2566 m_query->prepare(filterQuery);
2567 m_query->bindValue(0, fieldValue);
2568 bindFilterQuery(m_query.get(), 1, filterName);
2572 QList<IndexEntry> entries;
2573 while (m_query->next()) {
2574 QString title = m_query->value(0).toString();
2575 if (title.isEmpty())
2576 title = fieldValue +
" : "_L1 + m_query->value(3).toString();
2577 const QString nameSpace = m_query->value(1).toString();
2578 const QUrl url = buildQUrl(nameSpace,
2579 m_query->value(2).toString(),
2580 m_query->value(3).toString(),
2581 m_query->value(4).toString());
2582 IndexEntry newEntry{ title, url, QHelpDBReader::versionHeuristic(nameSpace) };
2583 if (!entries.contains(newEntry))
2584 entries.emplaceBack(newEntry);
2588 if (
std::all_of(entries.cbegin(), entries.cend(),
hasVersion))
2591 QList<QHelpLink> docList;
2592 docList.reserve(entries.size());
2593 std::transform(entries.cbegin(), entries.cend(),
std::back_inserter(docList), toHelpLink);
2599 QStringList namespaceList;
2602 return namespaceList;
2604 const QString filterlessQuery =
2606 "NamespaceTable.Name "
2611 const QString filterQuery = filterlessQuery
2612 + prepareFilterQuery(filterName);
2614 m_query->prepare(filterQuery);
2615 bindFilterQuery(m_query.get(), 0, filterName);
2619 while (m_query->next())
2620 namespaceList.append(m_query->value(0).toString());
2621 return namespaceList;
QStringList filters() const
QUrl findFile(const QUrl &url, const QStringList &filterAttributes) const
QByteArray fileData(const QUrl &url) const
QStringList customFilters() const
FileInfoList registeredDocumentations() const
QList< ContentsData > contentsForFilter(const QStringList &filterAttributes) const
~QHelpCollectionHandler()
QMap< QString, QVersionNumber > namespaceToVersion() const
QList< FileInfo > FileInfoList
QList< QVersionNumber > availableVersions() const
QString namespaceForFile(const QUrl &url, const QStringList &filterAttributes) const
QStringList filterAttributes() const
QMap< QString, QString > namespaceToComponent() const
QStringList availableComponents() const
QStringList indicesForFilter(const QStringList &filterAttributes) const
bool fileExists(const QUrl &url) const
bool openCollectionFile()
Transaction(const QString &connectionName)
static bool hasVersion(const IndexEntry &e)
static std::optional< qint64 > readMaxId(QSqlQuery *query, const QString &tableName, const QString &columnName, int count)
static QString getTitle(const QByteArray &contents)
static QHelpCollectionHandler::FileInfo extractFileInfo(const QUrl &url)
static QString prepareFilterQuery(int attributesCount, const QString &idTableName, const QString &idColumnName, const QString &filterTableName, const QString &filterColumnName)
static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName)
static bool versionLessThan(const IndexEntry &lhs, const IndexEntry &rhs)
static void bindFilterQuery(QSqlQuery *query, int startingBindPos, const QStringList &filterAttributes)
static constexpr int CollectionSchemaVersion
static QString writeSchemaVersionStatement()
static QString prepareFilterQuery(const QString &filterName)
static QHelpLink toHelpLink(const IndexEntry &e)
friend bool comparesEqual(const IndexEntry &lhs, const IndexEntry &rhs) noexcept