Rework the handling of storage/auth backends and config
[quassel.git] / src / core / core.cpp
index 4aab673..1a418f1 100644 (file)
@@ -18,6 +18,8 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
+#include <algorithm>
+
 #include <QCoreApplication>
 
 #include "core.h"
@@ -88,9 +90,6 @@ void Core::destroy()
 
 
 Core::Core()
-    : QObject(),
-      _storage(0),
-      _authenticator(0)
 {
 #ifdef HAVE_UMASK
     umask(S_IRWXG | S_IRWXO);
@@ -123,11 +122,11 @@ Core::Core()
 #   endif
         QSettings oldSettings(org, "Quassel Core");
         if (oldSettings.allKeys().count()) {
-            qWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
+            quWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
             foreach(QString key, oldSettings.allKeys())
             newSettings.setValue(key, oldSettings.value(key));
             newSettings.setValue("Config/Version", 1);
-            qWarning() << "*   Your core settings have been migrated to" << newSettings.fileName();
+            quWarning() << "*   Your core settings have been migrated to" << newSettings.fileName();
 
 #ifndef Q_OS_MAC /* we don't need to move the db and cert for mac */
 #ifdef Q_OS_WIN
@@ -145,9 +144,9 @@ Core::Core()
                 if (oldDb.exists()) {
                     bool success = oldDb.rename(Quassel::configDirPath() + "quassel-storage.sqlite");
                     if (success)
-                        qWarning() << "*   Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
+                        quWarning() << "*   Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
                     else
-                        qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
+                        quWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
                 }
             }
             // move certificate
@@ -156,12 +155,12 @@ Core::Core()
                 QFile cert(quasselDir + "quasselCert.pem");
                 bool success = cert.rename(Quassel::configDirPath() + "quasselCert.pem");
                 if (success)
-                    qWarning() << "*   Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
+                    quWarning() << "*   Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
                 else
-                    qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
+                    quWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
             }
 #endif /* !Q_OS_MAC */
-            qWarning() << "*** Migration completed.\n\n";
+            quWarning() << "*** Migration completed.\n\n";
         }
     }
     // MIGRATION end
@@ -174,6 +173,7 @@ Core::Core()
         exit(EXIT_FAILURE);
     }
 
+    // Set up storage and authentication backends
     registerStorageBackends();
     registerAuthenticators();
 
@@ -190,8 +190,10 @@ void Core::init()
     _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap());
 
     // Not entirely sure what is 'legacy' about the above, but it seems to be the way things work!
-    QVariantMap authSettings = cs.authSettings().toMap();
-    initAuthenticator(authSettings.value("Authenticator").toString(), authSettings.value("AuthProperties").toMap());
+    if (_configured) {
+        QVariantMap authSettings = cs.authSettings().toMap();
+        initAuthenticator(authSettings.value("Authenticator", "Database").toString(), authSettings.value("AuthProperties").toMap());
+    }
 
     if (Quassel::isOptionSet("select-backend") || Quassel::isOptionSet("select-authenticator")) {
         if (Quassel::isOptionSet("select-backend")) {
@@ -200,19 +202,18 @@ void Core::init()
         if (Quassel::isOptionSet("select-authenticator")) {
             selectAuthenticator(Quassel::optionValue("select-authenticator"));
         }
-        exit(0);
+        exit(EXIT_SUCCESS);
     }
 
     if (!_configured) {
-        if (!_storageBackends.count()) {
-            qWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
-            qWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
+        if (_registeredStorageBackends.size() == 0) {
+            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."));
-            exit(1); // TODO make this less brutal (especially for mono client -> popup)
+            exit(EXIT_FAILURE); // TODO make this less brutal (especially for mono client -> popup)
         }
-
-        qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
+        quWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
 
         if (!cs.isWritable()) {
             qWarning() << "Cannot write quasselcore configuration; probably a permission problem.";
@@ -247,8 +248,6 @@ Core::~Core()
         handler->deleteLater(); // disconnect non authed clients
     }
     qDeleteAll(_sessions);
-    qDeleteAll(_storageBackends);
-    qDeleteAll(_authenticators);
 }
 
 
@@ -270,18 +269,18 @@ void Core::saveState()
 void Core::restoreState()
 {
     if (!instance()->_configured) {
-        // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
+        // quWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
         return;
     }
     if (instance()->_sessions.count()) {
-        qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
+        quWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
         return;
     }
     CoreSettings s;
     /* We don't check, since we are at the first version since switching to Git
     uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
     if(statever < 1) {
-      qWarning() << qPrintable(tr("Core state too old, ignoring..."));
+      quWarning() << qPrintable(tr("Core state too old, ignoring..."));
       return;
     }
     */
@@ -337,7 +336,7 @@ QString Core::setupCore(const QString &adminUser, const QString &adminPassword,
 
 QString Core::setupCoreForInternalUsage()
 {
-    Q_ASSERT(!_storageBackends.isEmpty());
+    Q_ASSERT(!_registeredStorageBackends.empty());
 
     qsrand(QDateTime::currentDateTime().toTime_t());
     int pass = 0;
@@ -352,98 +351,47 @@ QString Core::setupCoreForInternalUsage()
 
 
 /*** Storage Handling ***/
-void Core::registerStorageBackends()
-{
-    // Register storage backends here!
-    registerStorageBackend(new SqliteStorage(this));
-    registerStorageBackend(new PostgreSqlStorage(this));
-}
 
-
-bool Core::registerStorageBackend(Storage *backend)
+template<typename Storage>
+void Core::registerStorageBackend()
 {
-    if (backend->isAvailable()) {
-        _storageBackends[backend->displayName()] = backend;
-        return true;
-    }
-    else {
+    auto backend = makeDeferredShared<Storage>(this);
+    if (backend->isAvailable())
+        _registeredStorageBackends.emplace_back(std::move(backend));
+    else
         backend->deleteLater();
-        return false;
-    }
-}
-
-
-void Core::unregisterStorageBackends()
-{
-    foreach(Storage *s, _storageBackends.values()) {
-        s->deleteLater();
-    }
-    _storageBackends.clear();
-}
-
-
-void Core::unregisterStorageBackend(Storage *backend)
-{
-    _storageBackends.remove(backend->displayName());
-    backend->deleteLater();
-}
-
-// Authentication handling, now independent from storage.
-// Register and unregister authenticators.
-
-void Core::registerAuthenticators()
-{
-    // Register new authentication backends here!
-    registerAuthenticator(new SqlAuthenticator(this));
-#ifdef HAVE_LDAP
-    registerAuthenticator(new LdapAuthenticator(this));
-#endif
-}
-
-
-bool Core::registerAuthenticator(Authenticator *authenticator)
-{
-    if (authenticator->isAvailable()) {
-        _authenticators[authenticator->backendId()] = authenticator;
-        return true;
-    }
-    else {
-        authenticator->deleteLater();
-        return false;
-    }
 }
 
 
-void Core::unregisterAuthenticators()
+void Core::registerStorageBackends()
 {
-    foreach(Authenticator* a, _authenticators.values()) {
-        a->deleteLater();
+    if (_registeredStorageBackends.empty()) {
+        registerStorageBackend<SqliteStorage>();
+        registerStorageBackend<PostgreSqlStorage>();
     }
-    _authenticators.clear();
 }
 
 
-void Core::unregisterAuthenticator(Authenticator *backend)
+DeferredSharedPtr<Storage> Core::storageBackend(const QString &backendId) const
 {
-    _authenticators.remove(backend->backendId());
-    backend->deleteLater();
+    auto it = std::find_if(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
+                           [backendId](const DeferredSharedPtr<Storage> &backend) {
+                               return backend->displayName() == backendId;
+                           });
+    return it != _registeredStorageBackends.end() ? *it : nullptr;
 }
 
 // old db settings:
 // "Type" => "sqlite"
 bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
 {
-    _storage = 0;
-
     if (backend.isEmpty()) {
+        quWarning() << "No storage backend selected!";
         return false;
     }
 
-    Storage *storage = 0;
-    if (_storageBackends.contains(backend)) {
-        storage = _storageBackends[backend];
-    }
-    else {
+    auto storage = storageBackend(backend);
+    if (!storage) {
         qCritical() << "Selected storage backend is not available:" << backend;
         return false;
     }
@@ -461,39 +409,91 @@ bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool
         exit(EXIT_FAILURE);
     case Storage::IsReady:
         // delete all other backends
-        _storageBackends.remove(backend);
-        unregisterStorageBackends();
-        connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
+        _registeredStorageBackends.clear();
+        connect(storage.get(), SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)),
+                this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
+        break;
     }
-    _storage = storage;
+    _storage = std::move(storage);
     return true;
 }
 
+
+void Core::syncStorage()
+{
+    if (_storage)
+        _storage->sync();
+}
+
+
+/*** Storage Access ***/
+bool Core::createNetwork(UserId user, NetworkInfo &info)
+{
+    NetworkId networkId = instance()->_storage->createNetwork(user, info);
+    if (!networkId.isValid())
+        return false;
+
+    info.networkId = networkId;
+    return true;
+}
+
+
+/*** Authenticators ***/
+
+// Authentication handling, now independent from storage.
+template<typename Authenticator>
+void Core::registerAuthenticator()
+{
+    auto authenticator = makeDeferredShared<Authenticator>(this);
+    if (authenticator->isAvailable())
+        _registeredAuthenticators.emplace_back(std::move(authenticator));
+    else
+        authenticator->deleteLater();
+}
+
+
+void Core::registerAuthenticators()
+{
+    if (_registeredAuthenticators.empty()) {
+        registerAuthenticator<SqlAuthenticator>();
+#ifdef HAVE_LDAP
+        registerAuthenticator<LdapAuthenticator>();
+#endif
+    }
+}
+
+
+DeferredSharedPtr<Authenticator> Core::authenticator(const QString &backendId) const
+{
+    auto it = std::find_if(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
+                           [backendId](const DeferredSharedPtr<Authenticator> &authenticator) {
+                               return authenticator->backendId() == backendId;
+                           });
+    return it != _registeredAuthenticators.end() ? *it : nullptr;
+}
+
+
 // FIXME: Apparently, this is the legacy way of initting storage backends?
 // If there's a not-legacy way, it should be used here
 bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings, bool setup)
 {
-    _authenticator = 0;
-
     if (backend.isEmpty()) {
+        quWarning() << "No authenticator selected!";
         return false;
     }
 
-    Authenticator *authenticator = 0;
-    if (_authenticators.contains(backend)) {
-        authenticator = _authenticators[backend];
-    }
-    else {
+    auto auth = authenticator(backend);
+    if (!auth) {
         qCritical() << "Selected auth backend is not available:" << backend;
         return false;
     }
 
-    Authenticator::State authState = authenticator->init(settings);
+    Authenticator::State authState = auth->init(settings);
     switch (authState) {
     case Authenticator::NeedsSetup:
         if (!setup)
             return false;  // trigger setup process
-        if (authenticator->setup(settings))
+        if (auth->setup(settings))
             return initAuthenticator(backend, settings, false);
     // if initialization wasn't successful, we quit to keep from coming up unconfigured
     case Authenticator::NotAvailable:
@@ -501,29 +501,10 @@ bool Core::initAuthenticator(const QString &backend, const QVariantMap &settings
         exit(EXIT_FAILURE);
     case Authenticator::IsReady:
         // delete all other backends
-        _authenticators.remove(backend);
-        unregisterAuthenticators();
+        _registeredAuthenticators.clear();
+        break;
     }
-    _authenticator = authenticator;
-    return true;
-}
-
-
-void Core::syncStorage()
-{
-    if (_storage)
-        _storage->sync();
-}
-
-
-/*** Storage Access ***/
-bool Core::createNetwork(UserId user, NetworkInfo &info)
-{
-    NetworkId networkId = instance()->_storage->createNetwork(user, info);
-    if (!networkId.isValid())
-        return false;
-
-    info.networkId = networkId;
+    _authenticator = std::move(auth);
     return true;
 }
 
@@ -746,7 +727,7 @@ void Core::setupInternalClientSession(InternalPeer *clientPeer)
         uid = _storage->internalUser();
     }
     else {
-        qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
+        quWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
         return;
     }
 
@@ -774,39 +755,55 @@ SessionThread *Core::sessionForUser(UserId uid, bool restore)
 
 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
 {
-    qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
+    quWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
 }
 
 
 QVariantList Core::backendInfo()
 {
-    QVariantList backends;
-    foreach(const Storage *backend, instance()->_storageBackends.values()) {
+    instance()->registerStorageBackends();
+
+    QVariantList backendInfos;
+    for (auto &&backend : instance()->_registeredStorageBackends) {
         QVariantMap v;
+        v["BackendId"]   = backend->backendId();
         v["DisplayName"] = backend->displayName();
         v["Description"] = backend->description();
-        v["SetupKeys"] = backend->setupKeys();
-        v["SetupDefaults"] = backend->setupDefaults();
-        v["IsDefault"] = isStorageBackendDefault(backend);
-        backends.append(v);
+        v["SetupData"]   = backend->setupData(); // ignored by legacy clients
+
+        // TODO Protocol Break: Remove legacy (cf. authenticatorInfo())
+        const auto &setupData = backend->setupData();
+        QStringList setupKeys;
+        QVariantMap setupDefaults;
+        for (int i = 0; i + 2 < setupData.size(); i += 3) {
+            setupKeys << setupData[i].toString();
+            setupDefaults[setupData[i].toString()] = setupData[i + 2];
+        }
+        v["SetupKeys"]     = setupKeys;
+        v["SetupDefaults"] = setupDefaults;
+        // TODO Protocol Break: Remove
+        v["IsDefault"]     = (backend->backendId() == "SQLite"); // newer clients will just use the first in the list
+
+        backendInfos << v;
     }
-    return backends;
+    return backendInfos;
 }
 
 
 QVariantList Core::authenticatorInfo()
 {
-    QVariantList backends;
-    foreach(const Authenticator *backend, instance()->_authenticators.values()) {
+    instance()->registerAuthenticators();
+
+    QVariantList authInfos;
+    for(auto &&backend : instance()->_registeredAuthenticators) {
         QVariantMap v;
-        v["BackendId"] = backend->backendId();
+        v["BackendId"]   = backend->backendId();
         v["DisplayName"] = backend->displayName();
         v["Description"] = backend->description();
-        v["SetupKeys"] = backend->setupKeys();
-        v["SetupDefaults"] = backend->setupDefaults();
-        backends.append(v);
+        v["SetupData"]   = backend->setupData();
+        authInfos << v;
     }
-    return backends;
+    return authInfos;
 }
 
 // migration / backend selection
@@ -814,14 +811,19 @@ bool Core::selectBackend(const QString &backend)
 {
     // reregister all storage backends
     registerStorageBackends();
-    if (!_storageBackends.contains(backend)) {
-        qWarning() << qPrintable(QString("Core::selectBackend(): unsupported backend: %1").arg(backend));
-        qWarning() << "    supported backends are:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
+    auto storage = storageBackend(backend);
+    if (!storage) {
+        QStringList backends;
+        std::transform(_registeredStorageBackends.begin(), _registeredStorageBackends.end(),
+                       std::back_inserter(backends), [](const DeferredSharedPtr<Storage>& backend) {
+                           return backend->displayName();
+                       });
+        quWarning() << qPrintable(tr("Unsupported storage backend: %1").arg(backend));
+        quWarning() << qPrintable(tr("Supported backends are:")) << qPrintable(backends.join(", "));
         return false;
     }
 
-    Storage *storage = _storageBackends[backend];
-    QVariantMap settings = promptForSettings(storage);
+    QVariantMap settings = promptForSettings(storage.get());
 
     Storage::State storageState = storage->init(settings);
     switch (storageState) {
@@ -829,64 +831,63 @@ bool Core::selectBackend(const QString &backend)
         if (!saveBackendSettings(backend, settings)) {
             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
         }
-        qWarning() << "Switched backend to:" << qPrintable(backend);
-        qWarning() << "Backend already initialized. Skipping Migration";
+        quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
+        quWarning() << qPrintable(tr("Backend already initialized. Skipping Migration..."));
         return true;
     case Storage::NotAvailable:
-        qCritical() << "Backend is not available:" << qPrintable(backend);
+        qCritical() << qPrintable(tr("Storage backend is not available: %1").arg(backend));
         return false;
     case Storage::NeedsSetup:
         if (!storage->setup(settings)) {
-            qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
+            quWarning() << qPrintable(tr("Unable to setup storage backend: %1").arg(backend));
             return false;
         }
 
         if (storage->init(settings) != Storage::IsReady) {
-            qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
+            quWarning() << qPrintable(tr("Unable to initialize storage backend: %1").arg(backend));
             return false;
         }
 
         if (!saveBackendSettings(backend, settings)) {
             qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
         }
-        qWarning() << "Switched backend to:" << qPrintable(backend);
+        quWarning() << qPrintable(tr("Switched storage backend to: %1").arg(backend));
         break;
     }
 
     // let's see if we have a current storage object we can migrate from
-    AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
-    AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
+    auto reader = getMigrationReader(_storage.get());
+    auto writer = getMigrationWriter(storage.get());
     if (reader && writer) {
-        qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
-        delete _storage;
-        _storage = 0;
-        delete storage;
-        storage = 0;
+        qDebug() << qPrintable(tr("Migrating storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
+        _storage.reset();
+        storage.reset();
         if (reader->migrateTo(writer)) {
             qDebug() << "Migration finished!";
+            qDebug() << qPrintable(tr("Migration finished!"));
             if (!saveBackendSettings(backend, settings)) {
                 qCritical() << qPrintable(QString("Could not save backend settings, probably a permission problem."));
                 return false;
             }
             return true;
         }
+        quWarning() << qPrintable(tr("Unable to migrate storage backend! (No migration writer for %1)").arg(backend));
         return false;
-        qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
     }
 
     // inform the user why we cannot merge
     if (!_storage) {
-        qWarning() << "No currently active backend. Skipping migration.";
+        quWarning() << qPrintable(tr("No currently active storage backend. Skipping migration..."));
     }
     else if (!reader) {
-        qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
+        quWarning() << qPrintable(tr("Currently active storage backend does not support migration: %1").arg(_storage->displayName()));
     }
     if (writer) {
-        qWarning() << "New backend does not support migration:" << qPrintable(backend);
+        quWarning() << qPrintable(tr("New storage backend does not support migration: %1").arg(backend));
     }
 
     // so we were unable to merge, but let's create a user \o/
-    _storage = storage;
+    _storage = std::move(storage);
     createUser();
     return true;
 }
@@ -897,41 +898,45 @@ bool Core::selectAuthenticator(const QString &backend)
 {
     // Register all authentication backends.
     registerAuthenticators();
-    if (!_authenticators.contains(backend)) {
-        qWarning() << qPrintable(QString("Core::selectAuthenticator(): unsupported backend: %1").arg(backend));
-        qWarning() << "    supported backends are:" << qPrintable(QStringList(_authenticators.keys()).join(", "));
+    auto auther = authenticator(backend);
+    if (!auther) {
+        QStringList authenticators;
+        std::transform(_registeredAuthenticators.begin(), _registeredAuthenticators.end(),
+                       std::back_inserter(authenticators), [](const DeferredSharedPtr<Authenticator>& authenticator) {
+                           return authenticator->displayName();
+                       });
+        quWarning() << qPrintable(tr("Unsupported authenticator: %1").arg(backend));
+        quWarning() << qPrintable(tr("Supported authenticators are:")) << qPrintable(authenticators.join(", "));
         return false;
     }
 
-    Authenticator *authenticator = _authenticators[backend];
-    QVariantMap settings = promptForSettings(authenticator);
+    QVariantMap settings = promptForSettings(auther.get());
 
-    Authenticator::State state = authenticator->init(settings);
+    Authenticator::State state = auther->init(settings);
     switch (state) {
     case Authenticator::IsReady:
         saveAuthenticatorSettings(backend, settings);
-        qWarning() << "Switched auth backend to:" << qPrintable(backend);
-//        qWarning() << "Auth backend already initialized. Skipping Migration";
+        quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
         return true;
     case Authenticator::NotAvailable:
-        qCritical() << "Auth backend is not available:" << qPrintable(backend);
+        qCritical() << qPrintable(tr("Authenticator is not available: %1").arg(backend));
         return false;
     case Authenticator::NeedsSetup:
-        if (!authenticator->setup(settings)) {
-            qWarning() << qPrintable(QString("Core::selectAuthenticator(): unable to setup authenticator: %1").arg(backend));
+        if (!auther->setup(settings)) {
+            quWarning() << qPrintable(tr("Unable to setup authenticator: %1").arg(backend));
             return false;
         }
 
-        if (authenticator->init(settings) != Authenticator::IsReady) {
-            qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize authenticator: %1").arg(backend));
+        if (auther->init(settings) != Authenticator::IsReady) {
+            quWarning() << qPrintable(tr("Unable to initialize authenticator: %1").arg(backend));
             return false;
         }
 
         saveAuthenticatorSettings(backend, settings);
-        qWarning() << "Switched auth backend to:" << qPrintable(backend);
+        quWarning() << qPrintable(tr("Switched authenticator to: %1").arg(backend));
     }
 
-    _authenticator = authenticator;
+    _authenticator = std::move(auther);
     return true;
 }
 
@@ -957,11 +962,11 @@ bool Core::createUser()
     enableStdInEcho();
 
     if (password != password2) {
-        qWarning() << "Passwords don't match!";
+        quWarning() << "Passwords don't match!";
         return false;
     }
     if (password.isEmpty()) {
-        qWarning() << "Password is empty!";
+        quWarning() << "Password is empty!";
         return false;
     }
 
@@ -970,7 +975,7 @@ bool Core::createUser()
         return true;
     }
     else {
-        qWarning() << "Unable to add user:" << qPrintable(username);
+        quWarning() << "Unable to add user:" << qPrintable(username);
         return false;
     }
 }
@@ -1005,11 +1010,11 @@ bool Core::changeUserPass(const QString &username)
     enableStdInEcho();
 
     if (password != password2) {
-        qWarning() << "Passwords don't match!";
+        quWarning() << "Passwords don't match!";
         return false;
     }
     if (password.isEmpty()) {
-        qWarning() << "Password is empty!";
+        quWarning() << "Password is empty!";
         return false;
     }
 
@@ -1018,7 +1023,7 @@ bool Core::changeUserPass(const QString &username)
         return true;
     }
     else {
-        qWarning() << "Failed to change password!";
+        quWarning() << "Failed to change password!";
         return false;
     }
 }
@@ -1054,30 +1059,30 @@ bool Core::canChangeUserPassword(UserId userId)
 }
 
 
-AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
+std::unique_ptr<AbstractSqlMigrationReader> Core::getMigrationReader(Storage *storage)
 {
     if (!storage)
-        return 0;
+        return nullptr;
 
     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
     if (!sqlStorage) {
         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
-        return 0;
+        return nullptr;
     }
 
     return sqlStorage->createMigrationReader();
 }
 
 
-AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
+std::unique_ptr<AbstractSqlMigrationWriter> Core::getMigrationWriter(Storage *storage)
 {
     if (!storage)
-        return 0;
+        return nullptr;
 
     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
     if (!sqlStorage) {
         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
-        return 0;
+        return nullptr;
     }
 
     return sqlStorage->createMigrationWriter();
@@ -1105,71 +1110,48 @@ void Core::saveAuthenticatorSettings(const QString &backend, const QVariantMap &
 
 // Generic version of promptForSettings that doesn't care what *type* of
 // backend it runs over.
-QVariantMap Core::promptForSettings(QStringList keys, QVariantMap defaults)
+template<typename Backend>
+QVariantMap Core::promptForSettings(const Backend *backend)
 {
     QVariantMap settings;
+    const QVariantList& setupData = backend->setupData();
 
-    if (keys.isEmpty())
+    if (setupData.isEmpty())
         return settings;
 
     QTextStream out(stdout);
     QTextStream in(stdin);
     out << "Default values are in brackets" << endl;
 
-    QString value;
-    foreach(QString key, keys) {
-        QVariant val;
-        if (defaults.contains(key)) {
-            val = defaults[key];
-        }
-        out << key;
-        if (!val.toString().isEmpty()) {
-            out << " (" << val.toString() << ")";
-        }
-        out << ": ";
-        out.flush();
+    for (int i = 0; i + 2 < setupData.size(); i += 3) {
+        QString key = setupData[i].toString();
+        out << setupData[i+1].toString() << " [" << setupData[i+2].toString() << "]: " << flush;
 
-        bool noEcho = QString("password").toLower().startsWith(key.toLower());
+        bool noEcho = key.toLower().contains("password");
         if (noEcho) {
             disableStdInEcho();
         }
-        value = in.readLine().trimmed();
+        QString input = in.readLine().trimmed();
         if (noEcho) {
             out << endl;
             enableStdInEcho();
         }
 
-        if (!value.isEmpty()) {
-            switch (defaults[key].type()) {
+        QVariant value{setupData[i+2]};
+        if (!input.isEmpty()) {
+            switch (value.type()) {
             case QVariant::Int:
-                val = QVariant(value.toInt());
+                value = input.toInt();
                 break;
             default:
-                val = QVariant(value);
+                value = input;
             }
         }
-        settings[key] = val;
+        settings[key] = value;
     }
     return settings;
 }
 
-// Since an auth and storage backend work basically the same way,
-// use polymorphism here on this routine.
-QVariantMap Core::promptForSettings(const Storage *storage)
-{
-    QStringList keys = storage->setupKeys();
-    QVariantMap defaults = storage->setupDefaults();
-    return Core::promptForSettings(keys, defaults);
-}
-
-
-QVariantMap Core::promptForSettings(const Authenticator *authenticator)
-{
-    QStringList keys = authenticator->setupKeys();
-    QVariantMap defaults = authenticator->setupDefaults();
-    return Core::promptForSettings(keys, defaults);
-}
-
 
 #ifdef Q_OS_WIN
 void Core::stdInEcho(bool on)
@@ -1184,7 +1166,6 @@ void Core::stdInEcho(bool on)
     SetConsoleMode(hStdin, mode);
 }
 
-
 #else
 void Core::stdInEcho(bool on)
 {
@@ -1197,5 +1178,4 @@ void Core::stdInEcho(bool on)
     tcsetattr(STDIN_FILENO, TCSANOW, &t);
 }
 
-
 #endif /* Q_OS_WIN */