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