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