/* * Copyright (C) by Duncan Mac-Vicar P. * Copyright (C) by Klaas Freitag * Copyright (C) by Daniel Molkentin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "application.h" #include #include "config.h" #include "account.h" #include "accountstate.h" #include "connectionvalidator.h" #include "folder.h" #include "folderman.h" #include "logger.h" #include "configfile.h" #include "socketapi.h" #include "sslerrordialog.h" #include "theme.h" #include "utility.h" #include "clientproxy.h" #include "sharedialog.h" #include "accountmanager.h" #include "creds/abstractcredentials.h" #include "updater/ocupdater.h" #include "excludedfiles.h" #include "config.h" #if defined(Q_OS_WIN) #include #endif #if defined(WITH_CRASHREPORTER) #include #endif #include #include #include class QSocket; namespace OCC { namespace { static const char optionsC[] = "Options:\n" " -h --help : show this help screen.\n" " --logwindow : open a window to show log output.\n" " --logfile : write log output to file .\n" " --logdir : write each sync log output in a new file\n" " in folder .\n" " --logexpire : removes logs older than hours.\n" " (to be used with --logdir)\n" " --logflush : flush the log file after every write.\n" " --confdir : Use the given configuration folder.\n" ; QString applicationTrPath() { QString devTrPath = qApp->applicationDirPath() + QString::fromLatin1("/../src/gui/"); if (QDir(devTrPath).exists()) { // might miss Qt, QtKeyChain, etc. qDebug() << "Running from build location! Translations may be incomplete!"; return devTrPath; } #if defined(Q_OS_WIN) return QApplication::applicationDirPath(); #elif defined(Q_OS_MAC) return QApplication::applicationDirPath()+QLatin1String("/../Resources/Translations"); // path defaults to app dir. #elif defined(Q_OS_UNIX) return QString::fromLatin1(SHAREDIR "/" APPLICATION_EXECUTABLE "/i18n/"); #endif } } // ---------------------------------------------------------------------------------- Application::Application(int &argc, char **argv) : SharedTools::QtSingleApplication(Theme::instance()->appName() ,argc, argv), _gui(0), _theme(Theme::instance()), _helpOnly(false), _versionOnly(false), _showLogWindow(false), _logExpire(0), _logFlush(false), _userTriggeredConnect(false), _debugMode(false) { _startedAt.start(); // TODO: Can't set this without breaking current config pathes // setOrganizationName(QLatin1String(APPLICATION_VENDOR)); setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN)); setApplicationName( _theme->appNameGUI() ); setWindowIcon( _theme->applicationIcon() ); #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) setAttribute(Qt::AA_UseHighDpiPixmaps, true); #endif parseOptions(arguments()); //no need to waste time; if ( _helpOnly || _versionOnly ) return; if (isRunning()) return; #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) && QT_VERSION < QT_VERSION_CHECK(5, 4, 2) // Workaround for QTBUG-44576: Make sure a stale QSettings lock file // is deleted. (Introduced in Qt 5.4.0 and fixed in Qt 5.4.2) { QString lockFilePath = ConfigFile().configFile() + QLatin1String(".lock"); QLockFile(lockFilePath).removeStaleLockFile(); } #endif #if defined(WITH_CRASHREPORTER) if (ConfigFile().crashReporter()) _crashHandler.reset(new CrashReporter::Handler( QDir::tempPath(), true, CRASHREPORTER_EXECUTABLE )); #endif setupLogging(); setupTranslations(); // Setup global excludes ConfigFile cfg; ExcludedFiles& excludes = ExcludedFiles::instance(); excludes.addExcludeFilePath( cfg.excludeFile(ConfigFile::SystemScope) ); excludes.addExcludeFilePath( cfg.excludeFile(ConfigFile::UserScope) ); excludes.reloadExcludes(); _folderManager.reset(new FolderMan); connect(this, SIGNAL(messageReceived(QString, QObject*)), SLOT(slotParseMessage(QString, QObject*))); AccountManager::instance()->restore(); FolderMan::instance()->setSyncEnabled(true); setQuitOnLastWindowClosed(false); _theme->setSystrayUseMonoIcons(cfg.monoIcons()); connect (_theme, SIGNAL(systrayUseMonoIconsChanged(bool)), SLOT(slotUseMonoIconsChanged(bool))); FolderMan::instance()->setupFolders(); _proxy.setupQtProxyFromConfig(); // folders have to be defined first, than we set up the Qt proxy. _gui = new ownCloudGui(this); if( _showLogWindow ) { _gui->slotToggleLogBrowser(); // _showLogWindow is set in parseOptions. } connect(AccountManager::instance(), SIGNAL(accountAdded(AccountState*)), SLOT(slotAccountStateAdded(AccountState*))); connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState*)), SLOT(slotAccountStateRemoved(AccountState*))); foreach (auto ai , AccountManager::instance()->accounts()) { slotAccountStateAdded(ai.data()); } connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString, bool)), _gui, SLOT(slotShowShareDialog(QString, QString, bool))); // startup procedure. connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection())); _checkConnectionTimer.setInterval(32 * 1000); // check for connection every 32 seconds. _checkConnectionTimer.start(); // Also check immediatly QTimer::singleShot( 0, this, SLOT( slotCheckConnection() )); // Update checks UpdaterScheduler *updaterScheduler = new UpdaterScheduler(this); connect(updaterScheduler, SIGNAL(updaterAnnouncement(QString, QString)), _gui, SLOT(slotShowTrayMessage(QString, QString))); connect(updaterScheduler, SIGNAL(requestRestart()), _folderManager.data(), SLOT(slotScheduleAppRestart())); // Cleanup at Quit. connect (this, SIGNAL(aboutToQuit()), SLOT(slotCleanup())); } Application::~Application() { // Make sure all folders are gone, otherwise removing the // accounts will remove the associated folders from the settings. if (_folderManager) { _folderManager->unloadAndDeleteAllFolders(); } // Remove the account from the account manager so it can be deleted. AccountManager::instance()->shutdown(); } void Application::slotAccountStateRemoved(AccountState *accountState) { if (_gui) { disconnect(accountState, SIGNAL(stateChanged(int)), _gui, SLOT(slotAccountStateChanged())); } if (_folderManager) { disconnect(accountState, SIGNAL(stateChanged(int)), _folderManager.data(), SLOT(slotAccountStateChanged())); } } void Application::slotAccountStateAdded(AccountState *accountState) { connect(accountState, SIGNAL(stateChanged(int)), _gui, SLOT(slotAccountStateChanged())); connect(accountState, SIGNAL(stateChanged(int)), _folderManager.data(), SLOT(slotAccountStateChanged())); } void Application::slotCleanup() { AccountManager::instance()->save(); FolderMan::instance()->unloadAndDeleteAllFolders(); _gui->slotShutdown(); _gui->deleteLater(); } void Application::slotCheckConnection() { auto list = AccountManager::instance()->accounts(); foreach (const auto &accountState , list) { AccountState::State state = accountState->state(); // Don't check if we're manually signed out or // when the error is permanent. if (state != AccountState::SignedOut && state != AccountState::ConfigurationError) { accountState->checkConnectivity(AccountState::NonInteractive); } } if (list.isEmpty()) { // let gui open the setup wizard _gui->slotOpenSettingsDialog(); _checkConnectionTimer.stop(); // don't popup the wizard on interval; } } void Application::slotCrash() { Utility::crash(); } void Application::slotownCloudWizardDone( int res ) { FolderMan *folderMan = FolderMan::instance(); if( res == QDialog::Accepted ) { int cnt = folderMan->setupFolders(); qDebug() << "Set up " << cnt << " folders."; // We have some sort of configuration. Enable autostart Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true); if (cnt == 0) { // The folder configuration was skipped _gui->slotShowSettings(); } } folderMan->setSyncEnabled( true ); if( res == QDialog::Accepted ) { _checkConnectionTimer.start(); slotCheckConnection(); } } void Application::setupLogging() { // might be called from second instance Logger::instance()->setLogFile(_logFile); Logger::instance()->setLogDir(_logDir); Logger::instance()->setLogExpire(_logExpire); Logger::instance()->setLogFlush(_logFlush); Logger::instance()->enterNextLogFile(); qDebug() << QString::fromLatin1( "################## %1 %2 (%3) %4").arg(_theme->appName()) .arg( QLocale::system().name() ) .arg(property("ui_lang").toString()) .arg(_theme->version()); } void Application::slotUseMonoIconsChanged(bool) { _gui->slotComputeOverallSyncStatus(); } void Application::slotParseMessage(const QString &msg, QObject*) { if (msg.startsWith(QLatin1String("MSG_PARSEOPTIONS:"))) { const int lengthOfMsgPrefix = 17; QStringList options = msg.mid(lengthOfMsgPrefix).split(QLatin1Char('|')); parseOptions(options); setupLogging(); } else if (msg.startsWith(QLatin1String("MSG_SHOWSETTINGS"))) { qDebug() << "Running for" << _startedAt.elapsed()/1000.0 << "sec"; if (isSessionRestored() && _startedAt.elapsed() < 10*1000) { // This call is mirrored with the one in int main() qWarning() << "Ignoring MSG_SHOWSETTINGS, possibly double-invocation of client via session restore and auto start"; return; } showSettingsDialog(); } } void Application::parseOptions(const QStringList &options) { QStringListIterator it(options); // skip file name; if (it.hasNext()) it.next(); //parse options; if help or bad option exit while (it.hasNext()) { QString option = it.next(); if (option == QLatin1String("--help") || option == QLatin1String("-h")) { setHelp(); break; } else if (option == QLatin1String("--logwindow") || option == QLatin1String("-l")) { _showLogWindow = true; } else if (option == QLatin1String("--logfile")) { if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { _logFile = it.next(); } else { showHint("Log file not specified"); } } else if (option == QLatin1String("--logdir")) { if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { _logDir = it.next(); } else { showHint("Log dir not specified"); } } else if (option == QLatin1String("--logexpire")) { if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { _logExpire = it.next().toInt(); } else { showHint("Log expiration not specified"); } } else if (option == QLatin1String("--logflush")) { _logFlush = true; } else if (option == QLatin1String("--confdir")) { if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) { QString confDir = it.next(); if (!ConfigFile::setConfDir( confDir )) { showHint("Invalid path passed to --confdir"); } } else { showHint("Path for confdir not specified"); } } else if (option == QLatin1String("--debug")) { _debugMode = true; } else if (option == QLatin1String("--version")) { _versionOnly = true; } else { showHint("Unrecognized option '" + option.toStdString() + "'"); } } } // Helpers for displaying messages. Note that there is no console on Windows. #ifdef Q_OS_WIN // Format as
 HTML
static inline void toHtml(QString &t)
{
    t.replace(QLatin1Char('&'), QLatin1String("&"));
    t.replace(QLatin1Char('<'), QLatin1String("<"));
    t.replace(QLatin1Char('>'), QLatin1String(">"));
    t.insert(0, QLatin1String("
"));
    t.append(QLatin1String("
")); } static void displayHelpText(QString t) // No console on Windows. { toHtml(t); QMessageBox::information(0, Theme::instance()->appNameGUI(), t); } #else static void displayHelpText(const QString &t) { std::cout << qPrintable(t); } #endif void Application::showHelp() { setHelp(); QString helpText; QTextStream stream(&helpText); stream << _theme->appName().toLatin1().constData() << QLatin1String(" version ") << _theme->version().toLatin1().constData() << endl; stream << QLatin1String("File synchronisation desktop utility.") << endl << endl << QLatin1String(optionsC); if (_theme->appName() == QLatin1String("ownCloud")) stream << endl << "For more information, see http://www.owncloud.org" << endl << endl; displayHelpText(helpText); } void Application::showVersion() { QString helpText; QTextStream stream(&helpText); stream << _theme->appName().toLatin1().constData() << QLatin1String(" version ") << _theme->version().toLatin1().constData() << endl; displayHelpText(helpText); } void Application::showHint(std::string errorHint) { static QString binName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); std::cerr << errorHint << std::endl; std::cerr << "Try '" << binName.toStdString() << " --help' for more information" << std::endl; std::exit(1); } bool Application::debugMode() { return _debugMode; } void Application::setHelp() { _helpOnly = true; } QString substLang(const QString &lang) { // Map the more apropriate script codes // to country codes as used by Qt and // transifex translation conventions. // Simplified Chinese if (lang == QLatin1String("zh_Hans")) return QLatin1String("zh_CN"); // Traditional Chinese if (lang == QLatin1String("zh_Hant")) return QLatin1String("zh_TW"); return lang; } void Application::setupTranslations() { QStringList uiLanguages; // uiLanguages crashes on Windows with 4.8.0 release builds #if (QT_VERSION >= 0x040801) || (QT_VERSION >= 0x040800 && !defined(Q_OS_WIN)) uiLanguages = QLocale::system().uiLanguages(); #else // older versions need to fall back to the systems locale uiLanguages << QLocale::system().name(); #endif QString enforcedLocale = Theme::instance()->enforcedLocale(); if (!enforcedLocale.isEmpty()) uiLanguages.prepend(enforcedLocale); QTranslator *translator = new QTranslator(this); QTranslator *qtTranslator = new QTranslator(this); QTranslator *qtkeychainTranslator = new QTranslator(this); foreach(QString lang, uiLanguages) { lang.replace(QLatin1Char('-'), QLatin1Char('_')); // work around QTBUG-25973 lang = substLang(lang); const QString trPath = applicationTrPath(); const QString trFile = QLatin1String("client_") + lang; if (translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en"))) { // Permissive approach: Qt and keychain translations // may be missing, but Qt translations must be there in order // for us to accept the language. Otherwise, we try with the next. // "en" is an exeption as it is the default language and may not // have a translation file provided. qDebug() << Q_FUNC_INFO << "Using" << lang << "translation"; setProperty("ui_lang", lang); const QString qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); const QString qtTrFile = QLatin1String("qt_") + lang; const QString qtBaseTrFile = QLatin1String("qtbase_") + lang; if (!qtTranslator->load(qtTrFile, qtTrPath)) { if (!qtTranslator->load(qtTrFile, trPath)) { qtTranslator->load(qtBaseTrFile, trPath); } } const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang; if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { qtkeychainTranslator->load(qtkeychainTrFile, trPath); } if (!translator->isEmpty()) installTranslator(translator); if (!qtTranslator->isEmpty()) installTranslator(qtTranslator); if (!qtkeychainTranslator->isEmpty()) installTranslator(qtkeychainTranslator); break; } if (property("ui_lang").isNull()) setProperty("ui_lang", "C"); } } bool Application::giveHelp() { return _helpOnly; } bool Application::versionOnly() { return _versionOnly; } void Application::showSettingsDialog() { _gui->slotShowSettings(); } } // namespace OCC