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>
28using namespace Qt::StringLiterals;
37 return "PRAGMA user_version = "_L1 + QString::number(CollectionSchemaVersion);
50 m_inTransaction = m_db.transaction();
68 m_inTransaction =
false;
79 , m_collectionFile(collectionFile)
81 const QFileInfo fi(m_collectionFile);
83 m_collectionFile = fi.absoluteFilePath();
96 emit that->error(tr(
"The collection file \"%1\" is not set up yet.").arg(m_collectionFile));
106 QSqlDatabase::removeDatabase(m_connectionName);
107 m_connectionName.clear();
120 const QFileInfo fi(m_collectionFile);
122 emit error(tr(
"The collection file \"%1\" does not exist.").arg(m_collectionFile));
125 if (fi.size() == 0) {
126 emit error(tr(
"The collection file \"%1\" is empty.").arg(m_collectionFile));
131 m_connectionName = QHelpGlobal::uniquifyConnectionName(
"QHelpCollectionHandler"_L1,
this);
133 QSqlDatabase db = QSqlDatabase::addDatabase(
"QSQLITE"_L1, m_connectionName);
135 && db.driver()->lastError().type() == QSqlError::ConnectionError) {
136 emit error(tr(
"Cannot load sqlite database driver."));
140 db.setDatabaseName(collectionFile());
142 m_query.reset(
new QSqlQuery(db));
145 QSqlDatabase::removeDatabase(m_connectionName);
146 emit error(tr(
"Cannot open collection file: %1").arg(collectionFile()));
151 if (!m_query->exec(
"PRAGMA user_version"_L1) || !m_query->next()) {
153 emit error(tr(
"Cannot read the version of collection file %1.").arg(collectionFile()));
157 const int schemaVersion = m_query->value(0).toInt();
160 emit error(tr(
"The collection file \"%1\" was created by a newer version of Qt Help.")
161 .arg(m_collectionFile));
168 m_query->exec(
"PRAGMA synchronous=OFF"_L1);
169 m_query->exec(
"PRAGMA cache_size=3000"_L1);
171 m_query->exec(
"SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' "
172 "AND Name=\'NamespaceTable\'"_L1);
175 const bool tablesExist = m_query->value(0).toInt() > 0;
177 if (!createTables(m_query.get())) {
179 emit error(tr(
"Cannot create tables in file %1.").arg(collectionFile()));
184 bool indexAndNamespaceFilterTablesMissing =
false;
186 const QStringList newTables = {
190 "FileFilterTable"_L1,
191 "IndexFilterTable"_L1,
192 "ContentsFilterTable"_L1,
193 "FileAttributeSetTable"_L1,
194 "OptimizedFilterTable"_L1,
199 "ComponentMapping"_L1,
200 "ComponentFilter"_L1,
204 QString queryString =
"SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"_L1;
205 queryString.append(
" AND (Name=\'"_L1);
206 queryString.append(newTables.join(
"\' OR Name=\'"_L1));
207 queryString.append(
"\')"_L1);
209 m_query->exec(queryString);
211 if (m_query->value(0).toInt() != newTables.size()) {
212 if (!recreateIndexAndNamespaceFilterTables(m_query.get())) {
213 emit error(tr(
"Cannot create index tables in file %1.").arg(collectionFile()));
218 indexAndNamespaceFilterTablesMissing = tablesExist;
221 if (schemaVersion < CollectionSchemaVersion
222 && !m_query->exec(writeSchemaVersionStatement())) {
223 emit error(tr(
"Cannot write the version of collection file %1.").arg(collectionFile()));
228 if (indexAndNamespaceFilterTablesMissing) {
229 for (
const QHelpCollectionHandler::FileInfo &info : docList) {
230 if (!registerIndexAndNamespaceFilterTables(info.namespaceName,
true)) {
231 emit error(tr(
"Cannot register index tables in file %1.").arg(collectionFile()));
238 QList<TimeStamp> timeStamps;
239 m_query->exec(
"SELECT NamespaceId, FolderId, FilePath, Size, TimeStamp FROM TimeStampTable"_L1);
240 while (m_query->next()) {
242 timeStamp.namespaceId = m_query->value(0).toInt();
243 timeStamp.folderId = m_query->value(1).toInt();
244 timeStamp.fileName = m_query->value(2).toString();
245 timeStamp.size = m_query->value(3).toLongLong();
246 timeStamp.timeStamp = m_query->value(4).toDateTime();
247 timeStamps.append(timeStamp);
250 QList<TimeStamp> toRemove;
251 for (
const TimeStamp &timeStamp : timeStamps) {
252 if (!isTimeStampCorrect(timeStamp))
253 toRemove.append(timeStamp);
259 for (
const TimeStamp &timeStamp : toRemove) {
260 if (!unregisterIndexTable(timeStamp.namespaceId, timeStamp.folderId)) {
261 emit error(tr(
"Cannot unregister index tables in file %1.").arg(collectionFile()));
266 emit error(tr(
"Cannot unregister index tables in file %1.").arg(collectionFile()));
270 for (
const QHelpCollectionHandler::FileInfo &info : docList) {
271 if (!hasTimeStampInfo(info.namespaceName)
272 && !registerIndexAndNamespaceFilterTables(info.namespaceName)) {
275 unregisterDocumentation(info.namespaceName);
283 const QFileInfo fi(collectionFile());
284 return QDir::isAbsolutePath(fileName)
286 : QFileInfo(fi.absolutePath() + u'/' + fileName).absoluteFilePath();
291 const QFileInfo fi(absoluteDocPath(timeStamp.fileName));
296 if (fi.size() != timeStamp.size)
299 if (fi.lastModified(QTimeZone::UTC) != timeStamp.timeStamp)
302 m_query->prepare(
"SELECT FilePath FROM NamespaceTable WHERE Id = ?"_L1);
303 m_query->bindValue(0, timeStamp.namespaceId);
304 if (!m_query->exec() || !m_query->next())
307 const QString oldFileName = m_query->value(0).toString();
309 if (oldFileName != timeStamp.fileName)
319 "TimeStampTable.NamespaceId "
323 "WHERE NamespaceTable.Id = TimeStampTable.NamespaceId "
324 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
325 m_query->bindValue(0, nameSpace);
326 if (!m_query->exec())
329 if (!m_query->next())
338 if (m_vacuumScheduled)
341 m_vacuumScheduled =
true;
342 QTimer::singleShot(0,
this, &QHelpCollectionHandler::execVacuum);
350 m_query->exec(
"VACUUM"_L1);
351 m_vacuumScheduled =
false;
359 const QFileInfo fi(fileName);
361 emit error(tr(
"The collection file \"%1\" already exists.").arg(fileName));
365 if (!fi.absoluteDir().exists() && !QDir().mkpath(fi.absolutePath())) {
366 emit error(tr(
"Cannot create directory: %1").arg(fi.absolutePath()));
370 const QString &colFile = fi.absoluteFilePath();
371 const QString &connectionName =
372 QHelpGlobal::uniquifyConnectionName(
"QHelpCollectionHandlerCopy"_L1,
this);
373 QSqlDatabase db = QSqlDatabase::addDatabase(
"QSQLITE"_L1, connectionName);
374 db.setDatabaseName(colFile);
376 emit error(tr(
"Cannot open collection file: %1").arg(colFile));
380 const auto copyTables = [&] {
381 QSqlQuery copyQuery(db);
382 copyQuery.exec(
"PRAGMA synchronous=OFF"_L1);
383 copyQuery.exec(
"PRAGMA cache_size=3000"_L1);
387 if (!createTables(©Query) || !recreateIndexAndNamespaceFilterTables(©Query)
388 || !copyQuery.exec(writeSchemaVersionStatement())) {
392 const QString &oldBaseDir = QFileInfo(collectionFile()).absolutePath();
393 const QFileInfo newColFi(colFile);
394 m_query->exec(
"SELECT Name, FilePath FROM NamespaceTable"_L1);
395 while (m_query->next()) {
396 copyQuery.prepare(
"INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
397 copyQuery.bindValue(0, m_query->value(0).toString());
398 QString oldFilePath = m_query->value(1).toString();
399 if (!QDir::isAbsolutePath(oldFilePath))
400 oldFilePath = oldBaseDir + u'/' + oldFilePath;
401 copyQuery.bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath));
402 if (!copyQuery.exec())
406 m_query->exec(
"SELECT NamespaceId, Name FROM FolderTable"_L1);
407 while (m_query->next()) {
408 copyQuery.prepare(
"INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
409 copyQuery.bindValue(0, m_query->value(0).toString());
410 copyQuery.bindValue(1, m_query->value(1).toString());
411 if (!copyQuery.exec())
415 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
416 while (m_query->next()) {
417 copyQuery.prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
418 copyQuery.bindValue(0, m_query->value(0).toString());
419 if (!copyQuery.exec())
423 m_query->exec(
"SELECT Name FROM FilterNameTable"_L1);
424 while (m_query->next()) {
425 copyQuery.prepare(
"INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
426 copyQuery.bindValue(0, m_query->value(0).toString());
427 if (!copyQuery.exec())
431 m_query->exec(
"SELECT NameId, FilterAttributeId FROM FilterTable"_L1);
432 while (m_query->next()) {
433 copyQuery.prepare(
"INSERT INTO FilterTable VALUES(?, ?)"_L1);
434 copyQuery.bindValue(0, m_query->value(0).toInt());
435 copyQuery.bindValue(1, m_query->value(1).toInt());
436 if (!copyQuery.exec())
440 m_query->exec(
"SELECT Key, Value FROM SettingsTable"_L1);
441 while (m_query->next()) {
442 if (m_query->value(0).toString() ==
"FTS5IndexedNamespaces"_L1)
444 copyQuery.prepare(
"INSERT INTO SettingsTable VALUES(?, ?)"_L1);
445 copyQuery.bindValue(0, m_query->value(0).toString());
446 copyQuery.bindValue(1, m_query->value(1));
447 if (!copyQuery.exec())
454 const bool copied = copyTables();
457 QSqlDatabase::removeDatabase(connectionName);
459 QFile::remove(colFile);
460 emit error(tr(
"Cannot copy collection file: %1").arg(colFile));
468 const QStringList tables = {
469 "CREATE TABLE NamespaceTable ("
470 "Id INTEGER PRIMARY KEY, "
472 "FilePath TEXT )"_L1,
473 "CREATE TABLE FolderTable ("
474 "Id INTEGER PRIMARY KEY, "
475 "NamespaceId INTEGER, "
477 "CREATE TABLE FilterAttributeTable ("
478 "Id INTEGER PRIMARY KEY, "
480 "CREATE TABLE FilterNameTable ("
481 "Id INTEGER PRIMARY KEY, "
483 "CREATE TABLE FilterTable ("
485 "FilterAttributeId INTEGER )"_L1,
486 "CREATE TABLE SettingsTable ("
487 "Key TEXT PRIMARY KEY, "
491 for (
const QString &q : tables) {
500 const QStringList tables = {
501 "DROP TABLE IF EXISTS FileNameTable"_L1,
502 "DROP TABLE IF EXISTS IndexTable"_L1,
503 "DROP TABLE IF EXISTS ContentsTable"_L1,
504 "DROP TABLE IF EXISTS FileFilterTable"_L1,
505 "DROP TABLE IF EXISTS IndexFilterTable"_L1,
506 "DROP TABLE IF EXISTS ContentsFilterTable"_L1,
507 "DROP TABLE IF EXISTS FileAttributeSetTable"_L1,
508 "DROP TABLE IF EXISTS OptimizedFilterTable"_L1,
509 "DROP TABLE IF EXISTS TimeStampTable"_L1,
510 "DROP TABLE IF EXISTS VersionTable"_L1,
511 "DROP TABLE IF EXISTS Filter"_L1,
512 "DROP TABLE IF EXISTS ComponentTable"_L1,
513 "DROP TABLE IF EXISTS ComponentMapping"_L1,
514 "DROP TABLE IF EXISTS ComponentFilter"_L1,
515 "DROP TABLE IF EXISTS VersionFilter"_L1,
516 "CREATE TABLE FileNameTable ("
519 "FileId INTEGER PRIMARY KEY, "
521 "CREATE TABLE IndexTable ("
522 "Id INTEGER PRIMARY KEY, "
525 "NamespaceId INTEGER, "
528 "CREATE TABLE ContentsTable ("
529 "Id INTEGER PRIMARY KEY, "
530 "NamespaceId INTEGER, "
532 "CREATE TABLE FileFilterTable ("
533 "FilterAttributeId INTEGER, "
534 "FileId INTEGER)"_L1,
535 "CREATE TABLE IndexFilterTable ("
536 "FilterAttributeId INTEGER, "
537 "IndexId INTEGER)"_L1,
538 "CREATE TABLE ContentsFilterTable ("
539 "FilterAttributeId INTEGER, "
540 "ContentsId INTEGER )"_L1,
541 "CREATE TABLE FileAttributeSetTable ("
542 "NamespaceId INTEGER, "
543 "FilterAttributeSetId INTEGER, "
544 "FilterAttributeId INTEGER)"_L1,
545 "CREATE TABLE OptimizedFilterTable ("
546 "NamespaceId INTEGER, "
547 "FilterAttributeId INTEGER)"_L1,
548 "CREATE TABLE TimeStampTable ("
549 "NamespaceId INTEGER, "
553 "TimeStamp TEXT)"_L1,
554 "CREATE TABLE VersionTable ("
555 "NamespaceId INTEGER, "
557 "CREATE TABLE Filter ("
558 "FilterId INTEGER PRIMARY KEY, "
560 "CREATE TABLE ComponentTable ("
561 "ComponentId INTEGER PRIMARY KEY, "
563 "CREATE TABLE ComponentMapping ("
564 "ComponentId INTEGER, "
565 "NamespaceId INTEGER)"_L1,
566 "CREATE TABLE ComponentFilter ("
567 "ComponentName TEXT, "
568 "FilterId INTEGER)"_L1,
569 "CREATE TABLE VersionFilter ("
571 "FilterId INTEGER)"_L1
574 for (
const QString &q : tables) {
585 m_query->exec(
"SELECT Name FROM FilterNameTable"_L1);
586 while (m_query->next())
587 list.append(m_query->value(0).toString());
596 m_query->exec(
"SELECT Name FROM Filter ORDER BY Name"_L1);
597 while (m_query->next())
598 list.append(m_query->value(0).toString());
607 m_query->exec(
"SELECT DISTINCT Name FROM ComponentTable ORDER BY Name"_L1);
608 while (m_query->next())
609 list.append(m_query->value(0).toString());
616 QList<QVersionNumber> list;
618 m_query->exec(
"SELECT DISTINCT Version FROM VersionTable ORDER BY Version"_L1);
619 while (m_query->next())
620 list.append(QVersionNumber::fromString(m_query->value(0).toString()));
627 QMap<QString, QString> result;
631 "NamespaceTable.Name, "
632 "ComponentTable.Name "
633 "FROM NamespaceTable, "
636 "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId "
637 "AND ComponentMapping.ComponentId = ComponentTable.ComponentId"_L1);
638 while (m_query->next())
639 result.insert(m_query->value(0).toString(), m_query->value(1).toString());
646 QMap<QString, QVersionNumber> result;
650 "NamespaceTable.Name, "
651 "VersionTable.Version "
652 "FROM NamespaceTable, "
654 "WHERE NamespaceTable.Id = VersionTable.NamespaceId"_L1);
655 while (m_query->next()) {
656 result.insert(m_query->value(0).toString(),
657 QVersionNumber::fromString(m_query->value(1).toString()));
665 QStringList components;
666 QList<QVersionNumber> versions;
669 "SELECT ComponentFilter.ComponentName "
670 "FROM ComponentFilter, Filter "
671 "WHERE ComponentFilter.FilterId = Filter.FilterId "
672 "AND Filter.Name = ? "
673 "ORDER BY ComponentFilter.ComponentName"_L1);
674 m_query->bindValue(0, filterName);
676 while (m_query->next())
677 components.append(m_query->value(0).toString());
680 "SELECT VersionFilter.Version "
681 "FROM VersionFilter, Filter "
682 "WHERE VersionFilter.FilterId = Filter.FilterId "
683 "AND Filter.Name = ? "
684 "ORDER BY VersionFilter.Version"_L1);
685 m_query->bindValue(0, filterName);
687 while (m_query->next())
688 versions.append(QVersionNumber::fromString(m_query->value(0).toString()));
691 QHelpFilterData data;
692 data.setComponents(components);
693 data.setVersions(versions);
698 const QHelpFilterData &filterData)
700 if (!removeFilter(filterName))
703 m_query->prepare(
"INSERT INTO Filter VALUES (NULL, ?)"_L1);
704 m_query->bindValue(0, filterName);
705 if (!m_query->exec())
708 const int filterId = m_query->lastInsertId().toInt();
710 QVariantList componentList;
711 QVariantList versionList;
712 QVariantList filterIdList;
714 const auto &components = filterData.components();
715 for (
const QString &component : components) {
716 componentList.append(component);
717 filterIdList.append(filterId);
720 m_query->prepare(
"INSERT INTO ComponentFilter VALUES (?, ?)"_L1);
721 m_query->addBindValue(componentList);
722 m_query->addBindValue(filterIdList);
723 if (!m_query->execBatch())
726 filterIdList.clear();
727 const auto &versions = filterData.versions();
728 for (
const QVersionNumber &version : versions) {
729 versionList.append(version.isNull() ? QString() : version.toString());
730 filterIdList.append(filterId);
733 m_query->prepare(
"INSERT INTO VersionFilter VALUES (?, ?)"_L1);
734 m_query->addBindValue(versionList);
735 m_query->addBindValue(filterIdList);
736 if (!m_query->execBatch())
744 m_query->prepare(
"SELECT FilterId FROM Filter WHERE Name = ?"_L1);
745 m_query->bindValue(0, filterName);
746 if (!m_query->exec())
749 if (!m_query->next())
752 const int filterId = m_query->value(0).toInt();
754 m_query->prepare(
"DELETE FROM Filter WHERE Filter.Name = ?"_L1);
755 m_query->bindValue(0, filterName);
756 if (!m_query->exec())
759 m_query->prepare(
"DELETE FROM ComponentFilter WHERE ComponentFilter.FilterId = ?"_L1);
760 m_query->bindValue(0, filterId);
761 if (!m_query->exec())
764 m_query->prepare(
"DELETE FROM VersionFilter WHERE VersionFilter.FilterId = ?"_L1);
765 m_query->bindValue(0, filterId);
766 if (!m_query->exec())
774 if (!isDBOpened() || filterName.isEmpty())
777 int filterNameId = -1;
778 m_query->prepare(
"SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
779 m_query->bindValue(0, filterName);
782 filterNameId = m_query->value(0).toInt();
784 if (filterNameId < 0) {
785 emit error(tr(
"Unknown filter \"%1\".").arg(filterName));
791 m_query->prepare(
"DELETE FROM FilterTable WHERE NameId=?"_L1);
792 m_query->bindValue(0, filterNameId);
793 if (!m_query->exec())
796 m_query->prepare(
"DELETE FROM FilterNameTable WHERE Id=?"_L1);
797 m_query->bindValue(0, filterNameId);
798 if (!m_query->exec())
805 const QStringList &attributes)
807 if (!isDBOpened() || filterName.isEmpty())
811 m_query->prepare(
"SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
812 m_query->bindValue(0, filterName);
815 nameId = m_query->value(0).toInt();
817 m_query->exec(
"SELECT Id, Name FROM FilterAttributeTable"_L1);
818 QStringList idsToInsert = attributes;
819 QMap<QString,
int> attributeMap;
820 while (m_query->next()) {
822 const QString attributeName = m_query->value(1).toString();
823 attributeMap.insert(attributeName, m_query->value(0).toInt());
824 idsToInsert.removeAll(attributeName);
829 for (
const QString &id : std::as_const(idsToInsert)) {
830 m_query->prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
831 m_query->bindValue(0, id);
832 if (!m_query->exec())
834 attributeMap.insert(id, m_query->lastInsertId().toInt());
838 m_query->prepare(
"INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
839 m_query->bindValue(0, filterName);
841 nameId = m_query->lastInsertId().toInt();
845 emit error(tr(
"Cannot register filter %1.").arg(filterName));
849 m_query->prepare(
"DELETE FROM FilterTable WHERE NameId=?"_L1);
850 m_query->bindValue(0, nameId);
851 if (!m_query->exec())
854 for (
const QString &att : attributes) {
855 m_query->prepare(
"INSERT INTO FilterTable VALUES(?, ?)"_L1);
856 m_query->bindValue(0, nameId);
857 m_query->bindValue(1, attributeMap[att]);
858 if (!m_query->exec())
865 const QString &namespaceName)
const
874 "NamespaceTable.Name, "
875 "NamespaceTable.FilePath, "
880 "WHERE NamespaceTable.Id = FolderTable.NamespaceId "
881 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
882 m_query->bindValue(0, namespaceName);
883 if (!m_query->exec() || !m_query->next())
886 fileInfo.namespaceName = m_query->value(0).toString();
887 fileInfo.fileName = m_query->value(1).toString();
888 fileInfo.folderName = m_query->value(2).toString();
903 "NamespaceTable.Name, "
904 "NamespaceTable.FilePath, "
909 "WHERE NamespaceTable.Id = FolderTable.NamespaceId"_L1);
911 while (m_query->next()) {
913 fileInfo.namespaceName = m_query->value(0).toString();
914 fileInfo.fileName = m_query->value(1).toString();
915 fileInfo.folderName = m_query->value(2).toString();
916 list.append(fileInfo);
927 QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName(
928 "QHelpCollectionHandler"_L1,
this),
nullptr);
930 emit error(tr(
"Cannot open documentation file %1.").arg(fileName));
934 const QString &ns = reader.namespaceName();
936 emit error(tr(
"Invalid documentation file \"%1\".").arg(fileName));
943 const QString absFileName = QFileInfo(fileName).absoluteFilePath();
945 for (
const FileInfo &info : registered) {
946 if (info.namespaceName != ns && absoluteDocPath(info.fileName) == absFileName)
947 unregisterDocumentation(info.namespaceName);
952 const int nsId = registerNamespace(ns, fileName);
956 const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId);
960 registerVersion(reader.version(), nsId);
961 registerFilterAttributes(reader.filterAttributeSets(), nsId);
962 const auto &customFilters = reader.customFilters();
963 for (
const QString &filterName : customFilters)
964 addCustomFilter(filterName, reader.filterAttributes(filterName));
966 if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName))
977 m_query->prepare(
"SELECT Id FROM NamespaceTable WHERE Name = ?"_L1);
978 m_query->bindValue(0, namespaceName);
981 if (!m_query->next()) {
982 emit error(tr(
"The namespace %1 was not registered.").arg(namespaceName));
986 const int nsId = m_query->value(0).toInt();
988 m_query->prepare(
"SELECT Id FROM FolderTable WHERE NamespaceId = ?"_L1);
989 m_query->bindValue(0, nsId);
992 if (!m_query->next()) {
993 emit error(tr(
"The namespace %1 was not registered.").arg(namespaceName));
997 const int vfId = m_query->value(0).toInt();
1001 m_query->prepare(
"DELETE FROM NamespaceTable WHERE Id = ?"_L1);
1002 m_query->bindValue(0, nsId);
1003 if (!m_query->exec())
1006 m_query->prepare(
"DELETE FROM FolderTable WHERE NamespaceId = ?"_L1);
1007 m_query->bindValue(0, nsId);
1008 if (!m_query->exec())
1011 if (!unregisterIndexTable(nsId, vfId) || !transaction
.commit())
1021 QHelpCollectionHandler::FileInfo fileInfo;
1023 if (!url.isValid() || url.toString().count(u'/') < 4
1024 || url.scheme() !=
"qthelp"_L1) {
1028 fileInfo.namespaceName = url.authority();
1029 fileInfo.fileName = url.path();
1030 if (fileInfo.fileName.startsWith(u'/'))
1031 fileInfo.fileName = fileInfo.fileName.mid(1);
1032 fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(u'/', 1));
1033 fileInfo.fileName.remove(0, fileInfo.folderName.size() + 1);
1043 const FileInfo fileInfo = extractFileInfo(url);
1044 if (fileInfo.namespaceName.isEmpty())
1048 "SELECT COUNT (DISTINCT NamespaceTable.Id) "
1053 "WHERE FolderTable.Name = ? "
1054 "AND FileNameTable.Name = ? "
1055 "AND FileNameTable.FolderId = FolderTable.Id "
1056 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1);
1057 m_query->bindValue(0, fileInfo.folderName);
1058 m_query->bindValue(1, fileInfo.fileName);
1059 if (!m_query->exec() || !m_query->next())
1062 const int count = m_query->value(0).toInt();
1070 if (filterName.isEmpty())
1073 return " AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) "
1079 "WHERE ComponentFilter.FilterId = Filter.FilterId "
1080 "AND Filter.Name = ?) "
1081 "OR NamespaceTable.Id IN ("
1083 "NamespaceTable.Id "
1087 "ComponentMapping, "
1090 "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id "
1091 "AND ComponentTable.ComponentId = ComponentMapping.ComponentId "
1092 "AND ((ComponentTable.Name = ComponentFilter.ComponentName) "
1093 "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) "
1094 "AND ComponentFilter.FilterId = Filter.FilterId "
1095 "AND Filter.Name = ?))"
1101 "WHERE VersionFilter.FilterId = Filter.FilterId "
1102 "AND Filter.Name = ?) "
1103 "OR NamespaceTable.Id IN ("
1105 "NamespaceTable.Id "
1111 "WHERE VersionFilter.FilterId = Filter.FilterId "
1112 "AND ((VersionFilter.Version = VersionTable.Version) "
1113 "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) "
1114 "AND VersionTable.NamespaceId = NamespaceTable.Id "
1115 "AND Filter.Name = ?))"
1121 if (filterName.isEmpty())
1124 query->bindValue(bindStart, filterName);
1125 query->bindValue(bindStart + 1, filterName);
1126 query->bindValue(bindStart + 2, filterName);
1127 query->bindValue(bindStart + 3, filterName);
1128 query->bindValue(bindStart + 4, filterName);
1132 const QString &idTableName,
1133 const QString &idColumnName,
1134 const QString &filterTableName,
1135 const QString &filterColumnName)
1137 if (!attributesCount)
1140 QString filterQuery =
" AND (%1.%2 IN ("_L1.arg(idTableName, idColumnName);
1142 const QString filterQueryTemplate =
1144 "FROM %1, FilterAttributeTable "
1145 "WHERE %1.FilterAttributeId = FilterAttributeTable.Id "
1146 "AND FilterAttributeTable.Name = ?"_L1.arg(filterTableName, filterColumnName);
1148 for (
int i = 0; i < attributesCount; ++i) {
1150 filterQuery.append(
" INTERSECT "_L1);
1151 filterQuery.append(filterQueryTemplate);
1154 filterQuery.append(
") OR NamespaceTable.Id IN ("_L1);
1156 const QString optimizedFilterQueryTemplate =
1157 "SELECT OptimizedFilterTable.NamespaceId "
1158 "FROM OptimizedFilterTable, FilterAttributeTable "
1159 "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id "
1160 "AND FilterAttributeTable.Name = ?"_L1;
1162 for (
int i = 0; i < attributesCount; ++i) {
1164 filterQuery.append(
" INTERSECT "_L1);
1165 filterQuery.append(optimizedFilterQueryTemplate);
1168 filterQuery.append(
"))"_L1);
1173static void bindFilterQuery(QSqlQuery *query,
int startingBindPos,
const QStringList &filterAttributes)
1175 for (
int i = 0; i < 2; ++i) {
1176 for (
int j = 0; j < filterAttributes.size(); j++) {
1177 query->bindValue(i * filterAttributes.size() + j + startingBindPos,
1178 filterAttributes.at(j));
1184 const QStringList &filterAttributes)
const
1189 const FileInfo fileInfo = extractFileInfo(url);
1190 if (fileInfo.namespaceName.isEmpty())
1193 const QString filterlessQuery =
1195 "NamespaceTable.Name "
1200 "WHERE FolderTable.Name = ? "
1201 "AND FileNameTable.Name = ? "
1202 "AND FileNameTable.FolderId = FolderTable.Id "
1203 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1205 const QString filterQuery = filterlessQuery
1206 + prepareFilterQuery(filterAttributes.size(),
"FileNameTable"_L1,
"FileId"_L1,
1207 "FileFilterTable"_L1,
"FileId"_L1);
1209 m_query->prepare(filterQuery);
1210 m_query->bindValue(0, fileInfo.folderName);
1211 m_query->bindValue(1, fileInfo.fileName);
1212 bindFilterQuery(m_query.get(), 2, filterAttributes);
1214 if (!m_query->exec())
1217 QStringList namespaceList;
1218 while (m_query->next())
1219 namespaceList.append(m_query->value(0).toString());
1221 if (namespaceList.isEmpty())
1224 if (namespaceList.contains(fileInfo.namespaceName))
1225 return fileInfo.namespaceName;
1227 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1231 QString fallbackNamespace;
1233 for (
const QString &ns : std::as_const(namespaceList)) {
1234 const QString nsVersion = namespaceVersion(ns);
1235 if (originalVersion == nsVersion)
1238 const QVersionNumber version = QVersionNumber::fromString(nsVersion);
1239 const int compare = QVersionNumber::compare(version, fallbackVersion);
1240 if (fallbackNamespace.isEmpty() || compare > 0
1241 || (compare == 0 && ns < fallbackNamespace)) {
1242 fallbackNamespace = ns;
1243 fallbackVersion = version;
1246 return fallbackNamespace;
1250 const QString &filterName)
const
1255 const FileInfo fileInfo = extractFileInfo(url);
1256 if (fileInfo.namespaceName.isEmpty())
1259 const QString filterlessQuery =
1261 "NamespaceTable.Name "
1266 "WHERE FolderTable.Name = ? "
1267 "AND FileNameTable.Name = ? "
1268 "AND FileNameTable.FolderId = FolderTable.Id "
1269 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1271 const QString filterQuery = filterlessQuery
1272 + prepareFilterQuery(filterName);
1274 m_query->prepare(filterQuery);
1275 m_query->bindValue(0, fileInfo.folderName);
1276 m_query->bindValue(1, fileInfo.fileName);
1277 bindFilterQuery(m_query.get(), 2, filterName);
1279 if (!m_query->exec())
1282 QStringList namespaceList;
1283 while (m_query->next())
1284 namespaceList.append(m_query->value(0).toString());
1286 if (namespaceList.isEmpty())
1289 if (namespaceList.contains(fileInfo.namespaceName))
1290 return fileInfo.namespaceName;
1292 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1296 QString fallbackNamespace;
1298 for (
const QString &ns : std::as_const(namespaceList)) {
1299 const QString nsVersion = namespaceVersion(ns);
1300 if (originalVersion == nsVersion)
1303 const QVersionNumber version = QVersionNumber::fromString(nsVersion);
1304 const int compare = QVersionNumber::compare(version, fallbackVersion);
1305 if (fallbackNamespace.isEmpty() || compare > 0
1306 || (compare == 0 && ns < fallbackNamespace)) {
1307 fallbackNamespace = ns;
1308 fallbackVersion = version;
1311 return fallbackNamespace;
1315 const QStringList &filterAttributes,
1316 const QString &extensionFilter)
const
1321 const QString extensionQuery = extensionFilter.isEmpty()
1322 ? QString() :
" AND FileNameTable.Name LIKE ?"_L1;
1323 const QString filterlessQuery =
1325 "FolderTable.Name, "
1326 "FileNameTable.Name "
1331 "WHERE FileNameTable.FolderId = FolderTable.Id "
1332 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1333 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1335 const QString filterQuery = filterlessQuery
1336 + prepareFilterQuery(filterAttributes.size(),
"FileNameTable"_L1,
"FileId"_L1,
1337 "FileFilterTable"_L1,
"FileId"_L1);
1339 m_query->prepare(filterQuery);
1340 m_query->bindValue(0, namespaceName);
1342 if (!extensionFilter.isEmpty()) {
1343 m_query->bindValue(bindCount,
"%.%1"_L1.arg(extensionFilter));
1346 bindFilterQuery(m_query.get(), bindCount, filterAttributes);
1348 if (!m_query->exec())
1351 QStringList fileNames;
1352 while (m_query->next())
1353 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1358 const QString &filterName,
1359 const QString &extensionFilter)
const
1364 const QString extensionQuery = extensionFilter.isEmpty()
1365 ? QString() :
" AND FileNameTable.Name LIKE ?"_L1;
1366 const QString filterlessQuery =
1368 "FolderTable.Name, "
1369 "FileNameTable.Name "
1374 "WHERE FileNameTable.FolderId = FolderTable.Id "
1375 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1376 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1378 const QString filterQuery = filterlessQuery
1379 + prepareFilterQuery(filterName);
1381 m_query->prepare(filterQuery);
1382 m_query->bindValue(0, namespaceName);
1384 if (!extensionFilter.isEmpty()) {
1385 m_query->bindValue(bindCount,
"%.%1"_L1.arg(extensionFilter));
1389 bindFilterQuery(m_query.get(), bindCount, filterName);
1391 if (!m_query->exec())
1394 QStringList fileNames;
1395 while (m_query->next())
1396 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1405 const QString namespaceName = namespaceForFile(url, filterAttributes);
1406 if (namespaceName.isEmpty())
1410 result.setAuthority(namespaceName);
1419 const QString namespaceName = namespaceForFile(url, filterName);
1420 if (namespaceName.isEmpty())
1424 result.setAuthority(namespaceName);
1433 const QString namespaceName = namespaceForFile(url, QString());
1434 if (namespaceName.isEmpty())
1437 const FileInfo fileInfo = extractFileInfo(url);
1439 const FileInfo docInfo = registeredDocumentation(namespaceName);
1440 const QString absFileName = absoluteDocPath(docInfo.fileName);
1442 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1443 docInfo.fileName,
const_cast<QHelpCollectionHandler *>(
this)),
nullptr);
1447 return reader.fileData(fileInfo.folderName, fileInfo.fileName);
1452 QStringList indices;
1457 const QString filterlessQuery =
1465 "WHERE IndexTable.FileId = FileNameTable.FileId "
1466 "AND FileNameTable.FolderId = FolderTable.Id "
1467 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1469 const QString filterQuery = filterlessQuery
1470 + prepareFilterQuery(filterAttributes.size(),
"IndexTable"_L1,
"Id"_L1,
1471 "IndexFilterTable"_L1,
"IndexId"_L1)
1472 +
" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1475 m_query->prepare(filterQuery);
1476 bindFilterQuery(m_query.get(), 0, filterAttributes);
1480 while (m_query->next())
1481 indices.append(m_query->value(0).toString());
1488 QStringList indices;
1493 const QString filterlessQuery =
1501 "WHERE IndexTable.FileId = FileNameTable.FileId "
1502 "AND FileNameTable.FolderId = FolderTable.Id "
1503 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1505 const QString filterQuery = filterlessQuery
1506 + prepareFilterQuery(filterName)
1507 +
" ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1509 m_query->prepare(filterQuery);
1510 bindFilterQuery(m_query.get(), 0, filterName);
1514 while (m_query->next())
1515 indices.append(m_query->value(0).toString());
1522 if (!contents.size())
1529 QDataStream s(contents);
1538 const QStringList &filterAttributes)
const
1543 const QString filterlessQuery =
1545 "NamespaceTable.Name, "
1546 "FolderTable.Name, "
1547 "ContentsTable.Data, "
1548 "VersionTable.Version "
1554 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1555 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1556 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1557 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1559 const QString filterQuery = filterlessQuery
1560 + prepareFilterQuery(filterAttributes.size(),
"ContentsTable"_L1,
"Id"_L1,
1561 "ContentsFilterTable"_L1,
"ContentsId"_L1);
1563 m_query->prepare(filterQuery);
1564 bindFilterQuery(m_query.get(), 0, filterAttributes);
1568 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1570 while (m_query->next()) {
1571 const QString namespaceName = m_query->value(0).toString();
1572 const QByteArray contents = m_query->value(2).toByteArray();
1573 const QString versionString = m_query->value(3).toString();
1575 const QString title = getTitle(contents);
1576 const QVersionNumber version = QVersionNumber::fromString(versionString);
1578 ContentsData &contentsData = contentsMap[title][version];
1579 contentsData.namespaceName = namespaceName;
1580 contentsData.folderName = m_query->value(1).toString();
1581 contentsData.contentsList.append(contents);
1584 QList<QHelpCollectionHandler::ContentsData> result;
1585 for (
const auto &versionContents : std::as_const(contentsMap)) {
1587 const auto itBegin = versionContents.constBegin();
1588 auto it = versionContents.constEnd();
1589 while (it != itBegin) {
1591 result.append(it.value());
1602 const QString filterlessQuery =
1604 "NamespaceTable.Name, "
1605 "FolderTable.Name, "
1606 "ContentsTable.Data, "
1607 "VersionTable.Version "
1613 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1614 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1615 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1616 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1618 const QString filterQuery = filterlessQuery + prepareFilterQuery(filterName);
1620 m_query->prepare(filterQuery);
1621 bindFilterQuery(m_query.get(), 0, filterName);
1625 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1627 while (m_query->next()) {
1628 const QString namespaceName = m_query->value(0).toString();
1629 const QByteArray contents = m_query->value(2).toByteArray();
1630 const QString versionString = m_query->value(3).toString();
1632 const QString title = getTitle(contents);
1633 const QVersionNumber version = QVersionNumber::fromString(versionString);
1635 ContentsData &contentsData = contentsMap[title][version];
1636 contentsData.namespaceName = namespaceName;
1637 contentsData.folderName = m_query->value(1).toString();
1638 contentsData.contentsList.append(contents);
1641 QList<QHelpCollectionHandler::ContentsData> result;
1642 for (
const auto &versionContents : std::as_const(contentsMap)) {
1644 const auto itBegin = versionContents.constBegin();
1645 auto it = versionContents.constEnd();
1646 while (it != itBegin) {
1648 result.append(it.value());
1659 m_query->prepare(
"DELETE FROM SettingsTable WHERE Key=?"_L1);
1660 m_query->bindValue(0, key);
1661 return m_query->exec();
1665 const QVariant &defaultValue)
const
1668 return defaultValue;
1670 m_query->prepare(
"SELECT COUNT(Key) FROM SettingsTable WHERE Key=?"_L1);
1671 m_query->bindValue(0, key);
1672 if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) {
1674 return defaultValue;
1678 m_query->prepare(
"SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1679 m_query->bindValue(0, key);
1680 if (m_query->exec() && m_query->next()) {
1681 const QVariant &value = m_query->value(0);
1685 return defaultValue;
1689 const QVariant &value)
1694 m_query->prepare(
"SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1695 m_query->bindValue(0, key);
1697 if (m_query->next()) {
1698 m_query->prepare(
"UPDATE SettingsTable SET Value=? where Key=?"_L1);
1699 m_query->bindValue(0, value);
1700 m_query->bindValue(1, key);
1702 m_query->prepare(
"INSERT INTO SettingsTable VALUES(?, ?)"_L1);
1703 m_query->bindValue(0, key);
1704 m_query->bindValue(1, value);
1706 return m_query->exec();
1715 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
1717 while (m_query->next())
1718 atts.insert(m_query->value(0).toString());
1720 for (
const QStringList &attributeSet : attributeSets) {
1721 for (
const QString &attribute : attributeSet) {
1722 if (!atts.contains(attribute)) {
1723 m_query->prepare(
"INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
1724 m_query->bindValue(0, attribute);
1729 return registerFileAttributeSets(attributeSets, nsId);
1738 if (attributeSets.isEmpty())
1742 QVariantList attributeSetIds;
1743 QVariantList filterAttributeIds;
1745 if (!m_query->exec(
"SELECT MAX(FilterAttributeSetId) FROM FileAttributeSetTable"_L1)
1746 || !m_query->next()) {
1750 int attributeSetId = m_query->value(0).toInt();
1752 for (
const QStringList &attributeSet : attributeSets) {
1755 for (
const QString &attribute : attributeSet) {
1756 m_query->prepare(
"SELECT Id FROM FilterAttributeTable WHERE Name=?"_L1);
1757 m_query->bindValue(0, attribute);
1759 if (!m_query->exec() || !m_query->next())
1763 attributeSetIds.append(attributeSetId);
1764 filterAttributeIds.append(m_query->value(0).toInt());
1768 m_query->prepare(
"INSERT INTO FileAttributeSetTable "
1769 "(NamespaceId, FilterAttributeSetId, FilterAttributeId) "
1770 "VALUES(?, ?, ?)"_L1);
1771 m_query->addBindValue(nsIds);
1772 m_query->addBindValue(attributeSetIds);
1773 m_query->addBindValue(filterAttributeIds);
1774 return m_query->execBatch();
1781 m_query->exec(
"SELECT Name FROM FilterAttributeTable"_L1);
1782 while (m_query->next())
1783 list.append(m_query->value(0).toString());
1794 "FilterAttributeTable.Name "
1796 "FilterAttributeTable, "
1799 "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId "
1800 "AND FilterTable.NameId = FilterNameTable.Id "
1801 "AND FilterNameTable.Name=?"_L1);
1802 m_query->bindValue(0, filterName);
1804 while (m_query->next())
1805 list.append(m_query->value(0).toString());
1817 "FileAttributeSetTable.FilterAttributeSetId, "
1818 "FilterAttributeTable.Name "
1820 "FileAttributeSetTable, "
1821 "FilterAttributeTable, "
1823 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
1824 "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id "
1825 "AND NamespaceTable.Name = ? "
1826 "ORDER BY FileAttributeSetTable.FilterAttributeSetId"_L1);
1827 m_query->bindValue(0, namespaceName);
1830 QList<QStringList> result;
1831 while (m_query->next()) {
1832 const int id = m_query->value(0).toInt();
1834 result.append(QStringList());
1837 result.last().append(m_query->value(1).toString());
1840 if (result.isEmpty())
1841 result.append(QStringList());
1852 "VersionTable.Version "
1856 "WHERE NamespaceTable.Name = ? "
1857 "AND NamespaceTable.Id = VersionTable.NamespaceId"_L1);
1858 m_query->bindValue(0, namespaceName);
1859 if (!m_query->exec() || !m_query->next())
1862 const QString ret = m_query->value(0).toString();
1869 const int errorValue = -1;
1873 m_query->prepare(
"SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?"_L1);
1874 m_query->bindValue(0, nspace);
1876 while (m_query->next()) {
1877 if (m_query->value(0).toInt() > 0) {
1878 emit error(tr(
"Namespace %1 already exists.").arg(nspace));
1883 QFileInfo fi(m_collectionFile);
1884 m_query->prepare(
"INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
1885 m_query->bindValue(0, nspace);
1886 m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName));
1887 int namespaceId = errorValue;
1888 if (m_query->exec()) {
1889 namespaceId = m_query->lastInsertId().toInt();
1892 if (namespaceId < 1) {
1893 emit error(tr(
"Cannot register namespace \"%1\".").arg(nspace));
1904 m_query->prepare(
"INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
1905 m_query->bindValue(0, namespaceId);
1906 m_query->bindValue(1, folderName);
1909 if (m_query->exec()) {
1910 virtualId = m_query->lastInsertId().toInt();
1913 if (virtualId < 1) {
1914 emit error(tr(
"Cannot register virtual folder '%1'.").arg(folderName));
1917 if (registerComponent(folderName, namespaceId) < 0)
1924 m_query->prepare(
"SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1925 m_query->bindValue(0, componentName);
1926 if (!m_query->exec())
1929 if (!m_query->next()) {
1930 m_query->prepare(
"INSERT INTO ComponentTable VALUES(NULL, ?)"_L1);
1931 m_query->bindValue(0, componentName);
1932 if (!m_query->exec())
1935 m_query->prepare(
"SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1936 m_query->bindValue(0, componentName);
1937 if (!m_query->exec() || !m_query->next())
1941 const int componentId = m_query->value(0).toInt();
1943 m_query->prepare(
"INSERT INTO ComponentMapping VALUES(?, ?)"_L1);
1944 m_query->bindValue(0, componentId);
1945 m_query->bindValue(1, namespaceId);
1946 if (!m_query->exec())
1957 m_query->prepare(
"INSERT INTO VersionTable (NamespaceId, Version) VALUES(?, ?)"_L1);
1958 m_query->addBindValue(namespaceId);
1959 m_query->addBindValue(version);
1960 return m_query->exec();
1964 const QString &nameSpace,
bool createDefaultVersionFilter)
1969 m_query->prepare(
"SELECT Id, FilePath FROM NamespaceTable WHERE Name=?"_L1);
1970 m_query->bindValue(0, nameSpace);
1972 if (!m_query->next())
1975 const int nsId = m_query->value(0).toInt();
1976 const QString fileName = m_query->value(1).toString();
1978 m_query->prepare(
"SELECT Id, Name FROM FolderTable WHERE NamespaceId=?"_L1);
1979 m_query->bindValue(0, nsId);
1981 if (!m_query->next())
1984 const int vfId = m_query->value(0).toInt();
1985 const QString vfName = m_query->value(1).toString();
1987 const QString absFileName = absoluteDocPath(fileName);
1988 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1989 fileName,
this),
this);
1993 registerComponent(vfName, nsId);
1994 registerVersion(reader.version(), nsId);
1995 if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId))
1998 if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName))
2001 if (createDefaultVersionFilter)
2002 createVersionFilter(reader.version());
2008 if (version.isEmpty())
2011 const QVersionNumber versionNumber = QVersionNumber::fromString(version);
2012 if (versionNumber.isNull())
2015 const QString filterName = tr(
"Version %1").arg(version);
2016 if (filters().contains(filterName))
2019 QHelpFilterData filterData;
2020 filterData.setVersions({versionNumber});
2021 setFilterData(filterName, filterData);
2025 int nsId,
int vfId,
const QString &fileName)
2029 QMap<QString, QVariantList> filterAttributeToNewFileId;
2031 QVariantList fileFolderIds;
2032 QVariantList fileNames;
2033 QVariantList fileTitles;
2034 const int fileSize = indexTable.fileItems.size();
2035 fileFolderIds.reserve(fileSize);
2036 fileNames.reserve(fileSize);
2037 fileTitles.reserve(fileSize);
2039 if (!m_query->exec(
"SELECT MAX(FileId) FROM FileNameTable"_L1) || !m_query->next())
2042 const int maxFileId = m_query->value(0).toInt();
2045 for (
const QHelpDBReader::FileItem &item : indexTable.fileItems) {
2046 fileFolderIds.append(vfId);
2047 fileNames.append(item.name);
2048 fileTitles.append(item.title);
2050 for (
const QString &filterAttribute : item.filterAttributes)
2051 filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1);
2055 m_query->prepare(
"INSERT INTO FileNameTable VALUES(?, ?, NULL, ?)"_L1);
2056 m_query->addBindValue(fileFolderIds);
2057 m_query->addBindValue(fileNames);
2058 m_query->addBindValue(fileTitles);
2059 if (!m_query->execBatch())
2062 for (
auto it = filterAttributeToNewFileId.cbegin(),
2063 end = filterAttributeToNewFileId.cend(); it != end; ++it) {
2064 const QString filterAttribute = it.key();
2065 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2066 m_query->bindValue(0, filterAttribute);
2067 if (!m_query->exec() || !m_query->next())
2070 const int attributeId = m_query->value(0).toInt();
2072 QVariantList attributeIds;
2073 for (
int i = 0; i < it.value().size(); i++)
2074 attributeIds.append(attributeId);
2076 m_query->prepare(
"INSERT INTO FileFilterTable VALUES(?, ?)"_L1);
2077 m_query->addBindValue(attributeIds);
2078 m_query->addBindValue(it.value());
2079 if (!m_query->execBatch())
2083 QMap<QString, QVariantList> filterAttributeToNewIndexId;
2085 if (!m_query->exec(
"SELECT MAX(Id) FROM IndexTable"_L1) || !m_query->next())
2088 const int maxIndexId = m_query->value(0).toInt();
2091 QVariantList indexNames;
2092 QVariantList indexIdentifiers;
2093 QVariantList indexNamespaceIds;
2094 QVariantList indexFileIds;
2095 QVariantList indexAnchors;
2096 const int indexSize = indexTable.indexItems.size();
2097 indexNames.reserve(indexSize);
2098 indexIdentifiers.reserve(indexSize);
2099 indexNamespaceIds.reserve(indexSize);
2100 indexFileIds.reserve(indexSize);
2101 indexAnchors.reserve(indexSize);
2103 for (
const QHelpDBReader::IndexItem &item : indexTable.indexItems) {
2106 if (item.fileId < 0 || item.fileId >= fileSize) {
2107 emit error(tr(
"Invalid file reference for keyword '%1' in '%2'.")
2108 .arg(item.name, fileName));
2111 indexNames.append(item.name);
2112 indexIdentifiers.append(item.identifier);
2113 indexNamespaceIds.append(nsId);
2114 indexFileIds.append(maxFileId + item.fileId + 1);
2115 indexAnchors.append(item.anchor);
2117 for (
const QString &filterAttribute : item.filterAttributes)
2118 filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1);
2122 m_query->prepare(
"INSERT INTO IndexTable VALUES(NULL, ?, ?, ?, ?, ?)"_L1);
2123 m_query->addBindValue(indexNames);
2124 m_query->addBindValue(indexIdentifiers);
2125 m_query->addBindValue(indexNamespaceIds);
2126 m_query->addBindValue(indexFileIds);
2127 m_query->addBindValue(indexAnchors);
2128 if (!m_query->execBatch())
2131 for (
auto it = filterAttributeToNewIndexId.cbegin(),
2132 end = filterAttributeToNewIndexId.cend(); it != end; ++it) {
2133 const QString filterAttribute = it.key();
2134 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2135 m_query->bindValue(0, filterAttribute);
2136 if (!m_query->exec() || !m_query->next())
2139 const int attributeId = m_query->value(0).toInt();
2141 QVariantList attributeIds;
2142 for (
int i = 0; i < it.value().size(); i++)
2143 attributeIds.append(attributeId);
2145 m_query->prepare(
"INSERT INTO IndexFilterTable VALUES(?, ?)"_L1);
2146 m_query->addBindValue(attributeIds);
2147 m_query->addBindValue(it.value());
2148 if (!m_query->execBatch())
2152 QMap<QString, QVariantList> filterAttributeToNewContentsId;
2154 QVariantList contentsNsIds;
2155 QVariantList contentsData;
2156 const int contentsSize = indexTable.contentsItems.size();
2157 contentsNsIds.reserve(contentsSize);
2158 contentsData.reserve(contentsSize);
2160 if (!m_query->exec(
"SELECT MAX(Id) FROM ContentsTable"_L1) || !m_query->next())
2163 const int maxContentsId = m_query->value(0).toInt();
2165 int newContentsId = 0;
2166 for (
const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) {
2167 contentsNsIds.append(nsId);
2168 contentsData.append(item.data);
2170 for (
const QString &filterAttribute : item.filterAttributes) {
2171 filterAttributeToNewContentsId[filterAttribute]
2172 .append(maxContentsId + newContentsId + 1);
2177 m_query->prepare(
"INSERT INTO ContentsTable VALUES(NULL, ?, ?)"_L1);
2178 m_query->addBindValue(contentsNsIds);
2179 m_query->addBindValue(contentsData);
2180 if (!m_query->execBatch())
2183 for (
auto it = filterAttributeToNewContentsId.cbegin(),
2184 end = filterAttributeToNewContentsId.cend(); it != end; ++it) {
2185 const QString filterAttribute = it.key();
2186 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2187 m_query->bindValue(0, filterAttribute);
2188 if (!m_query->exec() || !m_query->next())
2191 const int attributeId = m_query->value(0).toInt();
2193 QVariantList attributeIds;
2194 for (
int i = 0; i < it.value().size(); i++)
2195 attributeIds.append(attributeId);
2197 m_query->prepare(
"INSERT INTO ContentsFilterTable VALUES(?, ?)"_L1);
2198 m_query->addBindValue(attributeIds);
2199 m_query->addBindValue(it.value());
2200 if (!m_query->execBatch())
2204 QVariantList filterNsIds;
2205 QVariantList filterAttributeIds;
2206 for (
const QString &filterAttribute : indexTable.usedFilterAttributes) {
2207 filterNsIds.append(nsId);
2209 m_query->prepare(
"SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2210 m_query->bindValue(0, filterAttribute);
2211 if (!m_query->exec() || !m_query->next())
2214 filterAttributeIds.append(m_query->value(0).toInt());
2217 m_query->prepare(
"INSERT INTO OptimizedFilterTable "
2218 "(NamespaceId, FilterAttributeId) VALUES(?, ?)"_L1);
2219 m_query->addBindValue(filterNsIds);
2220 m_query->addBindValue(filterAttributeIds);
2221 if (!m_query->execBatch())
2224 m_query->prepare(
"INSERT INTO TimeStampTable "
2225 "(NamespaceId, FolderId, FilePath, Size, TimeStamp) "
2226 "VALUES(?, ?, ?, ?, ?)"_L1);
2227 m_query->addBindValue(nsId);
2228 m_query->addBindValue(vfId);
2229 m_query->addBindValue(fileName);
2230 const QFileInfo fi(absoluteDocPath(fileName));
2231 m_query->addBindValue(fi.size());
2232 QDateTime lastModified = fi.lastModified(QTimeZone::UTC);
2233 if (qEnvironmentVariableIsSet(
"SOURCE_DATE_EPOCH")) {
2234 const QString sourceDateEpochStr = qEnvironmentVariable(
"SOURCE_DATE_EPOCH");
2236 const qlonglong sourceDateEpoch = sourceDateEpochStr.toLongLong(&ok);
2237 if (ok && sourceDateEpoch < lastModified.toSecsSinceEpoch())
2238 lastModified.setSecsSinceEpoch(sourceDateEpoch);
2240 m_query->addBindValue(lastModified);
2241 if (!m_query->exec())
2249 m_query->prepare(
"DELETE FROM IndexFilterTable WHERE IndexId IN "
2250 "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)"_L1);
2251 m_query->bindValue(0, nsId);
2252 if (!m_query->exec())
2255 m_query->prepare(
"DELETE FROM IndexTable WHERE NamespaceId = ?"_L1);
2256 m_query->bindValue(0, nsId);
2257 if (!m_query->exec())
2260 m_query->prepare(
"DELETE FROM FileFilterTable WHERE FileId IN "
2261 "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)"_L1);
2262 m_query->bindValue(0, vfId);
2263 if (!m_query->exec())
2266 m_query->prepare(
"DELETE FROM FileNameTable WHERE FolderId = ?"_L1);
2267 m_query->bindValue(0, vfId);
2268 if (!m_query->exec())
2271 m_query->prepare(
"DELETE FROM ContentsFilterTable WHERE ContentsId IN "
2272 "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)"_L1);
2273 m_query->bindValue(0, nsId);
2274 if (!m_query->exec())
2277 m_query->prepare(
"DELETE FROM ContentsTable WHERE NamespaceId = ?"_L1);
2278 m_query->bindValue(0, nsId);
2279 if (!m_query->exec())
2282 m_query->prepare(
"DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?"_L1);
2283 m_query->bindValue(0, nsId);
2284 if (!m_query->exec())
2287 m_query->prepare(
"DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?"_L1);
2288 m_query->bindValue(0, nsId);
2289 if (!m_query->exec())
2292 m_query->prepare(
"DELETE FROM TimeStampTable WHERE NamespaceId = ?"_L1);
2293 m_query->bindValue(0, nsId);
2294 if (!m_query->exec())
2297 m_query->prepare(
"DELETE FROM VersionTable WHERE NamespaceId = ?"_L1);
2298 m_query->bindValue(0, nsId);
2299 if (!m_query->exec())
2302 m_query->prepare(
"SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2303 m_query->bindValue(0, nsId);
2304 if (!m_query->exec())
2307 if (!m_query->next())
2310 const int componentId = m_query->value(0).toInt();
2312 m_query->prepare(
"DELETE FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2313 m_query->bindValue(0, nsId);
2314 if (!m_query->exec())
2317 m_query->prepare(
"SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?"_L1);
2318 m_query->bindValue(0, componentId);
2319 if (!m_query->exec())
2322 if (!m_query->next()) {
2323 m_query->prepare(
"DELETE FROM ComponentTable WHERE ComponentId = ?"_L1);
2324 m_query->bindValue(0, componentId);
2325 if (!m_query->exec())
2333 const QString &relFileName,
const QString &anchor)
2336 url.setScheme(
"qthelp"_L1);
2337 url.setAuthority(ns);
2338 url.setPath(u'/' + folder + u'/' + relFileName);
2339 url.setFragment(anchor);
2344 const QString &id,
const QStringList &filterAttributes)
const
2346 return documentsForField(
"Identifier"_L1, id, filterAttributes);
2350 const QString &keyword,
const QStringList &filterAttributes)
const
2352 return documentsForField(
"Name"_L1, keyword, filterAttributes);
2356 const QString &fieldValue,
const QStringList &filterAttributes)
const
2361 const QString filterlessQuery =
2363 "FileNameTable.Title, "
2364 "NamespaceTable.Name, "
2365 "FolderTable.Name, "
2366 "FileNameTable.Name, "
2367 "IndexTable.Anchor "
2373 "WHERE IndexTable.FileId = FileNameTable.FileId "
2374 "AND FileNameTable.FolderId = FolderTable.Id "
2375 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2376 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2378 const QString filterQuery = filterlessQuery
2379 + prepareFilterQuery(filterAttributes.size(),
"IndexTable"_L1,
"Id"_L1,
2380 "IndexFilterTable"_L1,
"IndexId"_L1);
2382 m_query->prepare(filterQuery);
2383 m_query->bindValue(0, fieldValue);
2384 bindFilterQuery(m_query.get(), 1, filterAttributes);
2388 QList<QHelpLink> docList;
2389 while (m_query->next()) {
2390 QString title = m_query->value(0).toString();
2391 if (title.isEmpty())
2392 title = fieldValue +
" : "_L1 + m_query->value(3).toString();
2394 const QUrl url = buildQUrl(m_query->value(1).toString(),
2395 m_query->value(2).toString(),
2396 m_query->value(3).toString(),
2397 m_query->value(4).toString());
2398 docList.append(QHelpLink {url, title});
2404 const QString &id,
const QString &filterName)
const
2406 return documentsForField(
"Identifier"_L1, id, filterName);
2410 const QString &keyword,
const QString &filterName)
const
2412 return documentsForField(
"Name"_L1, keyword, filterName);
2416 const QString &fieldValue,
const QString &filterName)
const
2418 QMultiMap<QString, QUrl> linkMap;
2419 const auto documents = documentsForField(fieldName, fieldValue, filterName);
2420 for (
const auto &document : documents)
2421 linkMap.insert(document.title, document.url);
2434 return lhs.title == rhs.title && lhs.url == rhs.url && lhs.version == rhs.version;
2441 return !e.version.isNull();
2446 return lhs.version < rhs.version;
2451 return { e.url, e.title };
2455 const QString &fieldValue,
const QString &filterName)
const
2460 const QString filterlessQuery =
2462 "FileNameTable.Title, "
2463 "NamespaceTable.Name, "
2464 "FolderTable.Name, "
2465 "FileNameTable.Name, "
2466 "IndexTable.Anchor "
2472 "WHERE IndexTable.FileId = FileNameTable.FileId "
2473 "AND FileNameTable.FolderId = FolderTable.Id "
2474 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2475 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2477 const QString filterQuery = filterlessQuery
2478 + prepareFilterQuery(filterName)
2479 +
" ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"_L1;
2481 m_query->prepare(filterQuery);
2482 m_query->bindValue(0, fieldValue);
2483 bindFilterQuery(m_query.get(), 1, filterName);
2487 QList<IndexEntry> entries;
2488 while (m_query->next()) {
2489 QString title = m_query->value(0).toString();
2490 if (title.isEmpty())
2491 title = fieldValue +
" : "_L1 + m_query->value(3).toString();
2492 const QString nameSpace = m_query->value(1).toString();
2493 const QUrl url = buildQUrl(nameSpace,
2494 m_query->value(2).toString(),
2495 m_query->value(3).toString(),
2496 m_query->value(4).toString());
2497 IndexEntry newEntry{ title, url, QHelpDBReader::versionHeuristic(nameSpace) };
2498 if (!entries.contains(newEntry))
2499 entries.emplaceBack(newEntry);
2503 if (
std::all_of(entries.cbegin(), entries.cend(),
hasVersion))
2506 QList<QHelpLink> docList;
2507 docList.reserve(entries.size());
2508 std::transform(entries.cbegin(), entries.cend(),
std::back_inserter(docList), toHelpLink);
2514 QStringList namespaceList;
2517 return namespaceList;
2519 const QString filterlessQuery =
2521 "NamespaceTable.Name "
2526 const QString filterQuery = filterlessQuery
2527 + prepareFilterQuery(filterName);
2529 m_query->prepare(filterQuery);
2530 bindFilterQuery(m_query.get(), 0, filterName);
2534 while (m_query->next())
2535 namespaceList.append(m_query->value(0).toString());
2536 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 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