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

source code of qttools/src/assistant/help/qhelpcollectionhandler.cpp