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/qset.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 const auto &components = filterData.components();
638 for (const QString &component : components) {
639 componentList.append(component);
640 filterIdList.append(filterId);
641 }
642
643 m_query->prepare("INSERT INTO ComponentFilter VALUES (?, ?)"_L1);
644 m_query->addBindValue(componentList);
645 m_query->addBindValue(filterIdList);
646 if (!m_query->execBatch())
647 return false;
648
649 filterIdList.clear();
650 const auto &versions = filterData.versions();
651 for (const QVersionNumber &version : versions) {
652 versionList.append(version.isNull() ? QString() : version.toString());
653 filterIdList.append(filterId);
654 }
655
656 m_query->prepare("INSERT INTO VersionFilter VALUES (?, ?)"_L1);
657 m_query->addBindValue(versionList);
658 m_query->addBindValue(filterIdList);
659 if (!m_query->execBatch())
660 return false;
661
662 return true;
663}
664
665bool QHelpCollectionHandler::removeFilter(const QString &filterName)
666{
667 m_query->prepare("SELECT FilterId FROM Filter WHERE Name = ?"_L1);
668 m_query->bindValue(0, filterName);
669 if (!m_query->exec())
670 return false;
671
672 if (!m_query->next())
673 return true; // no filter in DB
674
675 const int filterId = m_query->value(0).toInt();
676
677 m_query->prepare("DELETE FROM Filter WHERE Filter.Name = ?"_L1);
678 m_query->bindValue(0, filterName);
679 if (!m_query->exec())
680 return false;
681
682 m_query->prepare("DELETE FROM ComponentFilter WHERE ComponentFilter.FilterId = ?"_L1);
683 m_query->bindValue(0, filterId);
684 if (!m_query->exec())
685 return false;
686
687 m_query->prepare("DELETE FROM VersionFilter WHERE VersionFilter.FilterId = ?"_L1);
688 m_query->bindValue(0, filterId);
689 if (!m_query->exec())
690 return false;
691
692 return true;
693}
694
695bool QHelpCollectionHandler::removeCustomFilter(const QString &filterName)
696{
697 if (!isDBOpened() || filterName.isEmpty())
698 return false;
699
700 int filterNameId = -1;
701 m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
702 m_query->bindValue(0, filterName);
703 m_query->exec();
704 if (m_query->next())
705 filterNameId = m_query->value(0).toInt();
706
707 if (filterNameId < 0) {
708 emit error(tr("Unknown filter \"%1\".").arg(filterName));
709 return false;
710 }
711
712 m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1);
713 m_query->bindValue(0, filterNameId);
714 m_query->exec();
715
716 m_query->prepare("DELETE FROM FilterNameTable WHERE Id=?"_L1);
717 m_query->bindValue(0, filterNameId);
718 m_query->exec();
719
720 return true;
721}
722
723bool QHelpCollectionHandler::addCustomFilter(const QString &filterName,
724 const QStringList &attributes)
725{
726 if (!isDBOpened() || filterName.isEmpty())
727 return false;
728
729 int nameId = -1;
730 m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1);
731 m_query->bindValue(0, filterName);
732 m_query->exec();
733 if (m_query->next())
734 nameId = m_query->value(0).toInt();
735
736 m_query->exec("SELECT Id, Name FROM FilterAttributeTable"_L1);
737 QStringList idsToInsert = attributes;
738 QMap<QString, int> attributeMap;
739 while (m_query->next()) {
740 // all old attributes
741 const QString attributeName = m_query->value(1).toString();
742 attributeMap.insert(attributeName, m_query->value(0).toInt());
743 idsToInsert.removeAll(attributeName);
744 }
745
746 for (const QString &id : std::as_const(idsToInsert)) {
747 m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
748 m_query->bindValue(0, id);
749 m_query->exec();
750 attributeMap.insert(id, m_query->lastInsertId().toInt());
751 }
752
753 if (nameId < 0) {
754 m_query->prepare("INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1);
755 m_query->bindValue(0, filterName);
756 if (m_query->exec())
757 nameId = m_query->lastInsertId().toInt();
758 }
759
760 if (nameId < 0) {
761 emit error(tr("Cannot register filter %1.").arg(filterName));
762 return false;
763 }
764
765 m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1);
766 m_query->bindValue(0, nameId);
767 m_query->exec();
768
769 for (const QString &att : attributes) {
770 m_query->prepare("INSERT INTO FilterTable VALUES(?, ?)"_L1);
771 m_query->bindValue(0, nameId);
772 m_query->bindValue(1, attributeMap[att]);
773 if (!m_query->exec())
774 return false;
775 }
776 return true;
777}
778
779QHelpCollectionHandler::FileInfo QHelpCollectionHandler::registeredDocumentation(
780 const QString &namespaceName) const
781{
782 FileInfo fileInfo;
783
784 if (!m_query)
785 return fileInfo;
786
787 m_query->prepare(
788 "SELECT "
789 "NamespaceTable.Name, "
790 "NamespaceTable.FilePath, "
791 "FolderTable.Name "
792 "FROM "
793 "NamespaceTable, "
794 "FolderTable "
795 "WHERE NamespaceTable.Id = FolderTable.NamespaceId "
796 "AND NamespaceTable.Name = ? LIMIT 1"_L1);
797 m_query->bindValue(0, namespaceName);
798 if (!m_query->exec() || !m_query->next())
799 return fileInfo;
800
801 fileInfo.namespaceName = m_query->value(0).toString();
802 fileInfo.fileName = m_query->value(1).toString();
803 fileInfo.folderName = m_query->value(2).toString();
804
805 m_query->clear();
806
807 return fileInfo;
808}
809
811{
812 FileInfoList list;
813 if (!m_query)
814 return list;
815
816 m_query->exec(
817 "SELECT "
818 "NamespaceTable.Name, "
819 "NamespaceTable.FilePath, "
820 "FolderTable.Name "
821 "FROM "
822 "NamespaceTable, "
823 "FolderTable "
824 "WHERE NamespaceTable.Id = FolderTable.NamespaceId"_L1);
825
826 while (m_query->next()) {
827 FileInfo fileInfo;
828 fileInfo.namespaceName = m_query->value(0).toString();
829 fileInfo.fileName = m_query->value(1).toString();
830 fileInfo.folderName = m_query->value(2).toString();
831 list.append(fileInfo);
832 }
833
834 return list;
835}
836
837bool QHelpCollectionHandler::registerDocumentation(const QString &fileName)
838{
839 if (!isDBOpened())
840 return false;
841
842 QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName(
843 "QHelpCollectionHandler"_L1, this), nullptr);
844 if (!reader.init()) {
845 emit error(tr("Cannot open documentation file %1.").arg(fileName));
846 return false;
847 }
848
849 const QString &ns = reader.namespaceName();
850 if (ns.isEmpty()) {
851 emit error(tr("Invalid documentation file \"%1\".").arg(fileName));
852 return false;
853 }
854
855 const int nsId = registerNamespace(ns, fileName);
856 if (nsId < 1)
857 return false;
858
859 const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId);
860 if (vfId < 1)
861 return false;
862
863 registerVersion(reader.version(), nsId);
864 registerFilterAttributes(reader.filterAttributeSets(), nsId); // qset, what happens when removing documentation?
865 const auto &customFilters = reader.customFilters();
866 for (const QString &filterName : customFilters)
867 addCustomFilter(filterName, reader.filterAttributes(filterName));
868
869 if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName))
870 return false;
871
872 return true;
873}
874
875bool QHelpCollectionHandler::unregisterDocumentation(const QString &namespaceName)
876{
877 if (!isDBOpened())
878 return false;
879
880 m_query->prepare("SELECT Id FROM NamespaceTable WHERE Name = ?"_L1);
881 m_query->bindValue(0, namespaceName);
882 m_query->exec();
883
884 if (!m_query->next()) {
885 emit error(tr("The namespace %1 was not registered.").arg(namespaceName));
886 return false;
887 }
888
889 const int nsId = m_query->value(0).toInt();
890
891 m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1);
892 m_query->bindValue(0, nsId);
893 if (!m_query->exec())
894 return false;
895
896 m_query->prepare("SELECT Id FROM FolderTable WHERE NamespaceId = ?"_L1);
897 m_query->bindValue(0, nsId);
898 m_query->exec();
899
900 if (!m_query->next()) {
901 emit error(tr("The namespace %1 was not registered.").arg(namespaceName));
902 return false;
903 }
904
905 const int vfId = m_query->value(0).toInt();
906
907 m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1);
908 m_query->bindValue(0, nsId);
909 if (!m_query->exec())
910 return false;
911
912 m_query->prepare("DELETE FROM FolderTable WHERE NamespaceId = ?"_L1);
913 m_query->bindValue(0, nsId);
914 if (!m_query->exec())
915 return false;
916
917 if (!unregisterIndexTable(nsId, vfId))
918 return false;
919
920 scheduleVacuum();
921
922 return true;
923}
924
926{
927 QHelpCollectionHandler::FileInfo fileInfo;
928
929 if (!url.isValid() || url.toString().count(u'/') < 4
930 || url.scheme() != "qthelp"_L1) {
931 return fileInfo;
932 }
933
934 fileInfo.namespaceName = url.authority();
935 fileInfo.fileName = url.path();
936 if (fileInfo.fileName.startsWith(u'/'))
937 fileInfo.fileName = fileInfo.fileName.mid(1);
938 fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(u'/', 1));
939 fileInfo.fileName.remove(0, fileInfo.folderName.size() + 1);
940
941 return fileInfo;
942}
943
944bool QHelpCollectionHandler::fileExists(const QUrl &url) const
945{
946 if (!isDBOpened())
947 return false;
948
949 const FileInfo fileInfo = extractFileInfo(url);
950 if (fileInfo.namespaceName.isEmpty())
951 return false;
952
953 m_query->prepare(
954 "SELECT COUNT (DISTINCT NamespaceTable.Id) "
955 "FROM "
956 "FileNameTable, "
957 "NamespaceTable, "
958 "FolderTable "
959 "WHERE FolderTable.Name = ? "
960 "AND FileNameTable.Name = ? "
961 "AND FileNameTable.FolderId = FolderTable.Id "
962 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1);
963 m_query->bindValue(0, fileInfo.folderName);
964 m_query->bindValue(1, fileInfo.fileName);
965 if (!m_query->exec() || !m_query->next())
966 return false;
967
968 const int count = m_query->value(0).toInt();
969 m_query->clear();
970
971 return count;
972}
973
974static QString prepareFilterQuery(const QString &filterName)
975{
976 if (filterName.isEmpty())
977 return {};
978
979 return " AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) "
980 "AND ("
981 "(NOT EXISTS(" // 1. filter by component
982 "SELECT * FROM "
983 "ComponentFilter, "
984 "Filter "
985 "WHERE ComponentFilter.FilterId = Filter.FilterId "
986 "AND Filter.Name = ?) "
987 "OR NamespaceTable.Id IN ("
988 "SELECT "
989 "NamespaceTable.Id "
990 "FROM "
991 "NamespaceTable, "
992 "ComponentTable, "
993 "ComponentMapping, "
994 "ComponentFilter, "
995 "Filter "
996 "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id "
997 "AND ComponentTable.ComponentId = ComponentMapping.ComponentId "
998 "AND ((ComponentTable.Name = ComponentFilter.ComponentName) "
999 "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) "
1000 "AND ComponentFilter.FilterId = Filter.FilterId "
1001 "AND Filter.Name = ?))"
1002 " AND "
1003 "(NOT EXISTS(" // 2. filter by version
1004 "SELECT * FROM "
1005 "VersionFilter, "
1006 "Filter "
1007 "WHERE VersionFilter.FilterId = Filter.FilterId "
1008 "AND Filter.Name = ?) "
1009 "OR NamespaceTable.Id IN ("
1010 "SELECT "
1011 "NamespaceTable.Id "
1012 "FROM "
1013 "NamespaceTable, "
1014 "VersionFilter, "
1015 "VersionTable, "
1016 "Filter "
1017 "WHERE VersionFilter.FilterId = Filter.FilterId "
1018 "AND ((VersionFilter.Version = VersionTable.Version) "
1019 "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) "
1020 "AND VersionTable.NamespaceId = NamespaceTable.Id "
1021 "AND Filter.Name = ?))"
1022 ")"_L1;
1023}
1024
1025static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName)
1026{
1027 if (filterName.isEmpty())
1028 return;
1029
1030 query->bindValue(bindStart, filterName);
1031 query->bindValue(bindStart + 1, filterName);
1032 query->bindValue(bindStart + 2, filterName);
1033 query->bindValue(bindStart + 3, filterName);
1034 query->bindValue(bindStart + 4, filterName);
1035}
1036
1037static QString prepareFilterQuery(int attributesCount,
1038 const QString &idTableName,
1039 const QString &idColumnName,
1040 const QString &filterTableName,
1041 const QString &filterColumnName)
1042{
1043 if (!attributesCount)
1044 return {};
1045
1046 QString filterQuery = " AND (%1.%2 IN ("_L1.arg(idTableName, idColumnName);
1047
1048 const QString filterQueryTemplate =
1049 "SELECT %1.%2 "
1050 "FROM %1, FilterAttributeTable "
1051 "WHERE %1.FilterAttributeId = FilterAttributeTable.Id "
1052 "AND FilterAttributeTable.Name = ?"_L1.arg(filterTableName, filterColumnName);
1053
1054 for (int i = 0; i < attributesCount; ++i) {
1055 if (i > 0)
1056 filterQuery.append(" INTERSECT "_L1);
1057 filterQuery.append(filterQueryTemplate);
1058 }
1059
1060 filterQuery.append(") OR NamespaceTable.Id IN ("_L1);
1061
1062 const QString optimizedFilterQueryTemplate =
1063 "SELECT OptimizedFilterTable.NamespaceId "
1064 "FROM OptimizedFilterTable, FilterAttributeTable "
1065 "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id "
1066 "AND FilterAttributeTable.Name = ?"_L1;
1067
1068 for (int i = 0; i < attributesCount; ++i) {
1069 if (i > 0)
1070 filterQuery.append(" INTERSECT "_L1);
1071 filterQuery.append(optimizedFilterQueryTemplate);
1072 }
1073
1074 filterQuery.append("))"_L1);
1075
1076 return filterQuery;
1077}
1078
1079static void bindFilterQuery(QSqlQuery *query, int startingBindPos, const QStringList &filterAttributes)
1080{
1081 for (int i = 0; i < 2; ++i) {
1082 for (int j = 0; j < filterAttributes.size(); j++) {
1083 query->bindValue(i * filterAttributes.size() + j + startingBindPos,
1084 filterAttributes.at(j));
1085 }
1086 }
1087}
1088
1090 const QStringList &filterAttributes) const
1091{
1092 if (!isDBOpened())
1093 return {};
1094
1095 const FileInfo fileInfo = extractFileInfo(url);
1096 if (fileInfo.namespaceName.isEmpty())
1097 return {};
1098
1099 const QString filterlessQuery =
1100 "SELECT DISTINCT "
1101 "NamespaceTable.Name "
1102 "FROM "
1103 "FileNameTable, "
1104 "NamespaceTable, "
1105 "FolderTable "
1106 "WHERE FolderTable.Name = ? "
1107 "AND FileNameTable.Name = ? "
1108 "AND FileNameTable.FolderId = FolderTable.Id "
1109 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1110
1111 const QString filterQuery = filterlessQuery
1112 + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1,
1113 "FileFilterTable"_L1, "FileId"_L1);
1114
1115 m_query->prepare(filterQuery);
1116 m_query->bindValue(0, fileInfo.folderName);
1117 m_query->bindValue(1, fileInfo.fileName);
1118 bindFilterQuery(m_query.get(), 2, filterAttributes);
1119
1120 if (!m_query->exec())
1121 return {};
1122
1123 QStringList namespaceList;
1124 while (m_query->next())
1125 namespaceList.append(m_query->value(0).toString());
1126
1127 if (namespaceList.isEmpty())
1128 return {};
1129
1130 if (namespaceList.contains(fileInfo.namespaceName))
1131 return fileInfo.namespaceName;
1132
1133 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1134
1135 for (const QString &ns : std::as_const(namespaceList)) {
1136 const QString nsVersion = namespaceVersion(ns);
1137 if (originalVersion == nsVersion)
1138 return ns;
1139 }
1140
1141 // TODO: still, we may like to return the ns for the highest available version
1142 return namespaceList.first();
1143}
1144
1145QString QHelpCollectionHandler::namespaceForFile(const QUrl &url,
1146 const QString &filterName) const
1147{
1148 if (!isDBOpened())
1149 return {};
1150
1151 const FileInfo fileInfo = extractFileInfo(url);
1152 if (fileInfo.namespaceName.isEmpty())
1153 return {};
1154
1155 const QString filterlessQuery =
1156 "SELECT DISTINCT "
1157 "NamespaceTable.Name "
1158 "FROM "
1159 "FileNameTable, "
1160 "NamespaceTable, "
1161 "FolderTable "
1162 "WHERE FolderTable.Name = ? "
1163 "AND FileNameTable.Name = ? "
1164 "AND FileNameTable.FolderId = FolderTable.Id "
1165 "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1;
1166
1167 const QString filterQuery = filterlessQuery
1168 + prepareFilterQuery(filterName);
1169
1170 m_query->prepare(filterQuery);
1171 m_query->bindValue(0, fileInfo.folderName);
1172 m_query->bindValue(1, fileInfo.fileName);
1173 bindFilterQuery(m_query.get(), 2, filterName);
1174
1175 if (!m_query->exec())
1176 return {};
1177
1178 QStringList namespaceList;
1179 while (m_query->next())
1180 namespaceList.append(m_query->value(0).toString());
1181
1182 if (namespaceList.isEmpty())
1183 return {};
1184
1185 if (namespaceList.contains(fileInfo.namespaceName))
1186 return fileInfo.namespaceName;
1187
1188 const QString originalVersion = namespaceVersion(fileInfo.namespaceName);
1189
1190 for (const QString &ns : std::as_const(namespaceList)) {
1191 const QString nsVersion = namespaceVersion(ns);
1192 if (originalVersion == nsVersion)
1193 return ns;
1194 }
1195
1196 // TODO: still, we may like to return the ns for the highest available version
1197 return namespaceList.first();
1198}
1199
1200QStringList QHelpCollectionHandler::files(const QString &namespaceName,
1201 const QStringList &filterAttributes,
1202 const QString &extensionFilter) const
1203{
1204 if (!isDBOpened())
1205 return {};
1206
1207 const QString extensionQuery = extensionFilter.isEmpty()
1208 ? QString() : " AND FileNameTable.Name LIKE ?"_L1;
1209 const QString filterlessQuery =
1210 "SELECT "
1211 "FolderTable.Name, "
1212 "FileNameTable.Name "
1213 "FROM "
1214 "FileNameTable, "
1215 "FolderTable, "
1216 "NamespaceTable "
1217 "WHERE FileNameTable.FolderId = FolderTable.Id "
1218 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1219 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1220
1221 const QString filterQuery = filterlessQuery
1222 + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1,
1223 "FileFilterTable"_L1, "FileId"_L1);
1224
1225 m_query->prepare(filterQuery);
1226 m_query->bindValue(0, namespaceName);
1227 int bindCount = 1;
1228 if (!extensionFilter.isEmpty()) {
1229 m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter));
1230 ++bindCount;
1231 }
1232 bindFilterQuery(m_query.get(), bindCount, filterAttributes);
1233
1234 if (!m_query->exec())
1235 return {};
1236
1237 QStringList fileNames;
1238 while (m_query->next())
1239 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1240 return fileNames;
1241}
1242
1243QStringList QHelpCollectionHandler::files(const QString &namespaceName,
1244 const QString &filterName,
1245 const QString &extensionFilter) const
1246{
1247 if (!isDBOpened())
1248 return {};
1249
1250 const QString extensionQuery = extensionFilter.isEmpty()
1251 ? QString() : " AND FileNameTable.Name LIKE ?"_L1;
1252 const QString filterlessQuery =
1253 "SELECT "
1254 "FolderTable.Name, "
1255 "FileNameTable.Name "
1256 "FROM "
1257 "FileNameTable, "
1258 "FolderTable, "
1259 "NamespaceTable "
1260 "WHERE FileNameTable.FolderId = FolderTable.Id "
1261 "AND FolderTable.NamespaceId = NamespaceTable.Id "
1262 "AND NamespaceTable.Name = ?"_L1 + extensionQuery;
1263
1264 const QString filterQuery = filterlessQuery
1265 + prepareFilterQuery(filterName);
1266
1267 m_query->prepare(filterQuery);
1268 m_query->bindValue(0, namespaceName);
1269 int bindCount = 1;
1270 if (!extensionFilter.isEmpty()) {
1271 m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter));
1272 ++bindCount;
1273 }
1274
1275 bindFilterQuery(m_query.get(), bindCount, filterName);
1276
1277 if (!m_query->exec())
1278 return{};
1279
1280 QStringList fileNames;
1281 while (m_query->next())
1282 fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString());
1283 return fileNames;
1284}
1285
1286QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filterAttributes) const
1287{
1288 if (!isDBOpened())
1289 return {};
1290
1291 const QString namespaceName = namespaceForFile(url, filterAttributes);
1292 if (namespaceName.isEmpty())
1293 return {};
1294
1295 QUrl result = url;
1296 result.setAuthority(namespaceName);
1297 return result;
1298}
1299
1300QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QString &filterName) const
1301{
1302 if (!isDBOpened())
1303 return {};
1304
1305 const QString namespaceName = namespaceForFile(url, filterName);
1306 if (namespaceName.isEmpty())
1307 return {};
1308
1309 QUrl result = url;
1310 result.setAuthority(namespaceName);
1311 return result;
1312}
1313
1315{
1316 if (!isDBOpened())
1317 return {};
1318
1319 const QString namespaceName = namespaceForFile(url, QString());
1320 if (namespaceName.isEmpty())
1321 return {};
1322
1323 const FileInfo fileInfo = extractFileInfo(url);
1324
1325 const FileInfo docInfo = registeredDocumentation(namespaceName);
1326 const QString absFileName = absoluteDocPath(docInfo.fileName);
1327
1328 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1329 docInfo.fileName, const_cast<QHelpCollectionHandler *>(this)), nullptr);
1330 if (!reader.init())
1331 return {};
1332
1333 return reader.fileData(fileInfo.folderName, fileInfo.fileName);
1334}
1335
1336QStringList QHelpCollectionHandler::indicesForFilter(const QStringList &filterAttributes) const
1337{
1338 QStringList indices;
1339
1340 if (!isDBOpened())
1341 return indices;
1342
1343 const QString filterlessQuery =
1344 "SELECT DISTINCT "
1345 "IndexTable.Name "
1346 "FROM "
1347 "IndexTable, "
1348 "FileNameTable, "
1349 "FolderTable, "
1350 "NamespaceTable "
1351 "WHERE IndexTable.FileId = FileNameTable.FileId "
1352 "AND FileNameTable.FolderId = FolderTable.Id "
1353 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1354
1355 const QString filterQuery = filterlessQuery
1356 + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1,
1357 "IndexFilterTable"_L1, "IndexId"_L1)
1358 + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1359 // this doesn't work: ASC COLLATE NOCASE
1360
1361 m_query->prepare(filterQuery);
1362 bindFilterQuery(m_query.get(), 0, filterAttributes);
1363
1364 m_query->exec();
1365
1366 while (m_query->next())
1367 indices.append(m_query->value(0).toString());
1368
1369 return indices;
1370}
1371
1372QStringList QHelpCollectionHandler::indicesForFilter(const QString &filterName) const
1373{
1374 QStringList indices;
1375
1376 if (!isDBOpened())
1377 return indices;
1378
1379 const QString filterlessQuery =
1380 "SELECT DISTINCT "
1381 "IndexTable.Name "
1382 "FROM "
1383 "IndexTable, "
1384 "FileNameTable, "
1385 "FolderTable, "
1386 "NamespaceTable "
1387 "WHERE IndexTable.FileId = FileNameTable.FileId "
1388 "AND FileNameTable.FolderId = FolderTable.Id "
1389 "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1;
1390
1391 const QString filterQuery = filterlessQuery
1392 + prepareFilterQuery(filterName)
1393 + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1;
1394
1395 m_query->prepare(filterQuery);
1396 bindFilterQuery(m_query.get(), 0, filterName);
1397
1398 m_query->exec();
1399
1400 while (m_query->next())
1401 indices.append(m_query->value(0).toString());
1402
1403 return indices;
1404}
1405
1406static QString getTitle(const QByteArray &contents)
1407{
1408 if (!contents.size())
1409 return {};
1410
1411 int depth = 0;
1412 QString link;
1413 QString title;
1414
1415 QDataStream s(contents);
1416 s >> depth;
1417 s >> link;
1418 s >> title;
1419
1420 return title;
1421}
1422
1424 const QStringList &filterAttributes) const
1425{
1426 if (!isDBOpened())
1427 return {};
1428
1429 const QString filterlessQuery =
1430 "SELECT DISTINCT "
1431 "NamespaceTable.Name, "
1432 "FolderTable.Name, "
1433 "ContentsTable.Data, "
1434 "VersionTable.Version "
1435 "FROM "
1436 "FolderTable, "
1437 "NamespaceTable, "
1438 "ContentsTable, "
1439 "VersionTable "
1440 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1441 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1442 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1443 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1444
1445 const QString filterQuery = filterlessQuery
1446 + prepareFilterQuery(filterAttributes.size(), "ContentsTable"_L1, "Id"_L1,
1447 "ContentsFilterTable"_L1, "ContentsId"_L1);
1448
1449 m_query->prepare(filterQuery);
1450 bindFilterQuery(m_query.get(), 0, filterAttributes);
1451
1452 m_query->exec();
1453
1454 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1455
1456 while (m_query->next()) {
1457 const QString namespaceName = m_query->value(0).toString();
1458 const QByteArray contents = m_query->value(2).toByteArray();
1459 const QString versionString = m_query->value(3).toString();
1460
1461 const QString title = getTitle(contents);
1462 const QVersionNumber version = QVersionNumber::fromString(versionString);
1463 // get existing or insert a new one otherwise
1464 ContentsData &contentsData = contentsMap[title][version];
1465 contentsData.namespaceName = namespaceName;
1466 contentsData.folderName = m_query->value(1).toString();
1467 contentsData.contentsList.append(contents);
1468 }
1469
1470 QList<QHelpCollectionHandler::ContentsData> result;
1471 for (const auto &versionContents : std::as_const(contentsMap)) {
1472 // insert items in the reverse order of version number
1473 const auto itBegin = versionContents.constBegin();
1474 auto it = versionContents.constEnd();
1475 while (it != itBegin) {
1476 --it;
1477 result.append(it.value());
1478 }
1479 }
1480 return result;
1481}
1482
1483QList<QHelpCollectionHandler::ContentsData> QHelpCollectionHandler::contentsForFilter(const QString &filterName) const
1484{
1485 if (!isDBOpened())
1486 return {};
1487
1488 const QString filterlessQuery =
1489 "SELECT DISTINCT "
1490 "NamespaceTable.Name, "
1491 "FolderTable.Name, "
1492 "ContentsTable.Data, "
1493 "VersionTable.Version "
1494 "FROM "
1495 "FolderTable, "
1496 "NamespaceTable, "
1497 "ContentsTable, "
1498 "VersionTable "
1499 "WHERE ContentsTable.NamespaceId = NamespaceTable.Id "
1500 "AND NamespaceTable.Id = FolderTable.NamespaceId "
1501 "AND ContentsTable.NamespaceId = NamespaceTable.Id "
1502 "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1;
1503
1504 const QString filterQuery = filterlessQuery + prepareFilterQuery(filterName);
1505
1506 m_query->prepare(filterQuery);
1507 bindFilterQuery(m_query.get(), 0, filterName);
1508
1509 m_query->exec();
1510
1511 QMap<QString, QMap<QVersionNumber, ContentsData>> contentsMap;
1512
1513 while (m_query->next()) {
1514 const QString namespaceName = m_query->value(0).toString();
1515 const QByteArray contents = m_query->value(2).toByteArray();
1516 const QString versionString = m_query->value(3).toString();
1517
1518 const QString title = getTitle(contents);
1519 const QVersionNumber version = QVersionNumber::fromString(versionString);
1520 // get existing or insert a new one otherwise
1521 ContentsData &contentsData = contentsMap[title][version];
1522 contentsData.namespaceName = namespaceName;
1523 contentsData.folderName = m_query->value(1).toString();
1524 contentsData.contentsList.append(contents);
1525 }
1526
1527 QList<QHelpCollectionHandler::ContentsData> result;
1528 for (const auto &versionContents : std::as_const(contentsMap)) {
1529 // insert items in the reverse order of version number
1530 const auto itBegin = versionContents.constBegin();
1531 auto it = versionContents.constEnd();
1532 while (it != itBegin) {
1533 --it;
1534 result.append(it.value());
1535 }
1536 }
1537 return result;
1538}
1539
1540bool QHelpCollectionHandler::removeCustomValue(const QString &key)
1541{
1542 if (!isDBOpened())
1543 return false;
1544
1545 m_query->prepare("DELETE FROM SettingsTable WHERE Key=?"_L1);
1546 m_query->bindValue(0, key);
1547 return m_query->exec();
1548}
1549
1550QVariant QHelpCollectionHandler::customValue(const QString &key,
1551 const QVariant &defaultValue) const
1552{
1553 if (!m_query)
1554 return defaultValue;
1555
1556 m_query->prepare("SELECT COUNT(Key) FROM SettingsTable WHERE Key=?"_L1);
1557 m_query->bindValue(0, key);
1558 if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) {
1559 m_query->clear();
1560 return defaultValue;
1561 }
1562
1563 m_query->clear();
1564 m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1565 m_query->bindValue(0, key);
1566 if (m_query->exec() && m_query->next()) {
1567 const QVariant &value = m_query->value(0);
1568 m_query->clear();
1569 return value;
1570 }
1571 return defaultValue;
1572}
1573
1574bool QHelpCollectionHandler::setCustomValue(const QString &key,
1575 const QVariant &value)
1576{
1577 if (!isDBOpened())
1578 return false;
1579
1580 m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1);
1581 m_query->bindValue(0, key);
1582 m_query->exec();
1583 if (m_query->next()) {
1584 m_query->prepare("UPDATE SettingsTable SET Value=? where Key=?"_L1);
1585 m_query->bindValue(0, value);
1586 m_query->bindValue(1, key);
1587 } else {
1588 m_query->prepare("INSERT INTO SettingsTable VALUES(?, ?)"_L1);
1589 m_query->bindValue(0, key);
1590 m_query->bindValue(1, value);
1591 }
1592 return m_query->exec();
1593}
1594
1595bool QHelpCollectionHandler::registerFilterAttributes(const QList<QStringList> &attributeSets,
1596 int nsId)
1597{
1598 if (!isDBOpened())
1599 return false;
1600
1601 m_query->exec("SELECT Name FROM FilterAttributeTable"_L1);
1602 QSet<QString> atts;
1603 while (m_query->next())
1604 atts.insert(m_query->value(0).toString());
1605
1606 for (const QStringList &attributeSet : attributeSets) {
1607 for (const QString &attribute : attributeSet) {
1608 if (!atts.contains(attribute)) {
1609 m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1);
1610 m_query->bindValue(0, attribute);
1611 m_query->exec();
1612 }
1613 }
1614 }
1615 return registerFileAttributeSets(attributeSets, nsId);
1616}
1617
1618bool QHelpCollectionHandler::registerFileAttributeSets(const QList<QStringList> &attributeSets,
1619 int nsId)
1620{
1621 if (!isDBOpened())
1622 return false;
1623
1624 if (attributeSets.isEmpty())
1625 return true;
1626
1627 QVariantList nsIds;
1628 QVariantList attributeSetIds;
1629 QVariantList filterAttributeIds;
1630
1631 if (!m_query->exec("SELECT MAX(FilterAttributeSetId) FROM FileAttributeSetTable"_L1)
1632 || !m_query->next()) {
1633 return false;
1634 }
1635
1636 int attributeSetId = m_query->value(0).toInt();
1637
1638 for (const QStringList &attributeSet : attributeSets) {
1639 ++attributeSetId;
1640
1641 for (const QString &attribute : attributeSet) {
1642 m_query->prepare("SELECT Id FROM FilterAttributeTable WHERE Name=?"_L1);
1643 m_query->bindValue(0, attribute);
1644
1645 if (!m_query->exec() || !m_query->next())
1646 return false;
1647
1648 nsIds.append(nsId);
1649 attributeSetIds.append(attributeSetId);
1650 filterAttributeIds.append(m_query->value(0).toInt());
1651 }
1652 }
1653
1654 m_query->prepare("INSERT INTO FileAttributeSetTable "
1655 "(NamespaceId, FilterAttributeSetId, FilterAttributeId) "
1656 "VALUES(?, ?, ?)"_L1);
1657 m_query->addBindValue(nsIds);
1658 m_query->addBindValue(attributeSetIds);
1659 m_query->addBindValue(filterAttributeIds);
1660 return m_query->execBatch();
1661}
1662
1664{
1665 QStringList list;
1666 if (m_query) {
1667 m_query->exec("SELECT Name FROM FilterAttributeTable"_L1);
1668 while (m_query->next())
1669 list.append(m_query->value(0).toString());
1670 }
1671 return list;
1672}
1673
1674QStringList QHelpCollectionHandler::filterAttributes(const QString &filterName) const
1675{
1676 QStringList list;
1677 if (m_query) {
1678 m_query->prepare(
1679 "SELECT "
1680 "FilterAttributeTable.Name "
1681 "FROM "
1682 "FilterAttributeTable, "
1683 "FilterTable, "
1684 "FilterNameTable "
1685 "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId "
1686 "AND FilterTable.NameId = FilterNameTable.Id "
1687 "AND FilterNameTable.Name=?"_L1);
1688 m_query->bindValue(0, filterName);
1689 m_query->exec();
1690 while (m_query->next())
1691 list.append(m_query->value(0).toString());
1692 }
1693 return list;
1694}
1695
1696QList<QStringList> QHelpCollectionHandler::filterAttributeSets(const QString &namespaceName) const
1697{
1698 if (!isDBOpened())
1699 return {};
1700
1701 m_query->prepare(
1702 "SELECT "
1703 "FileAttributeSetTable.FilterAttributeSetId, "
1704 "FilterAttributeTable.Name "
1705 "FROM "
1706 "FileAttributeSetTable, "
1707 "FilterAttributeTable, "
1708 "NamespaceTable "
1709 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
1710 "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id "
1711 "AND NamespaceTable.Name = ? "
1712 "ORDER BY FileAttributeSetTable.FilterAttributeSetId"_L1);
1713 m_query->bindValue(0, namespaceName);
1714 m_query->exec();
1715 int oldId = -1;
1716 QList<QStringList> result;
1717 while (m_query->next()) {
1718 const int id = m_query->value(0).toInt();
1719 if (id != oldId) {
1720 result.append(QStringList());
1721 oldId = id;
1722 }
1723 result.last().append(m_query->value(1).toString());
1724 }
1725
1726 if (result.isEmpty())
1727 result.append(QStringList());
1728 return result;
1729}
1730
1731QString QHelpCollectionHandler::namespaceVersion(const QString &namespaceName) const
1732{
1733 if (!m_query)
1734 return {};
1735
1736 m_query->prepare(
1737 "SELECT "
1738 "VersionTable.Version "
1739 "FROM "
1740 "NamespaceTable, "
1741 "VersionTable "
1742 "WHERE NamespaceTable.Name = ? "
1743 "AND NamespaceTable.Id = VersionTable.NamespaceId"_L1);
1744 m_query->bindValue(0, namespaceName);
1745 if (!m_query->exec() || !m_query->next())
1746 return {};
1747
1748 const QString ret = m_query->value(0).toString();
1749 m_query->clear();
1750 return ret;
1751}
1752
1753int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QString &fileName)
1754{
1755 const int errorValue = -1;
1756 if (!m_query)
1757 return errorValue;
1758
1759 m_query->prepare("SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?"_L1);
1760 m_query->bindValue(0, nspace);
1761 m_query->exec();
1762 while (m_query->next()) {
1763 if (m_query->value(0).toInt() > 0) {
1764 emit error(tr("Namespace %1 already exists.").arg(nspace));
1765 return errorValue;
1766 }
1767 }
1768
1769 QFileInfo fi(m_collectionFile);
1770 m_query->prepare("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1);
1771 m_query->bindValue(0, nspace);
1772 m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName));
1773 int namespaceId = errorValue;
1774 if (m_query->exec()) {
1775 namespaceId = m_query->lastInsertId().toInt();
1776 m_query->clear();
1777 }
1778 if (namespaceId < 1) {
1779 emit error(tr("Cannot register namespace \"%1\".").arg(nspace));
1780 return errorValue;
1781 }
1782 return namespaceId;
1783}
1784
1785int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId)
1786{
1787 if (!m_query)
1788 return false;
1789
1790 m_query->prepare("INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1);
1791 m_query->bindValue(0, namespaceId);
1792 m_query->bindValue(1, folderName);
1793
1794 int virtualId = -1;
1795 if (m_query->exec()) {
1796 virtualId = m_query->lastInsertId().toInt();
1797 m_query->clear();
1798 }
1799 if (virtualId < 1) {
1800 emit error(tr("Cannot register virtual folder '%1'.").arg(folderName));
1801 return -1;
1802 }
1803 if (registerComponent(folderName, namespaceId) < 0)
1804 return -1;
1805 return virtualId;
1806}
1807
1808int QHelpCollectionHandler::registerComponent(const QString &componentName, int namespaceId)
1809{
1810 m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1811 m_query->bindValue(0, componentName);
1812 if (!m_query->exec())
1813 return -1;
1814
1815 if (!m_query->next()) {
1816 m_query->prepare("INSERT INTO ComponentTable VALUES(NULL, ?)"_L1);
1817 m_query->bindValue(0, componentName);
1818 if (!m_query->exec())
1819 return -1;
1820
1821 m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1);
1822 m_query->bindValue(0, componentName);
1823 if (!m_query->exec() || !m_query->next())
1824 return -1;
1825 }
1826
1827 const int componentId = m_query->value(0).toInt();
1828
1829 m_query->prepare("INSERT INTO ComponentMapping VALUES(?, ?)"_L1);
1830 m_query->bindValue(0, componentId);
1831 m_query->bindValue(1, namespaceId);
1832 if (!m_query->exec())
1833 return -1;
1834
1835 return componentId;
1836}
1837
1838bool QHelpCollectionHandler::registerVersion(const QString &version, int namespaceId)
1839{
1840 if (!m_query)
1841 return false;
1842
1843 m_query->prepare("INSERT INTO VersionTable (NamespaceId, Version) VALUES(?, ?)"_L1);
1844 m_query->addBindValue(namespaceId);
1845 m_query->addBindValue(version);
1846 return m_query->exec();
1847}
1848
1849bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables(
1850 const QString &nameSpace, bool createDefaultVersionFilter)
1851{
1852 if (!isDBOpened())
1853 return false;
1854
1855 m_query->prepare("SELECT Id, FilePath FROM NamespaceTable WHERE Name=?"_L1);
1856 m_query->bindValue(0, nameSpace);
1857 m_query->exec();
1858 if (!m_query->next())
1859 return false;
1860
1861 const int nsId = m_query->value(0).toInt();
1862 const QString fileName = m_query->value(1).toString();
1863
1864 m_query->prepare("SELECT Id, Name FROM FolderTable WHERE NamespaceId=?"_L1);
1865 m_query->bindValue(0, nsId);
1866 m_query->exec();
1867 if (!m_query->next())
1868 return false;
1869
1870 const int vfId = m_query->value(0).toInt();
1871 const QString vfName = m_query->value(1).toString();
1872
1873 const QString absFileName = absoluteDocPath(fileName);
1874 QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName(
1875 fileName, this), this);
1876 if (!reader.init())
1877 return false;
1878
1879 registerComponent(vfName, nsId);
1880 registerVersion(reader.version(), nsId);
1881 if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId))
1882 return false;
1883
1884 if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName))
1885 return false;
1886
1887 if (createDefaultVersionFilter)
1888 createVersionFilter(reader.version());
1889 return true;
1890}
1891
1892void QHelpCollectionHandler::createVersionFilter(const QString &version)
1893{
1894 if (version.isEmpty())
1895 return;
1896
1897 const QVersionNumber versionNumber = QVersionNumber::fromString(version);
1898 if (versionNumber.isNull())
1899 return;
1900
1901 const QString filterName = tr("Version %1").arg(version);
1902 if (filters().contains(filterName))
1903 return;
1904
1905 QHelpFilterData filterData;
1906 filterData.setVersions({versionNumber});
1907 setFilterData(filterName, filterData);
1908}
1909
1910bool QHelpCollectionHandler::registerIndexTable(const QHelpDBReader::IndexTable &indexTable,
1911 int nsId, int vfId, const QString &fileName)
1912{
1913 Transaction transaction(m_connectionName);
1914
1915 QMap<QString, QVariantList> filterAttributeToNewFileId;
1916
1917 QVariantList fileFolderIds;
1918 QVariantList fileNames;
1919 QVariantList fileTitles;
1920 const int fileSize = indexTable.fileItems.size();
1921 fileFolderIds.reserve(fileSize);
1922 fileNames.reserve(fileSize);
1923 fileTitles.reserve(fileSize);
1924
1925 if (!m_query->exec("SELECT MAX(FileId) FROM FileNameTable"_L1) || !m_query->next())
1926 return false;
1927
1928 const int maxFileId = m_query->value(0).toInt();
1929
1930 int newFileId = 0;
1931 for (const QHelpDBReader::FileItem &item : indexTable.fileItems) {
1932 fileFolderIds.append(vfId);
1933 fileNames.append(item.name);
1934 fileTitles.append(item.title);
1935
1936 for (const QString &filterAttribute : item.filterAttributes)
1937 filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1);
1938 ++newFileId;
1939 }
1940
1941 m_query->prepare("INSERT INTO FileNameTable VALUES(?, ?, NULL, ?)"_L1);
1942 m_query->addBindValue(fileFolderIds);
1943 m_query->addBindValue(fileNames);
1944 m_query->addBindValue(fileTitles);
1945 if (!m_query->execBatch())
1946 return false;
1947
1948 for (auto it = filterAttributeToNewFileId.cbegin(),
1949 end = filterAttributeToNewFileId.cend(); it != end; ++it) {
1950 const QString filterAttribute = it.key();
1951 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
1952 m_query->bindValue(0, filterAttribute);
1953 if (!m_query->exec() || !m_query->next())
1954 return false;
1955
1956 const int attributeId = m_query->value(0).toInt();
1957
1958 QVariantList attributeIds;
1959 for (int i = 0; i < it.value().size(); i++)
1960 attributeIds.append(attributeId);
1961
1962 m_query->prepare("INSERT INTO FileFilterTable VALUES(?, ?)"_L1);
1963 m_query->addBindValue(attributeIds);
1964 m_query->addBindValue(it.value());
1965 if (!m_query->execBatch())
1966 return false;
1967 }
1968
1969 QMap<QString, QVariantList> filterAttributeToNewIndexId;
1970
1971 if (!m_query->exec("SELECT MAX(Id) FROM IndexTable"_L1) || !m_query->next())
1972 return false;
1973
1974 const int maxIndexId = m_query->value(0).toInt();
1975 int newIndexId = 0;
1976
1977 QVariantList indexNames;
1978 QVariantList indexIdentifiers;
1979 QVariantList indexNamespaceIds;
1980 QVariantList indexFileIds;
1981 QVariantList indexAnchors;
1982 const int indexSize = indexTable.indexItems.size();
1983 indexNames.reserve(indexSize);
1984 indexIdentifiers.reserve(indexSize);
1985 indexNamespaceIds.reserve(indexSize);
1986 indexFileIds.reserve(indexSize);
1987 indexAnchors.reserve(indexSize);
1988
1989 for (const QHelpDBReader::IndexItem &item : indexTable.indexItems) {
1990 indexNames.append(item.name);
1991 indexIdentifiers.append(item.identifier);
1992 indexNamespaceIds.append(nsId);
1993 indexFileIds.append(maxFileId + item.fileId + 1);
1994 indexAnchors.append(item.anchor);
1995
1996 for (const QString &filterAttribute : item.filterAttributes)
1997 filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1);
1998 ++newIndexId;
1999 }
2000
2001 m_query->prepare("INSERT INTO IndexTable VALUES(NULL, ?, ?, ?, ?, ?)"_L1);
2002 m_query->addBindValue(indexNames);
2003 m_query->addBindValue(indexIdentifiers);
2004 m_query->addBindValue(indexNamespaceIds);
2005 m_query->addBindValue(indexFileIds);
2006 m_query->addBindValue(indexAnchors);
2007 if (!m_query->execBatch())
2008 return false;
2009
2010 for (auto it = filterAttributeToNewIndexId.cbegin(),
2011 end = filterAttributeToNewIndexId.cend(); it != end; ++it) {
2012 const QString filterAttribute = it.key();
2013 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2014 m_query->bindValue(0, filterAttribute);
2015 if (!m_query->exec() || !m_query->next())
2016 return false;
2017
2018 const int attributeId = m_query->value(0).toInt();
2019
2020 QVariantList attributeIds;
2021 for (int i = 0; i < it.value().size(); i++)
2022 attributeIds.append(attributeId);
2023
2024 m_query->prepare("INSERT INTO IndexFilterTable VALUES(?, ?)"_L1);
2025 m_query->addBindValue(attributeIds);
2026 m_query->addBindValue(it.value());
2027 if (!m_query->execBatch())
2028 return false;
2029 }
2030
2031 QMap<QString, QVariantList> filterAttributeToNewContentsId;
2032
2033 QVariantList contentsNsIds;
2034 QVariantList contentsData;
2035 const int contentsSize = indexTable.contentsItems.size();
2036 contentsNsIds.reserve(contentsSize);
2037 contentsData.reserve(contentsSize);
2038
2039 if (!m_query->exec("SELECT MAX(Id) FROM ContentsTable"_L1) || !m_query->next())
2040 return false;
2041
2042 const int maxContentsId = m_query->value(0).toInt();
2043
2044 int newContentsId = 0;
2045 for (const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) {
2046 contentsNsIds.append(nsId);
2047 contentsData.append(item.data);
2048
2049 for (const QString &filterAttribute : item.filterAttributes) {
2050 filterAttributeToNewContentsId[filterAttribute]
2051 .append(maxContentsId + newContentsId + 1);
2052 }
2053 ++newContentsId;
2054 }
2055
2056 m_query->prepare("INSERT INTO ContentsTable VALUES(NULL, ?, ?)"_L1);
2057 m_query->addBindValue(contentsNsIds);
2058 m_query->addBindValue(contentsData);
2059 if (!m_query->execBatch())
2060 return false;
2061
2062 for (auto it = filterAttributeToNewContentsId.cbegin(),
2063 end = filterAttributeToNewContentsId.cend(); it != end; ++it) {
2064 const QString filterAttribute = it.key();
2065 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2066 m_query->bindValue(0, filterAttribute);
2067 if (!m_query->exec() || !m_query->next())
2068 return false;
2069
2070 const int attributeId = m_query->value(0).toInt();
2071
2072 QVariantList attributeIds;
2073 for (int i = 0; i < it.value().size(); i++)
2074 attributeIds.append(attributeId);
2075
2076 m_query->prepare("INSERT INTO ContentsFilterTable VALUES(?, ?)"_L1);
2077 m_query->addBindValue(attributeIds);
2078 m_query->addBindValue(it.value());
2079 if (!m_query->execBatch())
2080 return false;
2081 }
2082
2083 QVariantList filterNsIds;
2084 QVariantList filterAttributeIds;
2085 for (const QString &filterAttribute : indexTable.usedFilterAttributes) {
2086 filterNsIds.append(nsId);
2087
2088 m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1);
2089 m_query->bindValue(0, filterAttribute);
2090 if (!m_query->exec() || !m_query->next())
2091 return false;
2092
2093 filterAttributeIds.append(m_query->value(0).toInt());
2094 }
2095
2096 m_query->prepare("INSERT INTO OptimizedFilterTable "
2097 "(NamespaceId, FilterAttributeId) VALUES(?, ?)"_L1);
2098 m_query->addBindValue(filterNsIds);
2099 m_query->addBindValue(filterAttributeIds);
2100 if (!m_query->execBatch())
2101 return false;
2102
2103 m_query->prepare("INSERT INTO TimeStampTable "
2104 "(NamespaceId, FolderId, FilePath, Size, TimeStamp) "
2105 "VALUES(?, ?, ?, ?, ?)"_L1);
2106 m_query->addBindValue(nsId);
2107 m_query->addBindValue(vfId);
2108 m_query->addBindValue(fileName);
2109 const QFileInfo fi(absoluteDocPath(fileName));
2110 m_query->addBindValue(fi.size());
2111 QDateTime lastModified = fi.lastModified(QTimeZone::UTC);
2112 if (qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH")) {
2113 const QString sourceDateEpochStr = qEnvironmentVariable("SOURCE_DATE_EPOCH");
2114 bool ok;
2115 const qlonglong sourceDateEpoch = sourceDateEpochStr.toLongLong(&ok);
2116 if (ok && sourceDateEpoch < lastModified.toSecsSinceEpoch())
2117 lastModified.setSecsSinceEpoch(sourceDateEpoch);
2118 }
2119 m_query->addBindValue(lastModified);
2120 if (!m_query->exec())
2121 return false;
2122
2123 transaction.commit();
2124 return true;
2125}
2126
2127bool QHelpCollectionHandler::unregisterIndexTable(int nsId, int vfId)
2128{
2129 m_query->prepare("DELETE FROM IndexFilterTable WHERE IndexId IN "
2130 "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)"_L1);
2131 m_query->bindValue(0, nsId);
2132 if (!m_query->exec())
2133 return false;
2134
2135 m_query->prepare("DELETE FROM IndexTable WHERE NamespaceId = ?"_L1);
2136 m_query->bindValue(0, nsId);
2137 if (!m_query->exec())
2138 return false;
2139
2140 m_query->prepare("DELETE FROM FileFilterTable WHERE FileId IN "
2141 "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)"_L1);
2142 m_query->bindValue(0, vfId);
2143 if (!m_query->exec())
2144 return false;
2145
2146 m_query->prepare("DELETE FROM FileNameTable WHERE FolderId = ?"_L1);
2147 m_query->bindValue(0, vfId);
2148 if (!m_query->exec())
2149 return false;
2150
2151 m_query->prepare("DELETE FROM ContentsFilterTable WHERE ContentsId IN "
2152 "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)"_L1);
2153 m_query->bindValue(0, nsId);
2154 if (!m_query->exec())
2155 return false;
2156
2157 m_query->prepare("DELETE FROM ContentsTable WHERE NamespaceId = ?"_L1);
2158 m_query->bindValue(0, nsId);
2159 if (!m_query->exec())
2160 return false;
2161
2162 m_query->prepare("DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?"_L1);
2163 m_query->bindValue(0, nsId);
2164 if (!m_query->exec())
2165 return false;
2166
2167 m_query->prepare("DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?"_L1);
2168 m_query->bindValue(0, nsId);
2169 if (!m_query->exec())
2170 return false;
2171
2172 m_query->prepare("DELETE FROM TimeStampTable WHERE NamespaceId = ?"_L1);
2173 m_query->bindValue(0, nsId);
2174 if (!m_query->exec())
2175 return false;
2176
2177 m_query->prepare("DELETE FROM VersionTable WHERE NamespaceId = ?"_L1);
2178 m_query->bindValue(0, nsId);
2179 if (!m_query->exec())
2180 return false;
2181
2182 m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2183 m_query->bindValue(0, nsId);
2184 if (!m_query->exec())
2185 return false;
2186
2187 if (!m_query->next())
2188 return false;
2189
2190 const int componentId = m_query->value(0).toInt();
2191
2192 m_query->prepare("DELETE FROM ComponentMapping WHERE NamespaceId = ?"_L1);
2193 m_query->bindValue(0, nsId);
2194 if (!m_query->exec())
2195 return false;
2196
2197 m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?"_L1);
2198 m_query->bindValue(0, componentId);
2199 if (!m_query->exec())
2200 return false;
2201
2202 if (!m_query->next()) { // no more namespaces refer to the componentId
2203 m_query->prepare("DELETE FROM ComponentTable WHERE ComponentId = ?"_L1);
2204 m_query->bindValue(0, componentId);
2205 if (!m_query->exec())
2206 return false;
2207 }
2208
2209 return true;
2210}
2211
2212QUrl QHelpCollectionHandler::buildQUrl(const QString &ns, const QString &folder,
2213 const QString &relFileName, const QString &anchor)
2214{
2215 QUrl url;
2216 url.setScheme("qthelp"_L1);
2217 url.setAuthority(ns);
2218 url.setPath(u'/' + folder + u'/' + relFileName);
2219 url.setFragment(anchor);
2220 return url;
2221}
2222
2223QList<QHelpLink> QHelpCollectionHandler::documentsForIdentifier(
2224 const QString &id, const QStringList &filterAttributes) const
2225{
2226 return documentsForField("Identifier"_L1, id, filterAttributes);
2227}
2228
2229QList<QHelpLink> QHelpCollectionHandler::documentsForKeyword(
2230 const QString &keyword, const QStringList &filterAttributes) const
2231{
2232 return documentsForField("Name"_L1, keyword, filterAttributes);
2233}
2234
2235QList<QHelpLink> QHelpCollectionHandler::documentsForField(const QString &fieldName,
2236 const QString &fieldValue, const QStringList &filterAttributes) const
2237{
2238 if (!isDBOpened())
2239 return {};
2240
2241 const QString filterlessQuery =
2242 "SELECT "
2243 "FileNameTable.Title, "
2244 "NamespaceTable.Name, "
2245 "FolderTable.Name, "
2246 "FileNameTable.Name, "
2247 "IndexTable.Anchor "
2248 "FROM "
2249 "IndexTable, "
2250 "FileNameTable, "
2251 "FolderTable, "
2252 "NamespaceTable "
2253 "WHERE IndexTable.FileId = FileNameTable.FileId "
2254 "AND FileNameTable.FolderId = FolderTable.Id "
2255 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2256 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2257
2258 const QString filterQuery = filterlessQuery
2259 + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1,
2260 "IndexFilterTable"_L1, "IndexId"_L1);
2261
2262 m_query->prepare(filterQuery);
2263 m_query->bindValue(0, fieldValue);
2264 bindFilterQuery(m_query.get(), 1, filterAttributes);
2265
2266 m_query->exec();
2267
2268 QList<QHelpLink> docList;
2269 while (m_query->next()) {
2270 QString title = m_query->value(0).toString();
2271 if (title.isEmpty()) // generate a title + corresponding path
2272 title = fieldValue + " : "_L1 + m_query->value(3).toString();
2273
2274 const QUrl url = buildQUrl(m_query->value(1).toString(),
2275 m_query->value(2).toString(),
2276 m_query->value(3).toString(),
2277 m_query->value(4).toString());
2278 docList.append(QHelpLink {url, title});
2279 }
2280 return docList;
2281}
2282
2283QList<QHelpLink> QHelpCollectionHandler::documentsForIdentifier(
2284 const QString &id, const QString &filterName) const
2285{
2286 return documentsForField("Identifier"_L1, id, filterName);
2287}
2288
2289QList<QHelpLink> QHelpCollectionHandler::documentsForKeyword(
2290 const QString &keyword, const QString &filterName) const
2291{
2292 return documentsForField("Name"_L1, keyword, filterName);
2293}
2294
2295QMultiMap<QString, QUrl> QHelpCollectionHandler::linksForField(const QString &fieldName,
2296 const QString &fieldValue, const QString &filterName) const
2297{
2298 QMultiMap<QString, QUrl> linkMap;
2299 const auto documents = documentsForField(fieldName, fieldValue, filterName);
2300 for (const auto &document : documents)
2301 linkMap.insert(document.title, document.url);
2302 return linkMap;
2303}
2304
2305QList<QHelpLink> QHelpCollectionHandler::documentsForField(const QString &fieldName,
2306 const QString &fieldValue, const QString &filterName) const
2307{
2308 if (!isDBOpened())
2309 return {};
2310
2311 const QString filterlessQuery =
2312 "SELECT "
2313 "FileNameTable.Title, "
2314 "NamespaceTable.Name, "
2315 "FolderTable.Name, "
2316 "FileNameTable.Name, "
2317 "IndexTable.Anchor "
2318 "FROM "
2319 "IndexTable, "
2320 "FileNameTable, "
2321 "FolderTable, "
2322 "NamespaceTable "
2323 "WHERE IndexTable.FileId = FileNameTable.FileId "
2324 "AND FileNameTable.FolderId = FolderTable.Id "
2325 "AND IndexTable.NamespaceId = NamespaceTable.Id "
2326 "AND IndexTable.%1 = ?"_L1.arg(fieldName);
2327
2328 const QString filterQuery = filterlessQuery
2329 + prepareFilterQuery(filterName)
2330 + " ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"_L1;
2331
2332 m_query->prepare(filterQuery);
2333 m_query->bindValue(0, fieldValue);
2334 bindFilterQuery(m_query.get(), 1, filterName);
2335
2336 m_query->exec();
2337
2338 QList<QHelpLink> docList;
2339 while (m_query->next()) {
2340 QString title = m_query->value(0).toString();
2341 if (title.isEmpty()) // generate a title + corresponding path
2342 title = fieldValue + " : "_L1 + m_query->value(3).toString();
2343
2344 const QUrl url = buildQUrl(m_query->value(1).toString(),
2345 m_query->value(2).toString(),
2346 m_query->value(3).toString(),
2347 m_query->value(4).toString());
2348 docList.append(QHelpLink {url, title});
2349 }
2350 return docList;
2351}
2352
2353QStringList QHelpCollectionHandler::namespacesForFilter(const QString &filterName) const
2354{
2355 QStringList namespaceList;
2356
2357 if (!isDBOpened())
2358 return namespaceList;
2359
2360 const QString filterlessQuery =
2361 "SELECT "
2362 "NamespaceTable.Name "
2363 "FROM "
2364 "NamespaceTable "
2365 "WHERE TRUE"_L1;
2366
2367 const QString filterQuery = filterlessQuery
2368 + prepareFilterQuery(filterName);
2369
2370 m_query->prepare(filterQuery);
2371 bindFilterQuery(m_query.get(), 0, filterName);
2372
2373 m_query->exec();
2374
2375 while (m_query->next())
2376 namespaceList.append(m_query->value(0).toString());
2377 return namespaceList;
2378}
2379
2380QT_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)