application.cpp 31 KB

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