1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3#include "tracer.h"
4
5#include <QtCore/QDir>
6#include <QtCore/QFileInfo>
7#include <QtCore/QLibraryInfo>
8#include <QtCore/QLocale>
9#include <QtCore/QScopedPointer>
10#include <QtCore/QStringList>
11#include <QtCore/QTranslator>
12#include <QtCore/QUrl>
13
14#include <QtWidgets/QApplication>
15#include <QtGui/QDesktopServices>
16
17#include <QtHelp/QHelpEngine>
18#include <QtHelp/QHelpSearchEngine>
19
20#include <QtSql/QSqlDatabase>
21
22#if defined(BROWSER_QTWEBKIT)
23#include <QtGui/QFont>
24#include <QWebSettings>
25#endif
26
27#include "../shared/collectionconfiguration.h"
28#include "helpenginewrapper.h"
29#include "mainwindow.h"
30#include "cmdlineparser.h"
31
32// #define TRACING_REQUESTED
33
34QT_USE_NAMESPACE
35
36namespace {
37
38void
39updateLastPagesOnUnregister(QHelpEngineCore& helpEngine, const QString& nsName)
40{
41 TRACE_OBJ
42 int lastPage = CollectionConfiguration::lastTabPage(helpEngine);
43 QStringList currentPages = CollectionConfiguration::lastShownPages(helpEngine);
44 if (!currentPages.isEmpty()) {
45 QStringList zoomList = CollectionConfiguration::lastZoomFactors(helpEngine);
46 while (zoomList.size() < currentPages.size())
47 zoomList.append(t: CollectionConfiguration::DefaultZoomFactor);
48
49 for (int i = currentPages.size(); --i >= 0;) {
50 if (QUrl(currentPages.at(i)).host() == nsName) {
51 zoomList.removeAt(i);
52 currentPages.removeAt(i);
53 lastPage = (lastPage == (i + 1)) ? 1 : lastPage;
54 }
55 }
56
57 CollectionConfiguration::setLastShownPages(helpEngine, lastShownPages: currentPages);
58 CollectionConfiguration::setLastTabPage(helpEngine, lastPage);
59 CollectionConfiguration::setLastZoomFactors(helPEngine&: helpEngine, lastZoomFactors: zoomList);
60 }
61}
62
63void stripNonexistingDocs(QHelpEngineCore& collection)
64{
65 TRACE_OBJ
66 const QStringList &namespaces = collection.registeredDocumentations();
67 for (const QString &ns : namespaces) {
68 QFileInfo fi(collection.documentationFileName(namespaceName: ns));
69 if (!fi.exists() || !fi.isFile())
70 collection.unregisterDocumentation(namespaceName: ns);
71 }
72}
73
74QString indexFilesFolder(const QString &collectionFile)
75{
76 TRACE_OBJ
77 QString indexFilesFolder = QLatin1String(".fulltextsearch");
78 if (!collectionFile.isEmpty()) {
79 QFileInfo fi(collectionFile);
80 indexFilesFolder = QLatin1Char('.') +
81 fi.fileName().left(n: fi.fileName().lastIndexOf(s: QLatin1String(".qhc")));
82 }
83 return indexFilesFolder;
84}
85
86/*
87 * Returns the expected absolute file path of the cached collection file
88 * correspondinging to the given collection's file.
89 * It may or may not exist yet.
90 */
91QString constructCachedCollectionFilePath(const QHelpEngineCore &collection)
92{
93 TRACE_OBJ
94 const QString &filePath = collection.collectionFile();
95 const QString &fileName = QFileInfo(filePath).fileName();
96 const QString &cacheDir = CollectionConfiguration::cacheDir(helpEngine: collection);
97 const QString &dir = !cacheDir.isEmpty()
98 && CollectionConfiguration::cacheDirIsRelativeToCollection(helpEngine: collection)
99 ? QFileInfo(filePath).dir().absolutePath()
100 + QDir::separator() + cacheDir
101 : MainWindow::collectionFileDirectory(createDir: false, cacheDir);
102 return dir + QDir::separator() + fileName;
103}
104
105bool synchronizeDocs(QHelpEngineCore &collection,
106 QHelpEngineCore &cachedCollection,
107 CmdLineParser &cmd)
108{
109 TRACE_OBJ
110 const QDateTime &lastCollectionRegisterTime =
111 CollectionConfiguration::lastRegisterTime(helpEngine: collection);
112 if (!lastCollectionRegisterTime.isValid() || lastCollectionRegisterTime
113 < CollectionConfiguration::lastRegisterTime(helpEngine: cachedCollection))
114 return true;
115
116 const QStringList &docs = collection.registeredDocumentations();
117 const QStringList &cachedDocs = cachedCollection.registeredDocumentations();
118
119 /*
120 * Ensure that the cached collection contains all docs that
121 * the collection contains.
122 */
123 for (const QString &doc : docs) {
124 if (!cachedDocs.contains(str: doc)) {
125 const QString &docFile = collection.documentationFileName(namespaceName: doc);
126 if (!cachedCollection.registerDocumentation(documentationFileName: docFile)) {
127 cmd.showMessage(msg: QCoreApplication::translate(context: "Assistant",
128 key: "Error registering documentation file '%1': %2").
129 arg(a: docFile).arg(a: cachedCollection.error()), error: true);
130 return false;
131 }
132 }
133 }
134
135 CollectionConfiguration::updateLastRegisterTime(helpEngine&: cachedCollection);
136
137 return true;
138}
139
140bool removeSearchIndex(const QString &collectionFile)
141{
142 TRACE_OBJ
143 QString path = QFileInfo(collectionFile).path();
144 path += QLatin1Char('/') + indexFilesFolder(collectionFile);
145
146 QDir dir(path);
147 if (!dir.exists())
148 return false;
149
150 const QStringList &list = dir.entryList(filters: QDir::Files | QDir::Hidden);
151 for (const QString &item : list)
152 dir.remove(fileName: item);
153 return true;
154}
155
156QCoreApplication* createApplication(int &argc, char *argv[])
157{
158 TRACE_OBJ
159#ifndef Q_OS_WIN
160 // Look for arguments that imply command-line mode.
161 const char * cmdModeArgs[] = {
162 "-help", "-register", "-unregister", "-remove-search-index",
163 "-rebuild-search-index"
164 };
165 for (int i = 1; i < argc; ++i) {
166 for (size_t j = 0; j < sizeof cmdModeArgs/sizeof *cmdModeArgs; ++j) {
167 if (strcmp(s1: argv[i], s2: cmdModeArgs[j]) == 0)
168 return new QCoreApplication(argc, argv);
169 }
170 }
171#endif
172 QApplication *app = new QApplication(argc, argv);
173 app->connect(sender: app, signal: &QGuiApplication::lastWindowClosed,
174 slot: &QCoreApplication::quit);
175 return app;
176}
177
178bool registerDocumentation(QHelpEngineCore &collection, CmdLineParser &cmd,
179 bool printSuccess)
180{
181 TRACE_OBJ
182 if (!collection.registerDocumentation(documentationFileName: cmd.helpFile())) {
183 cmd.showMessage(msg: QCoreApplication::translate(context: "Assistant",
184 key: "Could not register documentation file\n%1\n\nReason:\n%2")
185 .arg(a: cmd.helpFile()).arg(a: collection.error()), error: true);
186 return false;
187 }
188 if (printSuccess)
189 cmd.showMessage(msg: QCoreApplication::translate(context: "Assistant",
190 key: "Documentation successfully registered."),
191 error: false);
192 CollectionConfiguration::updateLastRegisterTime(helpEngine&: collection);
193 return true;
194}
195
196bool unregisterDocumentation(QHelpEngineCore &collection,
197 const QString &namespaceName, CmdLineParser &cmd, bool printSuccess)
198{
199 TRACE_OBJ
200 if (!collection.unregisterDocumentation(namespaceName)) {
201 cmd.showMessage(msg: QCoreApplication::translate(context: "Assistant",
202 key: "Could not unregister documentation"
203 " file\n%1\n\nReason:\n%2").
204 arg(a: cmd.helpFile()).arg(a: collection.error()), error: true);
205 return false;
206 }
207 updateLastPagesOnUnregister(helpEngine&: collection, nsName: namespaceName);
208 if (printSuccess)
209 cmd.showMessage(msg: QCoreApplication::translate(context: "Assistant",
210 key: "Documentation successfully unregistered."),
211 error: false);
212 return true;
213}
214
215void setupTranslation(const QString &fileName, const QString &dir)
216{
217 QTranslator *translator = new QTranslator(QCoreApplication::instance());
218 if (translator->load(locale: QLocale(), filename: fileName, prefix: QLatin1String("_"), directory: dir))
219 QCoreApplication::installTranslator(messageFile: translator);
220}
221
222void setupTranslations()
223{
224 TRACE_OBJ
225 const QString &resourceDir
226 = QLibraryInfo::path(p: QLibraryInfo::TranslationsPath);
227 setupTranslation(fileName: QLatin1String("assistant"), dir: resourceDir);
228 setupTranslation(fileName: QLatin1String("qt"), dir: resourceDir);
229 setupTranslation(fileName: QLatin1String("qt_help"), dir: resourceDir);
230}
231
232} // Anonymous namespace.
233
234enum ExitStatus {
235 ExitSuccess = 0,
236 ExitFailure,
237 NoExit
238};
239
240static ExitStatus preliminarySetup(CmdLineParser *cmd)
241{
242 /*
243 * Create the collection objects that we need. We always have the
244 * cached collection file. Depending on whether the user specified
245 * one, we also may have an input collection file.
246 */
247 const QString collectionFile = cmd->collectionFile();
248 const bool collectionFileGiven = !collectionFile.isEmpty();
249 QScopedPointer<QHelpEngineCore> collection;
250 if (collectionFileGiven) {
251 collection.reset(other: new QHelpEngineCore(collectionFile));
252 if (!collection->setupData()) {
253 cmd->showMessage(msg: QCoreApplication::translate(context: "Assistant",
254 key: "Error reading collection file '%1': %2.")
255 .arg(a: collectionFile).arg(a: collection->error()), error: true);
256 return ExitFailure;
257 }
258 }
259 const QString &cachedCollectionFile = collectionFileGiven
260 ? constructCachedCollectionFilePath(collection: *collection)
261 : MainWindow::defaultHelpCollectionFileName();
262 if (collectionFileGiven && !QFileInfo(cachedCollectionFile).exists()
263 && !collection->copyCollectionFile(fileName: cachedCollectionFile)) {
264 cmd->showMessage(msg: QCoreApplication::translate(context: "Assistant",
265 key: "Error creating collection file '%1': %2.")
266 .arg(a: cachedCollectionFile).arg(a: collection->error()), error: true);
267 return ExitFailure;
268 }
269 QHelpEngineCore cachedCollection(cachedCollectionFile);
270 if (!cachedCollection.setupData()) {
271 cmd->showMessage(msg: QCoreApplication::translate(context: "Assistant",
272 key: "Error reading collection file '%1': %2.")
273 .arg(a: cachedCollectionFile)
274 .arg(a: cachedCollection.error()), error: true);
275 return ExitFailure;
276 }
277
278 stripNonexistingDocs(collection&: cachedCollection);
279 if (collectionFileGiven) {
280 if (CollectionConfiguration::isNewer(newer: *collection, older: cachedCollection))
281 CollectionConfiguration::copyConfiguration(source: *collection,
282 target&: cachedCollection);
283 if (!synchronizeDocs(collection&: *collection, cachedCollection, cmd&: *cmd))
284 return ExitFailure;
285 }
286
287 if (cmd->registerRequest() != CmdLineParser::None) {
288 const QStringList &cachedDocs =
289 cachedCollection.registeredDocumentations();
290 const QString &namespaceName =
291 QHelpEngineCore::namespaceName(documentationFileName: cmd->helpFile());
292 if (cmd->registerRequest() == CmdLineParser::Register) {
293 if (collectionFileGiven
294 && !registerDocumentation(collection&: *collection, cmd&: *cmd, printSuccess: true))
295 return ExitFailure;
296 if (!cachedDocs.contains(str: namespaceName)
297 && !registerDocumentation(collection&: cachedCollection, cmd&: *cmd, printSuccess: !collectionFileGiven))
298 return ExitFailure;
299 return ExitSuccess;
300 }
301 if (cmd->registerRequest() == CmdLineParser::Unregister) {
302 if (collectionFileGiven
303 && !unregisterDocumentation(collection&: *collection, namespaceName, cmd&: *cmd, printSuccess: true))
304 return ExitFailure;
305 if (cachedDocs.contains(str: namespaceName)
306 && !unregisterDocumentation(collection&: cachedCollection, namespaceName,
307 cmd&: *cmd, printSuccess: !collectionFileGiven))
308 return ExitFailure;
309 return ExitSuccess;
310 }
311 }
312
313 if (cmd->removeSearchIndex()) {
314 return removeSearchIndex(collectionFile: cachedCollectionFile)
315 ? ExitSuccess : ExitFailure;
316 }
317
318 if (!QSqlDatabase::isDriverAvailable(name: QLatin1String("QSQLITE"))) {
319 cmd->showMessage(msg: QCoreApplication::translate(context: "Assistant",
320 key: "Cannot load sqlite database driver!"),
321 error: true);
322 return ExitFailure;
323 }
324
325 if (!cmd->currentFilter().isEmpty()) {
326 if (collectionFileGiven)
327 collection->setCurrentFilter(cmd->currentFilter());
328 cachedCollection.setCurrentFilter(cmd->currentFilter());
329 }
330
331 if (collectionFileGiven)
332 cmd->setCollectionFile(cachedCollectionFile);
333
334 return NoExit;
335}
336
337int main(int argc, char *argv[])
338{
339 TRACE_OBJ
340 QScopedPointer<QCoreApplication> a(createApplication(argc, argv));
341#if QT_CONFIG(library)
342 a->addLibraryPath(a->applicationDirPath() + QLatin1String("/plugins"));
343#endif
344 setupTranslations();
345
346#if defined(BROWSER_QTWEBKIT)
347 if (qobject_cast<QApplication *>(a.data())) {
348 QFont f;
349 f.setStyleHint(QFont::SansSerif);
350 QWebSettings::globalSettings()->setFontFamily(QWebSettings::StandardFont, f.defaultFamily());
351 }
352#endif // BROWSER_QTWEBKIT
353
354 // Parse arguments.
355 CmdLineParser cmd(a->arguments());
356 CmdLineParser::Result res = cmd.parse();
357 if (res == CmdLineParser::Help)
358 return 0;
359 else if (res == CmdLineParser::Error)
360 return -1;
361
362 const ExitStatus status = preliminarySetup(cmd: &cmd);
363 switch (status) {
364 case ExitFailure: return EXIT_FAILURE;
365 case ExitSuccess: return EXIT_SUCCESS;
366 default: break;
367 }
368
369 MainWindow *w = new MainWindow(&cmd);
370 w->show();
371
372 /*
373 * We need to be careful here: The main window has to be deleted before
374 * the help engine wrapper, which has to be deleted before the
375 * QApplication.
376 */
377 const int retval = a->exec();
378 delete w;
379 HelpEngineWrapper::removeInstance();
380 return retval;
381}
382

source code of qttools/src/assistant/assistant/main.cpp