modernize: Use auto where the type is clear from context
[quassel.git] / src / core / core.cpp
index cf943b9..35b46b6 100644 (file)
 #include "coreauthhandler.h"
 #include "coresession.h"
 #include "coresettings.h"
-#include "logger.h"
 #include "internalpeer.h"
+#include "logmessage.h"
 #include "network.h"
 #include "postgresqlstorage.h"
 #include "quassel.h"
 #include "sqlauthenticator.h"
 #include "sqlitestorage.h"
+#include "types.h"
 #include "util.h"
 
-// Currently building with LDAP bindings is optional.
 #ifdef HAVE_LDAP
-#include "ldapauthenticator.h"
+#  include "ldapauthenticator.h"
 #endif
 
 // migration related
@@ -66,21 +66,11 @@ public:
 // ==============================
 //  Core
 // ==============================
-Core *Core::_instance{nullptr};
-
-Core *Core::instance()
-{
-    return _instance;
-}
-
 
 Core::Core()
+    : Singleton<Core>{this}
 {
-    if (_instance) {
-        qWarning() << "Recreating core instance!";
-        delete _instance;
-    }
-    _instance = this;
+    Q_INIT_RESOURCE(sql);
 
     // Parent all QObject-derived attributes, so when the Core instance gets moved into another
     // thread, they get moved with it
@@ -92,29 +82,21 @@ Core::Core()
 
 Core::~Core()
 {
-    saveState();
     qDeleteAll(_connectingClients);
     qDeleteAll(_sessions);
     syncStorage();
-    _instance = nullptr;
 }
 
 
-bool Core::init()
+void Core::init()
 {
     _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
 
-    if (Quassel::runMode() == Quassel::RunMode::CoreOnly) {
-        Quassel::loadTranslation(QLocale::system());
-    }
-
     // check settings version
     // so far, we only have 1
     CoreSettings s;
     if (s.version() != 1) {
-        qCritical() << "Invalid core settings version, terminating!";
-        QCoreApplication::exit(EXIT_FAILURE);
-        return false;
+        throw ExitException{EXIT_FAILURE, tr("Invalid core settings version!")};
     }
 
     // Set up storage and authentication backends
@@ -135,7 +117,8 @@ bool Core::init()
     if (config_from_environment) {
         db_backend = environment.value("DB_BACKEND");
         auth_authenticator = environment.value("AUTH_AUTHENTICATOR");
-    } else {
+    }
+    else {
         CoreSettings cs;
 
         QVariantMap dbsettings = cs.storageSettings().toMap();
@@ -149,12 +132,15 @@ bool Core::init()
         writeError = !cs.isWritable();
     }
 
-    // legacy
-    _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
-
-    // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work!
-    if (_configured) {
-        initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
+    try {
+        _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment);
+        if (_configured) {
+            _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment);
+        }
+    }
+    catch (ExitException) {
+        // Try again later
+        _configured = false;
     }
 
     if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
@@ -165,36 +151,36 @@ bool Core::init()
         if (Quassel::isOptionSet("select-authenticator")) {
             success &= selectAuthenticator(Quassel::optionValue("select-authenticator"));
         }
-        QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
-        return success;
+        throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
     }
 
     if (!_configured) {
         if (config_from_environment) {
-            _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
-            initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
+            try {
+                _configured = initStorage(db_backend, db_connectionProperties, environment, config_from_environment, true);
+                if (_configured) {
+                    _configured = initAuthenticator(auth_authenticator, auth_properties, environment, config_from_environment, true);
+                }
+            }
+            catch (ExitException e) {
+                throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment: %1").arg(e.errorString)};
+            }
 
             if (!_configured) {
-                qWarning() << "Cannot configure from environment";
-                QCoreApplication::exit(EXIT_FAILURE);
-                return false;
+                throw ExitException{EXIT_FAILURE, tr("Cannot configure from environment!")};
             }
         }
         else {
             if (_registeredStorageBackends.empty()) {
-                quWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
-                quWarning()
-                        << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
-                                         "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
-                                         "to work."));
-                QCoreApplication::exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup)
-                return false;
+                throw ExitException{EXIT_FAILURE,
+                                    tr("Could not initialize any storage backend! Exiting...\n"
+                                       "Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
+                                       "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
+                                       "to work.")};
             }
 
             if (writeError) {
-                qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
-                QCoreApplication::exit(EXIT_FAILURE);
-                return false;
+                throw ExitException{EXIT_FAILURE, tr("Cannot write quasselcore configuration; probably a permission problem.")};
             }
 
             quInfo() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
@@ -203,14 +189,12 @@ bool Core::init()
     else {
         if (Quassel::isOptionSet("add-user")) {
             bool success = createUser();
-            QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
-            return success;
+            throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
         }
 
         if (Quassel::isOptionSet("change-userpass")) {
             bool success = changeUserPass(Quassel::optionValue("change-userpass"));
-            QCoreApplication::exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
-            return success;
+            throw ExitException{success ? EXIT_SUCCESS : EXIT_FAILURE};
         }
 
         _strictIdentEnabled = Quassel::isOptionSet("strict-ident");
@@ -222,6 +206,11 @@ bool Core::init()
             _oidentdConfigGenerator = new OidentdConfigGenerator(this);
         }
 
+
+        if (Quassel::isOptionSet("ident-daemon")) {
+            _identServer = new IdentServer(this);
+        }
+
         Quassel::registerReloadHandler([]() {
             // Currently, only reloading SSL certificates and the sysident cache is supported
             if (Core::instance()) {
@@ -240,8 +229,7 @@ bool Core::init()
     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
 
     if (!startListening()) {
-        QCoreApplication::exit(EXIT_FAILURE);  // TODO make this less brutal
-        return false;
+        throw ExitException{EXIT_FAILURE, tr("Cannot open port for listening!")};
     }
 
     if (_configured && !Quassel::isOptionSet("norestore")) {
@@ -254,10 +242,53 @@ bool Core::init()
         connectInternalPeer(_pendingInternalConnection);
         _pendingInternalConnection = {};
     }
+}
 
-    return true;
+
+void Core::initAsync()
+{
+    try {
+        init();
+    }
+    catch (ExitException e) {
+        emit exitRequested(e.exitCode, e.errorString);
+    }
 }
 
+
+void Core::shutdown()
+{
+    quInfo() << "Core shutting down...";
+
+    saveState();
+
+    for (auto &&client : _connectingClients) {
+        client->deleteLater();
+    }
+    _connectingClients.clear();
+
+    if (_sessions.isEmpty()) {
+        emit shutdownComplete();
+        return;
+    }
+
+    for (auto &&session : _sessions) {
+        connect(session, SIGNAL(shutdownComplete(SessionThread*)), this, SLOT(onSessionShutdown(SessionThread*)));
+        session->shutdown();
+    }
+}
+
+
+void Core::onSessionShutdown(SessionThread *session)
+{
+    _sessions.take(_sessions.key(session))->deleteLater();
+    if (_sessions.isEmpty()) {
+        quInfo() << "Core shutdown complete!";
+        emit shutdownComplete();
+    }
+}
+
+
 /*** Session Restore ***/
 
 void Core::saveState()
@@ -320,14 +351,21 @@ QString Core::setupCore(const QString &adminUser, const QString &adminPassword,
     if (adminUser.isEmpty() || adminPassword.isEmpty()) {
         return tr("Admin user or password not set.");
     }
-    if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
-        return tr("Could not setup storage!");
-    }
+    try {
+        if (!(_configured = initStorage(backend, setupData, {}, false, true))) {
+            return tr("Could not setup storage!");
+        }
 
-    quInfo() << "Selected authenticator:" << authenticator;
-    if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
-    {
-        return tr("Could not setup authenticator!");
+        quInfo() << "Selected authenticator:" << authenticator;
+        if (!(_configured = initAuthenticator(authenticator, authSetupData, {}, false, true)))
+        {
+            return tr("Could not setup authenticator!");
+        }
+    }
+    catch (ExitException e) {
+        // Event loop is running, so trigger an exit rather than throwing an exception
+        QCoreApplication::exit(e.exitCode);
+        return e.errorString.isEmpty() ? tr("Fatal failure while trying to setup, terminating") : e.errorString;
     }
 
     if (!saveBackendSettings(backend, setupData)) {
@@ -390,8 +428,7 @@ DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
     return it != _registeredStorageBackends.end() ? *it : nullptr;
 }
 
-// old db settings:
-// "Type" => "sqlite"
+
 bool Core::initStorage(const QString &backend, const QVariantMap &settings,
                        const QProcessEnvironment &environment, bool loadFromEnvironment, bool setup)
 {
@@ -417,12 +454,12 @@ bool Core::initStorage(const QString &backend, const QVariantMap &settings,
             return initStorage(backend, settings, environment, loadFromEnvironment, false);
         return false;
 
-    // if initialization wasn't successful, we quit to keep from coming up unconfigured
     case Storage::NotAvailable:
-        qCritical() << "FATAL: Selected storage backend is not available:" << backend;
         if (!setup) {
-            QCoreApplication::exit(EXIT_FAILURE);
+            // If initialization wasn't successful, we quit to keep from coming up unconfigured
+            throw ExitException{EXIT_FAILURE, tr("Selected storage backend %1 is not available.").arg(backend)};
         }
+        qCritical() << "Selected storage backend is not available:" << backend;
         return false;
 
     case Storage::IsReady:
@@ -517,12 +554,12 @@ bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings
             return initAuthenticator(backend, settings, environment, loadFromEnvironment, false);
         return false;
 
-    // if initialization wasn't successful, we quit to keep from coming up unconfigured
     case Authenticator::NotAvailable:
-        qCritical() << "FATAL: Selected auth backend is not available:" << backend;
         if (!setup) {
-            QCoreApplication::exit(EXIT_FAILURE);
+            // If initialization wasn't successful, we quit to keep from coming up unconfigured
+            throw ExitException{EXIT_FAILURE, tr("Selected auth backend %1 is not available.").arg(backend)};
         }
+        qCritical() << "Selected auth backend is not available:" << backend;
         return false;
 
     case Authenticator::IsReady:
@@ -540,7 +577,7 @@ bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings
 bool Core::sslSupported()
 {
 #ifdef HAVE_SSL
-    SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
+    auto *sslServer = qobject_cast<SslServer *>(&instance()->_server);
     return sslServer && sslServer->isCertValid();
 #else
     return false;
@@ -551,10 +588,10 @@ bool Core::sslSupported()
 bool Core::reloadCerts()
 {
 #ifdef HAVE_SSL
-    SslServer *sslServerv4 = qobject_cast<SslServer *>(&_server);
+    auto *sslServerv4 = qobject_cast<SslServer *>(&_server);
     bool retv4 = sslServerv4->reloadCerts();
 
-    SslServer *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
+    auto *sslServerv6 = qobject_cast<SslServer *>(&_v6server);
     bool retv6 = sslServerv6->reloadCerts();
 
     return retv4 && retv6;
@@ -665,12 +702,20 @@ bool Core::startListening()
     if (!success)
         quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
 
+    if (_identServer) {
+        _identServer->startListening();
+    }
+
     return success;
 }
 
 
 void Core::stopListening(const QString &reason)
 {
+    if (_identServer) {
+        _identServer->stopListening(reason);
+    }
+
     bool wasListening = false;
     if (_server.isListening()) {
         wasListening = true;
@@ -691,12 +736,12 @@ void Core::stopListening(const QString &reason)
 
 void Core::incomingConnection()
 {
-    QTcpServer *server = qobject_cast<QTcpServer *>(sender());
+    auto *server = qobject_cast<QTcpServer *>(sender());
     Q_ASSERT(server);
     while (server->hasPendingConnections()) {
         QTcpSocket *socket = server->nextPendingConnection();
 
-        CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
+        auto *handler = new CoreAuthHandler(socket, this);
         _connectingClients.insert(handler);
 
         connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
@@ -715,7 +760,7 @@ void Core::incomingConnection()
 // Potentially called during the initialization phase (before handing the connection off to the session)
 void Core::clientDisconnected()
 {
-    CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
+    auto *handler = qobject_cast<CoreAuthHandler *>(sender());
     Q_ASSERT(handler);
 
     quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
@@ -734,11 +779,11 @@ void Core::clientDisconnected()
 
 void Core::setupClientSession(RemotePeer *peer, UserId uid)
 {
-    CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
+    auto *handler = qobject_cast<CoreAuthHandler *>(sender());
     Q_ASSERT(handler);
 
     // From now on everything is handled by the client session
-    disconnect(handler, 0, this, 0);
+    disconnect(handler, nullptr, this, nullptr);
     _connectingClients.remove(handler);
     handler->deleteLater();
 
@@ -754,7 +799,7 @@ void Core::setupClientSession(RemotePeer *peer, UserId uid)
 void Core::customEvent(QEvent *event)
 {
     if (event->type() == AddClientEventId) {
-        AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
+        auto *addClientEvent = static_cast<AddClientEvent *>(event);
         addClientHelper(addClientEvent->peer, addClientEvent->userId);
         return;
     }
@@ -784,7 +829,11 @@ void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
 {
     if (!_configured) {
         stopListening();
-        setupCoreForInternalUsage();
+        auto errorString = setupCoreForInternalUsage();
+        if (!errorString.isEmpty()) {
+            emit exitRequested(EXIT_FAILURE, errorString);
+            return;
+        }
     }
 
     UserId uid;
@@ -793,7 +842,7 @@ void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
     }
     else {
         quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
-        QCoreApplication::exit(EXIT_FAILURE);
+        emit exitRequested(EXIT_FAILURE, tr("Cannot setup storage backend."));
         return;
     }
 
@@ -802,7 +851,7 @@ void Core::setupInternalClientSession(QPointer<InternalPeer> clientPeer)
         return;
     }
 
-    InternalPeer *corePeer = new InternalPeer(this);
+    auto *corePeer = new InternalPeer(this);
     corePeer->setPeer(clientPeer);
     clientPeer->setPeer(corePeer);
 
@@ -817,10 +866,7 @@ SessionThread *Core::sessionForUser(UserId uid, bool restore)
     if (_sessions.contains(uid))
         return _sessions[uid];
 
-    SessionThread *session = new SessionThread(uid, restore, strictIdentEnabled(), this);
-    _sessions[uid] = session;
-    session->start();
-    return session;
+    return (_sessions[uid] = new SessionThread(uid, restore, strictIdentEnabled(), this));
 }
 
 
@@ -1135,7 +1181,7 @@ std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *st
     if (!storage)
         return nullptr;
 
-    AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
+    auto *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
     if (!sqlStorage) {
         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
         return nullptr;
@@ -1150,7 +1196,7 @@ std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *st
     if (!storage)
         return nullptr;
 
-    AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
+    auto *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
     if (!sqlStorage) {
         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
         return nullptr;