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
4#include "qhelpenginecore.h"
5#include "qhelpengine_p.h"
6#include "qhelpdbreader_p.h"
7#include "qhelpcollectionhandler_p.h"
8#include "qhelpfilterengine.h"
9
10#include <QtCore/QDir>
11#include <QtCore/QFile>
12#include <QtCore/QPluginLoader>
13#include <QtCore/QFileInfo>
14#include <QtCore/QThread>
15#include <QtHelp/QHelpLink>
16#include <QtWidgets/QApplication>
17#include <QtSql/QSqlQuery>
18
19QT_BEGIN_NAMESPACE
20
21void QHelpEngineCorePrivate::init(const QString &collectionFile,
22 QHelpEngineCore *helpEngineCore)
23{
24 q = helpEngineCore;
25 collectionHandler = new QHelpCollectionHandler(collectionFile, helpEngineCore);
26 connect(sender: collectionHandler, signal: &QHelpCollectionHandler::error,
27 context: this, slot: &QHelpEngineCorePrivate::errorReceived);
28 filterEngine->setCollectionHandler(collectionHandler);
29 needsSetup = true;
30}
31
32QHelpEngineCorePrivate::~QHelpEngineCorePrivate()
33{
34 delete collectionHandler;
35}
36
37bool QHelpEngineCorePrivate::setup()
38{
39 error.clear();
40 if (!needsSetup)
41 return true;
42
43 needsSetup = false;
44 emit q->setupStarted();
45
46 const QVariant readOnlyVariant = q->property(name: "_q_readonly");
47 const bool readOnly = readOnlyVariant.isValid()
48 ? readOnlyVariant.toBool() : q->isReadOnly();
49 collectionHandler->setReadOnly(readOnly);
50 const bool opened = collectionHandler->openCollectionFile();
51 if (opened)
52 q->currentFilter();
53
54 emit q->setupFinished();
55
56 return opened;
57}
58
59void QHelpEngineCorePrivate::errorReceived(const QString &msg)
60{
61 error = msg;
62}
63
64/*!
65 \class QHelpEngineCore
66 \since 4.4
67 \inmodule QtHelp
68 \brief The QHelpEngineCore class provides the core functionality
69 of the help system.
70
71 Before the help engine can be used, it must be initialized by
72 calling setupData(). At the beginning of the setup process the
73 signal setupStarted() is emitted. From this point on until
74 the signal setupFinished() is emitted, is the help data in an
75 undefined meaning unusable state.
76
77 The core help engine can be used to perform different tasks.
78 By calling documentsForIdentifier() the engine returns
79 URLs specifying the file locations inside the help system. The
80 actual file data can then be retrieved by calling fileData().
81
82 The help engine can contain any number of custom filters.
83 The management of the filters, including adding new filters,
84 changing filter definitions, or removing existing filters,
85 is done through the QHelpFilterEngine class, which can be accessed
86 by the filterEngine() method.
87
88 \note QHelpFilterEngine replaces the older filter API that is
89 deprecated since Qt 5.13. Call setUsesFilterEngine() with \c true to
90 enable the new functionality.
91
92 The core help engine has two modes:
93 \list
94 \li Read-only mode, where the help collection file is not changed
95 unless explicitly requested. This also works if the
96 collection file is in a read-only location,
97 and is the default.
98 \li Fully writable mode, which requires the help collection
99 file to be writable.
100 \endlist
101 The mode can be changed by calling setReadOnly() method, prior to
102 calling setupData().
103
104 The help engine also offers the possibility to set and read values
105 in a persistent way comparable to ini files or Windows registry
106 entries. For more information see setValue() or value().
107
108 This class does not offer any GUI components or functionality for
109 indices or contents. If you need one of those use QHelpEngine
110 instead.
111*/
112
113/*!
114 \fn void QHelpEngineCore::setupStarted()
115
116 This signal is emitted when setup is started.
117*/
118
119/*!
120 \fn void QHelpEngineCore::setupFinished()
121
122 This signal is emitted when the setup is complete.
123*/
124
125/*!
126 \fn void QHelpEngineCore::readersAboutToBeInvalidated()
127 \deprecated
128*/
129
130/*!
131 \fn void QHelpEngineCore::currentFilterChanged(const QString &newFilter)
132 \deprecated
133
134 QHelpFilterEngine::filterActivated() should be used instead.
135
136 This signal is emitted when the current filter is changed to
137 \a newFilter.
138*/
139
140/*!
141 \fn void QHelpEngineCore::warning(const QString &msg)
142
143 This signal is emitted when a non critical error occurs.
144 The warning message is stored in \a msg.
145*/
146
147/*!
148 Constructs a new core help engine with a \a parent. The help engine
149 uses the information stored in the \a collectionFile to provide help.
150 If the collection file does not exist yet, it'll be created.
151*/
152QHelpEngineCore::QHelpEngineCore(const QString &collectionFile, QObject *parent)
153 : QObject(parent)
154{
155 d = new QHelpEngineCorePrivate();
156 d->filterEngine = new QHelpFilterEngine(this);
157 d->init(collectionFile, helpEngineCore: this);
158}
159
160/*!
161 \internal
162*/
163QHelpEngineCore::QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate,
164 QObject *parent)
165 : QObject(parent)
166{
167 d = helpEngineCorePrivate;
168 d->filterEngine = new QHelpFilterEngine(this);
169}
170
171/*!
172 Destructs the help engine.
173*/
174QHelpEngineCore::~QHelpEngineCore()
175{
176 delete d;
177}
178
179/*!
180 \property QHelpEngineCore::collectionFile
181 \brief the absolute file name of the collection file currently used.
182 \since 4.5
183
184 Setting this property leaves the help engine in an invalid state. It is
185 important to invoke setupData() or any getter function in order to setup
186 the help engine again.
187*/
188QString QHelpEngineCore::collectionFile() const
189{
190 return d->collectionHandler->collectionFile();
191}
192
193void QHelpEngineCore::setCollectionFile(const QString &fileName)
194{
195 if (fileName == collectionFile())
196 return;
197
198 if (d->collectionHandler) {
199 delete d->collectionHandler;
200 d->collectionHandler = nullptr;
201 }
202 d->init(collectionFile: fileName, helpEngineCore: this);
203 d->needsSetup = true;
204}
205
206/*!
207 \property QHelpEngineCore::readOnly
208 \brief whether the help engine is read-only.
209 \since 6.0
210
211 In read-only mode, the user can use the help engine
212 with a collection file installed in a read-only location.
213 In this case, some functionality won't be accessible,
214 like registering additional documentation, filter editing,
215 or any action that would require changes to the
216 collection file. Setting it to \c false enables the full
217 functionality of the help engine.
218
219 By default, this property is \c true.
220*/
221bool QHelpEngineCore::isReadOnly() const
222{
223 return d->readOnly;
224}
225
226void QHelpEngineCore::setReadOnly(bool enable)
227{
228 d->readOnly = enable;
229}
230
231/*!
232 \since 5.13
233
234 Returns the filter engine associated with this help engine.
235 The filter engine allows for adding, changing, and removing existing
236 filters for this help engine. To use the engine you also have to call
237 \l setUsesFilterEngine() set to \c true.
238*/
239QHelpFilterEngine *QHelpEngineCore::filterEngine() const
240{
241 return d->filterEngine;
242}
243
244/*!
245 Sets up the help engine by processing the information found
246 in the collection file and returns true if successful; otherwise
247 returns false.
248
249 By calling the function, the help
250 engine is forced to initialize itself immediately. Most of
251 the times, this function does not have to be called
252 explicitly because getter functions which depend on a correctly
253 set up help engine do that themselves.
254
255 \note \c{qsqlite4.dll} needs to be deployed with the application as the
256 help system uses the sqlite driver when loading help collections.
257*/
258bool QHelpEngineCore::setupData()
259{
260 d->needsSetup = true;
261 return d->setup();
262}
263
264/*!
265 Creates the file \a fileName and copies all contents from
266 the current collection file into the newly created file,
267 and returns true if successful; otherwise returns false.
268
269 The copying process makes sure that file references to Qt
270 Collection files (\c{.qch}) files are updated accordingly.
271*/
272bool QHelpEngineCore::copyCollectionFile(const QString &fileName)
273{
274 if (!d->setup())
275 return false;
276 return d->collectionHandler->copyCollectionFile(fileName);
277}
278
279/*!
280 Returns the namespace name defined for the Qt compressed help file (.qch)
281 specified by its \a documentationFileName. If the file is not valid, an
282 empty string is returned.
283
284 \sa documentationFileName()
285*/
286QString QHelpEngineCore::namespaceName(const QString &documentationFileName)
287{
288 QHelpDBReader reader(documentationFileName,
289 QHelpGlobal::uniquifyConnectionName(name: QLatin1String("GetNamespaceName"),
290 pointer: QThread::currentThread()), nullptr);
291 if (reader.init())
292 return reader.namespaceName();
293 return QString();
294}
295
296/*!
297 Registers the Qt compressed help file (.qch) contained in the file
298 \a documentationFileName. One compressed help file, uniquely
299 identified by its namespace can only be registered once.
300 True is returned if the registration was successful, otherwise
301 false.
302
303 \sa unregisterDocumentation(), error()
304*/
305bool QHelpEngineCore::registerDocumentation(const QString &documentationFileName)
306{
307 d->error.clear();
308 d->needsSetup = true;
309 return d->collectionHandler->registerDocumentation(fileName: documentationFileName);
310}
311
312/*!
313 Unregisters the Qt compressed help file (.qch) identified by its
314 \a namespaceName from the help collection. Returns true
315 on success, otherwise false.
316
317 \sa registerDocumentation(), error()
318*/
319bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName)
320{
321 d->error.clear();
322 d->needsSetup = true;
323 return d->collectionHandler->unregisterDocumentation(namespaceName);
324}
325
326/*!
327 Returns the absolute file name of the Qt compressed help file (.qch)
328 identified by the \a namespaceName. If there is no Qt compressed help file
329 with the specified namespace registered, an empty string is returned.
330
331 \sa namespaceName()
332*/
333QString QHelpEngineCore::documentationFileName(const QString &namespaceName)
334{
335 if (!d->setup())
336 return QString();
337
338 const QHelpCollectionHandler::FileInfo fileInfo =
339 d->collectionHandler->registeredDocumentation(namespaceName);
340
341 if (fileInfo.namespaceName.isEmpty())
342 return QString();
343
344 if (QDir::isAbsolutePath(path: fileInfo.fileName))
345 return fileInfo.fileName;
346
347 return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath()
348 + QLatin1Char('/') + fileInfo.fileName).absoluteFilePath();
349}
350
351/*!
352 Returns a list of all registered Qt compressed help files of the current collection file.
353 The returned names are the namespaces of the registered Qt compressed help files (.qch).
354*/
355QStringList QHelpEngineCore::registeredDocumentations() const
356{
357 QStringList list;
358 if (!d->setup())
359 return list;
360 const QHelpCollectionHandler::FileInfoList &docList
361 = d->collectionHandler->registeredDocumentations();
362 for (const QHelpCollectionHandler::FileInfo &info : docList)
363 list.append(t: info.namespaceName);
364 return list;
365}
366
367/*!
368 \deprecated
369
370 QHelpFilterEngine::filters() should be used instead.
371
372 Returns a list of custom filters.
373
374 \sa addCustomFilter(), removeCustomFilter()
375*/
376QStringList QHelpEngineCore::customFilters() const
377{
378 if (!d->setup())
379 return QStringList();
380 return d->collectionHandler->customFilters();
381}
382
383/*!
384 \deprecated
385
386 QHelpFilterEngine::setFilterData() should be used instead.
387
388 Adds the new custom filter \a filterName. The filter attributes
389 are specified by \a attributes. If the filter already exists,
390 its attribute set is replaced. The function returns true if
391 the operation succeeded, otherwise it returns false.
392
393 \sa customFilters(), removeCustomFilter()
394*/
395bool QHelpEngineCore::addCustomFilter(const QString &filterName,
396 const QStringList &attributes)
397{
398 d->error.clear();
399 d->needsSetup = true;
400 return d->collectionHandler->addCustomFilter(filterName, attributes);
401}
402
403/*!
404 \deprecated
405
406 QHelpFilterEngine::removeFilter() should be used instead.
407
408 Returns true if the filter \a filterName was removed successfully,
409 otherwise false.
410
411 \sa addCustomFilter(), customFilters()
412*/
413bool QHelpEngineCore::removeCustomFilter(const QString &filterName)
414{
415 d->error.clear();
416 d->needsSetup = true;
417 return d->collectionHandler->removeCustomFilter(filterName);
418}
419
420/*!
421 \deprecated
422
423 QHelpFilterEngine::availableComponents() should be used instead.
424
425 Returns a list of all defined filter attributes.
426*/
427QStringList QHelpEngineCore::filterAttributes() const
428{
429 if (!d->setup())
430 return QStringList();
431 return d->collectionHandler->filterAttributes();
432}
433
434/*!
435 \deprecated
436
437 QHelpFilterEngine::filterData() should be used instead.
438
439 Returns a list of filter attributes used by the custom
440 filter \a filterName.
441*/
442QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const
443{
444 if (!d->setup())
445 return QStringList();
446 return d->collectionHandler->filterAttributes(filterName);
447}
448
449/*!
450 \deprecated
451 \property QHelpEngineCore::currentFilter
452 \brief the name of the custom filter currently applied.
453 \since 4.5
454
455 QHelpFilterEngine::activeFilter() should be used instead.
456
457 Setting this property will save the new custom filter permanently in the
458 help collection file. To set a custom filter without saving it
459 permanently, disable the auto save filter mode.
460
461 \sa autoSaveFilter()
462*/
463QString QHelpEngineCore::currentFilter() const
464{
465 if (!d->setup())
466 return QString();
467
468 if (d->currentFilter.isEmpty()) {
469 const QString &filter =
470 d->collectionHandler->customValue(key: QLatin1String("CurrentFilter"),
471 defaultValue: QString()).toString();
472 if (!filter.isEmpty()
473 && d->collectionHandler->customFilters().contains(str: filter))
474 d->currentFilter = filter;
475 }
476 return d->currentFilter;
477}
478
479void QHelpEngineCore::setCurrentFilter(const QString &filterName)
480{
481 if (!d->setup() || filterName == d->currentFilter)
482 return;
483 d->currentFilter = filterName;
484 if (d->autoSaveFilter) {
485 d->collectionHandler->setCustomValue(key: QLatin1String("CurrentFilter"),
486 value: d->currentFilter);
487 }
488 emit currentFilterChanged(newFilter: d->currentFilter);
489}
490
491/*!
492 \deprecated
493
494 QHelpFilterEngine::filterData() should be used instead.
495
496 Returns a list of filter attributes for the different filter sections
497 defined in the Qt compressed help file with the given namespace
498 \a namespaceName.
499*/
500QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const
501{
502 if (!d->setup())
503 return QList<QStringList>();
504
505 return d->collectionHandler->filterAttributeSets(namespaceName);
506}
507
508/*!
509 \deprecated
510
511 files() should be used instead.
512
513 Returns a list of files contained in the Qt compressed help file \a
514 namespaceName. The files can be filtered by \a filterAttributes as
515 well as by their extension \a extensionFilter (e.g. 'html').
516*/
517QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
518 const QStringList &filterAttributes,
519 const QString &extensionFilter)
520{
521 QList<QUrl> res;
522 if (!d->setup())
523 return res;
524
525 QUrl url;
526 url.setScheme(QLatin1String("qthelp"));
527 url.setAuthority(authority: namespaceName);
528
529 const QStringList &files = d->collectionHandler->files(
530 namespaceName, filterAttributes, extensionFilter);
531 for (const QString &file : files) {
532 url.setPath(path: QLatin1String("/") + file);
533 res.append(t: url);
534 }
535 return res;
536}
537
538/*!
539 Returns a list of files contained in the Qt compressed help file
540 for \a namespaceName. The files can be filtered by \a filterName as
541 well as by their extension \a extensionFilter (for example, 'html').
542*/
543QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
544 const QString &filterName,
545 const QString &extensionFilter)
546{
547 QList<QUrl> res;
548 if (!d->setup())
549 return res;
550
551 QUrl url;
552 url.setScheme(QLatin1String("qthelp"));
553 url.setAuthority(authority: namespaceName);
554
555 const QStringList &files = d->collectionHandler->files(
556 namespaceName, filterName, extensionFilter);
557 for (const QString &file : files) {
558 url.setPath(path: QLatin1String("/") + file);
559 res.append(t: url);
560 }
561 return res;
562}
563
564/*!
565 Returns the corrected URL for the \a url that may refer to
566 a different namespace defined by the virtual folder defined
567 as a part of the \a url. If the virtual folder matches the namespace
568 of the \a url, the method just checks if the file exists and returns
569 the same \a url. When the virtual folder doesn't match the namespace
570 of the \a url, it tries to find the best matching namespace according
571 to the active filter. When the namespace is found, it returns the
572 corrected URL if the file exists, otherwise it returns an invalid URL.
573*/
574QUrl QHelpEngineCore::findFile(const QUrl &url) const
575{
576 if (!d->setup())
577 return url;
578
579 QUrl result = d->usesFilterEngine
580 ? d->collectionHandler->findFile(url, filterName: d->filterEngine->activeFilter())
581 : d->collectionHandler->findFile(url, filterAttributes: filterAttributes(filterName: currentFilter())); // obsolete
582 if (!result.isEmpty())
583 return result;
584
585 result = d->usesFilterEngine
586 ? d->collectionHandler->findFile(url, filterName: QString())
587 : d->collectionHandler->findFile(url, filterAttributes: QStringList()); // obsolete
588 if (!result.isEmpty())
589 return result;
590
591 return url;
592}
593
594/*!
595 Returns the data of the file specified by \a url. If the
596 file does not exist, an empty QByteArray is returned.
597
598 \sa findFile()
599*/
600QByteArray QHelpEngineCore::fileData(const QUrl &url) const
601{
602 if (!d->setup())
603 return QByteArray();
604
605 return d->collectionHandler->fileData(url);
606}
607
608/*!
609 \since 5.15
610
611 Returns a list of all the document links found for the \a id.
612 The returned list contents depend on the current filter, and therefore only the keywords
613 registered for the current filter will be returned.
614*/
615QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id) const
616{
617 return documentsForIdentifier(id, filterName: d->usesFilterEngine
618 ? d->filterEngine->activeFilter()
619 : d->currentFilter);
620}
621
622/*!
623 \since 5.15
624
625 Returns a list of the document links found for the \a id, filtered by \a filterName.
626 The returned list contents depend on the passed filter, and therefore only the keywords
627 registered for this filter will be returned. If you want to get all results unfiltered,
628 pass empty string as \a filterName.
629*/
630QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id, const QString &filterName) const
631{
632 if (!d->setup())
633 return QList<QHelpLink>();
634
635 if (d->usesFilterEngine)
636 return d->collectionHandler->documentsForIdentifier(id, filterName);
637
638 return d->collectionHandler->documentsForIdentifier(id, filterAttributes: filterAttributes(filterName));
639}
640
641/*!
642 \since 5.15
643
644 Returns a list of all the document links found for the \a keyword.
645 The returned list contents depend on the current filter, and therefore only the keywords
646 registered for the current filter will be returned.
647*/
648QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword) const
649{
650 return documentsForKeyword(keyword, filterName: d->usesFilterEngine
651 ? d->filterEngine->activeFilter()
652 : d->currentFilter);
653}
654
655/*!
656 \since 5.15
657
658 Returns a list of the document links found for the \a keyword, filtered by \a filterName.
659 The returned list contents depend on the passed filter, and therefore only the keywords
660 registered for this filter will be returned. If you want to get all results unfiltered,
661 pass empty string as \a filterName.
662*/
663QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword, const QString &filterName) const
664{
665 if (!d->setup())
666 return QList<QHelpLink>();
667
668 if (d->usesFilterEngine)
669 return d->collectionHandler->documentsForKeyword(keyword, filterName);
670
671 return d->collectionHandler->documentsForKeyword(keyword, filterAttributes: filterAttributes(filterName));
672}
673
674/*!
675 Removes the \a key from the settings section in the
676 collection file. Returns true if the value was removed
677 successfully, otherwise false.
678
679 \sa customValue(), setCustomValue()
680*/
681bool QHelpEngineCore::removeCustomValue(const QString &key)
682{
683 d->error.clear();
684 return d->collectionHandler->removeCustomValue(key);
685}
686
687/*!
688 Returns the value assigned to the \a key. If the requested
689 key does not exist, the specified \a defaultValue is
690 returned.
691
692 \sa setCustomValue(), removeCustomValue()
693*/
694QVariant QHelpEngineCore::customValue(const QString &key, const QVariant &defaultValue) const
695{
696 if (!d->setup())
697 return QVariant();
698 return d->collectionHandler->customValue(key, defaultValue);
699}
700
701/*!
702 Save the \a value under the \a key. If the key already exist,
703 the value will be overwritten. Returns true if the value was
704 saved successfully, otherwise false.
705
706 \sa customValue(), removeCustomValue()
707*/
708bool QHelpEngineCore::setCustomValue(const QString &key, const QVariant &value)
709{
710 d->error.clear();
711 return d->collectionHandler->setCustomValue(key, value);
712}
713
714/*!
715 Returns the meta data for the Qt compressed help file \a
716 documentationFileName. If there is no data available for
717 \a name, an invalid QVariant() is returned. The meta
718 data is defined when creating the Qt compressed help file and
719 cannot be modified later. Common meta data includes e.g.
720 the author of the documentation.
721*/
722QVariant QHelpEngineCore::metaData(const QString &documentationFileName,
723 const QString &name)
724{
725 QHelpDBReader reader(documentationFileName, QLatin1String("GetMetaData"), nullptr);
726
727 if (reader.init())
728 return reader.metaData(name);
729 return QVariant();
730}
731
732/*!
733 Returns a description of the last error that occurred.
734*/
735QString QHelpEngineCore::error() const
736{
737 return d->error;
738}
739
740/*!
741 \property QHelpEngineCore::autoSaveFilter
742 \brief whether QHelpEngineCore is in auto save filter mode or not.
743 \since 4.5
744
745 If QHelpEngineCore is in auto save filter mode, the current filter is
746 automatically saved when it is changed by the QHelpFilterEngine::setActiveFilter()
747 function. The filter is saved persistently in the help collection file.
748
749 By default, this mode is on.
750*/
751void QHelpEngineCore::setAutoSaveFilter(bool save)
752{
753 d->autoSaveFilter = save;
754}
755
756bool QHelpEngineCore::autoSaveFilter() const
757{
758 return d->autoSaveFilter;
759}
760
761/*!
762 \since 5.13
763
764 Enables or disables the new filter engine functionality
765 inside the help engine, according to the passed \a uses parameter.
766
767 \sa filterEngine()
768*/
769void QHelpEngineCore::setUsesFilterEngine(bool uses)
770{
771 d->usesFilterEngine = uses;
772}
773
774/*!
775 \since 5.13
776
777 Returns whether the help engine uses the new filter functionality.
778
779 \sa filterEngine()
780*/
781bool QHelpEngineCore::usesFilterEngine() const
782{
783 return d->usesFilterEngine;
784}
785
786QT_END_NAMESPACE
787

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