Fix crash when accessing the content model.
[qt:qt.git] / tools / assistant / lib / qhelpenginecore.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qhelpenginecore.h"
43 #include "qhelpengine_p.h"
44 #include "qhelpdbreader_p.h"
45 #include "qhelpcollectionhandler_p.h"
46
47 #include <QtCore/QDir>
48 #include <QtCore/QFile>
49 #include <QtCore/QLibrary>
50 #include <QtCore/QPluginLoader>
51 #include <QtCore/QFileInfo>
52 #include <QtCore/QThread>
53 #include <QtGui/QApplication>
54 #include <QtSql/QSqlQuery>
55
56 QT_BEGIN_NAMESPACE
57
58 QHelpEngineCorePrivate::QHelpEngineCorePrivate()
59 {
60     QHelpGlobal::uniquifyConnectionName(QString(), this);
61     autoSaveFilter = true;
62 }
63
64 void QHelpEngineCorePrivate::init(const QString &collectionFile,
65                                   QHelpEngineCore *helpEngineCore)
66 {
67     q = helpEngineCore;
68     collectionHandler = new QHelpCollectionHandler(collectionFile, helpEngineCore);
69     connect(collectionHandler, SIGNAL(error(QString)),
70         this, SLOT(errorReceived(QString)));
71     needsSetup = true;
72 }
73
74 QHelpEngineCorePrivate::~QHelpEngineCorePrivate()
75 {
76     delete collectionHandler;
77     clearMaps();
78 }
79
80 void QHelpEngineCorePrivate::clearMaps()
81 {
82     emit q->readersAboutToBeInvalidated();
83     QMap<QString, QHelpDBReader*>::iterator it = readerMap.begin();
84     while (it != readerMap.end()) {
85         delete it.value();
86         ++it;
87     }
88     readerMap.clear();
89     fileNameReaderMap.clear();
90     virtualFolderMap.clear();
91     orderedFileNameList.clear();
92     nameSpaceVirtualFolderMap.clear();
93 }
94
95 bool QHelpEngineCorePrivate::setup()
96 {
97     error.clear();
98     if (!needsSetup)
99         return true;
100
101     needsSetup = false;
102     emit q->setupStarted();
103     clearMaps();
104
105     if (!collectionHandler->openCollectionFile()) {
106         emit q->setupFinished();
107         return false;
108     }
109
110     const QHelpCollectionHandler::DocInfoList docList =
111         collectionHandler->registeredDocumentations();
112     QFileInfo fi(collectionHandler->collectionFile());
113     QString absFileName;
114     foreach(const QHelpCollectionHandler::DocInfo &info, docList) {
115         if (QDir::isAbsolutePath(info.fileName)) {
116             absFileName = info.fileName;
117         } else {
118             absFileName = QFileInfo(fi.absolutePath() + QDir::separator() + info.fileName)
119                 .absoluteFilePath();
120         }
121         QHelpDBReader *reader = new QHelpDBReader(absFileName,
122             QHelpGlobal::uniquifyConnectionName(info.fileName, this), this);
123         if (!reader->init()) {
124             emit q->warning(QHelpEngineCore::tr("Cannot open documentation file %1: %2!")
125                 .arg(absFileName, reader->errorMessage()));
126             continue;
127         }
128
129         readerMap.insert(info.namespaceName, reader);
130         fileNameReaderMap.insert(absFileName, reader);
131         virtualFolderMap.insert(info.folderName, reader);
132         orderedFileNameList.append(absFileName);
133         nameSpaceVirtualFolderMap.insert(info.namespaceName, info.folderName);
134     }
135     q->currentFilter();
136     emit q->setupFinished();
137     return true;
138 }
139
140 void QHelpEngineCorePrivate::errorReceived(const QString &msg)
141 {
142     error = msg;
143 }
144
145
146
147 /*!
148     \class QHelpEngineCore
149     \since 4.4
150     \inmodule QtHelp
151     \brief The QHelpEngineCore class provides the core functionality
152     of the help system.
153
154     Before the help engine can be used, it must be initialized by
155     calling setupData(). At the beginning of the setup process the
156     signal setupStarted() is emitted. From this point on until
157     the signal setupFinished() is emitted, is the help data in an
158     undefined meaning unusable state.
159
160     The core help engine can be used to perform different tasks.
161     By calling linksForIdentifier() the engine returns
162     urls specifying the file locations inside the help system. The
163     actual file data can then be retrived by calling fileData(). In
164     contrast to all other functions in this class, linksForIdentifier()
165     depends on the currently set custom filter. Depending on the filter,
166     the function may return different hits.
167
168     Every help engine can contain any number of custom filters. A custom
169     filter is defined by a name and set of filter attributes and can be
170     added to the help engine by calling addCustomFilter(). Analogous,
171     it is removed by calling removeCustomFilter(). customFilters() returns
172     all defined filters.
173
174     The help engine also offers the possibility to set and read values
175     in a persistant way comparable to ini files or Windows registry
176     entries. For more information see setValue() or value().
177
178     This class does not offer any GUI components or functionality for
179     indices or contents. If you need one of those use QHelpEngine
180     instead.
181
182     When creating a custom help viewer the viewer can be
183     configured by writing a custom collection file which could contain various
184     keywords to be used to configure the help engine. These keywords and values
185     and their meaning can be found in the help information for
186     \l{assistant-custom-help-viewer.html#creating-a-custom-help-collection-file}
187     {creating a custom help collection file} for Assistant.
188 */
189
190 /*!
191     \fn void QHelpEngineCore::setupStarted()
192
193     This signal is emitted when setup is started.
194 */
195
196 /*!
197     \fn void QHelpEngineCore::setupFinished()
198
199     This signal is emitted when the setup is complete.
200 */
201
202 /*!
203     \fn void QHelpEngineCore::currentFilterChanged(const QString &newFilter)
204
205     This signal is emitted when the current filter is changed to
206     \a newFilter.
207 */
208
209 /*!
210     \fn void QHelpEngineCore::warning(const QString &msg)
211
212     This signal is emitted when a non critical error occurs.
213     The warning message is stored in \a msg.
214 */
215
216 /*!
217     Constructs a new core help engine with a \a parent. The help engine
218     uses the information stored in the \a collectionFile to provide help.
219     If the collection file does not exist yet, it'll be created.
220 */
221 QHelpEngineCore::QHelpEngineCore(const QString &collectionFile, QObject *parent)
222     : QObject(parent)
223 {
224     d = new QHelpEngineCorePrivate();
225     d->init(collectionFile, this);
226 }
227
228 /*!
229     \internal
230 */
231 QHelpEngineCore::QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate,
232                                  QObject *parent)
233     : QObject(parent)
234 {
235     d = helpEngineCorePrivate;
236 }
237
238 /*!
239     Destructs the help engine.
240 */
241 QHelpEngineCore::~QHelpEngineCore()
242 {
243     delete d;
244 }
245
246 /*!
247     \property QHelpEngineCore::collectionFile
248     \brief the absolute file name of the collection file currently used.
249     \since 4.5
250
251     Setting this property leaves the help engine in an invalid state. It is
252     important to invoke setupData() or any getter function in order to setup
253     the help engine again.
254 */
255 QString QHelpEngineCore::collectionFile() const
256 {
257     return d->collectionHandler->collectionFile();
258 }
259
260 void QHelpEngineCore::setCollectionFile(const QString &fileName)
261 {
262     if (fileName == collectionFile())
263         return;
264
265     if (d->collectionHandler) {
266         delete d->collectionHandler;
267         d->collectionHandler = 0;
268         d->clearMaps();
269     }
270     d->init(fileName, this);
271     d->needsSetup = true;
272 }
273
274 /*!
275     Sets up the help engine by processing the information found
276     in the collection file and returns true if successful; otherwise
277     returns false.
278
279     By calling the function, the help
280     engine is forced to initialize itself immediately. Most of
281     the times, this function does not have to be called
282     explicitly because getter functions which depend on a correctly
283     set up help engine do that themselves.
284
285     \note \c{qsqlite4.dll} needs to be deployed with the application as the
286     help system uses the sqlite driver when loading help collections.
287 */
288 bool QHelpEngineCore::setupData()
289 {
290     d->needsSetup = true;
291     return d->setup();
292 }
293
294 /*!
295     Creates the file \a fileName and copies all contents from
296     the current collection file into the newly created file,
297     and returns true if successful; otherwise returns false.
298
299     The copying process makes sure that file references to Qt
300     Collection files (\c{.qch}) files are updated accordingly.
301 */
302 bool QHelpEngineCore::copyCollectionFile(const QString &fileName)
303 {
304     if (!d->setup())
305         return false;
306     return d->collectionHandler->copyCollectionFile(fileName);
307 }
308
309 /*!
310     Returns the namespace name defined for the Qt compressed help file (.qch)
311     specified by its \a documentationFileName. If the file is not valid, an
312     empty string is returned.
313
314     \sa documentationFileName()
315 */
316 QString QHelpEngineCore::namespaceName(const QString &documentationFileName)
317 {
318     QHelpDBReader reader(documentationFileName,
319         QHelpGlobal::uniquifyConnectionName(QLatin1String("GetNamespaceName"),
320         QThread::currentThread()), 0);
321     if (reader.init())
322         return reader.namespaceName();
323     return QString();
324 }
325
326 /*!
327     Registers the Qt compressed help file (.qch) contained in the file
328     \a documentationFileName. One compressed help file, uniquely
329     identified by its namespace can only be registered once.
330     True is returned if the registration was successful, otherwise
331     false.
332
333     \sa unregisterDocumentation(), error()
334 */
335 bool QHelpEngineCore::registerDocumentation(const QString &documentationFileName)
336 {
337     d->error.clear();
338     d->needsSetup = true;
339     return d->collectionHandler->registerDocumentation(documentationFileName);
340 }
341
342 /*!
343     Unregisters the Qt compressed help file (.qch) identified by its
344     \a namespaceName from the help collection. Returns true
345     on success, otherwise false.
346
347     \sa registerDocumentation(), error()
348 */
349 bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName)
350 {
351     d->error.clear();
352     d->needsSetup = true;
353     return d->collectionHandler->unregisterDocumentation(namespaceName);
354 }
355
356 /*!
357     Returns the absolute file name of the Qt compressed help file (.qch)
358     identified by the \a namespaceName. If there is no Qt compressed help file
359     with the specified namespace registered, an empty string is returned.
360
361     \sa namespaceName()
362 */
363 QString QHelpEngineCore::documentationFileName(const QString &namespaceName)
364 {
365     if (d->setup()) {
366         const QHelpCollectionHandler::DocInfoList docList =
367             d->collectionHandler->registeredDocumentations();
368         foreach(const QHelpCollectionHandler::DocInfo &info, docList) {
369             if (info.namespaceName == namespaceName) {
370                 if (QDir::isAbsolutePath(info.fileName))
371                     return info.fileName;
372
373                 QFileInfo fi(d->collectionHandler->collectionFile());
374                 fi.setFile(fi.absolutePath() + QDir::separator() + info.fileName);
375                 return fi.absoluteFilePath();
376             }
377         }
378     }
379     return QString();
380 }
381
382 /*!
383     Returns a list of all registered Qt compressed help files of the current collection file.
384     The returned names are the namespaces of the registered Qt compressed help files (.qch).
385 */
386 QStringList QHelpEngineCore::registeredDocumentations() const
387 {
388     QStringList list;
389     if (!d->setup())
390         return list;
391     const QHelpCollectionHandler::DocInfoList docList = d->collectionHandler->registeredDocumentations();
392     foreach(const QHelpCollectionHandler::DocInfo &info, docList) {
393         list.append(info.namespaceName);
394     }
395     return list;
396 }
397
398 /*!
399     Returns a list of custom filters.
400
401     \sa addCustomFilter(), removeCustomFilter()
402 */
403 QStringList QHelpEngineCore::customFilters() const
404 {
405     if (!d->setup())
406         return QStringList();
407     return d->collectionHandler->customFilters();
408 }
409
410 /*!
411     Adds the new custom filter \a filterName. The filter attributes
412     are specified by \a attributes. If the filter already exists,
413     its attribute set is replaced. The function returns true if
414     the operation succeeded, otherwise it returns false.
415
416     \sa customFilters(), removeCustomFilter()
417 */
418 bool QHelpEngineCore::addCustomFilter(const QString &filterName,
419                                       const QStringList &attributes)
420 {
421     d->error.clear();
422     d->needsSetup = true;
423     return d->collectionHandler->addCustomFilter(filterName,
424         attributes);
425 }
426
427 /*!
428     Returns true if the filter \a filterName was removed successfully,
429     otherwise false.
430
431     \sa addCustomFilter(), customFilters()
432 */
433 bool QHelpEngineCore::removeCustomFilter(const QString &filterName)
434 {
435     d->error.clear();
436     d->needsSetup = true;
437     return d->collectionHandler->removeCustomFilter(filterName);
438 }
439
440 /*!
441     Returns a list of all defined filter attributes.
442 */
443 QStringList QHelpEngineCore::filterAttributes() const
444 {
445     if (!d->setup())
446         return QStringList();
447     return d->collectionHandler->filterAttributes();
448 }
449
450 /*!
451     Returns a list of filter attributes used by the custom
452     filter \a filterName.
453 */
454 QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const
455 {
456     if (!d->setup())
457         return QStringList();
458     return d->collectionHandler->filterAttributes(filterName);
459 }
460
461 /*!
462     \property QHelpEngineCore::currentFilter
463     \brief the name of the custom filter currently applied.
464     \since 4.5
465
466     Setting this property will save the new custom filter permanently in the
467     help collection file. To set a custom filter without saving it
468     permanently, disable the auto save filter mode.
469
470     \sa autoSaveFilter()
471 */
472 QString QHelpEngineCore::currentFilter() const
473 {
474     if (!d->setup())
475         return QString();
476
477     if (d->currentFilter.isEmpty()) {
478         QString filter =
479             d->collectionHandler->customValue(QLatin1String("CurrentFilter"),
480                 QString()).toString();
481         if (!filter.isEmpty()
482             && d->collectionHandler->customFilters().contains(filter))
483             d->currentFilter = filter;
484     }
485     return d->currentFilter;
486 }
487
488 void QHelpEngineCore::setCurrentFilter(const QString &filterName)
489 {
490     if (!d->setup() || filterName == d->currentFilter)
491         return;
492     d->currentFilter = filterName;
493     if (d->autoSaveFilter) {
494         d->collectionHandler->setCustomValue(QLatin1String("CurrentFilter"),
495             d->currentFilter);
496     }
497     emit currentFilterChanged(d->currentFilter);
498 }
499
500 /*!
501     Returns a list of filter attributes for the different filter sections
502     defined in the Qt compressed help file with the given namespace
503     \a namespaceName.
504 */
505 QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const
506 {
507     if (d->setup()) {
508         QHelpDBReader *reader = d->readerMap.value(namespaceName);
509         if (reader)
510             return reader->filterAttributeSets();
511     }
512     return QList<QStringList>();
513 }
514
515 /*!
516     Returns a list of files contained in the Qt compressed help file \a
517     namespaceName. The files can be filtered by \a filterAttributes as
518     well as by their extension \a extensionFilter (e.g. 'html').
519 */
520 QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
521                                    const QStringList &filterAttributes,
522                                    const QString &extensionFilter)
523 {
524     QList<QUrl> res;
525     if (!d->setup())
526         return res;
527     QHelpDBReader *reader = d->readerMap.value(namespaceName);
528     if (!reader) {
529         d->error = tr("The specified namespace does not exist!");
530         return res;
531     }
532
533     QUrl url;
534     url.setScheme(QLatin1String("qthelp"));
535     url.setAuthority(namespaceName);
536
537     const QStringList files = reader->files(filterAttributes, extensionFilter);
538     foreach (const QString &file, files) {
539         url.setPath(QLatin1String("/") + file);
540         res.append(url);
541     }
542     return res;
543 }
544
545 /*!
546     Returns an invalid URL if the file \a url cannot be found.
547     If the file exists, either the same url is returned or a
548     different url if the file is located in a different namespace
549     which is merged via a common virtual folder.
550 */
551 QUrl QHelpEngineCore::findFile(const QUrl &url) const
552 {
553     QUrl res;
554     if (!d->setup() || !url.isValid() || url.toString().count(QLatin1Char('/')) < 4
555         || url.scheme() != QLatin1String("qthelp"))
556         return res;
557
558     QString ns = url.authority();
559     QString filePath = url.path();
560     if (filePath.startsWith(QLatin1Char('/')))
561         filePath = filePath.mid(1);
562     QString virtualFolder = filePath.mid(0, filePath.indexOf(QLatin1Char('/'), 1));
563     filePath = filePath.mid(virtualFolder.length()+1);
564
565     QHelpDBReader *defaultReader = 0;
566     if (d->readerMap.contains(ns)) {
567         defaultReader = d->readerMap.value(ns);
568         if (defaultReader->fileExists(virtualFolder, filePath))
569             return url;
570     }
571
572     QStringList filterAtts = filterAttributes(currentFilter());
573     foreach (QHelpDBReader *reader, d->virtualFolderMap.values(virtualFolder)) {
574         if (reader == defaultReader)
575             continue;
576         if (reader->fileExists(virtualFolder, filePath, filterAtts)) {
577             res = url;
578             res.setAuthority(reader->namespaceName());
579             return res;
580         }
581     }
582
583     foreach (QHelpDBReader *reader, d->virtualFolderMap.values(virtualFolder)) {
584         if (reader == defaultReader)
585             continue;
586         if (reader->fileExists(virtualFolder, filePath)) {
587             res = url;
588             res.setAuthority(reader->namespaceName());
589             break;
590         }
591     }
592
593     return res;
594 }
595
596 /*!
597     Returns the data of the file specified by \a url. If the
598     file does not exist, an empty QByteArray is returned.
599
600     \sa findFile()
601 */
602 QByteArray QHelpEngineCore::fileData(const QUrl &url) const
603 {
604     if (!d->setup() || !url.isValid() || url.toString().count(QLatin1Char('/')) < 4
605         || url.scheme() != QLatin1String("qthelp"))
606         return QByteArray();
607
608     QString ns = url.authority();
609     QString filePath = url.path();
610     if (filePath.startsWith(QLatin1Char('/')))
611         filePath = filePath.mid(1);
612     QString virtualFolder = filePath.mid(0, filePath.indexOf(QLatin1Char('/'), 1));
613     filePath = filePath.mid(virtualFolder.length()+1);
614
615     QByteArray ba;
616     QHelpDBReader *defaultReader = 0;
617     if (d->readerMap.contains(ns)) {
618         defaultReader = d->readerMap.value(ns);
619         ba = defaultReader->fileData(virtualFolder, filePath);
620     }
621
622     if (ba.isEmpty()) {
623         foreach (QHelpDBReader *reader, d->virtualFolderMap.values(virtualFolder)) {
624             if (reader == defaultReader)
625                 continue;
626             ba = reader->fileData(virtualFolder, filePath);
627             if (!ba.isEmpty())
628                 return ba;
629         }
630     }
631     return ba;
632 }
633
634 /*!
635     Returns a map of hits found for the \a id. A hit contains the
636     title of the document and the url where the keyword is located.
637     The result depends on the current filter, meaning only the keywords
638     registered for the current filter will be returned.
639 */
640 QMap<QString, QUrl> QHelpEngineCore::linksForIdentifier(const QString &id) const
641 {
642     QMap<QString, QUrl> linkMap;
643     if (!d->setup())
644         return linkMap;
645
646     QStringList atts = filterAttributes(d->currentFilter);
647     foreach (QHelpDBReader *reader, d->readerMap)
648         reader->linksForIdentifier(id, atts, linkMap);
649
650     return linkMap;
651 }
652
653 /*!
654     Removes the \a key from the settings section in the
655     collection file. Returns true if the value was removed
656     successfully, otherwise false.
657
658     \sa customValue(), setCustomValue()
659 */
660 bool QHelpEngineCore::removeCustomValue(const QString &key)
661 {
662     d->error.clear();
663     return d->collectionHandler->removeCustomValue(key);
664 }
665
666 /*!
667     Returns the value assigned to the \a key. If the requested
668     key does not exist, the specified \a defaultValue is
669     returned.
670
671     \sa setCustomValue(), removeCustomValue()
672 */
673 QVariant QHelpEngineCore::customValue(const QString &key, const QVariant &defaultValue) const
674 {
675     if (!d->setup())
676         return QVariant();
677     return d->collectionHandler->customValue(key, defaultValue);
678 }
679
680 /*!
681     Save the \a value under the \a key. If the key already exist,
682     the value will be overwritten. Returns true if the value was
683     saved successfully, otherwise false.
684
685     \sa customValue(), removeCustomValue()
686 */
687 bool QHelpEngineCore::setCustomValue(const QString &key, const QVariant &value)
688 {
689     d->error.clear();
690     return d->collectionHandler->setCustomValue(key, value);
691 }
692
693 /*!
694     Returns the meta data for the Qt compressed help file \a
695     documentationFileName. If there is no data available for
696     \a name, an invalid QVariant() is returned. The meta
697     data is defined when creating the Qt compressed help file and
698     cannot be modified later. Common meta data includes e.g.
699     the author of the documentation.
700 */
701 QVariant QHelpEngineCore::metaData(const QString &documentationFileName,
702                                    const QString &name)
703 {
704     QHelpDBReader reader(documentationFileName, QLatin1String("GetMetaData"), 0);
705
706     if (reader.init())
707         return reader.metaData(name);
708     return QVariant();
709 }
710
711 /*!
712     Returns a description of the last error that occurred.
713 */
714 QString QHelpEngineCore::error() const
715 {
716     return d->error;
717 }
718
719 /*!
720     \property QHelpEngineCore::autoSaveFilter
721     \brief whether QHelpEngineCore is in auto save filter mode or not.
722     \since 4.5
723
724     If QHelpEngineCore is in auto save filter mode, the current filter is
725     automatically saved when it is changed by the setCurrentFilter()
726     function. The filter is saved persistently in the help collection file.
727
728     By default, this mode is on.
729 */
730 void QHelpEngineCore::setAutoSaveFilter(bool save)
731 {
732     d->autoSaveFilter = save;
733 }
734
735 bool QHelpEngineCore::autoSaveFilter() const
736 {
737     return d->autoSaveFilter;
738 }
739
740 QT_END_NAMESPACE