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
57class QSocket;
58
59namespace OCC {
60
61Q_LOGGING_CATEGORY(lcApplication, "gui.application", QtInfoMsg)
62
63namespace {
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
98bool 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
158Application::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
318Application::~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
330void 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
353void 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
367void 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?
379void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf)
380{
381 if (cnf.state() & QNetworkConfiguration::Active) {
382 QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection);
383 }
384}
385
386void 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
409void Application::slotCrash()
410{
411 Utility::crash();
412}
413
414void 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
442void 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
460void Application::slotUseMonoIconsChanged(bool)
461{
462 _gui->slotComputeOverallSyncStatus();
463}
464
465void 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
483void 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
545static 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
555static void displayHelpText(const QString &t)
556{
557 std::cout << qUtf8Printable(t);
558}
559#endif
560
561void 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
582void Application::showVersion()
583{
584 displayHelpText(Theme::instance()->versionSwitchOutput());
585}
586
587void 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
595bool Application::debugMode()
596{
597 return _debugMode;
598}
599
600void Application::setHelp()
601{
602 _helpOnly = true;
603}
604
605QString 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
620void 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
679bool Application::giveHelp()
680{
681 return _helpOnly;
682}
683
684bool Application::versionOnly()
685{
686 return _versionOnly;
687}
688
689void Application::showSettingsDialog()
690{
691 _gui->slotShowSettings();
692}
693
694void 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
719bool 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