1 | /* |
2 | * Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org> |
3 | * Copyright (C) by Klaas Freitag <freitag@owncloud.com> |
4 | * Copyright (C) by Daniel Molkentin <danimo@owncloud.com> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, but |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * for more details. |
15 | */ |
16 | |
17 | #include "application.h" |
18 | |
19 | #include <iostream> |
20 | #include <random> |
21 | |
22 | #include "config.h" |
23 | #include "account.h" |
24 | #include "accountstate.h" |
25 | #include "connectionvalidator.h" |
26 | #include "folder.h" |
27 | #include "folderman.h" |
28 | #include "logger.h" |
29 | #include "configfile.h" |
30 | #include "socketapi.h" |
31 | #include "sslerrordialog.h" |
32 | #include "theme.h" |
33 | #include "clientproxy.h" |
34 | #include "sharedialog.h" |
35 | #include "accountmanager.h" |
36 | #include "creds/abstractcredentials.h" |
37 | #include "updater/ocupdater.h" |
38 | #include "owncloudsetupwizard.h" |
39 | #include "version.h" |
40 | #include "csync_exclude.h" |
41 | |
42 | #include "config.h" |
43 | |
44 | #if defined(Q_OS_WIN) |
45 | #include <windows.h> |
46 | #endif |
47 | |
48 | #if defined(WITH_CRASHREPORTER) |
49 | #include <libcrashreporter-handler/Handler.h> |
50 | #endif |
51 | |
52 | #include <QTranslator> |
53 | #include <QMenu> |
54 | #include <QMessageBox> |
55 | #include <QDesktopServices> |
56 | |
57 | class QSocket; |
58 | |
59 | namespace OCC { |
60 | |
61 | Q_LOGGING_CATEGORY(lcApplication, "gui.application" , QtInfoMsg) |
62 | |
63 | namespace { |
64 | |
65 | static const char optionsC[] = |
66 | "Options:\n" |
67 | " -h --help : show this help screen.\n" |
68 | " --logwindow : open a window to show log output.\n" |
69 | " --logfile <filename> : write log output to file <filename>.\n" |
70 | " --logdir <name> : write each sync log output in a new file\n" |
71 | " in folder <name>.\n" |
72 | " --logexpire <hours> : removes logs older than <hours> hours.\n" |
73 | " (to be used with --logdir)\n" |
74 | " --logflush : flush the log file after every write.\n" |
75 | " --logdebug : also output debug-level messages in the log.\n" |
76 | " --confdir <dirname> : Use the given configuration folder.\n" ; |
77 | |
78 | QString applicationTrPath() |
79 | { |
80 | QString devTrPath = qApp->applicationDirPath() + QString::fromLatin1("/../src/gui/" ); |
81 | if (QDir(devTrPath).exists()) { |
82 | // might miss Qt, QtKeyChain, etc. |
83 | qCWarning(lcApplication) << "Running from build location! Translations may be incomplete!" ; |
84 | return devTrPath; |
85 | } |
86 | #if defined(Q_OS_WIN) |
87 | return QApplication::applicationDirPath(); |
88 | #elif defined(Q_OS_MAC) |
89 | return QApplication::applicationDirPath() + QLatin1String("/../Resources/Translations" ); // path defaults to app dir. |
90 | #elif defined(Q_OS_UNIX) |
91 | return QString::fromLatin1(SHAREDIR "/" APPLICATION_EXECUTABLE "/i18n/" ); |
92 | #endif |
93 | } |
94 | } |
95 | |
96 | // ---------------------------------------------------------------------------------- |
97 | |
98 | bool Application::configVersionMigration() |
99 | { |
100 | QStringList deleteKeys, ignoreKeys; |
101 | AccountManager::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); |
102 | FolderMan::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); |
103 | |
104 | ConfigFile configFile; |
105 | |
106 | // Did the client version change? |
107 | // (The client version is adjusted further down) |
108 | bool versionChanged = configFile.clientVersionString() != MIRALL_VERSION_STRING; |
109 | |
110 | // We want to message the user either for destructive changes, |
111 | // or if we're ignoring something and the client version changed. |
112 | bool warningMessage = !deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && versionChanged); |
113 | |
114 | if (!versionChanged && !warningMessage) |
115 | return true; |
116 | |
117 | const auto backupFile = configFile.backup(); |
118 | |
119 | if (warningMessage) { |
120 | QString boldMessage; |
121 | if (!deleteKeys.isEmpty()) { |
122 | boldMessage = tr("Continuing will mean <b>deleting these settings</b>." ); |
123 | } else { |
124 | boldMessage = tr("Continuing will mean <b>ignoring these settings</b>." ); |
125 | } |
126 | |
127 | QMessageBox box( |
128 | QMessageBox::Warning, |
129 | APPLICATION_SHORTNAME, |
130 | tr("Some settings were configured in newer versions of this client and " |
131 | "use features that are not available in this version.<br>" |
132 | "<br>" |
133 | "%1<br>" |
134 | "<br>" |
135 | "The current configuration file was already backed up to <i>%2</i>." ) |
136 | .arg(boldMessage, backupFile)); |
137 | box.addButton(tr("Quit" ), QMessageBox::AcceptRole); |
138 | auto continueBtn = box.addButton(tr("Continue" ), QMessageBox::DestructiveRole); |
139 | |
140 | box.exec(); |
141 | if (box.clickedButton() != continueBtn) { |
142 | QTimer::singleShot(0, qApp, SLOT(quit())); |
143 | return false; |
144 | } |
145 | |
146 | auto settings = ConfigFile::settingsWithGroup("foo" ); |
147 | settings->endGroup(); |
148 | |
149 | // Wipe confusing keys from the future, ignore the others |
150 | for (const auto &badKey : deleteKeys) |
151 | settings->remove(badKey); |
152 | } |
153 | |
154 | configFile.setClientVersionString(MIRALL_VERSION_STRING); |
155 | return true; |
156 | } |
157 | |
158 | Application::Application(int &argc, char **argv) |
159 | : SharedTools::QtSingleApplication(Theme::instance()->appName(), argc, argv) |
160 | , _gui(0) |
161 | , _theme(Theme::instance()) |
162 | , _helpOnly(false) |
163 | , _versionOnly(false) |
164 | , _showLogWindow(false) |
165 | , _logExpire(0) |
166 | , _logFlush(false) |
167 | , _logDebug(false) |
168 | , _userTriggeredConnect(false) |
169 | , _debugMode(false) |
170 | { |
171 | _startedAt.start(); |
172 | |
173 | qsrand(std::random_device()()); |
174 | |
175 | #ifdef Q_OS_WIN |
176 | // Ensure OpenSSL config file is only loaded from app directory |
177 | QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf" ); |
178 | qputenv("OPENSSL_CONF" , opensslConf.toLocal8Bit()); |
179 | #endif |
180 | |
181 | // TODO: Can't set this without breaking current config paths |
182 | // setOrganizationName(QLatin1String(APPLICATION_VENDOR)); |
183 | setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN)); |
184 | setApplicationName(_theme->appName()); |
185 | setWindowIcon(_theme->applicationIcon()); |
186 | setAttribute(Qt::AA_UseHighDpiPixmaps, true); |
187 | |
188 | auto confDir = ConfigFile().configPath(); |
189 | if (confDir.endsWith('/')) confDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move. |
190 | if (!QFileInfo(confDir).isDir()) { |
191 | // Migrate from version <= 2.4 |
192 | setApplicationName(_theme->appNameGUI()); |
193 | #ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 |
194 | #define QT_WARNING_DISABLE_DEPRECATED QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") |
195 | #endif |
196 | QT_WARNING_PUSH |
197 | QT_WARNING_DISABLE_DEPRECATED |
198 | // We need to use the deprecated QDesktopServices::storageLocation because of its Qt4 |
199 | // behavior of adding "data" to the path |
200 | QString oldDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); |
201 | if (oldDir.endsWith('/')) oldDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move. |
202 | QT_WARNING_POP |
203 | setApplicationName(_theme->appName()); |
204 | if (QFileInfo(oldDir).isDir()) { |
205 | qCInfo(lcApplication) << "Migrating old config from" << oldDir << "to" << confDir; |
206 | if (!QFile::rename(oldDir, confDir)) { |
207 | qCWarning(lcApplication) << "Failed to move the old config file to its new location (" << oldDir << "to" << confDir << ")" ; |
208 | } else { |
209 | #ifndef Q_OS_WIN |
210 | // Create a symbolic link so a downgrade of the client would still find the config. |
211 | QFile::link(confDir, oldDir); |
212 | #endif |
213 | } |
214 | } |
215 | } |
216 | |
217 | parseOptions(arguments()); |
218 | //no need to waste time; |
219 | if (_helpOnly || _versionOnly) |
220 | return; |
221 | |
222 | if (isRunning()) |
223 | return; |
224 | |
225 | #if defined(WITH_CRASHREPORTER) |
226 | if (ConfigFile().crashReporter()) |
227 | _crashHandler.reset(new CrashReporter::Handler(QDir::tempPath(), true, CRASHREPORTER_EXECUTABLE)); |
228 | #endif |
229 | |
230 | setupLogging(); |
231 | setupTranslations(); |
232 | |
233 | if (!configVersionMigration()) { |
234 | return; |
235 | } |
236 | |
237 | ConfigFile cfg; |
238 | // The timeout is initialized with an environment variable, if not, override with the value from the config |
239 | if (!AbstractNetworkJob::httpTimeout) |
240 | AbstractNetworkJob::httpTimeout = cfg.timeout(); |
241 | |
242 | _folderManager.reset(new FolderMan); |
243 | |
244 | connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); |
245 | |
246 | if (!AccountManager::instance()->restore()) { |
247 | // If there is an error reading the account settings, try again |
248 | // after a couple of seconds, if that fails, give up. |
249 | // (non-existence is not an error) |
250 | Utility::sleep(5); |
251 | if (!AccountManager::instance()->restore()) { |
252 | qCCritical(lcApplication) << "Could not read the account settings, quitting" ; |
253 | QMessageBox::critical( |
254 | 0, |
255 | tr("Error accessing the configuration file" ), |
256 | tr("There was an error while accessing the configuration " |
257 | "file at %1." ) |
258 | .arg(ConfigFile().configFile()), |
259 | tr("Quit ownCloud" )); |
260 | QTimer::singleShot(0, qApp, SLOT(quit())); |
261 | return; |
262 | } |
263 | } |
264 | |
265 | FolderMan::instance()->setSyncEnabled(true); |
266 | |
267 | setQuitOnLastWindowClosed(false); |
268 | |
269 | _theme->setSystrayUseMonoIcons(cfg.monoIcons()); |
270 | connect(_theme, &Theme::systrayUseMonoIconsChanged, this, &Application::slotUseMonoIconsChanged); |
271 | |
272 | // Setting up the gui class will allow tray notifications for the |
273 | // setup that follows, like folder setup |
274 | _gui = new ownCloudGui(this); |
275 | if (_showLogWindow) { |
276 | _gui->slotToggleLogBrowser(); // _showLogWindow is set in parseOptions. |
277 | } |
278 | |
279 | FolderMan::instance()->setupFolders(); |
280 | _proxy.setupQtProxyFromConfig(); // folders have to be defined first, than we set up the Qt proxy. |
281 | |
282 | // Enable word wrapping of QInputDialog (#4197) |
283 | setStyleSheet("QInputDialog QLabel { qproperty-wordWrap:1; }" ); |
284 | |
285 | connect(AccountManager::instance(), &AccountManager::accountAdded, |
286 | this, &Application::slotAccountStateAdded); |
287 | connect(AccountManager::instance(), &AccountManager::accountRemoved, |
288 | this, &Application::slotAccountStateRemoved); |
289 | foreach (auto ai, AccountManager::instance()->accounts()) { |
290 | slotAccountStateAdded(ai.data()); |
291 | } |
292 | |
293 | connect(FolderMan::instance()->socketApi(), &SocketApi::shareCommandReceived, |
294 | _gui.data(), &ownCloudGui::slotShowShareDialog); |
295 | |
296 | // startup procedure. |
297 | connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection); |
298 | _checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); // check for connection every 32 seconds. |
299 | _checkConnectionTimer.start(); |
300 | // Also check immediately |
301 | QTimer::singleShot(0, this, &Application::slotCheckConnection); |
302 | |
303 | // Can't use onlineStateChanged because it is always true on modern systems because of many interfaces |
304 | connect(&_networkConfigurationManager, &QNetworkConfigurationManager::configurationChanged, |
305 | this, &Application::slotSystemOnlineConfigurationChanged); |
306 | |
307 | // Update checks |
308 | UpdaterScheduler *updaterScheduler = new UpdaterScheduler(this); |
309 | connect(updaterScheduler, &UpdaterScheduler::updaterAnnouncement, |
310 | _gui.data(), &ownCloudGui::slotShowTrayMessage); |
311 | connect(updaterScheduler, &UpdaterScheduler::requestRestart, |
312 | _folderManager.data(), &FolderMan::slotScheduleAppRestart); |
313 | |
314 | // Cleanup at Quit. |
315 | connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup); |
316 | } |
317 | |
318 | Application::~Application() |
319 | { |
320 | // Make sure all folders are gone, otherwise removing the |
321 | // accounts will remove the associated folders from the settings. |
322 | if (_folderManager) { |
323 | _folderManager->unloadAndDeleteAllFolders(); |
324 | } |
325 | |
326 | // Remove the account from the account manager so it can be deleted. |
327 | AccountManager::instance()->shutdown(); |
328 | } |
329 | |
330 | void Application::slotAccountStateRemoved(AccountState *accountState) |
331 | { |
332 | if (_gui) { |
333 | disconnect(accountState, &AccountState::stateChanged, |
334 | _gui.data(), &ownCloudGui::slotAccountStateChanged); |
335 | disconnect(accountState->account().data(), &Account::serverVersionChanged, |
336 | _gui.data(), &ownCloudGui::slotTrayMessageIfServerUnsupported); |
337 | } |
338 | if (_folderManager) { |
339 | disconnect(accountState, &AccountState::stateChanged, |
340 | _folderManager.data(), &FolderMan::slotAccountStateChanged); |
341 | disconnect(accountState->account().data(), &Account::serverVersionChanged, |
342 | _folderManager.data(), &FolderMan::slotServerVersionChanged); |
343 | } |
344 | |
345 | // if there is no more account, show the wizard. |
346 | if (AccountManager::instance()->accounts().isEmpty()) { |
347 | // allow to add a new account if there is non any more. Always think |
348 | // about single account theming! |
349 | OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int))); |
350 | } |
351 | } |
352 | |
353 | void Application::slotAccountStateAdded(AccountState *accountState) |
354 | { |
355 | connect(accountState, &AccountState::stateChanged, |
356 | _gui.data(), &ownCloudGui::slotAccountStateChanged); |
357 | connect(accountState->account().data(), &Account::serverVersionChanged, |
358 | _gui.data(), &ownCloudGui::slotTrayMessageIfServerUnsupported); |
359 | connect(accountState, &AccountState::stateChanged, |
360 | _folderManager.data(), &FolderMan::slotAccountStateChanged); |
361 | connect(accountState->account().data(), &Account::serverVersionChanged, |
362 | _folderManager.data(), &FolderMan::slotServerVersionChanged); |
363 | |
364 | _gui->slotTrayMessageIfServerUnsupported(accountState->account().data()); |
365 | } |
366 | |
367 | void Application::slotCleanup() |
368 | { |
369 | AccountManager::instance()->save(); |
370 | FolderMan::instance()->unloadAndDeleteAllFolders(); |
371 | |
372 | _gui->slotShutdown(); |
373 | _gui->deleteLater(); |
374 | } |
375 | |
376 | // FIXME: This is not ideal yet since a ConnectionValidator might already be running and is in |
377 | // progress of timing out in some seconds. |
378 | // Maybe we need 2 validators, one triggered by timer, one by network configuration changes? |
379 | void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf) |
380 | { |
381 | if (cnf.state() & QNetworkConfiguration::Active) { |
382 | QMetaObject::invokeMethod(this, "slotCheckConnection" , Qt::QueuedConnection); |
383 | } |
384 | } |
385 | |
386 | void Application::slotCheckConnection() |
387 | { |
388 | auto list = AccountManager::instance()->accounts(); |
389 | foreach (const auto &accountState, list) { |
390 | AccountState::State state = accountState->state(); |
391 | |
392 | // Don't check if we're manually signed out or |
393 | // when the error is permanent. |
394 | if (state != AccountState::SignedOut |
395 | && state != AccountState::ConfigurationError |
396 | && state != AccountState::AskingCredentials) { |
397 | accountState->checkConnectivity(); |
398 | } |
399 | } |
400 | |
401 | if (list.isEmpty()) { |
402 | // let gui open the setup wizard |
403 | _gui->slotOpenSettingsDialog(); |
404 | |
405 | _checkConnectionTimer.stop(); // don't popup the wizard on interval; |
406 | } |
407 | } |
408 | |
409 | void Application::slotCrash() |
410 | { |
411 | Utility::crash(); |
412 | } |
413 | |
414 | void Application::slotownCloudWizardDone(int res) |
415 | { |
416 | AccountManager *accountMan = AccountManager::instance(); |
417 | FolderMan *folderMan = FolderMan::instance(); |
418 | |
419 | // During the wizard, scheduling of new syncs is disabled |
420 | folderMan->setSyncEnabled(true); |
421 | |
422 | if (res == QDialog::Accepted) { |
423 | // Check connectivity of the newly created account |
424 | _checkConnectionTimer.start(); |
425 | slotCheckConnection(); |
426 | |
427 | // If one account is configured: enable autostart |
428 | bool shouldSetAutoStart = (accountMan->accounts().size() == 1); |
429 | #ifdef Q_OS_MAC |
430 | // Don't auto start when not being 'installed' |
431 | shouldSetAutoStart = shouldSetAutoStart |
432 | && QCoreApplication::applicationDirPath().startsWith("/Applications/" ); |
433 | #endif |
434 | if (shouldSetAutoStart) { |
435 | Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true); |
436 | } |
437 | |
438 | _gui->slotShowSettings(); |
439 | } |
440 | } |
441 | |
442 | void Application::setupLogging() |
443 | { |
444 | // might be called from second instance |
445 | auto logger = Logger::instance(); |
446 | logger->setLogFile(_logFile); |
447 | logger->setLogDir(_logDir); |
448 | logger->setLogExpire(_logExpire); |
449 | logger->setLogFlush(_logFlush); |
450 | logger->setLogDebug(_logDebug); |
451 | if (!logger->isLoggingToFile() && ConfigFile().automaticLogDir()) { |
452 | logger->setupTemporaryFolderLogDir(); |
453 | } |
454 | |
455 | logger->enterNextLogFile(); |
456 | |
457 | qCInfo(lcApplication) << QString::fromLatin1("################## %1 locale:[%2] ui_lang:[%3] version:[%4] os:[%5]" ).arg(_theme->appName()).arg(QLocale::system().name()).arg(property("ui_lang" ).toString()).arg(_theme->version()).arg(Utility::platformName()); |
458 | } |
459 | |
460 | void Application::slotUseMonoIconsChanged(bool) |
461 | { |
462 | _gui->slotComputeOverallSyncStatus(); |
463 | } |
464 | |
465 | void Application::slotParseMessage(const QString &msg, QObject *) |
466 | { |
467 | if (msg.startsWith(QLatin1String("MSG_PARSEOPTIONS:" ))) { |
468 | const int lengthOfMsgPrefix = 17; |
469 | QStringList options = msg.mid(lengthOfMsgPrefix).split(QLatin1Char('|')); |
470 | parseOptions(options); |
471 | setupLogging(); |
472 | } else if (msg.startsWith(QLatin1String("MSG_SHOWSETTINGS" ))) { |
473 | qCInfo(lcApplication) << "Running for" << _startedAt.elapsed() / 1000.0 << "sec" ; |
474 | if (_startedAt.elapsed() < 10 * 1000) { |
475 | // This call is mirrored with the one in int main() |
476 | qCWarning(lcApplication) << "Ignoring MSG_SHOWSETTINGS, possibly double-invocation of client via session restore and auto start" ; |
477 | return; |
478 | } |
479 | showSettingsDialog(); |
480 | } |
481 | } |
482 | |
483 | void Application::parseOptions(const QStringList &options) |
484 | { |
485 | QStringListIterator it(options); |
486 | // skip file name; |
487 | if (it.hasNext()) |
488 | it.next(); |
489 | |
490 | //parse options; if help or bad option exit |
491 | while (it.hasNext()) { |
492 | QString option = it.next(); |
493 | if (option == QLatin1String("--help" ) || option == QLatin1String("-h" )) { |
494 | setHelp(); |
495 | break; |
496 | } else if (option == QLatin1String("--logwindow" ) || option == QLatin1String("-l" )) { |
497 | _showLogWindow = true; |
498 | } else if (option == QLatin1String("--logfile" )) { |
499 | if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--" ))) { |
500 | _logFile = it.next(); |
501 | } else { |
502 | showHint("Log file not specified" ); |
503 | } |
504 | } else if (option == QLatin1String("--logdir" )) { |
505 | if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--" ))) { |
506 | _logDir = it.next(); |
507 | } else { |
508 | showHint("Log dir not specified" ); |
509 | } |
510 | } else if (option == QLatin1String("--logexpire" )) { |
511 | if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--" ))) { |
512 | _logExpire = it.next().toInt(); |
513 | } else { |
514 | showHint("Log expiration not specified" ); |
515 | } |
516 | } else if (option == QLatin1String("--logflush" )) { |
517 | _logFlush = true; |
518 | } else if (option == QLatin1String("--logdebug" )) { |
519 | _logDebug = true; |
520 | } else if (option == QLatin1String("--confdir" )) { |
521 | if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--" ))) { |
522 | QString confDir = it.next(); |
523 | if (!ConfigFile::setConfDir(confDir)) { |
524 | showHint("Invalid path passed to --confdir" ); |
525 | } |
526 | } else { |
527 | showHint("Path for confdir not specified" ); |
528 | } |
529 | } else if (option == QLatin1String("--debug" )) { |
530 | _logDebug = true; |
531 | _debugMode = true; |
532 | } else if (option == QLatin1String("--version" )) { |
533 | _versionOnly = true; |
534 | } else if (option.endsWith(QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX))) { |
535 | // virtual file, open it after the Folder were created (if the app is not terminated) |
536 | QTimer::singleShot(0, this, [this, option] { openVirtualFile(option); }); |
537 | } else { |
538 | showHint("Unrecognized option '" + option.toStdString() + "'" ); |
539 | } |
540 | } |
541 | } |
542 | |
543 | // Helpers for displaying messages. Note that there is no console on Windows. |
544 | #ifdef Q_OS_WIN |
545 | static void displayHelpText(const QString &t) // No console on Windows. |
546 | { |
547 | QString spaces(80, ' '); // Add a line of non-wrapped space to make the messagebox wide enough. |
548 | QString text = QLatin1String("<qt><pre style='white-space:pre-wrap'>" ) |
549 | + t.toHtmlEscaped() + QLatin1String("</pre><pre>" ) + spaces + QLatin1String("</pre></qt>" ); |
550 | QMessageBox::information(0, Theme::instance()->appNameGUI(), text); |
551 | } |
552 | |
553 | #else |
554 | |
555 | static void displayHelpText(const QString &t) |
556 | { |
557 | std::cout << qUtf8Printable(t); |
558 | } |
559 | #endif |
560 | |
561 | void Application::showHelp() |
562 | { |
563 | setHelp(); |
564 | QString helpText; |
565 | QTextStream stream(&helpText); |
566 | stream << _theme->appName() |
567 | << QLatin1String(" version " ) |
568 | << _theme->version() << endl; |
569 | |
570 | stream << QLatin1String("File synchronisation desktop utility." ) << endl |
571 | << endl |
572 | << QLatin1String(optionsC); |
573 | |
574 | if (_theme->appName() == QLatin1String("ownCloud" )) |
575 | stream << endl |
576 | << "For more information, see http://www.owncloud.org" << endl |
577 | << endl; |
578 | |
579 | displayHelpText(helpText); |
580 | } |
581 | |
582 | void Application::showVersion() |
583 | { |
584 | displayHelpText(Theme::instance()->versionSwitchOutput()); |
585 | } |
586 | |
587 | void Application::showHint(std::string errorHint) |
588 | { |
589 | static QString binName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); |
590 | std::cerr << errorHint << std::endl; |
591 | std::cerr << "Try '" << binName.toStdString() << " --help' for more information" << std::endl; |
592 | std::exit(1); |
593 | } |
594 | |
595 | bool Application::debugMode() |
596 | { |
597 | return _debugMode; |
598 | } |
599 | |
600 | void Application::setHelp() |
601 | { |
602 | _helpOnly = true; |
603 | } |
604 | |
605 | QString substLang(const QString &lang) |
606 | { |
607 | // Map the more appropriate script codes |
608 | // to country codes as used by Qt and |
609 | // transifex translation conventions. |
610 | |
611 | // Simplified Chinese |
612 | if (lang == QLatin1String("zh_Hans" )) |
613 | return QLatin1String("zh_CN" ); |
614 | // Traditional Chinese |
615 | if (lang == QLatin1String("zh_Hant" )) |
616 | return QLatin1String("zh_TW" ); |
617 | return lang; |
618 | } |
619 | |
620 | void Application::setupTranslations() |
621 | { |
622 | QStringList uiLanguages; |
623 | // uiLanguages crashes on Windows with 4.8.0 release builds |
624 | #if (QT_VERSION >= 0x040801) || (QT_VERSION >= 0x040800 && !defined(Q_OS_WIN)) |
625 | uiLanguages = QLocale::system().uiLanguages(); |
626 | #else |
627 | // older versions need to fall back to the systems locale |
628 | uiLanguages << QLocale::system().name(); |
629 | #endif |
630 | |
631 | QString enforcedLocale = Theme::instance()->enforcedLocale(); |
632 | if (!enforcedLocale.isEmpty()) |
633 | uiLanguages.prepend(enforcedLocale); |
634 | |
635 | QTranslator *translator = new QTranslator(this); |
636 | QTranslator *qtTranslator = new QTranslator(this); |
637 | QTranslator *qtkeychainTranslator = new QTranslator(this); |
638 | |
639 | foreach (QString lang, uiLanguages) { |
640 | lang.replace(QLatin1Char('-'), QLatin1Char('_')); // work around QTBUG-25973 |
641 | lang = substLang(lang); |
642 | const QString trPath = applicationTrPath(); |
643 | const QString trFile = QLatin1String("client_" ) + lang; |
644 | if (translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en" ))) { |
645 | // Permissive approach: Qt and keychain translations |
646 | // may be missing, but Qt translations must be there in order |
647 | // for us to accept the language. Otherwise, we try with the next. |
648 | // "en" is an exception as it is the default language and may not |
649 | // have a translation file provided. |
650 | qCInfo(lcApplication) << "Using" << lang << "translation" ; |
651 | setProperty("ui_lang" , lang); |
652 | const QString qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); |
653 | const QString qtTrFile = QLatin1String("qt_" ) + lang; |
654 | const QString qtBaseTrFile = QLatin1String("qtbase_" ) + lang; |
655 | if (!qtTranslator->load(qtTrFile, qtTrPath)) { |
656 | if (!qtTranslator->load(qtTrFile, trPath)) { |
657 | if (!qtTranslator->load(qtBaseTrFile, qtTrPath)) { |
658 | qtTranslator->load(qtBaseTrFile, trPath); |
659 | } |
660 | } |
661 | } |
662 | const QString qtkeychainTrFile = QLatin1String("qtkeychain_" ) + lang; |
663 | if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { |
664 | qtkeychainTranslator->load(qtkeychainTrFile, trPath); |
665 | } |
666 | if (!translator->isEmpty()) |
667 | installTranslator(translator); |
668 | if (!qtTranslator->isEmpty()) |
669 | installTranslator(qtTranslator); |
670 | if (!qtkeychainTranslator->isEmpty()) |
671 | installTranslator(qtkeychainTranslator); |
672 | break; |
673 | } |
674 | if (property("ui_lang" ).isNull()) |
675 | setProperty("ui_lang" , "C" ); |
676 | } |
677 | } |
678 | |
679 | bool Application::giveHelp() |
680 | { |
681 | return _helpOnly; |
682 | } |
683 | |
684 | bool Application::versionOnly() |
685 | { |
686 | return _versionOnly; |
687 | } |
688 | |
689 | void Application::showSettingsDialog() |
690 | { |
691 | _gui->slotShowSettings(); |
692 | } |
693 | |
694 | void Application::openVirtualFile(const QString &filename) |
695 | { |
696 | QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX); |
697 | if (!filename.endsWith(virtualFileExt)) { |
698 | qWarning(lcApplication) << "Can only handle file ending in .owncloud. Unable to open" << filename; |
699 | return; |
700 | } |
701 | QString relativePath; |
702 | auto folder = FolderMan::instance()->folderForPath(filename, &relativePath); |
703 | if (!folder) { |
704 | qWarning(lcApplication) << "Can't find sync folder for" << filename; |
705 | // TODO: show a QMessageBox for errors |
706 | return; |
707 | } |
708 | folder->downloadVirtualFile(relativePath); |
709 | QString normalName = filename.left(filename.size() - virtualFileExt.size()); |
710 | auto con = QSharedPointer<QMetaObject::Connection>::create(); |
711 | *con = QObject::connect(folder, &Folder::syncFinished, [con, normalName] { |
712 | QObject::disconnect(*con); |
713 | if (QFile::exists(normalName)) { |
714 | QDesktopServices::openUrl(QUrl::fromLocalFile(normalName)); |
715 | } |
716 | }); |
717 | } |
718 | |
719 | bool Application::event(QEvent *event) |
720 | { |
721 | #ifdef Q_OS_MAC |
722 | if (event->type() == QEvent::FileOpen) { |
723 | QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event); |
724 | qCDebug(lcApplication) << "QFileOpenEvent" << openEvent->file(); |
725 | // virtual file, open it after the Folder were created (if the app is not terminated) |
726 | QString fn = openEvent->file(); |
727 | QTimer::singleShot(0, this, [this, fn] { openVirtualFile(fn); }); |
728 | } |
729 | #endif |
730 | return SharedTools::QtSingleApplication::event(event); |
731 | } |
732 | |
733 | } // namespace OCC |
734 | |