Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhelpcollectionhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "qhelp_global.h"
9#include "qhelplink.h"
10
11#include <QtCore/qdatastream.h>
12#include <QtCore/qdatetime.h>
13#include <QtCore/qdir.h>
14#include <QtCore/qfileinfo.h>
15#include <QtCore/qset.h>
16#include <QtCore/qtimer.h>
17#include <QtCore/qversionnumber.h>
18#include <QtSql/qsqldriver.h>
19#include <QtSql/qsqlerror.h>
20#include <QtSql/qsqlquery.h>
21
22#include <algorithm>
23#include <iterator>
24
25QT_BEGIN_NAMESPACE
26
27using namespace Qt::StringLiterals;
28
30{
31public:
33
34 Transaction(const QString &connectionName)
37 {
38 if (m_inTransaction)
39 m_inTransaction = m_db.transaction();
40 }
41
43 {
44 if (m_inTransaction)
45 m_db.rollback();
46 }
47
48 void commit()
49 {
50 if (!m_inTransaction)
51 return;
52
53 m_db.commit();
54 m_inTransaction = false;
55 }
56
57private:
58 QSqlDatabase m_db;
59 bool m_inTransaction;
60};
61
62QHelpCollectionHandler::QHelpCollectionHandler(const QString &collectionFile, QObject *parent)
63 : QObject(parent)
64 , m_collectionFile(collectionFile)
65{
66 const QFileInfo fi(m_collectionFile);
67 if (!fi.isAbsolute())
68 m_collectionFile = fi.absoluteFilePath();
69}
70
75
76bool QHelpCollectionHandler::isDBOpened() const
77{
78 if (m_query)
79 return true;
80 auto *that = const_cast<QHelpCollectionHandler *>(this);
81 emit that->error(tr("The collection file \"%1\" is not set up yet.").arg(m_collectionFile));
82 return false;
83}
84
85void QHelpCollectionHandler::closeDB()
86{
87 if (!m_query)
88 return;
89
90 m_query.reset();
91 QSqlDatabase::removeDatabase(m_connectionName);
92 m_connectionName.clear();
93}
94
96{
97 if (m_query)
98 return true;
99
100 m_connectionName = QHelpGlobal::uniquifyConnectionName("QHelpCollectionHandler"_L1, this);
101 {
102 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, m_connectionName);
103 if (db.driver()
104 && db.driver()->lastError().type() == QSqlError::ConnectionError) {
105 emit error(tr("Cannot load sqlite database driver."));
106 return false;
107 }
108
109 db.setDatabaseName(collectionFile());
110 if (db.open())
111 m_query.reset(new QSqlQuery(db));
112
113 if (!m_query) {
114 QSqlDatabase::removeDatabase(m_connectionName);
115 emit error(tr("Cannot open collection file: %1").arg(collectionFile()));
116 return false;
117 }
118 }
119
120 if (m_readOnly)
121 return true;
122
123 m_query->exec("PRAGMA synchronous=OFF"_L1);
124 m_query->exec("PRAGMA cache_size=3000"_L1);
125
126 m_query->exec("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' "
127 "AND Name=\'NamespaceTable\'"_L1);
128 m_query->next();
129
130 const bool tablesExist = m_query->value(0).toInt() > 0;
131 if (!tablesExist) {
132 if (!createTables(m_query.get())) {
133 closeDB();
134 emit error(tr("Cannot create tables in file %1.").arg(collectionFile()));
135 return false;
136 }
137 }
138
139 bool indexAndNamespaceFilterTablesMissing = false;
140
141 const QStringList newTables = {
142 "IndexTable"_L1,
143 "FileNameTable"_L1,
144 "ContentsTable"_L1,
145 "FileFilterTable"_L1,
146 "IndexFilterTable"_L1,
147 "ContentsFilterTable"_L1,
148 "FileAttributeSetTable"_L1,
149 "OptimizedFilterTable"_L1,
150 "TimeStampTable"_L1,
151 "VersionTable"_L1,
152 "Filter"_L1,
153 "ComponentTable"_L1,
154 "ComponentMapping"_L1,
155 "ComponentFilter"_L1,
156 "VersionFilter"_L1
157 };
158
159 QString queryString = "SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"_L1;
160 queryString.append(" AND (Name=\'"_L1);
161 queryString.append(newTables.join("\' OR Name=\'"_L1));
162 queryString.append("\')"_L1);
163
164 m_query->exec(queryString);
165 m_query->next();
166 if (m_query->value(0).toInt() != newTables.size()) {
167 if (!recreateIndexAndNamespaceFilterTables(m_query.get())) {
168 emit error(tr("Cannot create index tables in file %1.").arg(collectionFile()));
169 return false;
170 }
171
172 // Old tables exist, index tables didn't, recreate index tables only in this case
173 indexAndNamespaceFilterTablesMissing = tablesExist;
174 }
175
177 if (indexAndNamespaceFilterTablesMissing) {
178 for (const QHelpCollectionHandler::FileInfo &info : docList) {
179 if (!registerIndexAndNamespaceFilterTables(info.namespaceName, true)) {
180 emit error(tr("Cannot register index tables in file %1.").arg(collectionFile()));
181 return false;
182 }
183 }
184 return true;
185 }
186
187 QList<TimeStamp> timeStamps;
188 m_query->exec("SELECT NamespaceId, FolderId, FilePath, Size, TimeStamp FROM TimeStampTable"_L1);
189 while (m_query->next()) {
190 TimeStamp timeStamp;
191 timeStamp.namespaceId = m_query->value(0).toInt();
192 timeStamp.folderId = m_query->value(1).toInt();
193 timeStamp.fileName = m_query->value(2).toString();
194 timeStamp.size = m_query->value(3).toInt();
195 timeStamp.timeStamp = m_query->value(4).toDateTime();
196 timeStamps.append(timeStamp);
197 }
198
199 QList<TimeStamp> toRemove;
200 for (const TimeStamp &timeStamp : timeStamps) {
201 if (!isTimeStampCorrect(timeStamp))
202 toRemove.append(timeStamp);
203 }
204
205 // TODO: we may optimize when toRemove.size() == timeStamps.size().
206 // In this case we remove all records from tables.
207 Transaction transaction(m_connectionName);
208 for (const TimeStamp &timeStamp : toRemove) {
209 if (!unregisterIndexTable(timeStamp.namespaceId, timeStamp.folderId)) {
210 emit error(tr("Cannot unregister index tables in file %1.").arg(collectionFile()));
211 return false;
212 }
213 }
214 transaction.commit();
215
216 for (const QHelpCollectionHandler::FileInfo &info : docList) {
217 if (!hasTimeStampInfo(info.namespaceName)
218 && !registerIndexAndNamespaceFilterTables(info.namespaceName)) {
219 // we may have a doc registered without a timestamp
220 // and the doc may be missing currently
221 unregisterDocumentation(info.namespaceName);
222 }
223 }
224 return true;
225}
226
227QString QHelpCollectionHandler::absoluteDocPath(const QString &fileName) const
228{
229 const QFileInfo fi(collectionFile());
230 return QDir::isAbsolutePath(fileName)
231 ? fileName
232 : QFileInfo(fi.absolutePath() + u'/' + fileName).absoluteFilePath();
233}
234
235bool QHelpCollectionHandler::isTimeStampCorrect(const TimeStamp &timeStamp) const
236{
237 const QFileInfo fi(absoluteDocPath(timeStamp.fileName));
238
239 if (!fi.exists())
240 return false;
241
242 if (fi.size() != timeStamp.size)
243 return false;
244
245 if (fi.lastModified(QTimeZone::UTC) != timeStamp.timeStamp)
246 return false;
247
248 m_query->prepare("SELECT FilePath FROM NamespaceTable WHERE Id = ?"_L1);
249 m_query->bindValue(0, timeStamp.namespaceId);
250 if (!m_query->exec() || !m_query->next())
251 return false;
252
253 const QString oldFileName = m_query->value(0).toString();
254 m_query->clear();
255 if (oldFileName != timeStamp.fileName)
256 return false;
257
258 return true;
259}
260
261bool QHelpCollectionHandler::hasTimeStampInfo(const QString &nameSpace) const
262{
263 m_query->prepare(
264 "SELECT "
265 "TimeStampTable.NamespaceId "
266 "FROM "
267 "NamespaceTable, "
268 "TimeStampTable "
269 "WHERE NamespaceTable.Id = TimeStampTable.NamespaceId "
270 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
271 m_query->bindValue(0, nameSpace);
272 if (!m_query->exec())
273 return false;
274
275 if (!m_query->next())
276 return false;
277
278 m_query->clear();
279 return true;
280}
281
282void QHelpCollectionHandler::scheduleVacuum()
283{
284 if (m_vacuumScheduled)
285 return;
286
287 m_vacuumScheduled = true;
288 QTimer::singleShot(0, this, &QHelpCollectionHandler::execVacuum);
289}
290
291void QHelpCollectionHandler::execVacuum()
292{
293 if (!m_query)
294 return;
295
296 m_query->exec("VACUUM"_L1);
297 m_vacuumScheduled = false;
298}
299
300bool QHelpCollectionHandler::copyCollectionFile(const QString &fileName)
301{
302 if (!m_query)
303 return false;
304
305 const QFileInfo fi(fileName);
306 if (fi.exists()) {
307 emit error(tr("The collection file \"%1\" already exists.").arg(fileName));
308 return false;
309 }
310
311 if (!fi.absoluteDir().exists() && !QDir().mkpath(fi.absolutePath())) {
312 emit error(tr("Cannot create directory: %1").arg(fi.absolutePath()));
313 return false;
314 }
315
316 const QString &colFile = fi.absoluteFilePath();
317 const QString &connectionName =
318 QHelpGlobal::uniquifyConnectionName("QHelpCollectionHandlerCopy"_L1, this);
319 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, connectionName);
320 db.setDatabaseName(colFile);
321 if (!db.open()) {
322 emit error(tr("Cannot open collection file: %1").arg(colFile));
323 return false;
324 }
325
326 QSqlQuery copyQuery(db);
327 copyQuery.exec("PRAGMA synchronous=OFF"_L1);
328 copyQuery.exec("PRAGMA cache_size=3000"_L1);
329
330 if (!createTables(&copyQuery) || !recreateIndexAndNamespaceFilterTables(&copyQuery)) {
331 emit error(tr("Cannot copy collection file: %1").arg(colFile));
332 return false;
333 }
334
335 const QString &oldBaseDir = QFileInfo(collectionFile()).absolutePath();
336 const QFileInfo newColFi(colFile);
337 m_query->exec("SELECT Name, FilePath FROM NamespaceTable"_L1);
338 while (m_query->next()) {
339 copyQuery.prepare("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
340 copyQuery.bindValue(0, m_query->value(0).toString());
341 QString oldFilePath = m_query->value(1).toString();
342 if (!QDir::isAbsolutePath(oldFilePath))
343 oldFilePath = oldBaseDir + u'/' + oldFilePath;
344 copyQuery.bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath));
345 copyQuery.exec();
346 }
347
348 m_query->exec("SELECT NamespaceId, Name FROM FolderTable"_L1);
349 while (m_query->next()) {
350 copyQuery.prepare("INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
351 copyQuery.bindValue(0, m_query->value(0).toString());
352 copyQuery.bindValue(1, m_query->value(1).toString());
353 copyQuery.exec();
354 }
355
356 m_query->exec("SELECT Name FROM FilterAttributeTable"_L1);
357 while (m_query->next()) {
358 copyQuery.prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
359 copyQuery.bindValue(0, m_query->value(0).toString());
360 copyQuery.exec();
361 }
362
363 m_query->exec("SELECT Name FROM FilterNameTable"_L1);
364 while (m_query->next()) {
365 copyQuery.prepare("INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
366 copyQuery.bindValue(0, m_query->value(0).toString());
367 copyQuery.exec();
368 }
369
370 m_query->exec("SELECT NameId, FilterAttributeId FROM FilterTable"_L1);
371 while (m_query->next()) {
372 copyQuery.prepare("INSERT INTO FilterTable VALUES(?, ?)"_L1);
373 copyQuery.bindValue(0, m_query->value(0).toInt());
374 copyQuery.bindValue(1, m_query->value(1).toInt());
375 copyQuery.exec();
376 }
377
378 m_query->exec("SELECT Key, Value FROM SettingsTable"_L1);
379 while (m_query->next()) {
380 if (m_query->value(0).toString() == "FTS5IndexedNamespaces"_L1)
381 continue;
382 copyQuery.prepare("INSERT INTO SettingsTable VALUES(?, ?)"_L1);
383 copyQuery.bindValue(0, m_query->value(0).toString());
384 copyQuery.bindValue(1, m_query->value(1));
385 copyQuery.exec();
386 }
387
388 copyQuery.clear();
389 QSqlDatabase::removeDatabase(connectionName);
390 return true;
391}
392
393bool QHelpCollectionHandler::createTables(QSqlQuery *query)
394{
395 const QStringList tables = {
396 "CREATE TABLE NamespaceTable ("
397 "Id INTEGER PRIMARY KEY, "
398 "Name TEXT, "
399 "FilePath TEXT )"_L1,
400 "CREATE TABLE FolderTable ("
401 "Id INTEGER PRIMARY KEY, "
402 "NamespaceId INTEGER, "
403 "Name TEXT )"_L1,
404 "CREATE TABLE FilterAttributeTable ("
405 "Id INTEGER PRIMARY KEY, "
406 "Name TEXT )"_L1,
407 "CREATE TABLE FilterNameTable ("
408 "Id INTEGER PRIMARY KEY, "
409 "Name TEXT )"_L1,
410 "CREATE TABLE FilterTable ("
411 "NameId INTEGER, "
412 "FilterAttributeId INTEGER )"_L1,
413 "CREATE TABLE SettingsTable ("
414 "Key TEXT PRIMARY KEY, "
415 "Value BLOB )"_L1
416 };
417
418 for (const QString &q : tables) {
419 if (!query->exec(q))
420 return false;
421 }
422 return true;
423}
424
425bool QHelpCollectionHandler::recreateIndexAndNamespaceFilterTables(QSqlQuery *query)
426{
427 const QStringList tables = {
428 "DROP TABLE IF EXISTS FileNameTable"_L1,
429 "DROP TABLE IF EXISTS IndexTable"_L1,
430 "DROP TABLE IF EXISTS ContentsTable"_L1,
431 "DROP TABLE IF EXISTS FileFilterTable"_L1, // legacy
432 "DROP TABLE IF EXISTS IndexFilterTable"_L1, // legacy
433 "DROP TABLE IF EXISTS ContentsFilterTable"_L1, // legacy
434 "DROP TABLE IF EXISTS FileAttributeSetTable"_L1, // legacy
435 "DROP TABLE IF EXISTS OptimizedFilterTable"_L1, // legacy
436 "DROP TABLE IF EXISTS TimeStampTable"_L1,
437 "DROP TABLE IF EXISTS VersionTable"_L1,
438 "DROP TABLE IF EXISTS Filter"_L1,
439 "DROP TABLE IF EXISTS ComponentTable"_L1,
440 "DROP TABLE IF EXISTS ComponentMapping"_L1,
441 "DROP TABLE IF EXISTS ComponentFilter"_L1,
442 "DROP TABLE IF EXISTS VersionFilter"_L1,
443 "CREATE TABLE FileNameTable ("
444 "FolderId INTEGER, "
445 "Name TEXT, "
446 "FileId INTEGER PRIMARY KEY, "
447 "Title TEXT)"_L1,
448 "CREATE TABLE IndexTable ("
449 "Id INTEGER PRIMARY KEY, "
450 "Name TEXT, "
451 "Identifier TEXT, "
452 "NamespaceId INTEGER, "
453 "FileId INTEGER, "
454 "Anchor TEXT)"_L1,
455 "CREATE TABLE ContentsTable ("
456 "Id INTEGER PRIMARY KEY, "
457 "NamespaceId INTEGER, "
458 "Data BLOB)"_L1,
459 "CREATE TABLE FileFilterTable ("
460 "FilterAttributeId INTEGER, "
461 "FileId INTEGER)"_L1,
462 "CREATE TABLE IndexFilterTable ("
463 "FilterAttributeId INTEGER, "
464 "IndexId INTEGER)"_L1,
465 "CREATE TABLE ContentsFilterTable ("
466 "FilterAttributeId INTEGER, "
467 "ContentsId INTEGER )"_L1,
468 "CREATE TABLE FileAttributeSetTable ("
469 "NamespaceId INTEGER, "
470 "FilterAttributeSetId INTEGER, "
471 "FilterAttributeId INTEGER)"_L1,
472 "CREATE TABLE OptimizedFilterTable ("
473 "NamespaceId INTEGER, "
474 "FilterAttributeId INTEGER)"_L1,
475 "CREATE TABLE TimeStampTable ("
476 "NamespaceId INTEGER, "
477 "FolderId INTEGER, "
478 "FilePath TEXT, "
479 "Size INTEGER, "
480 "TimeStamp TEXT)"_L1,
481 "CREATE TABLE VersionTable ("
482 "NamespaceId INTEGER, "
483 "Version TEXT)"_L1,
484 "CREATE TABLE Filter ("
485 "FilterId INTEGER PRIMARY KEY, "
486 "Name TEXT)"_L1,
487 "CREATE TABLE ComponentTable ("
488 "ComponentId INTEGER PRIMARY KEY, "
489 "Name TEXT)"_L1,
490 "CREATE TABLE ComponentMapping ("
491 "ComponentId INTEGER, "
492 "NamespaceId INTEGER)"_L1,
493 "CREATE TABLE ComponentFilter ("
494 "ComponentName TEXT, "
495 "FilterId INTEGER)"_L1,
496 "CREATE TABLE VersionFilter ("
497 "Version TEXT, "
498 "FilterId INTEGER)"_L1
499 };
500
501 for (const QString &q : tables) {
502 if (!query->exec(q))
503 return false;
504 }
505 return true;
506}
507
509{
510 QStringList list;
511 if (m_query) {
512 m_query->exec("SELECT Name FROM FilterNameTable"_L1);
513 while (m_query->next())
514 list.append(m_query->value(0).toString());
515 }
516 return list;
517}
518
520{
521 QStringList list;
522 if (m_query) {
523 m_query->exec("SELECT Name FROM Filter ORDER BY Name"_L1);
524 while (m_query->next())
525 list.append(m_query->value(0).toString());
526 }
527 return list;
528}
529
531{
532 QStringList list;
533 if (m_query) {
534 m_query->exec("SELECT DISTINCT Name FROM ComponentTable ORDER BY Name"_L1);
535 while (m_query->next())
536 list.append(m_query->value(0).toString());
537 }
538 return list;
539}
540
542{
543 QList<QVersionNumber> list;
544 if (m_query) {
545 m_query->exec("SELECT DISTINCT Version FROM VersionTable ORDER BY Version"_L1);
546 while (m_query->next())
547 list.append(QVersionNumber::fromString(m_query->value(0).toString()));
548 }
549 return list;
550}
551
553{
554 QMap<QString, QString> result;
555 if (m_query) {
556 m_query->exec(
557 "SELECT "
558 "NamespaceTable.Name, "
559 "ComponentTable.Name "
560 "FROM NamespaceTable, "
561 "ComponentTable, "
562 "ComponentMapping "
563 "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId "
564 "AND ComponentMapping.ComponentId = ComponentTable.ComponentId"_L1);
565 while (m_query->next())
566 result.insert(m_query->value(0).toString(), m_query->value(1).toString());
567 }
568 return result;
569}
570
572{
573 QMap<QString, QVersionNumber> result;
574 if (m_query) {
575 m_query->exec(
576 "SELECT "
577 "NamespaceTable.Name, "
578 "VersionTable.Version "
579 "FROM NamespaceTable, "
580 "VersionTable "
581 "WHERE NamespaceTable.Id = VersionTable.NamespaceId"_L1);
582 while (m_query->next()) {
583 result.insert(m_query->value(0).toString(),
584 QVersionNumber::fromString(m_query->value(1).toString()));
585 }
586 }
587 return result;
588}
589
590QHelpFilterData QHelpCollectionHandler::filterData(const QString &filterName) const
591{
592 QStringList components;
593 QList<QVersionNumber> versions;
594 if (m_query) {
595 m_query->prepare(
596 "SELECT ComponentFilter.ComponentName "
597 "FROM ComponentFilter, Filter "
598 "WHERE ComponentFilter.FilterId = Filter.FilterId "
599 "AND Filter.Name = ? "
600 "ORDER BY ComponentFilter.ComponentName"_L1);
601 m_query->bindValue(0, filterName);
602 m_query->exec();
603 while (m_query->next())
604 components.append(m_query->value(0).toString());
605
606 m_query->prepare(
607 "SELECT VersionFilter.Version "
608 "FROM VersionFilter, Filter "
609 "WHERE VersionFilter.FilterId = Filter.FilterId "
610 "AND Filter.Name = ? "
611 "ORDER BY VersionFilter.Version"_L1);
612 m_query->bindValue(0, filterName);
613 m_query->exec();
614 while (m_query->next())
615 versions.append(QVersionNumber::fromString(m_query->value(0).toString()));
616
617 }
618 QHelpFilterData data;
619 data.setComponents(components);
620 data.setVersions(versions);
621 return data;
622}
623
624bool QHelpCollectionHandler::setFilterData(const QString &filterName,
625 const QHelpFilterData &filterData)
626{
627 if (!removeFilter(filterName))
628 return false;
629
630 m_query->prepare("INSERT INTO Filter VALUES (NULL, ?)"_L1);
631 m_query->bindValue(0, filterName);
632 if (!m_query->exec())
633 return false;
634
635 const int filterId = m_query->lastInsertId().toInt();
636
637 QVariantList componentList;
638 QVariantList versionList;
639 QVariantList filterIdList;
640
641 const auto &components = filterData.components();
642 for (const QString &component : components) {
643 componentList.append(component);
644 filterIdList.append(filterId);
645 }
646
647 m_query->prepare("INSERT INTO ComponentFilter VALUES (?, ?)"_L1);
648 m_query->addBindValue(componentList);
649 m_query->addBindValue(filterIdList);
650 if (!m_query->execBatch())
651 return false;
652
653 filterIdList.clear();
654 const auto &versions = filterData.versions();
655 for (const QVersionNumber &version : versions) {
656 versionList.append(version.isNull() ? QString() : version.toString());
657 filterIdList.append(filterId);
658 }
659
660 m_query->prepare("INSERT INTO VersionFilter VALUES (?, ?)"_L1);
661 m_query->addBindValue(versionList);
662 m_query->addBindValue(filterIdList);
663 if (!m_query->execBatch())
664 return false;
665
666 return true;
667}
668
669bool QHelpCollectionHandler::removeFilter(const QString &filterName)
670{
671 m_query->prepare("SELECT FilterId FROM Filter WHERE Name = ?"_L1);
672 m_query->bindValue(0, filterName);
673 if (!m_query->exec())
674 return false;
675
676 if (!m_query->next())
677 return true; // no filter in DB
678
679 const int filterId = m_query->value(0).toInt();
680
681 m_query->prepare("DELETE FROM Filter WHERE Filter.Name = ?"_L1);
682 m_query->bindValue(0, filterName);
683 if (!m_query->exec())
684 return false;
685
686 m_query->prepare("DELETE FROM ComponentFilter WHERE ComponentFilter.FilterId = ?"_L1);
687 m_query->bindValue(0, filterId);
688 if (!m_query->exec())
689 return false;
690
691 m_query->prepare("DELETE FROM VersionFilter WHERE VersionFilter.FilterId = ?"_L1);
692 m_query->bindValue(0, filterId);
693 if (!m_query->exec())
694 return false;
695
696 return true;
697}
698
699bool QHelpCollectionHandler::removeCustomFilter(const QString &filterName)
700{
701 if (!isDBOpened() || filterName.isEmpty())
702 return false;
703
704 int filterNameId = -1;
705 m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
706 m_query->bindValue(0, filterName);
707 m_query->exec();
708 if (m_query->next())
709 filterNameId = m_query->value(0).toInt();
710
711 if (filterNameId < 0) {
712 emit error(tr("Unknown filter \"%1\".").arg(filterName));
713 return false;
714 }
715
716 m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1);
717 m_query->bindValue(0, filterNameId);
718 m_query->exec();
719
720 m_query->prepare("DELETE FROM FilterNameTable WHERE Id=?"_L1);
721 m_query->bindValue(0, filterNameId);
722 m_query->exec();
723
724 return true;
725}
726
727bool QHelpCollectionHandler::addCustomFilter(const QString &filterName,
728 const QStringList &attributes)
729{
730 if (!isDBOpened() || filterName.isEmpty())
731 return false;
732
733 int nameId = -1;
734 m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
735 m_query->bindValue(0, filterName);
736 m_query->exec();
737 if (m_query->next())
738 nameId = m_query->value(0).toInt();
739
740 m_query->exec("SELECT Id, Name FROM FilterAttributeTable"_L1);
741 QStringList idsToInsert = attributes;
742 QMap<QString, int> attributeMap;
743 while (m_query->next()) {
744 // all old attributes
745 const QString attributeName = m_query->value(1).toString();
746 attributeMap.insert(attributeName, m_query->value(0).toInt());
747 idsToInsert.removeAll(attributeName);
748 }
749
750 for (const QString &id : std::as_const(idsToInsert)) {
751 m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
752 m_query->bindValue(0, id);
753 m_query->exec();
754 attributeMap.insert(id, m_query->lastInsertId().toInt());
755 }
756
757 if (nameId < 0) {
758 m_query->prepare("INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
759 m_query->bindValue(0, filterName);
760 if (m_query->exec())
761 nameId = m_query->lastInsertId().toInt();
762 }
763
764 if (nameId < 0) {
765 emit error(tr("Cannot register filter %1.").arg(filterName));
766 return false;
767 }
768
769 m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1);
770 m_query->bindValue(0, nameId);
771 m_query->exec();
772
773 for (const QString &att : attributes) {
774 m_query->prepare("INSERT INTO FilterTable VALUES(?, ?)"_L1);
775 m_query->bindValue(0, nameId);
776 m_query->bindValue(1, attributeMap[att]);
777 if (!m_query->exec())
778 return false;
779 }
780 return true;
781}
782
783QHelpCollectionHandler::FileInfo QHelpCollectionHandler::registeredDocumentation(
784 const QString &namespaceName) const
785{
786 FileInfo fileInfo;
787
788 if (!m_query)
789 return fileInfo;
790
791 m_query->prepare(
792 "SELECT "
793 "NamespaceTable.Name, "
794 "NamespaceTable.FilePath, "
795 "FolderTable.Name "
796 "FROM "
797 "NamespaceTable, "
798 "FolderTable "
799 "WHERE NamespaceTable.Id = FolderTable.NamespaceId "
800 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
801 m_query->bindValue(0, namespaceName);
802 if (!m_query->exec() || !m_query->next())
803 return fileInfo;
804
805 fileInfo.namespaceName = m_query->value(0).toString();
806 fileInfo.fileName = m_query->value(1).toString();
807 fileInfo.folderName = m_query->value(2).toString();
808
809 m_query->clear();
810
811 return fileInfo;
812}
813
815{
816 FileInfoList list;
817 if (!m_query)
818 return list;
819
820 m_query->exec(
821 "SELECT "
822 "NamespaceTable.Name, "
823 "NamespaceTable.FilePath, "
824 "FolderTable.Name "
825 "FROM "
826 "NamespaceTable, "
827 "FolderTable "
828 "WHERE NamespaceTable.Id = FolderTable.NamespaceId"_L1);
829
830 while (m_query->next()) {
831 FileInfo fileInfo;
832 fileInfo.namespaceName = m_query->value(0).toString();
833 fileInfo.fileName = m_query->value(1).toString();
834 fileInfo.folderName = m_query->value(2).toString();
835 list.append(fileInfo);
836 }
837
838 return list;
839}
840
841bool QHelpCollectionHandler::registerDocumentation(const QString &fileName)
842{
843 if (!isDBOpened())
844 return false;
845
846 QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName(
847 "QHelpCollectionHandler"_L1, this), nullptr);
848 if (!reader.init()) {
849 emit error(tr("Cannot open documentation file %1.").arg(fileName));
850 return false;
851 }
852
853 const QString &ns = reader.namespaceName();
854 if (ns.isEmpty()) {
855 emit error(tr("Invalid documentation file \"%1\".").arg(fileName));
856 return false;
857 }
858
859 const int nsId = registerNamespace(ns, fileName);
860 if (nsId < 1)
861 return false;
862
863 const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId);
864 if (vfId < 1)
865 return false;
866
867 registerVersion(reader.version(), nsId);
868 registerFilterAttributes(reader.filterAttributeSets(), nsId); // qset, what happens when removing documentation?
869 const auto &customFilters = reader.customFilters();
870 for (const QString &filterName : customFilters)
871 addCustomFilter(filterName, reader.filterAttributes(filterName));
872
873 if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName))
874 return false;
875
876 return true;
877}
878
879bool QHelpCollectionHandler::unregisterDocumentation(const QString &namespaceName)
880{
881 if (!isDBOpened())
882 return false;
883
884 m_query->prepare("SELECT Id FROM NamespaceTable WHERE Name = ?"_L1);
885 m_query->bindValue(0, namespaceName);
886 m_query->exec();
887
888 if (!m_query->next()) {
889 emit error(tr("The namespace %1 was not registered.").arg(namespaceName));
890 return false;
891 }
892
893 const int nsId = m_query->value(0).toInt();
894
895 m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1);
896 m_query->bindValue(0, nsId);
897 if (!m_query->exec())
898 return false;
899
900 m_query->prepare("SELECT Id FROM FolderTable WHERE NamespaceId = ?"_L1);
901 m_query->bindValue(0, nsId);
902 m_query->exec();
903
904 if (!m_query->next()) {
905 emit error(tr("The namespace %1 was not registered.").arg(namespaceName));
906 return false;
907 }
908
909 const int vfId = m_query->value(0).toInt();
910
911 m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1);
912 m_query->bindValue(0, nsId);
913 if (!m_query->exec())
914 return false;
915
916 m_query->prepare("DELETE FROM FolderTable WHERE NamespaceId = ?"_L1);
917 m_query->bindValue(0, nsId);
918 if (!m_query->exec())
919 return false;
920
921 if (!unregisterIndexTable(nsId, vfId))
922 return false;
923
924 scheduleVacuum();
925
926 return true;
927}
928
930{
931 QHelpCollectionHandler::FileInfo fileInfo;
932
933 if (!url.isValid() || url.toString().count(u'/') < 4
934 || url.scheme() != "qthelp"_L1) {
935 return fileInfo;
936 }
937
938 fileInfo.namespaceName = url.authority();
939 fileInfo.fileName = url.path();
940 if (fileInfo.fileName.startsWith(u'/'))
941 fileInfo.fileName = fileInfo.fileName.mid(1);
942 fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(u'/', 1));
943 fileInfo.fileName.remove(0, fileInfo.folderName.size() + 1);
944
945 return fileInfo;
946}
947
948bool QHelpCollectionHandler::fileExists(const QUrl &url) const
949{
950 if (!isDBOpened())
951 return false;
952
953 const FileInfo fileInfo = extractFileInfo(url);
954 if (fileInfo.namespaceName.isEmpty())
955 return false;
956
957 m_query->prepare(
958 "SELECT COUNT (DISTINCT NamespaceTable.Id) "
959 "FROM "
960 "FileNameTable, "
961 "NamespaceTable, "
962 "FolderTable "
963 "WHERE FolderTable.Name = ? "
964 "AND FileNameTable.Name = ? "
965 "AND FileNameTable.FolderId = FolderTable.Id "
966 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1);
967 m_query->bindValue(0, fileInfo.folderName);
968 m_query->bindValue(1, fileInfo.fileName);
969 if (!m_query->exec() || !m_query->next())
970 return false;
971
972 const int count = m_query->value(0).toInt();
973 m_query->clear();
974
975 return count;
976}
977
978static QString prepareFilterQuery(const QString &filterName)
979{
980 if (filterName.isEmpty())
981 return {};
982
983 return " AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) "
984 "AND ("
985 "(NOT EXISTS(" // 1. filter by component
986 "SELECT * FROM "
987 "ComponentFilter, "
988 "Filter "
989 "WHERE ComponentFilter.FilterId = Filter.FilterId "
990 "AND Filter.Name = ?) "
991 "OR NamespaceTable.Id IN ("
992 "SELECT "
993 "NamespaceTable.Id "
994 "FROM "
995 "NamespaceTable, "
996 "ComponentTable, "
997 "ComponentMapping, "
998 "ComponentFilter, "
999 "Filter "
1000 "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id "
1001 "AND ComponentTable.ComponentId = ComponentMapping.ComponentId "
1002 "AND ((ComponentTable.Name = ComponentFilter.ComponentName) "
1003 "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) "
1004 "AND ComponentFilter.FilterId = Filter.FilterId "
1005 "AND Filter.Name = ?))"
1006 " AND "
1007 "(NOT EXISTS(" // 2. filter by version
1008 "SELECT * FROM "
1009 "VersionFilter, "
1010 "Filter "
1011 "WHERE VersionFilter.FilterId = Filter.FilterId "
1012 "AND Filter.Name = ?) "
1013 "OR NamespaceTable.Id IN ("
1014 "SELECT "
1015 "NamespaceTable.Id "
1016 "FROM "
1017 "NamespaceTable, "
1018 "VersionFilter, "
1019 "VersionTable, "
1020 "Filter "
1021 "WHERE VersionFilter.FilterId = Filter.FilterId "
1022 "AND ((VersionFilter.Version = VersionTable.Version) "
1023 "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) "
1024 "AND VersionTable.NamespaceId = NamespaceTable.Id "
1025 "AND Filter.Name = ?))"
1026 ")"_L1;
1027}
1028
1029static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName)
1030{
1031 if (filterName.isEmpty())
1032 return;
1033
1034 query->bindValue(bindStart, filterName);
1035 query->bindValue(bindStart + 1, filterName);
1036 query->bindValue(bindStart + 2, filterName);
1037 query->bindValue(bindStart + 3, filterName);
1038 query->bindValue(bindStart + 4, filterName);
1039}
1040
1041static QString prepareFilterQuery(int attributesCount,
1042 const QString &idTableName,
1043 const QString &idColumnName,
1044 const QString &filterTableName,
1045 const QString &filterColumnName)
1046{
1047 if (!attributesCount)
1048 return {};
1049
1050 QString filterQuery = " AND (%1.%2 IN ("_L1.arg(idTableName, idColumnName);
1051
1052 const QString filterQueryTemplate =
1053 "SELECT %1.%2 "
1054 "FROM %1, FilterAttributeTable "
1055 "WHERE %1.FilterAttributeId = FilterAttributeTable.Id "
1056 "AND FilterAttributeTable.Name = ?"_L1.arg(filterTableName, filterColumnName);
1057
1058 for (int i = 0; i < attributesCount; ++i) {
1059 if (i > 0)
1060 filterQuery.append(" INTERSECT "_L1);
1061 filterQuery.append(filterQueryTemplate);
1062 }
1063
1064 filterQuery.append(") OR NamespaceTable.Id IN ("_L1);
1065
1066 const QString optimizedFilterQueryTemplate =
1067 "SELECT OptimizedFilterTable.NamespaceId "
1068 "FROM OptimizedFilterTable, FilterAttributeTable "
1069 "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id "
1070 "AND FilterAttributeTable.Name = ?"_L1;
1071
1072 for (int i = 0; i < attributesCount; ++i) {
1073 if (i > 0)
1074 filterQuery.append(" INTERSECT "_L1);
1075 filterQuery.append(optimizedFilterQueryTemplate);
1076 }
1077
1078 filterQuery.append("))"_L1);
1079
1080 return filterQuery;
1081}
1082
1083static void bindFilterQuery(QSqlQuery *query, int startingBindPos, const QStringList &filterAttributes)
1084{
1085 for (int i = 0; i < 2; ++i) {
1086 for (int j = 0; j < filterAttributes.size(); j++) {
1087 query->bindValue(i * filterAttributes.size() + j + startingBindPos,
1088 filterAttributes.at(j));
1089 }
1090 }
1091}
1092
1094 const QStringList &filterAttributes) const
1095{
1096 if (!isDBOpened())
1097 return {};
1098
1099 const FileInfo fileInfo = extractFileInfo(url);
1100 if (fileInfo.namespaceName.isEmpty())
1101 return {};
1102
1103 const QString filterlessQuery =
1104 "SELECT DISTINCT "
1105 "NamespaceTable.Name "
1106 "FROM "
1107 "FileNameTable, "
1108 "NamespaceTable, "
1109 "FolderTable "
1110 "WHERE FolderTable.Name = ? "
1111 "AND FileNameTable.Name = ? "
1112 "AND FileNameTable.FolderId = FolderTable.Id "
1113 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1114
1115 const QString filterQuery = filterlessQuery
1116 + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1,
1117 "FileFilterTable"_L1, "FileId"_L1);
1118
1119 m_query->prepare(filterQuery);
1120 m_query->bindValue(0, fileInfo.folderName);
1121 m_query->bindValue(1, fileInfo.fileName);
1122 bindFilterQuery(m_query.get(), 2, filterAttributes);
1123
1124 if (!m_query->exec())
1125 return {};
1126
1127 QStringList namespaceList;
1128 while (m_query->next())
1129 namespaceList.append(m_query->value(0).toString());
1130
1131 if (namespaceList.isEmpty())
1132 return {};
1133
1134 if (namespaceList.contains(fileInfo.namespaceName))
1135 return fileInfo.namespaceName;
1136
1137 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1138
1139 for (const QString &ns : std::as_const(namespaceList)) {
1140 const QString nsVersion = namespaceVersion(ns);
1141 if (originalVersion == nsVersion)
1142 return ns;
1143 }
1144
1145 // TODO: still, we may like to return the ns for the highest available version
1146 return namespaceList.first();
1147}
1148
1149QString QHelpCollectionHandler::namespaceForFile(const QUrl &url,
1150 const QString &filterName) const
1151{
1152 if (!isDBOpened())
1153 return {};
1154
1155 const FileInfo fileInfo = extractFileInfo(url);
1156 if (fileInfo.namespaceName.isEmpty())
1157 return {};
1158
1159 const QString filterlessQuery =
1160 "SELECT DISTINCT "
1161 "NamespaceTable.Name "
1162 "FROM "
1163 "FileNameTable, "
1164 "NamespaceTable, "
1165 "FolderTable "
1166 "WHERE FolderTable.Name = ? "
1167 "AND FileNameTable.Name = ? "
1168 "AND FileNameTable.FolderId = FolderTable.Id "
1169 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1170
1171 const QString filterQuery = filterlessQuery
1172 + prepareFilterQuery(filterName);
1173
1174 m_query->prepare(filterQuery);
1175 m_query->bindValue(0, fileInfo.folderName);
1176 m_query->bindValue(1, fileInfo.fileName);
1177 bindFilterQuery(m_query.get(), 2, filterName);
1178
1179 if (!m_query->exec())
1180 return {};
1181
1182 QStringList namespaceList;
1183 while (m_query->next())
1184 namespaceList.append(m_query->value(0).toString());
1185
1186 if (namespaceList.isEmpty())
1187 return {};
1188
1189 if (namespaceList.contains(fileInfo.namespaceName))
1190 return fileInfo.namespaceName;
1191
1192 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1193
1194 for (const QString &ns : std::as_const(namespaceList)) {
1195 const QString nsVersion = namespaceVersion(ns);
1196 if (originalVersion == nsVersion)
1197 return ns;
1198 }
1199
1200 // TODO: still, we may like to return the ns for the highest available version
1201 return namespaceList.first();
1202}
1203
1204QStringList QHelpCollectionHandler::files(const QString &namespaceName,
1205 const QStringList &filterAttributes,
1206 const QString &extensionFilter) const
1207{
1208 if (!isDBOpened())
1209 return {};
1210
1211 const QString extensionQuery = extensionFilter.isEmpty()
1212 ? QString() : " AND FileNameTable.Name LIKE ?"_L1;
1213 const QString filterlessQuery =
1214 "SELECT "
1215 "FolderTable.Name, "
1216 "FileNameTable.Name "
1217 "FROM "
1218 "FileNameTable, "
1219 "FolderTable, "
1220 "NamespaceTable "
1221 "WHERE FileNameTable.FolderId = FolderTable.Id "
1222 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1223 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1224
1225 const QString filterQuery = filterlessQuery
1226 + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1,
1227 "FileFilterTable"_L1, "FileId"_L1);
1228
1229 m_query->prepare(filterQuery);
1230 m_query->bindValue(0, namespaceName);
1231 int bindCount = 1;
1232 if (!extensionFilter.isEmpty()) {
1233 m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter));
1234 ++bindCount;
1235 }
1236 bindFilterQuery(m_query.get(), bindCount, filterAttributes);
1237
1238 if (!m_query->exec())
1239 return {};
1240
1241 QStringList fileNames;
1242 while (m_query->next())
1243 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1244 return fileNames;
1245}
1246
1247QStringList QHelpCollectionHandler::files(const QString &namespaceName,
1248 const QString &filterName,
1249 const QString &extensionFilter) const
1250{
1251 if (!isDBOpened())
1252 return {};
1253
1254 const QString extensionQuery = extensionFilter.isEmpty()
1255 ? QString() : " AND FileNameTable.Name LIKE ?"_L1;
1256 const QString filterlessQuery =
1257 "SELECT "
1258 "FolderTable.Name, "
1259 "FileNameTable.Name "
1260 "FROM "
1261 "FileNameTable, "
1262 "FolderTable, "
1263 "NamespaceTable "
1264 "WHERE FileNameTable.FolderId = FolderTable.Id "
1265 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1266 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1267
1268 const QString filterQuery = filterlessQuery
1269 + prepareFilterQuery(filterName);
1270
1271 m_query->prepare(filterQuery);
1272 m_query->bindValue(0, namespaceName);
1273 int bindCount = 1;
1274 if (!extensionFilter.isEmpty()) {
1275 m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter));
1276 ++bindCount;
1277 }
1278
1279 bindFilterQuery(m_query.get(), bindCount, filterName);
1280
1281 if (!m_query->exec())
1282 return{};
1283
1284 QStringList fileNames;
1285 while (m_query->next())
1286 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1287 return fileNames;
1288}
1289
1290QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filterAttributes) const
1291{
1292 if (!isDBOpened())
1293 return {};
1294
1295 const QString namespaceName = namespaceForFile(url, filterAttributes);
1296 if (namespaceName.isEmpty())
1297 return {};
1298
1299 QUrl result = url;
1300 result.setAuthority(namespaceName);
1301 return result;
1302}
1303
1304QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QString &filterName) const
1305{
1306 if (!isDBOpened())
1307 return {};
1308
1309 const QString namespaceName = namespaceForFile(url, filterName);
1310 if (namespaceName.isEmpty())
1311 return {};
1312
1313 QUrl result = url;
1314 result.setAuthority(namespaceName);
1315 return result;
1316}
1317
1319{
1320 if (!isDBOpened())
1321 return {};
1322
1323 const QString namespaceName = namespaceForFile(url, QString());
1324 if (namespaceName.isEmpty())
1325 return {};
1326
1327 const FileInfo fileInfo = extractFileInfo(url);
1328
1329 const FileInfo docInfo = registeredDocumentation(namespaceName);
1330 const QString absFileName = absoluteDocPath(docInfo.fileName);
1331
1332 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1333 docInfo.fileName, const_cast<QHelpCollectionHandler *>(this)), nullptr);
1334 if (!reader.init())
1335 return {};
1336
1337 return reader.fileData(fileInfo.folderName, fileInfo.fileName);
1338}
1339
1340QStringList QHelpCollectionHandler::indicesForFilter(const QStringList &filterAttributes) const
1341{
1342 QStringList indices;
1343
1344 if (!isDBOpened())
1345 return indices;
1346
1347 const QString filterlessQuery =
1348 "SELECT DISTINCT "
1349 "IndexTable.Name "
1350 "FROM "
1351 "IndexTable, "
1352 "FileNameTable, "
1353 "FolderTable, "
1354 "NamespaceTable "
1355 "WHERE IndexTable.FileId = FileNameTable.FileId "
1356 "AND FileNameTable.FolderId = FolderTable.Id "
1357 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1358
1359 const QString filterQuery = filterlessQuery
1360 + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1,
1361 "IndexFilterTable"_L1, "IndexId"_L1)
1362 + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1363 // this doesn't work: ASC COLLATE NOCASE
1364
1365 m_query->prepare(filterQuery);
1366 bindFilterQuery(m_query.get(), 0, filterAttributes);
1367
1368 m_query->exec();
1369
1370 while (m_query->next())
1371 indices.append(m_query->value(0).toString());
1372
1373 return indices;
1374}
1375
1376QStringList QHelpCollectionHandler::indicesForFilter(const QString &filterName) const
1377{
1378 QStringList indices;
1379
1380 if (!isDBOpened())
1381 return indices;
1382
1383 const QString filterlessQuery =
1384 "SELECT DISTINCT "
1385 "IndexTable.Name "
1386 "FROM "
1387 "IndexTable, "
1388 "FileNameTable, "
1389 "FolderTable, "
1390 "NamespaceTable "
1391 "WHERE IndexTable.FileId = FileNameTable.FileId "
1392 "AND FileNameTable.FolderId = FolderTable.Id "
1393 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1394
1395 const QString filterQuery = filterlessQuery
1396 + prepareFilterQuery(filterName)
1397 + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1398
1399 m_query->prepare(filterQuery);
1400 bindFilterQuery(m_query.get(), 0, filterName);
1401
1402 m_query->exec();
1403
1404 while (m_query->next())
1405 indices.append(m_query->value(0).toString());
1406
1407 return indices;
1408}
1409
1410static QString getTitle(const QByteArray &contents)
1411{
1412 if (!contents.size())
1413 return {};
1414
1415 int depth = 0;
1416 QString link;
1417 QString title;
1418
1419 QDataStream s(contents);
1420 s >> depth;
1421 s >> link;
1422 s >> title;
1423
1424 return title;
1425}
1426
1428 const QStringList &filterAttributes) const
1429{
1430 if (!isDBOpened())
1431 return {};
1432
1433 const QString filterlessQuery =
1434 "SELECT DISTINCT "
1435 "NamespaceTable.Name, "
1436 "FolderTable.Name, "
1437 "ContentsTable.Data, "
1438 "VersionTable.Version "
1439 "FROM "
1440 "FolderTable, "
1441 "NamespaceTable, "
1442 "ContentsTable, "
1443 "VersionTable "
1444 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1445 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1446 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1447 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1448
1449 const QString filterQuery = filterlessQuery
1450 + prepareFilterQuery(filterAttributes.size(), "ContentsTable"_L1, "Id"_L1,
1451 "ContentsFilterTable"_L1, "ContentsId"_L1);
1452
1453 m_query->prepare(filterQuery);
1454 bindFilterQuery(m_query.get(), 0, filterAttributes);
1455
1456 m_query->exec();
1457
1458 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1459
1460 while (m_query->next()) {
1461 const QString namespaceName = m_query->value(0).toString();
1462 const QByteArray contents = m_query->value(2).toByteArray();
1463 const QString versionString = m_query->value(3).toString();
1464
1465 const QString title = getTitle(contents);
1466 const QVersionNumber version = QVersionNumber::fromString(versionString);
1467 // get existing or insert a new one otherwise
1468 ContentsData &contentsData = contentsMap[title][version];
1469 contentsData.namespaceName = namespaceName;
1470 contentsData.folderName = m_query->value(1).toString();
1471 contentsData.contentsList.append(contents);
1472 }
1473
1474 QList<QHelpCollectionHandler::ContentsData> result;
1475 for (const auto &versionContents : std::as_const(contentsMap)) {
1476 // insert items in the reverse order of version number
1477 const auto itBegin = versionContents.constBegin();
1478 auto it = versionContents.constEnd();
1479 while (it != itBegin) {
1480 --it;
1481 result.append(it.value());
1482 }
1483 }
1484 return result;
1485}
1486
1487QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForFilter(const QString &filterName) const
1488{
1489 if (!isDBOpened())
1490 return {};
1491
1492 const QString filterlessQuery =
1493 "SELECT DISTINCT "
1494 "NamespaceTable.Name, "
1495 "FolderTable.Name, "
1496 "ContentsTable.Data, "
1497 "VersionTable.Version "
1498 "FROM "
1499 "FolderTable, "
1500 "NamespaceTable, "
1501 "ContentsTable, "
1502 "VersionTable "
1503 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1504 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1505 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1506 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1507
1508 const QString filterQuery = filterlessQuery + prepareFilterQuery(filterName);
1509
1510 m_query->prepare(filterQuery);
1511 bindFilterQuery(m_query.get(), 0, filterName);
1512
1513 m_query->exec();
1514
1515 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1516
1517 while (m_query->next()) {
1518 const QString namespaceName = m_query->value(0).toString();
1519 const QByteArray contents = m_query->value(2).toByteArray();
1520 const QString versionString = m_query->value(3).toString();
1521
1522 const QString title = getTitle(contents);
1523 const QVersionNumber version = QVersionNumber::fromString(versionString);
1524 // get existing or insert a new one otherwise
1525 ContentsData &contentsData = contentsMap[title][version];
1526 contentsData.namespaceName = namespaceName;
1527 contentsData.folderName = m_query->value(1).toString();
1528 contentsData.contentsList.append(contents);
1529 }
1530
1531 QList<QHelpCollectionHandler::ContentsData> result;
1532 for (const auto &versionContents : std::as_const(contentsMap)) {
1533 // insert items in the reverse order of version number
1534 const auto itBegin = versionContents.constBegin();
1535 auto it = versionContents.constEnd();
1536 while (it != itBegin) {
1537 --it;
1538 result.append(it.value());
1539 }
1540 }
1541 return result;
1542}
1543
1544bool QHelpCollectionHandler::removeCustomValue(const QString &key)
1545{
1546 if (!isDBOpened())
1547 return false;
1548
1549 m_query->prepare("DELETE FROM SettingsTable WHERE Key=?"_L1);
1550 m_query->bindValue(0, key);
1551 return m_query->exec();
1552}
1553
1554QVariant QHelpCollectionHandler::customValue(const QString &key,
1555 const QVariant &defaultValue) const
1556{
1557 if (!m_query)
1558 return defaultValue;
1559
1560 m_query->prepare("SELECT COUNT(Key) FROM SettingsTable WHERE Key=?"_L1);
1561 m_query->bindValue(0, key);
1562 if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) {
1563 m_query->clear();
1564 return defaultValue;
1565 }
1566
1567 m_query->clear();
1568 m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1569 m_query->bindValue(0, key);
1570 if (m_query->exec() && m_query->next()) {
1571 const QVariant &value = m_query->value(0);
1572 m_query->clear();
1573 return value;
1574 }
1575 return defaultValue;
1576}
1577
1578bool QHelpCollectionHandler::setCustomValue(const QString &key,
1579 const QVariant &value)
1580{
1581 if (!isDBOpened())
1582 return false;
1583
1584 m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1585 m_query->bindValue(0, key);
1586 m_query->exec();
1587 if (m_query->next()) {
1588 m_query->prepare("UPDATE SettingsTable SET Value=? where Key=?"_L1);
1589 m_query->bindValue(0, value);
1590 m_query->bindValue(1, key);
1591 } else {
1592 m_query->prepare("INSERT INTO SettingsTable VALUES(?, ?)"_L1);
1593 m_query->bindValue(0, key);
1594 m_query->bindValue(1, value);
1595 }
1596 return m_query->exec();
1597}
1598
1599bool QHelpCollectionHandler::registerFilterAttributes(const QList<QStringList> &attributeSets,
1600 int nsId)
1601{
1602 if (!isDBOpened())
1603 return false;
1604
1605 m_query->exec("SELECT Name FROM FilterAttributeTable"_L1);
1606 QSet<QString> atts;
1607 while (m_query->next())
1608 atts.insert(m_query->value(0).toString());
1609
1610 for (const QStringList &attributeSet : attributeSets) {
1611 for (const QString &attribute : attributeSet) {
1612 if (!atts.contains(attribute)) {
1613 m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
1614 m_query->bindValue(0, attribute);
1615 m_query->exec();
1616 }
1617 }
1618 }
1619 return registerFileAttributeSets(attributeSets, nsId);
1620}
1621
1622bool QHelpCollectionHandler::registerFileAttributeSets(const QList<QStringList> &attributeSets,
1623 int nsId)
1624{
1625 if (!isDBOpened())
1626 return false;
1627
1628 if (attributeSets.isEmpty())
1629 return true;
1630
1631 QVariantList nsIds;
1632 QVariantList attributeSetIds;
1633 QVariantList filterAttributeIds;
1634
1635 if (!m_query->exec("SELECT MAX(FilterAttributeSetId) FROM FileAttributeSetTable"_L1)
1636 || !m_query->next()) {
1637 return false;
1638 }
1639
1640 int attributeSetId = m_query->value(0).toInt();
1641
1642 for (const QStringList &attributeSet : attributeSets) {
1643 ++attributeSetId;
1644
1645 for (const QString &attribute : attributeSet) {
1646 m_query->prepare("SELECT Id FROM FilterAttributeTable WHERE Name=?"_L1);
1647 m_query->bindValue(0, attribute);
1648
1649 if (!m_query->exec() || !m_query->next())
1650 return false;
1651
1652 nsIds.append(nsId);
1653 attributeSetIds.append(attributeSetId);
1654 filterAttributeIds.append(m_query->value(0).toInt());
1655 }
1656 }
1657
1658 m_query->prepare("INSERT INTO FileAttributeSetTable "
1659 "(NamespaceId, FilterAttributeSetId, FilterAttributeId) "
1660 "VALUES(?, ?, ?)"_L1);
1661 m_query->addBindValue(nsIds);
1662 m_query->addBindValue(attributeSetIds);
1663 m_query->addBindValue(filterAttributeIds);
1664 return m_query->execBatch();
1665}
1666
1668{
1669 QStringList list;
1670 if (m_query) {
1671 m_query->exec("SELECT Name FROM FilterAttributeTable"_L1);
1672 while (m_query->next())
1673 list.append(m_query->value(0).toString());
1674 }
1675 return list;
1676}
1677
1678QStringList QHelpCollectionHandler::filterAttributes(const QString &filterName) const
1679{
1680 QStringList list;
1681 if (m_query) {
1682 m_query->prepare(
1683 "SELECT "
1684 "FilterAttributeTable.Name "
1685 "FROM "
1686 "FilterAttributeTable, "
1687 "FilterTable, "
1688 "FilterNameTable "
1689 "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId "
1690 "AND FilterTable.NameId = FilterNameTable.Id "
1691 "AND FilterNameTable.Name=?"_L1);
1692 m_query->bindValue(0, filterName);
1693 m_query->exec();
1694 while (m_query->next())
1695 list.append(m_query->value(0).toString());
1696 }
1697 return list;
1698}
1699
1700QList<QStringList> QHelpCollectionHandler::filterAttributeSets(const QString &namespaceName) const
1701{
1702 if (!isDBOpened())
1703 return {};
1704
1705 m_query->prepare(
1706 "SELECT "
1707 "FileAttributeSetTable.FilterAttributeSetId, "
1708 "FilterAttributeTable.Name "
1709 "FROM "
1710 "FileAttributeSetTable, "
1711 "FilterAttributeTable, "
1712 "NamespaceTable "
1713 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
1714 "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id "
1715 "AND NamespaceTable.Name = ? "
1716 "ORDER BY FileAttributeSetTable.FilterAttributeSetId"_L1);
1717 m_query->bindValue(0, namespaceName);
1718 m_query->exec();
1719 int oldId = -1;
1720 QList<QStringList> result;
1721 while (m_query->next()) {
1722 const int id = m_query->value(0).toInt();
1723 if (id != oldId) {
1724 result.append(QStringList());
1725 oldId = id;
1726 }
1727 result.last().append(m_query->value(1).toString());
1728 }
1729
1730 if (result.isEmpty())
1731 result.append(QStringList());
1732 return result;
1733}
1734
1735QString QHelpCollectionHandler::namespaceVersion(const QString &namespaceName) const
1736{
1737 if (!m_query)
1738 return {};
1739
1740 m_query->prepare(
1741 "SELECT "
1742 "VersionTable.Version "
1743 "FROM "
1744 "NamespaceTable, "
1745 "VersionTable "
1746 "WHERE NamespaceTable.Name = ? "
1747 "AND NamespaceTable.Id = VersionTable.NamespaceId"_L1);
1748 m_query->bindValue(0, namespaceName);
1749 if (!m_query->exec() || !m_query->next())
1750 return {};
1751
1752 const QString ret = m_query->value(0).toString();
1753 m_query->clear();
1754 return ret;
1755}
1756
1757int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QString &fileName)
1758{
1759 const int errorValue = -1;
1760 if (!m_query)
1761 return errorValue;
1762
1763 m_query->prepare("SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?"_L1);
1764 m_query->bindValue(0, nspace);
1765 m_query->exec();
1766 while (m_query->next()) {
1767 if (m_query->value(0).toInt() > 0) {
1768 emit error(tr("Namespace %1 already exists.").arg(nspace));
1769 return errorValue;
1770 }
1771 }
1772
1773 QFileInfo fi(m_collectionFile);
1774 m_query->prepare("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
1775 m_query->bindValue(0, nspace);
1776 m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName));
1777 int namespaceId = errorValue;
1778 if (m_query->exec()) {
1779 namespaceId = m_query->lastInsertId().toInt();
1780 m_query->clear();
1781 }
1782 if (namespaceId < 1) {
1783 emit error(tr("Cannot register namespace \"%1\".").arg(nspace));
1784 return errorValue;
1785 }
1786 return namespaceId;
1787}
1788
1789int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId)
1790{
1791 if (!m_query)
1792 return false;
1793
1794 m_query->prepare("INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
1795 m_query->bindValue(0, namespaceId);
1796 m_query->bindValue(1, folderName);
1797
1798 int virtualId = -1;
1799 if (m_query->exec()) {
1800 virtualId = m_query->lastInsertId().toInt();
1801 m_query->clear();
1802 }
1803 if (virtualId < 1) {
1804 emit error(tr("Cannot register virtual folder '%1'.").arg(folderName));
1805 return -1;
1806 }
1807 if (registerComponent(folderName, namespaceId) < 0)
1808 return -1;
1809 return virtualId;
1810}
1811
1812int QHelpCollectionHandler::registerComponent(const QString &componentName, int namespaceId)
1813{
1814 m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1815 m_query->bindValue(0, componentName);
1816 if (!m_query->exec())
1817 return -1;
1818
1819 if (!m_query->next()) {
1820 m_query->prepare("INSERT INTO ComponentTable VALUES(NULL, ?)"_L1);
1821 m_query->bindValue(0, componentName);
1822 if (!m_query->exec())
1823 return -1;
1824
1825 m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1826 m_query->bindValue(0, componentName);
1827 if (!m_query->exec() || !m_query->next())
1828 return -1;
1829 }
1830
1831 const int componentId = m_query->value(0).toInt();
1832
1833 m_query->prepare("INSERT INTO ComponentMapping VALUES(?, ?)"_L1);
1834 m_query->bindValue(0, componentId);
1835 m_query->bindValue(1, namespaceId);
1836 if (!m_query->exec())
1837 return -1;
1838
1839 return componentId;
1840}
1841
1842bool QHelpCollectionHandler::registerVersion(const QString &version, int namespaceId)
1843{
1844 if (!m_query)
1845 return false;
1846
1847 m_query->prepare("INSERT INTO VersionTable (NamespaceId, Version) VALUES(?, ?)"_L1);
1848 m_query->addBindValue(namespaceId);
1849 m_query->addBindValue(version);
1850 return m_query->exec();
1851}
1852
1853bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(
1854 const QString &nameSpace, bool createDefaultVersionFilter)
1855{
1856 if (!isDBOpened())
1857 return false;
1858
1859 m_query->prepare("SELECT Id, FilePath FROM NamespaceTable WHERE Name=?"_L1);
1860 m_query->bindValue(0, nameSpace);
1861 m_query->exec();
1862 if (!m_query->next())
1863 return false;
1864
1865 const int nsId = m_query->value(0).toInt();
1866 const QString fileName = m_query->value(1).toString();
1867
1868 m_query->prepare("SELECT Id, Name FROM FolderTable WHERE NamespaceId=?"_L1);
1869 m_query->bindValue(0, nsId);
1870 m_query->exec();
1871 if (!m_query->next())
1872 return false;
1873
1874 const int vfId = m_query->value(0).toInt();
1875 const QString vfName = m_query->value(1).toString();
1876
1877 const QString absFileName = absoluteDocPath(fileName);
1878 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1879 fileName, this), this);
1880 if (!reader.init())
1881 return false;
1882
1883 registerComponent(vfName, nsId);
1884 registerVersion(reader.version(), nsId);
1885 if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId))
1886 return false;
1887
1888 if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName))
1889 return false;
1890
1891 if (createDefaultVersionFilter)
1892 createVersionFilter(reader.version());
1893 return true;
1894}
1895
1896void QHelpCollectionHandler::createVersionFilter(const QString &version)
1897{
1898 if (version.isEmpty())
1899 return;
1900
1901 const QVersionNumber versionNumber = QVersionNumber::fromString(version);
1902 if (versionNumber.isNull())
1903 return;
1904
1905 const QString filterName = tr("Version %1").arg(version);
1906 if (filters().contains(filterName))
1907 return;
1908
1909 QHelpFilterData filterData;
1910 filterData.setVersions({versionNumber});
1911 setFilterData(filterName, filterData);
1912}
1913
1914bool QHelpCollectionHandler::registerIndexTable(const QHelpDBReader::IndexTable &indexTable,
1915 int nsId, int vfId, const QString &fileName)
1916{
1917 Transaction transaction(m_connectionName);
1918
1919 QMap<QString, QVariantList> filterAttributeToNewFileId;
1920
1921 QVariantList fileFolderIds;
1922 QVariantList fileNames;
1923 QVariantList fileTitles;
1924 const int fileSize = indexTable.fileItems.size();
1925 fileFolderIds.reserve(fileSize);
1926 fileNames.reserve(fileSize);
1927 fileTitles.reserve(fileSize);
1928
1929 if (!m_query->exec("SELECT MAX(FileId) FROM FileNameTable"_L1) || !m_query->next())
1930 return false;
1931
1932 const int maxFileId = m_query->value(0).toInt();
1933
1934 int newFileId = 0;
1935 for (const QHelpDBReader::FileItem &item : indexTable.fileItems) {
1936 fileFolderIds.append(vfId);
1937 fileNames.append(item.name);
1938 fileTitles.append(item.title);
1939
1940 for (const QString &filterAttribute : item.filterAttributes)
1941 filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1);
1942 ++newFileId;
1943 }
1944
1945 m_query->prepare("INSERT INTO FileNameTable VALUES(?, ?, NULL, ?)"_L1);
1946 m_query->addBindValue(fileFolderIds);
1947 m_query->addBindValue(fileNames);
1948 m_query->addBindValue(fileTitles);
1949 if (!m_query->execBatch())
1950 return false;
1951
1952 for (auto it = filterAttributeToNewFileId.cbegin(),
1953 end = filterAttributeToNewFileId.cend(); it != end; ++it) {
1954 const QString filterAttribute = it.key();
1955 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
1956 m_query->bindValue(0, filterAttribute);
1957 if (!m_query->exec() || !m_query->next())
1958 return false;
1959
1960 const int attributeId = m_query->value(0).toInt();
1961
1962 QVariantList attributeIds;
1963 for (int i = 0; i < it.value().size(); i++)
1964 attributeIds.append(attributeId);
1965
1966 m_query->prepare("INSERT INTO FileFilterTable VALUES(?, ?)"_L1);
1967 m_query->addBindValue(attributeIds);
1968 m_query->addBindValue(it.value());
1969 if (!m_query->execBatch())
1970 return false;
1971 }
1972
1973 QMap<QString, QVariantList> filterAttributeToNewIndexId;
1974
1975 if (!m_query->exec("SELECT MAX(Id) FROM IndexTable"_L1) || !m_query->next())
1976 return false;
1977
1978 const int maxIndexId = m_query->value(0).toInt();
1979 int newIndexId = 0;
1980
1981 QVariantList indexNames;
1982 QVariantList indexIdentifiers;
1983 QVariantList indexNamespaceIds;
1984 QVariantList indexFileIds;
1985 QVariantList indexAnchors;
1986 const int indexSize = indexTable.indexItems.size();
1987 indexNames.reserve(indexSize);
1988 indexIdentifiers.reserve(indexSize);
1989 indexNamespaceIds.reserve(indexSize);
1990 indexFileIds.reserve(indexSize);
1991 indexAnchors.reserve(indexSize);
1992
1993 for (const QHelpDBReader::IndexItem &item : indexTable.indexItems) {
1994 indexNames.append(item.name);
1995 indexIdentifiers.append(item.identifier);
1996 indexNamespaceIds.append(nsId);
1997 indexFileIds.append(maxFileId + item.fileId + 1);
1998 indexAnchors.append(item.anchor);
1999
2000 for (const QString &filterAttribute : item.filterAttributes)
2001 filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1);
2002 ++newIndexId;
2003 }
2004
2005 m_query->prepare("INSERT INTO IndexTable VALUES(NULL, ?, ?, ?, ?, ?)"_L1);
2006 m_query->addBindValue(indexNames);
2007 m_query->addBindValue(indexIdentifiers);
2008 m_query->addBindValue(indexNamespaceIds);
2009 m_query->addBindValue(indexFileIds);
2010 m_query->addBindValue(indexAnchors);
2011 if (!m_query->execBatch())
2012 return false;
2013
2014 for (auto it = filterAttributeToNewIndexId.cbegin(),
2015 end = filterAttributeToNewIndexId.cend(); it != end; ++it) {
2016 const QString filterAttribute = it.key();
2017 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2018 m_query->bindValue(0, filterAttribute);
2019 if (!m_query->exec() || !m_query->next())
2020 return false;
2021
2022 const int attributeId = m_query->value(0).toInt();
2023
2024 QVariantList attributeIds;
2025 for (int i = 0; i < it.value().size(); i++)
2026 attributeIds.append(attributeId);
2027
2028 m_query->prepare("INSERT INTO IndexFilterTable VALUES(?, ?)"_L1);
2029 m_query->addBindValue(attributeIds);
2030 m_query->addBindValue(it.value());
2031 if (!m_query->execBatch())
2032 return false;
2033 }
2034
2035 QMap<QString, QVariantList> filterAttributeToNewContentsId;
2036
2037 QVariantList contentsNsIds;
2038 QVariantList contentsData;
2039 const int contentsSize = indexTable.contentsItems.size();
2040 contentsNsIds.reserve(contentsSize);
2041 contentsData.reserve(contentsSize);
2042
2043 if (!m_query->exec("SELECT MAX(Id) FROM ContentsTable"_L1) || !m_query->next())
2044 return false;
2045
2046 const int maxContentsId = m_query->value(0).toInt();
2047
2048 int newContentsId = 0;
2049 for (const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) {
2050 contentsNsIds.append(nsId);
2051 contentsData.append(item.data);
2052
2053 for (const QString &filterAttribute : item.filterAttributes) {
2054 filterAttributeToNewContentsId[filterAttribute]
2055 .append(maxContentsId + newContentsId + 1);
2056 }
2057 ++newContentsId;
2058 }
2059
2060 m_query->prepare("INSERT INTO ContentsTable VALUES(NULL, ?, ?)"_L1);
2061 m_query->addBindValue(contentsNsIds);
2062 m_query->addBindValue(contentsData);
2063 if (!m_query->execBatch())
2064 return false;
2065
2066 for (auto it = filterAttributeToNewContentsId.cbegin(),
2067 end = filterAttributeToNewContentsId.cend(); it != end; ++it) {
2068 const QString filterAttribute = it.key();
2069 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2070 m_query->bindValue(0, filterAttribute);
2071 if (!m_query->exec() || !m_query->next())
2072 return false;
2073
2074 const int attributeId = m_query->value(0).toInt();
2075
2076 QVariantList attributeIds;
2077 for (int i = 0; i < it.value().size(); i++)
2078 attributeIds.append(attributeId);
2079
2080 m_query->prepare("INSERT INTO ContentsFilterTable VALUES(?, ?)"_L1);
2081 m_query->addBindValue(attributeIds);
2082 m_query->addBindValue(it.value());
2083 if (!m_query->execBatch())
2084 return false;
2085 }
2086
2087 QVariantList filterNsIds;
2088 QVariantList filterAttributeIds;
2089 for (const QString &filterAttribute : indexTable.usedFilterAttributes) {
2090 filterNsIds.append(nsId);
2091
2092 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2093 m_query->bindValue(0, filterAttribute);
2094 if (!m_query->exec() || !m_query->next())
2095 return false;
2096
2097 filterAttributeIds.append(m_query->value(0).toInt());
2098 }
2099
2100 m_query->prepare("INSERT INTO OptimizedFilterTable "
2101 "(NamespaceId, FilterAttributeId) VALUES(?, ?)"_L1);
2102 m_query->addBindValue(filterNsIds);
2103 m_query->addBindValue(filterAttributeIds);
2104 if (!m_query->execBatch())
2105 return false;
2106
2107 m_query->prepare("INSERT INTO TimeStampTable "
2108 "(NamespaceId, FolderId, FilePath, Size, TimeStamp) "
2109 "VALUES(?, ?, ?, ?, ?)"_L1);
2110 m_query->addBindValue(nsId);
2111 m_query->addBindValue(vfId);
2112 m_query->addBindValue(fileName);
2113 const QFileInfo fi(absoluteDocPath(fileName));
2114 m_query->addBindValue(fi.size());
2115 QDateTime lastModified = fi.lastModified(QTimeZone::UTC);
2116 if (qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH")) {
2117 const QString sourceDateEpochStr = qEnvironmentVariable("SOURCE_DATE_EPOCH");
2118 bool ok;
2119 const qlonglong sourceDateEpoch = sourceDateEpochStr.toLongLong(&ok);
2120 if (ok && sourceDateEpoch < lastModified.toSecsSinceEpoch())
2121 lastModified.setSecsSinceEpoch(sourceDateEpoch);
2122 }
2123 m_query->addBindValue(lastModified);
2124 if (!m_query->exec())
2125 return false;
2126
2127 transaction.commit();
2128 return true;
2129}
2130
2131bool QHelpCollectionHandler::unregisterIndexTable(int nsId, int vfId)
2132{
2133 m_query->prepare("DELETE FROM IndexFilterTable WHERE IndexId IN "
2134 "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)"_L1);
2135 m_query->bindValue(0, nsId);
2136 if (!m_query->exec())
2137 return false;
2138
2139 m_query->prepare("DELETE FROM IndexTable WHERE NamespaceId = ?"_L1);
2140 m_query->bindValue(0, nsId);
2141 if (!m_query->exec())
2142 return false;
2143
2144 m_query->prepare("DELETE FROM FileFilterTable WHERE FileId IN "
2145 "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)"_L1);
2146 m_query->bindValue(0, vfId);
2147 if (!m_query->exec())
2148 return false;
2149
2150 m_query->prepare("DELETE FROM FileNameTable WHERE FolderId = ?"_L1);
2151 m_query->bindValue(0, vfId);
2152 if (!m_query->exec())
2153 return false;
2154
2155 m_query->prepare("DELETE FROM ContentsFilterTable WHERE ContentsId IN "
2156 "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)"_L1);
2157 m_query->bindValue(0, nsId);
2158 if (!m_query->exec())
2159 return false;
2160
2161 m_query->prepare("DELETE FROM ContentsTable WHERE NamespaceId = ?"_L1);
2162 m_query->bindValue(0, nsId);
2163 if (!m_query->exec())
2164 return false;
2165
2166 m_query->prepare("DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?"_L1);
2167 m_query->bindValue(0, nsId);
2168 if (!m_query->exec())
2169 return false;
2170
2171 m_query->prepare("DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?"_L1);
2172 m_query->bindValue(0, nsId);
2173 if (!m_query->exec())
2174 return false;
2175
2176 m_query->prepare("DELETE FROM TimeStampTable WHERE NamespaceId = ?"_L1);
2177 m_query->bindValue(0, nsId);
2178 if (!m_query->exec())
2179 return false;
2180
2181 m_query->prepare("DELETE FROM VersionTable WHERE NamespaceId = ?"_L1);
2182 m_query->bindValue(0, nsId);
2183 if (!m_query->exec())
2184 return false;
2185
2186 m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2187 m_query->bindValue(0, nsId);
2188 if (!m_query->exec())
2189 return false;
2190
2191 if (!m_query->next())
2192 return false;
2193
2194 const int componentId = m_query->value(0).toInt();
2195
2196 m_query->prepare("DELETE FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2197 m_query->bindValue(0, nsId);
2198 if (!m_query->exec())
2199 return false;
2200
2201 m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?"_L1);
2202 m_query->bindValue(0, componentId);
2203 if (!m_query->exec())
2204 return false;
2205
2206 if (!m_query->next()) { // no more namespaces refer to the componentId
2207 m_query->prepare("DELETE FROM ComponentTable WHERE ComponentId = ?"_L1);
2208 m_query->bindValue(0, componentId);
2209 if (!m_query->exec())
2210 return false;
2211 }
2212
2213 return true;
2214}
2215
2216QUrl QHelpCollectionHandler::buildQUrl(const QString &ns, const QString &folder,
2217 const QString &relFileName, const QString &anchor)
2218{
2219 QUrl url;
2220 url.setScheme("qthelp"_L1);
2221 url.setAuthority(ns);
2222 url.setPath(u'/' + folder + u'/' + relFileName);
2223 url.setFragment(anchor);
2224 return url;
2225}
2226
2227QList<QHelpLink> QHelpCollectionHandler::documentsForIdentifier(
2228 const QString &id, const QStringList &filterAttributes) const
2229{
2230 return documentsForField("Identifier"_L1, id, filterAttributes);
2231}
2232
2233QList<QHelpLink> QHelpCollectionHandler::documentsForKeyword(
2234 const QString &keyword, const QStringList &filterAttributes) const
2235{
2236 return documentsForField("Name"_L1, keyword, filterAttributes);
2237}
2238
2239QList<QHelpLink> QHelpCollectionHandler::documentsForField(const QString &fieldName,
2240 const QString &fieldValue, const QStringList &filterAttributes) const
2241{
2242 if (!isDBOpened())
2243 return {};
2244
2245 const QString filterlessQuery =
2246 "SELECT "
2247 "FileNameTable.Title, "
2248 "NamespaceTable.Name, "
2249 "FolderTable.Name, "
2250 "FileNameTable.Name, "
2251 "IndexTable.Anchor "
2252 "FROM "
2253 "IndexTable, "
2254 "FileNameTable, "
2255 "FolderTable, "
2256 "NamespaceTable "
2257 "WHERE IndexTable.FileId = FileNameTable.FileId "
2258 "AND FileNameTable.FolderId = FolderTable.Id "
2259 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2260 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2261
2262 const QString filterQuery = filterlessQuery
2263 + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1,
2264 "IndexFilterTable"_L1, "IndexId"_L1);
2265
2266 m_query->prepare(filterQuery);
2267 m_query->bindValue(0, fieldValue);
2268 bindFilterQuery(m_query.get(), 1, filterAttributes);
2269
2270 m_query->exec();
2271
2272 QList<QHelpLink> docList;
2273 while (m_query->next()) {
2274 QString title = m_query->value(0).toString();
2275 if (title.isEmpty()) // generate a title + corresponding path
2276 title = fieldValue + " : "_L1 + m_query->value(3).toString();
2277
2278 const QUrl url = buildQUrl(m_query->value(1).toString(),
2279 m_query->value(2).toString(),
2280 m_query->value(3).toString(),
2281 m_query->value(4).toString());
2282 docList.append(QHelpLink {url, title});
2283 }
2284 return docList;
2285}
2286
2287QList<QHelpLink> QHelpCollectionHandler::documentsForIdentifier(
2288 const QString &id, const QString &filterName) const
2289{
2290 return documentsForField("Identifier"_L1, id, filterName);
2291}
2292
2293QList<QHelpLink> QHelpCollectionHandler::documentsForKeyword(
2294 const QString &keyword, const QString &filterName) const
2295{
2296 return documentsForField("Name"_L1, keyword, filterName);
2297}
2298
2299QMultiMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldName,
2300 const QString &fieldValue, const QString &filterName) const
2301{
2302 QMultiMap<QString, QUrl> linkMap;
2303 const auto documents = documentsForField(fieldName, fieldValue, filterName);
2304 for (const auto &document : documents)
2305 linkMap.insert(document.title, document.url);
2306 return linkMap;
2307}
2308
2309struct IndexEntry
2310{
2311 QString title;
2314
2315private:
2316 friend bool comparesEqual(const IndexEntry &lhs, const IndexEntry &rhs) noexcept
2317 {
2318 return lhs.title == rhs.title && lhs.url == rhs.url && lhs.version == rhs.version;
2319 }
2321};
2322
2323static inline bool hasVersion(const IndexEntry &e)
2324{
2325 return !e.version.isNull();
2326}
2327
2328static bool versionLessThan(const IndexEntry &lhs, const IndexEntry &rhs)
2329{
2330 return lhs.version < rhs.version;
2331}
2332
2334{
2335 return { e.url, e.title };
2336}
2337
2338QList<QHelpLink> QHelpCollectionHandler::documentsForField(const QString &fieldName,
2339 const QString &fieldValue, const QString &filterName) const
2340{
2341 if (!isDBOpened())
2342 return {};
2343
2344 const QString filterlessQuery =
2345 "SELECT "
2346 "FileNameTable.Title, "
2347 "NamespaceTable.Name, "
2348 "FolderTable.Name, "
2349 "FileNameTable.Name, "
2350 "IndexTable.Anchor "
2351 "FROM "
2352 "IndexTable, "
2353 "FileNameTable, "
2354 "FolderTable, "
2355 "NamespaceTable "
2356 "WHERE IndexTable.FileId = FileNameTable.FileId "
2357 "AND FileNameTable.FolderId = FolderTable.Id "
2358 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2359 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2360
2361 const QString filterQuery = filterlessQuery
2362 + prepareFilterQuery(filterName)
2363 + " ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"_L1;
2364
2365 m_query->prepare(filterQuery);
2366 m_query->bindValue(0, fieldValue);
2367 bindFilterQuery(m_query.get(), 1, filterName);
2368
2369 m_query->exec();
2370
2371 QList<IndexEntry> entries;
2372 while (m_query->next()) {
2373 QString title = m_query->value(0).toString();
2374 if (title.isEmpty()) // generate a title + corresponding path
2375 title = fieldValue + " : "_L1 + m_query->value(3).toString();
2376 const QString nameSpace = m_query->value(1).toString();
2377 const QUrl url = buildQUrl(nameSpace,
2378 m_query->value(2).toString(),
2379 m_query->value(3).toString(),
2380 m_query->value(4).toString());
2381 IndexEntry newEntry{ title, url, QHelpDBReader::versionHeuristic(nameSpace) };
2382 if (!entries.contains(newEntry))
2383 entries.emplaceBack(newEntry);
2384 }
2385
2386 // Reverse sort by trailing version number on the namespace, newest first
2387 if (std::all_of(entries.cbegin(), entries.cend(), hasVersion))
2388 std::stable_sort(entries.rbegin(), entries.rend(), versionLessThan);
2389
2390 QList<QHelpLink> docList;
2391 docList.reserve(entries.size());
2392 std::transform(entries.cbegin(), entries.cend(), std::back_inserter(docList), toHelpLink);
2393 return docList;
2394}
2395
2396QStringList QHelpCollectionHandler::namespacesForFilter(const QString &filterName) const
2397{
2398 QStringList namespaceList;
2399
2400 if (!isDBOpened())
2401 return namespaceList;
2402
2403 const QString filterlessQuery =
2404 "SELECT "
2405 "NamespaceTable.Name "
2406 "FROM "
2407 "NamespaceTable "
2408 "WHERE TRUE"_L1;
2409
2410 const QString filterQuery = filterlessQuery
2411 + prepareFilterQuery(filterName);
2412
2413 m_query->prepare(filterQuery);
2414 bindFilterQuery(m_query.get(), 0, filterName);
2415
2416 m_query->exec();
2417
2418 while (m_query->next())
2419 namespaceList.append(m_query->value(0).toString());
2420 return namespaceList;
2421}
2422
2423QT_END_NAMESPACE
QUrl findFile(const QUrl &url, const QStringList &filterAttributes) const
QByteArray fileData(const QUrl &url) const
FileInfoList registeredDocumentations() const
QList< ContentsData > contentsForFilter(const QStringList &filterAttributes) const
QMap< QString, QVersionNumber > namespaceToVersion() const
QList< QVersionNumber > availableVersions() const
QString namespaceForFile(const QUrl &url, const QStringList &filterAttributes) const
QMap< QString, QString > namespaceToComponent() const
QStringList availableComponents() const
QStringList indicesForFilter(const QStringList &filterAttributes) const
bool fileExists(const QUrl &url) const
\inmodule QtCore
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 QString prepareFilterQuery(const QString &filterName)
static QHelpLink toHelpLink(const IndexEntry &e)
friend bool comparesEqual(const IndexEntry &lhs, const IndexEntry &rhs) noexcept