Rework the handling of storage/auth backends and config
authorManuel Nickschas <sputnick@quassel-irc.org>
Tue, 1 Nov 2016 16:34:16 +0000 (17:34 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 30 Aug 2017 21:55:08 +0000 (23:55 +0200)
The legacy way of transmitting backend settings to the client
for initial core configuration was pretty weird, especially regarding
the setupKeys/setupDefaults mess that required guesswork on the
client side as of the property types. With the introduction of
authenticators, an opportunity presented itself to improve this.

Upon fixing this, I realized that lots of pretty crazy legacy code
was involved, and partially even duplicated for authenticator
handling. I could not keep myself from cleaning that up.
In the process, the affected parts of the code were modernized too,
leveraging Modern C++ and the STL where possible.

The setupKeys/setupDefaults properties of the backend info structs
are now deprecated and replaced by a setupData list of field IDs,
translatable display names, and default values/types. To keep
legacy clients working, the old properties are still transmitted
until the next protocol break.

20 files changed:
src/core/abstractsqlstorage.cpp
src/core/abstractsqlstorage.h
src/core/authenticator.cpp
src/core/authenticator.h
src/core/core.cpp
src/core/core.h
src/core/ldapauthenticator.cpp
src/core/ldapauthenticator.h
src/core/postgresqlstorage.cpp
src/core/postgresqlstorage.h
src/core/sqlauthenticator.cpp
src/core/sqlauthenticator.h
src/core/sqlitestorage.cpp
src/core/sqlitestorage.h
src/core/storage.h
src/qtui/coreconfigwizard.cpp
src/qtui/coreconfigwizard.h
src/qtui/ui/coreconfigwizardauthenticationselectionpage.ui
src/qtui/ui/coreconfigwizardstorageselectionpage.ui
src/qtui/ui/coreconfigwizardsyncpage.ui

index c100d4e..a0cf64c 100644 (file)
@@ -144,7 +144,7 @@ Storage::State AbstractSqlStorage::init(const QVariantMap &settings)
         }
     }
 
-    quInfo() << qPrintable(displayName()) << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
+    quInfo() << qPrintable(displayName()) << "storage backend is ready. Schema version:" << installedSchemaVersion();
     return IsReady;
 }
 
index 6c469df..8a58be9 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef ABSTRACTSQLSTORAGE_H
-#define ABSTRACTSQLSTORAGE_H
+#pragma once
 
 #include "storage.h"
 
+#include <memory>
+
 #include <QSqlDatabase>
 #include <QSqlQuery>
 #include <QSqlError>
@@ -38,8 +39,8 @@ public:
     AbstractSqlStorage(QObject *parent = 0);
     virtual ~AbstractSqlStorage();
 
-    virtual inline AbstractSqlMigrationReader *createMigrationReader() { return 0; }
-    virtual inline AbstractSqlMigrationWriter *createMigrationWriter() { return 0; }
+    virtual std::unique_ptr<AbstractSqlMigrationReader> createMigrationReader() { return {}; }
+    virtual std::unique_ptr<AbstractSqlMigrationWriter> createMigrationWriter() { return {}; }
 
 public slots:
     virtual State init(const QVariantMap &settings = QVariantMap());
@@ -357,6 +358,3 @@ public:
     virtual inline bool postProcess() { return true; }
     friend class AbstractSqlMigrationReader;
 };
-
-
-#endif
index 21b6e48..16c83ce 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
+// Make moc happy
 #include "authenticator.h"
-
-Authenticator::Authenticator(QObject *parent)
-    : QObject(parent)
-{
-}
-
+#include "moc_authenticator.cpp"
index 3bec6f6..55930d9 100644 (file)
@@ -32,8 +32,8 @@ class Authenticator : public QObject {
     Q_OBJECT
 
 public:
-    Authenticator(QObject *parent = 0);
-    virtual ~Authenticator() {};
+    using QObject::QObject;
+    ~Authenticator() override = default;
 
     enum State {
         IsReady,      // ready to go
@@ -64,16 +64,18 @@ public slots:
     /** \return A string that can be displayed by the client to describe the authenticator */
     virtual QString description() const = 0;
 
-    //! Returns a list of properties required to use the authenticator backend
-    virtual QStringList setupKeys() const = 0;
+    //! Returns data required to configure the authenticator backend
+    /**
+     * A list of flattened triples for each field: {key, translated field name, default value}
+     * The default value's type determines the kind of input widget to be shown
+     * (int -> QSpinBox; QString -> QLineEdit)
+     * \return A list of triples defining the data to be shown in the configuration dialog
+     */
+    virtual QVariantList setupData() const = 0;
 
     //! Checks if the authenticator allows manual password changes from inside quassel.
     virtual bool canChangePassword() const = 0;
 
-    //! Returns a map where the keys are are properties to use the authenticator backend
-    /*  the values are QVariants with default values */
-    virtual QVariantMap setupDefaults() const = 0;
-
     //! Setup the authenticator provider.
     /** This prepares the authenticator provider (e.g. create tables, etc.) for use within Quassel.
      *  \param settings   Hostname, port, username, password, ...
@@ -93,7 +95,4 @@ public slots:
      *  \return A valid UserId if the password matches the username; 0 else
      */
     virtual UserId validateUser(const QString &user, const QString &password) = 0;
-
-private:
-
 };
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 */
index 66fc50b..333d067 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef CORE_H
-#define CORE_H
+#pragma once
+
+#include <memory>
+#include <vector>
 
 #include <QDateTime>
 #include <QString>
@@ -36,6 +38,7 @@
 
 #include "authenticator.h"
 #include "bufferinfo.h"
+#include "deferredptr.h"
 #include "message.h"
 #include "oidentdconfiggenerator.h"
 #include "sessionthread.h"
@@ -543,19 +546,6 @@ public:
     static QVariantList backendInfo();
     static QVariantList authenticatorInfo();
 
-    /**
-     * Checks if a storage backend is the default storage backend. This
-     * hardcodes this information into the core (not the client).
-     *
-     * \param backend    The backend to check.
-     *
-     * @return True if storage backend is default, false otherwise.
-     */
-    static inline bool isStorageBackendDefault(const Storage *backend)
-    {
-        return (backend->displayName() == "SQLite") ? true : false;
-    }
-
     static QString setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData, const QString &authenticator, const QVariantMap &authSetupMap);
 
     static inline QTimer &syncTimer() { return instance()->_storageSyncTimer; }
@@ -607,34 +597,34 @@ private:
     //void processCoreSetup(QTcpSocket *socket, QVariantMap &msg);
     QString setupCoreForInternalUsage();
 
-    void registerStorageBackends();
-    bool registerStorageBackend(Storage *);
-    void unregisterStorageBackends();
-    void unregisterStorageBackend(Storage *);
+    bool createUser();
 
+    template<typename Storage>
+    void registerStorageBackend();
+
+    template<typename Authenticator>
+    void registerAuthenticator();
+
+    void registerStorageBackends();
     void registerAuthenticators();
-    bool registerAuthenticator(Authenticator *);
-    void unregisterAuthenticators();
-    void unregisterAuthenticator(Authenticator *);
+
+    DeferredSharedPtr<Storage>       storageBackend(const QString& backendId) const;
+    DeferredSharedPtr<Authenticator> authenticator(const QString& authenticatorId) const;
 
     bool selectBackend(const QString &backend);
     bool selectAuthenticator(const QString &backend);
-    bool createUser();
 
     bool saveBackendSettings(const QString &backend, const QVariantMap &settings);
     void saveAuthenticatorSettings(const QString &backend, const QVariantMap &settings);
 
-    QVariantMap promptForSettings(const Storage *storage);
-    QVariantMap promptForSettings(const Authenticator *authenticator);
-    QVariantMap promptForSettings(QStringList keys, QVariantMap defaults);
+    template<typename Backend>
+    QVariantMap promptForSettings(const Backend *backend);
 
 private:
     QSet<CoreAuthHandler *> _connectingClients;
     QHash<UserId, SessionThread *> _sessions;
-
-    // Have both a storage backend and an authenticator backend.
-    Storage *_storage;
-    Authenticator *_authenticator;
+    DeferredSharedPtr<Storage>       _storage;        ///< Active storage backend
+    DeferredSharedPtr<Authenticator> _authenticator;  ///< Active authenticator
     QTimer _storageSyncTimer;
 
 #ifdef HAVE_SSL
@@ -643,21 +633,18 @@ private:
     QTcpServer _server, _v6server;
 #endif
 
-    OidentdConfigGenerator *_oidentdConfigGenerator;
+    OidentdConfigGenerator *_oidentdConfigGenerator {nullptr};
 
-    QHash<QString, Storage *> _storageBackends;
-    QHash<QString, Authenticator *> _authenticators;
+    std::vector<DeferredSharedPtr<Storage>>       _registeredStorageBackends;
+    std::vector<DeferredSharedPtr<Authenticator>> _registeredAuthenticators;
 
     QDateTime _startTime;
 
     bool _configured;
 
-    static AbstractSqlMigrationReader *getMigrationReader(Storage *storage);
-    static AbstractSqlMigrationWriter *getMigrationWriter(Storage *storage);
+    static std::unique_ptr<AbstractSqlMigrationReader> getMigrationReader(Storage *storage);
+    static std::unique_ptr<AbstractSqlMigrationWriter> getMigrationWriter(Storage *storage);
     static void stdInEcho(bool on);
     static inline void enableStdInEcho() { stdInEcho(true); }
     static inline void disableStdInEcho() { stdInEcho(false); }
 };
-
-
-#endif
index 227df19..34768fa 100644 (file)
@@ -83,28 +83,20 @@ QString LdapAuthenticator::description() const
     return tr("Authenticate users using an LDAP server.");
 }
 
-QStringList LdapAuthenticator::setupKeys() const
-{
-    // The parameters needed for LDAP.
-    QStringList keys;
-    keys << "Hostname"
-         << "Port"
-         << "Bind DN"
-         << "Bind Password"
-         << "Base DN"
-         << "Filter"
-         << "UID Attribute";
-    return keys;
-}
 
-
-QVariantMap LdapAuthenticator::setupDefaults() const
+QVariantList LdapAuthenticator::setupData() const
 {
-    QVariantMap map;
-    map["Hostname"] = QVariant(QString("ldap://localhost"));
-    map["Port"] = QVariant(DEFAULT_LDAP_PORT);
-    map["UID Attribute"] = QVariant(QString("uid"));
-    return map;
+    // The parameters needed for LDAP.
+    QVariantList data;
+    data << "Hostname"     << tr("Hostname")      << QString{"ldap://localhost"}
+         << "Port"         << tr("Port")          << DEFAULT_LDAP_PORT
+         << "BindDN"       << tr("Bind DN")       << QString{}
+         << "BindPassword" << tr("Bind Password") << QString{}
+         << "BaseDN"       << tr("Base DN")       << QString{}
+         << "Filter"       << tr("Filter")        << QString{}
+         << "UidAttribute" << tr("UID Attribute") << QString{"uid"}
+         ;
+    return data;
 }
 
 
@@ -112,11 +104,11 @@ void LdapAuthenticator::setAuthProperties(const QVariantMap &properties)
 {
     _hostName = properties["Hostname"].toString();
     _port = properties["Port"].toInt();
-    _baseDN = properties["Base DN"].toString();
+    _bindDN = properties["BindDN"].toString();
+    _bindPassword = properties["BindPassword"].toString();
+    _baseDN = properties["BaseDN"].toString();
     _filter = properties["Filter"].toString();
-    _bindDN = properties["Bind DN"].toString();
-    _bindPassword = properties["Bind Password"].toString();
-    _uidAttribute = properties["UID Attribute"].toString();
+    _uidAttribute = properties["UidAttribute"].toString();
 }
 
 // TODO: this code is sufficiently general that in the future, perhaps an abstract
@@ -160,11 +152,11 @@ Authenticator::State LdapAuthenticator::init(const QVariantMap &settings)
 
     bool status = ldapConnect();
     if (!status) {
-        quInfo() << qPrintable(backendId()) << "Authenticator cannot connect.";
+        quInfo() << qPrintable(backendId()) << "authenticator cannot connect.";
         return NotAvailable;
     }
 
-    quInfo() << qPrintable(backendId()) << "Authenticator is ready.";
+    quInfo() << qPrintable(backendId()) << "authenticator is ready.";
     return IsReady;
 }
 
index 8415496..4e58612 100644 (file)
@@ -43,7 +43,7 @@
 //#endif
 
 // Default LDAP server port.
-#define DEFAULT_LDAP_PORT 389
+constexpr int DEFAULT_LDAP_PORT = 389;
 
 class LdapAuthenticator : public Authenticator
 {
@@ -51,34 +51,33 @@ class LdapAuthenticator : public Authenticator
 
 public:
     LdapAuthenticator(QObject *parent = 0);
-    virtual ~LdapAuthenticator();
+    ~LdapAuthenticator() override;
 
 public slots:
     /* General */
-    bool isAvailable() const;
-    QString backendId() const;
-    QString displayName() const;
-    QString description() const;
-    virtual QStringList setupKeys() const;
-    virtual QVariantMap setupDefaults() const;
+    bool isAvailable() const override;
+    QString backendId() const override;
+    QString displayName() const override;
+    QString description() const override;
+    QVariantList setupData() const override;
 
-    virtual inline bool canChangePassword() const { return false; }
+    bool canChangePassword() const override { return false; }
 
-    bool setup(const QVariantMap &settings = QVariantMap());
-    State init(const QVariantMap &settings = QVariantMap());
-    UserId validateUser(const QString &user, const QString &password);
+    bool setup(const QVariantMap &settings = {}) override;
+    State init(const QVariantMap &settings = {}) override;
+    UserId validateUser(const QString &user, const QString &password) override;
 
 protected:
-    virtual void setAuthProperties(const QVariantMap &properties);
+    void setAuthProperties(const QVariantMap &properties);
     bool ldapConnect();
     void ldapDisconnect();
     bool ldapAuth(const QString &username, const QString &password);
 
     // Protected methods for retrieving info about the LDAP connection.
-    inline virtual QString hostName() { return _hostName; }
-    inline virtual int port() { return _port; }
-    inline virtual QString bindDN() { return _bindDN; }
-    inline virtual QString baseDN() { return _baseDN; }
+    QString hostName() const { return _hostName; }
+    int port() const { return _port; }
+    QString bindDN() const { return _bindDN; }
+    QString baseDN() const { return _baseDN; }
 
 private:
     QString _hostName;
@@ -90,6 +89,5 @@ private:
     QString _uidAttribute;
 
     // The actual connection object.
-    LDAP *_connection;
-
+    LDAP *_connection {nullptr};
 };
index ce543ae..ac6dec9 100644 (file)
@@ -38,9 +38,9 @@ PostgreSqlStorage::~PostgreSqlStorage()
 }
 
 
-AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter()
+std::unique_ptr<AbstractSqlMigrationWriter> PostgreSqlStorage::createMigrationWriter()
 {
-    PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
+    auto writer = new PostgreSqlMigrationWriter();
     QVariantMap properties;
     properties["Username"] = _userName;
     properties["Password"] = _password;
@@ -48,51 +48,50 @@ AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter()
     properties["Port"] = _port;
     properties["Database"] = _databaseName;
     writer->setConnectionProperties(properties);
-    return writer;
+    return std::unique_ptr<AbstractSqlMigrationWriter>{writer};
 }
 
 
 bool PostgreSqlStorage::isAvailable() const
 {
-    qDebug() << QSqlDatabase::drivers();
-    if (!QSqlDatabase::isDriverAvailable("QPSQL")) return false;
+    if (!QSqlDatabase::isDriverAvailable("QPSQL")) {
+        quWarning() << qPrintable(tr("PostgreSQL driver plugin not available for Qt. Installed drivers:"))
+                    << qPrintable(QSqlDatabase::drivers().join(", "));
+        return false;
+    }
     return true;
 }
 
 
-QString PostgreSqlStorage::displayName() const
+QString PostgreSqlStorage::backendId() const
 {
     return QString("PostgreSQL");
 }
 
 
-QString PostgreSqlStorage::description() const
+QString PostgreSqlStorage::displayName() const
 {
-    // FIXME: proper description
-    return tr("PostgreSQL Turbo Bomber HD!");
+    return backendId(); // Note: Pre-0.13 clients use the displayName property for backend idenfication
 }
 
 
-QStringList PostgreSqlStorage::setupKeys() const
+QString PostgreSqlStorage::description() const
 {
-    QStringList keys;
-    keys << "Username"
-         << "Password"
-         << "Hostname"
-         << "Port"
-         << "Database";
-    return keys;
+    // FIXME: proper description
+    return tr("PostgreSQL Turbo Bomber HD!");
 }
 
 
-QVariantMap PostgreSqlStorage::setupDefaults() const
+QVariantList PostgreSqlStorage::setupData() const
 {
-    QVariantMap map;
-    map["Username"] = QVariant(QString("quassel"));
-    map["Hostname"] = QVariant(QString("localhost"));
-    map["Port"] = QVariant(5432);
-    map["Database"] = QVariant(QString("quassel"));
-    return map;
+    QVariantList data;
+    data << "Username" << tr("Username") << QString("quassel")
+         << "Password" << tr("Password") << QString()
+         << "Hostname" << tr("Hostname") << QString("localhost")
+         << "Port"     << tr("Port")     << 5432
+         << "Database" << tr("Database") << QString("quassel")
+         ;
+    return data;
 }
 
 
index 530e7aa..c11a819 100644 (file)
@@ -34,15 +34,15 @@ public:
     PostgreSqlStorage(QObject *parent = 0);
     virtual ~PostgreSqlStorage();
 
-    virtual AbstractSqlMigrationWriter *createMigrationWriter();
+    virtual std::unique_ptr<AbstractSqlMigrationWriter> createMigrationWriter();
 
 public slots:
     /* General */
     virtual bool isAvailable() const;
+    virtual QString backendId() const;
     virtual QString displayName() const;
     virtual QString description() const;
-    virtual QStringList setupKeys() const;
-    virtual QVariantMap setupDefaults() const;
+    virtual QVariantList setupData() const;
 
     // TODO: Add functions for configuring the backlog handling, i.e. defining auto-cleanup settings etc
 
index 3be817b..b6984d2 100644 (file)
@@ -61,8 +61,8 @@ QString SqlAuthenticator::displayName() const
 
 QString SqlAuthenticator::description() const
 {
-    return tr("Do not auth against any remote authentication service, but instead save a hashed and salted password "
-              "in the selected database.");
+    return tr("Do not authenticate against any remote service, but instead save a hashed and salted password "
+              "in the database selected in the next step.");
 }
 
 
@@ -86,6 +86,6 @@ Authenticator::State SqlAuthenticator::init(const QVariantMap &settings)
     // TODO: FIXME: this should check if the storage provider is ready, but I don't
     // know if there's an exposed way to do that at the moment.
 
-    quInfo() << qPrintable(backendId()) << "Authenticator is ready.";
+    quInfo() << qPrintable(backendId()) << "authenticator is ready.";
     return IsReady;
 }
index b5a010a..41b366f 100644 (file)
@@ -36,8 +36,7 @@ public slots:
     QString backendId() const;
     QString displayName() const;
     QString description() const;
-    virtual inline QStringList setupKeys() const { return QStringList(); }
-    virtual inline QVariantMap setupDefaults() const { return QVariantMap(); }
+    virtual inline QVariantList setupData() const { return {}; }
 
     virtual inline bool canChangePassword() const { return true; }
 
@@ -47,5 +46,4 @@ public slots:
 
     /* User handling */
     //virtual UserId getUserId(const QString &username);
-
 };
index 6a8965b..c14d7cb 100644 (file)
@@ -46,12 +46,19 @@ bool SqliteStorage::isAvailable() const
 }
 
 
+QString SqliteStorage::backendId() const
+{
+    return QString("SQLite");
+}
+
+
 QString SqliteStorage::displayName() const
 {
+    // Note: Pre-0.13 clients use the displayName property for backend idenfication
     // We identify the backend to use for the monolithic core by its displayname.
     // so only change this string if you _really_ have to and make sure the core
     // setup for the mono client still works ;)
-    return QString("SQLite");
+    return backendId();
 }
 
 
index 8287f38..54f1cb4 100644 (file)
@@ -35,15 +35,15 @@ public:
     SqliteStorage(QObject *parent = 0);
     virtual ~SqliteStorage();
 
-    virtual AbstractSqlMigrationReader *createMigrationReader();
+    virtual std::unique_ptr<AbstractSqlMigrationReader> createMigrationReader();
 
 public slots:
     /* General */
 
     bool isAvailable() const;
+    QString backendId() const;
     QString displayName() const;
-    virtual inline QStringList setupKeys() const { return QStringList(); }
-    virtual inline QVariantMap setupDefaults() const { return QVariantMap(); }
+    virtual inline QVariantList setupData() const { return {}; }
     QString description() const;
 
     // TODO: Add functions for configuring the backlog handling, i.e. defining auto-cleanup settings etc
@@ -161,9 +161,9 @@ private:
 };
 
 
-inline AbstractSqlMigrationReader *SqliteStorage::createMigrationReader()
+inline std::unique_ptr<AbstractSqlMigrationReader> SqliteStorage::createMigrationReader()
 {
-    return new SqliteMigrationReader();
+    return std::unique_ptr<AbstractSqlMigrationReader>{new SqliteMigrationReader()};
 }
 
 
index 29a8df8..fec8d86 100644 (file)
@@ -46,7 +46,7 @@ public:
         Sha1,
         Sha2_512,
         Latest=Sha2_512
-        
+
     };
 
 public slots:
@@ -59,6 +59,10 @@ public slots:
      */
     virtual bool isAvailable() const = 0;
 
+    //! Returns the identifier of the authenticator backend
+    /** \return A string that can be used by the client to identify the authenticator backend */
+    virtual QString backendId() const = 0;
+
     //! Returns the display name of the storage backend
     /** \return A string that can be used by the client to name the storage backend */
     virtual QString displayName() const = 0;
@@ -67,12 +71,14 @@ public slots:
     /** \return A string that can be displayed by the client to describe the storage backend */
     virtual QString description() const = 0;
 
-    //! Returns a list of properties required to use the storage backend
-    virtual QStringList setupKeys() const = 0;
-
-    //! Returns a map where the keys are are properties to use the storage backend
-    /*  the values are QVariants with default values */
-    virtual QVariantMap setupDefaults() const = 0;
+    //! Returns data required to configure the authenticator backend
+    /**
+     * A list of flattened triples for each field: {key, translated field name, default value}
+     * The default value's type determines the kind of input widget to be shown
+     * (int -> QSpinBox; QString -> QLineEdit)
+     * \return A list of triples defining the data to be shown in the configuration dialog
+     */
+    virtual QVariantList setupData() const = 0;
 
     //! Setup the storage provider.
     /** This prepares the storage provider (e.g. create tables, etc.) for use within Quassel.
index 401fd61..18133f0 100644 (file)
 
 #include "client.h"
 
-CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QList<QVariant> &backends, const QList<QVariant> &authenticators, QWidget *parent)
+namespace {
+
+template<typename FieldInfo>
+void createFieldWidgets(QGroupBox *fieldBox, const std::vector<FieldInfo> &fieldInfos)
+{
+    // Create a config UI based on the field types sent from the backend
+    // We make some assumptions here (like integer range and password field names) that may not
+    // hold true for future authenticator types - but the only way around it for now would be to
+    // provide specialized config widgets for those (which may be a good idea anyway, e.g. if we
+    // think about client-side translations...)
+
+    QFormLayout *formLayout = new QFormLayout;
+    for (auto &&fieldInfo : fieldInfos) {
+        QWidget *widget {nullptr};
+        switch (std::get<2>(fieldInfo).type()) {
+            case QVariant::Int:
+                widget = new QSpinBox(fieldBox);
+                // Here we assume that int fields are always in 16 bit range, like ports
+                static_cast<QSpinBox *>(widget)->setMinimum(0);
+                static_cast<QSpinBox *>(widget)->setMaximum(65535);
+                static_cast<QSpinBox *>(widget)->setValue(std::get<2>(fieldInfo).toInt());
+                break;
+            case QVariant::String:
+                widget = new QLineEdit(std::get<2>(fieldInfo).toString(), fieldBox);
+                // Here we assume that fields named something with "password" are actual password inputs
+                if (std::get<0>(fieldInfo).toLower().contains("password"))
+                    static_cast<QLineEdit *>(widget)->setEchoMode(QLineEdit::Password);
+                break;
+            default:
+                qWarning() << "Unsupported type for backend property" << std::get<0>(fieldInfo);
+        }
+        if (widget) {
+            widget->setObjectName(std::get<0>(fieldInfo));
+            formLayout->addRow(std::get<1>(fieldInfo) + ":", widget);
+        }
+    }
+    fieldBox->setLayout(formLayout);
+}
+
+
+template<typename FieldInfo>
+QVariantMap propertiesFromFieldWidgets(QGroupBox *fieldBox, const std::vector<FieldInfo> &fieldInfos)
+{
+    QVariantMap properties;
+    if (!fieldBox)
+        return properties;
+
+    for (auto &&fieldInfo : fieldInfos) {
+        QString key = std::get<0>(fieldInfo);
+        QVariant value;
+        switch (std::get<2>(fieldInfo).type()) {
+            case QVariant::Int: {
+                QSpinBox *spinBox = fieldBox->findChild<QSpinBox *>(key);
+                if (spinBox)
+                    value = spinBox->value();
+                else
+                    qWarning() << "Could not find child widget for field" << key;
+                break;
+            }
+            case QVariant::String: {
+                QLineEdit *lineEdit = fieldBox->findChild<QLineEdit *>(key);
+                if (lineEdit)
+                    value = lineEdit->text();
+                else
+                    qWarning() << "Could not find child widget for field" << key;
+                break;
+            }
+            default:
+                qWarning() << "Unsupported type for backend property" << key;
+        }
+        properties[key] = std::move(value);
+    }
+    return properties;
+}
+
+} // anon
+
+
+CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QVariantList &backendInfos, const QVariantList &authInfos, QWidget *parent)
     : QWizard(parent),
-    _connection(connection)
+    _connection{connection}
 {
     setModal(true);
     setAttribute(Qt::WA_DeleteOnClose);
 
-    foreach(const QVariant &v, backends)
-        _backends[v.toMap()["DisplayName"].toString()] = v;
-
-    foreach(const QVariant &v, authenticators)
-        _authenticators[v.toMap()["BackendId"].toString()] = v;
-
     setPage(IntroPage, new CoreConfigWizardPages::IntroPage(this));
     setPage(AdminUserPage, new CoreConfigWizardPages::AdminUserPage(this));
-    setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(_authenticators, this));
-    setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(_backends, this));
+    setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(authInfos, this));
+    setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(backendInfos, this));
     syncPage = new CoreConfigWizardPages::SyncPage(this);
     connect(syncPage, SIGNAL(setupCore(const QString &, const QVariantMap &, const QString &, const QVariantMap &)),
             SLOT(prepareCoreSetup(const QString &, const QVariantMap &, const QString &, const QVariantMap &)));
@@ -84,18 +156,6 @@ CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QList<QVari
 }
 
 
-QHash<QString, QVariant> CoreConfigWizard::backends() const
-{
-    return _backends;
-}
-
-
-QHash<QString, QVariant> CoreConfigWizard::authenticators() const
-{
-    return _authenticators;
-}
-
-
 void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMap &properties, const QString &authenticator, const QVariantMap &authProperties)
 {
     // Prevent the user from changing any settings he already specified...
@@ -117,7 +177,7 @@ void CoreConfigWizard::coreSetupSuccess()
 {
     syncPage->setStatus(tr("Your core has been successfully configured. Logging you in..."));
     syncPage->setError(false);
-    syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error);
+    syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success);
     coreConnection()->loginToCore(field("adminUser.user").toString(), field("adminUser.password").toString(), field("adminUser.rememberPasswd").toBool());
 }
 
@@ -186,10 +246,6 @@ AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent)
     registerField("adminUser.password*", ui.password);
     registerField("adminUser.password2*", ui.password2);
     registerField("adminUser.rememberPasswd", ui.rememberPasswd);
-
-    //ui.user->setText("foo");
-    //ui.password->setText("foo");
-    //ui.password2->setText("foo");
 }
 
 
@@ -213,10 +269,8 @@ bool AdminUserPage::isComplete() const
 
 /*** Authentication Selection Page ***/
 
-AuthenticationSelectionPage::AuthenticationSelectionPage(const QHash<QString, QVariant> &backends, QWidget *parent)
-    : QWizardPage(parent),
-    _connectionBox(0),
-    _backends(backends)
+AuthenticationSelectionPage::AuthenticationSelectionPage(const QVariantList &authInfos, QWidget *parent)
+    : QWizardPage(parent)
 {
     ui.setupUi(this);
 
@@ -225,11 +279,24 @@ AuthenticationSelectionPage::AuthenticationSelectionPage(const QHash<QString, QV
 
     registerField("authentication.backend", ui.backendList);
 
-    foreach(QString key, _backends.keys()) {
-        ui.backendList->addItem(_backends[key].toMap()["DisplayName"].toString(), key);
+    for (auto &&authInfo : authInfos) {
+        auto props = authInfo.toMap();
+        // Extract field infos to avoid having to reparse the list
+        std::vector<FieldInfo> fields;
+        const auto &list = props["SetupData"].toList();
+        for (int i = 0; i + 2 < list.size(); i += 3) {
+            fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
+        }
+        props.remove("SetupData");
+
+        _authProperties.emplace_back(props);
+        _authFields.emplace_back(std::move(fields));
+
+        // Create entry in authenticator selector
+        ui.backendList->addItem(props["DisplayName"].toString(), props["BackendId"].toString());
     }
 
-    on_backendList_currentIndexChanged();
+    ui.backendList->setCurrentIndex(0);
 }
 
 
@@ -239,125 +306,95 @@ int AuthenticationSelectionPage::nextId() const
 }
 
 
-QString AuthenticationSelectionPage::selectedBackend() const
+QString AuthenticationSelectionPage::displayName() const
 {
     return ui.backendList->currentText();
 }
 
 
-QVariantMap AuthenticationSelectionPage::authProperties() const
+QString AuthenticationSelectionPage::authenticator() const
 {
-    QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString();
+#if QT_VERSION >= 0x050200
+    return ui.backendList->currentData().toString();
+#else
+    return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
+#endif
+}
 
-    QVariantMap properties;
-    QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList();
-    if (!setupKeys.isEmpty()) {
-        QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap();
-        foreach(QString key, setupKeys) {
-            QWidget *widget = _connectionBox->findChild<QWidget *>(key);
-            QVariant def;
-            if (defaults.contains(key)) {
-                def = defaults[key];
-            }
-            switch (def.type()) {
-            case QVariant::Int:
-            {
-                QSpinBox *spinbox = qobject_cast<QSpinBox *>(widget);
-                Q_ASSERT(spinbox);
-                def = QVariant(spinbox->value());
-            }
-            break;
-            default:
-            {
-                QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget);
-                Q_ASSERT(lineEdit);
-                def = QVariant(lineEdit->text());
-            }
-            }
-            properties[key] = def;
-        }
-    }
-    return properties;
+
+QVariantMap AuthenticationSelectionPage::authProperties() const
+{
+    return propertiesFromFieldWidgets(_fieldBox, _authFields[ui.backendList->currentIndex()]);
 }
 
 
-void AuthenticationSelectionPage::on_backendList_currentIndexChanged()
+void AuthenticationSelectionPage::on_backendList_currentIndexChanged(int index)
 {
-    QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString();
-    ui.description->setText(_backends[backend].toMap()["Description"].toString());
+    ui.description->setText(_authProperties[index]["Description"].toString());
 
-    if (_connectionBox) {
-        layout()->removeWidget(_connectionBox);
-        _connectionBox->deleteLater();
-        _connectionBox = 0;
+    if (_fieldBox) {
+        layout()->removeWidget(_fieldBox);
+        _fieldBox->deleteLater();
+        _fieldBox = nullptr;
     }
-
-    QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList();
-    if (!setupKeys.isEmpty()) {
-        QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap();
-        QGroupBox *propertyBox = new QGroupBox(this);
-        propertyBox->setTitle(tr("Connection Properties"));
-        QFormLayout *formlayout = new QFormLayout;
-
-        foreach(QString key, setupKeys) {
-            QWidget *widget = 0;
-            QVariant def;
-            if (defaults.contains(key)) {
-                def = defaults[key];
-            }
-            switch (def.type()) {
-            case QVariant::Int:
-            {
-                QSpinBox *spinbox = new QSpinBox(propertyBox);
-                spinbox->setMaximum(64000);
-                spinbox->setValue(def.toInt());
-                widget = spinbox;
-            }
-            break;
-            default:
-            {
-                QLineEdit *lineEdit = new QLineEdit(def.toString(), propertyBox);
-                if (key.toLower().contains("password")) {
-                    lineEdit->setEchoMode(QLineEdit::Password);
-                }
-                widget = lineEdit;
-            }
-            }
-            widget->setObjectName(key);
-            formlayout->addRow(key + ":", widget);
-        }
-        propertyBox->setLayout(formlayout);
-        static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, propertyBox);
-        _connectionBox = propertyBox;
+    if (!_authFields[index].empty()) {
+        _fieldBox = new QGroupBox(this);
+        _fieldBox->setTitle(tr("Authentication Settings"));
+        createFieldWidgets(_fieldBox, _authFields[index]);
+        static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, _fieldBox);
     }
 }
 
 /*** Storage Selection Page ***/
 
-StorageSelectionPage::StorageSelectionPage(const QHash<QString, QVariant> &backends, QWidget *parent)
-    : QWizardPage(parent),
-    _connectionBox(0),
-    _backends(backends)
+StorageSelectionPage::StorageSelectionPage(const QVariantList &backendInfos, QWidget *parent)
+    : QWizardPage(parent)
 {
     ui.setupUi(this);
 
     setTitle(tr("Select Storage Backend"));
-    setSubTitle(tr("Please select a database backend for the Quassel Core storage to store the backlog and other data in."));
+    setSubTitle(tr("Please select a storage backend for Quassel Core."));
     setCommitPage(true);
 
     registerField("storage.backend", ui.backendList);
 
-    int defaultIndex = 0;
-    foreach(QString key, _backends.keys()) {
-        ui.backendList->addItem(_backends[key].toMap()["DisplayName"].toString(), key);
-        if (_backends[key].toMap()["IsDefault"].toBool()) {
-            defaultIndex = ui.backendList->count() - 1;
+    int defaultIndex {0};  // Legacy cores send backend infos in arbitrary order
+
+    for (auto &&backendInfo : backendInfos) {
+        auto props = backendInfo.toMap();
+        // Extract field infos to avoid having to reparse the list
+        std::vector<FieldInfo> fields;
+
+        // Legacy cores (prior to 0.13) didn't send SetupData for storage backends; deal with this
+        if (!props.contains("SetupData")) {
+            const auto &defaultValues = props["SetupDefaults"].toMap();
+            for (auto &&key : props["SetupKeys"].toStringList()) {
+                fields.emplace_back(std::make_tuple(key, key, defaultValues.value(key, QString{})));
+            }
+            if (props.value("IsDefault", false).toBool()) {
+                defaultIndex = ui.backendList->count();
+            }
         }
+        else {
+            const auto &list = props["SetupData"].toList();
+            for (int i = 0; i + 2 < list.size(); i += 3) {
+                fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
+            }
+            props.remove("SetupData");
+        }
+        props.remove("SetupKeys");
+        props.remove("SetupDefaults");
+        // Legacy cores (prior to 0.13) don't send the BackendId property
+        if (!props.contains("BackendId"))
+            props["BackendId"] = props["DisplayName"];
+        _backendProperties.emplace_back(props);
+        _backendFields.emplace_back(std::move(fields));
+
+        // Create entry in backend selector
+        ui.backendList->addItem(props["DisplayName"].toString(), props["BackendId"].toString());
     }
 
     ui.backendList->setCurrentIndex(defaultIndex);
-
-    on_backendList_currentIndexChanged();
 }
 
 
@@ -367,121 +404,42 @@ int StorageSelectionPage::nextId() const
 }
 
 
-QString StorageSelectionPage::selectedBackend() const
+QString StorageSelectionPage::displayName() const
 {
     return ui.backendList->currentText();
 }
 
 
-QVariantMap StorageSelectionPage::connectionProperties() const
+QString StorageSelectionPage::backend() const
 {
-    QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString();
+#if QT_VERSION >= 0x050200
+    return ui.backendList->currentData().toString();
+#else
+    return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
+#endif
+}
 
-    QVariantMap properties;
-    QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList();
-    if (!setupKeys.isEmpty()) {
-        QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap();
-        foreach(QString key, setupKeys) {
-            QWidget *widget = _connectionBox->findChild<QWidget *>(key);
-            QVariant def;
-            if (defaults.contains(key)) {
-                def = defaults[key];
-            }
-            switch (def.type()) {
-            case QVariant::Int:
-            {
-                QSpinBox *spinbox = qobject_cast<QSpinBox *>(widget);
-                Q_ASSERT(spinbox);
-                def = QVariant(spinbox->value());
-            }
-            break;
-            default:
-            {
-                QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget);
-                Q_ASSERT(lineEdit);
-                def = QVariant(lineEdit->text());
-            }
-            }
-            properties[key] = def;
-        }
-    }
-    qDebug() << properties;
-
-//   QVariantMap properties = _backends[backend].toMap()["ConnectionProperties"].toMap();
-//   if(!properties.isEmpty() && _connectionBox) {
-//     QVariantMap::iterator propertyIter = properties.begin();
-//     while(propertyIter != properties.constEnd()) {
-//       QWidget *widget = _connectionBox->findChild<QWidget *>(propertyIter.key());
-//       switch(propertyIter.value().type()) {
-//       case QVariant::Int:
-//      {
-//        QSpinBox *spinbox = qobject_cast<QSpinBox *>(widget);
-//        Q_ASSERT(spinbox);
-//        propertyIter.value() = QVariant(spinbox->value());
-//      }
-//      break;
-//       default:
-//      {
-//        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget);
-//        Q_ASSERT(lineEdit);
-//        propertyIter.value() = QVariant(lineEdit->text());
-//      }
-//       }
-//       propertyIter++;
-//     }
-//   }
-    return properties;
+
+QVariantMap StorageSelectionPage::backendProperties() const
+{
+    return propertiesFromFieldWidgets(_fieldBox, _backendFields[ui.backendList->currentIndex()]);
 }
 
 
-void StorageSelectionPage::on_backendList_currentIndexChanged()
+void StorageSelectionPage::on_backendList_currentIndexChanged(int index)
 {
-    QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString();
-    ui.description->setText(_backends[backend].toMap()["Description"].toString());
+    ui.description->setText(_backendProperties[index]["Description"].toString());
 
-    if (_connectionBox) {
-        layout()->removeWidget(_connectionBox);
-        _connectionBox->deleteLater();
-        _connectionBox = 0;
+    if (_fieldBox) {
+        layout()->removeWidget(_fieldBox);
+        _fieldBox->deleteLater();
+        _fieldBox = nullptr;
     }
-
-    QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList();
-    if (!setupKeys.isEmpty()) {
-        QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap();
-        QGroupBox *propertyBox = new QGroupBox(this);
-        propertyBox->setTitle(tr("Connection Properties"));
-        QFormLayout *formlayout = new QFormLayout;
-
-        foreach(QString key, setupKeys) {
-            QWidget *widget = 0;
-            QVariant def;
-            if (defaults.contains(key)) {
-                def = defaults[key];
-            }
-            switch (def.type()) {
-            case QVariant::Int:
-            {
-                QSpinBox *spinbox = new QSpinBox(propertyBox);
-                spinbox->setMaximum(64000);
-                spinbox->setValue(def.toInt());
-                widget = spinbox;
-            }
-            break;
-            default:
-            {
-                QLineEdit *lineEdit = new QLineEdit(def.toString(), propertyBox);
-                if (key.toLower().contains("password")) {
-                    lineEdit->setEchoMode(QLineEdit::Password);
-                }
-                widget = lineEdit;
-            }
-            }
-            widget->setObjectName(key);
-            formlayout->addRow(key + ":", widget);
-        }
-        propertyBox->setLayout(formlayout);
-        static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, propertyBox);
-        _connectionBox = propertyBox;
+    if (!_backendFields[index].empty()) {
+        _fieldBox = new QGroupBox(this);
+        _fieldBox->setTitle(tr("Storage Settings"));
+        createFieldWidgets(_fieldBox, _backendFields[index]);
+        static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, _fieldBox);
     }
 }
 
@@ -492,45 +450,45 @@ SyncPage::SyncPage(QWidget *parent) : QWizardPage(parent)
 {
     ui.setupUi(this);
     setTitle(tr("Storing Your Settings"));
-    setSubTitle(tr("Your settings are now stored in the core, and you will be logged in automatically."));
+    setSubTitle(tr("Your settings are now being stored in the core, and you will be logged in automatically."));
 }
 
 
 void SyncPage::initializePage()
 {
-    complete = false;
-    hasError = false;
+    _complete = false;
+    _hasError = false;
+    emit completeChanged();
 
     // Fill in sync info about the storage layer.
     StorageSelectionPage *storagePage = qobject_cast<StorageSelectionPage *>(wizard()->page(CoreConfigWizard::StorageSelectionPage));
-    QString backend = storagePage->selectedBackend();
-    QVariantMap properties = storagePage->connectionProperties();
-    Q_ASSERT(!backend.isEmpty());
-    ui.backend->setText(backend);
+    QString backend = storagePage->backend();
+    QVariantMap backendProperties = storagePage->backendProperties();
+    ui.backend->setText(storagePage->displayName());
 
     // Fill in sync info about the authentication layer.
     AuthenticationSelectionPage *authPage = qobject_cast<AuthenticationSelectionPage *>(wizard()->page(CoreConfigWizard::AuthenticationSelectionPage));
-    QString authenticator = authPage->selectedBackend();
+    QString authenticator = authPage->authenticator();
     QVariantMap authProperties = authPage->authProperties();
-    Q_ASSERT(!authenticator.isEmpty());
-    ui.authenticator->setText(authenticator);
+    ui.authenticator->setText(authPage->displayName());
 
     ui.user->setText(wizard()->field("adminUser.user").toString());
 
-    emit setupCore(backend, properties, authenticator, authProperties);
+    emit setupCore(backend, backendProperties, authenticator, authProperties);
 }
 
 
 int SyncPage::nextId() const
 {
-    if (!hasError) return -1;
+    if (!_hasError)
+        return -1;
     return CoreConfigWizard::SyncRelayPage;
 }
 
 
 bool SyncPage::isComplete() const
 {
-    return complete;
+    return _complete || _hasError;
 }
 
 
@@ -542,13 +500,15 @@ void SyncPage::setStatus(const QString &status)
 
 void SyncPage::setError(bool e)
 {
-    hasError = e;
+    _hasError = e;
+    setFinalPage(!e);
+    emit completeChanged();
 }
 
 
 void SyncPage::setComplete(bool c)
 {
-    complete = c;
+    _complete = c;
     completeChanged();
 }
 
@@ -566,18 +526,6 @@ void SyncRelayPage::setMode(Mode m)
     mode = m;
 }
 
-
-/*
-void SyncRelayPage::initializePage() {
-  return;
-  if(mode == Success) {
-    wizard()->accept();
-  } else {
-    emit startOver();
-  }
-}
-*/
-
 int SyncRelayPage::nextId() const
 {
     emit startOver();
index bcc765f..5b09a87 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef _CORECONFIGWIZARD_H_
-#define _CORECONFIGWIZARD_H_
+#pragma once
+
+#include <tuple>
+#include <vector>
 
-#include <QHash>
 #include <QWizard>
 #include <QVariantMap>
 
@@ -54,9 +55,7 @@ public:
         ConclusionPage
     };
 
-    CoreConfigWizard(CoreConnection *connection, const QList<QVariant> &backends, const QList<QVariant> &authenticators, QWidget *parent = 0);
-    QHash<QString, QVariant> backends() const;
-    QHash<QString, QVariant> authenticators() const;
+    CoreConfigWizard(CoreConnection *connection, const QVariantList &backendInfos, const QVariantList &authInfos, QWidget *parent = 0);
 
     inline CoreConnection *coreConnection() const { return _connection; }
 
@@ -75,9 +74,6 @@ private slots:
     void startOver();
 
 private:
-    QHash<QString, QVariant> _backends;
-    QHash<QString, QVariant> _authenticators;
-
     CoreConfigWizardPages::SyncPage *syncPage;
     CoreConfigWizardPages::SyncRelayPage *syncRelayPage;
 
@@ -114,37 +110,45 @@ private:
 class AuthenticationSelectionPage : public QWizardPage
 {
     Q_OBJECT
+    using FieldInfo = std::tuple<QString, QString, QVariant>;
 
 public:
-    AuthenticationSelectionPage(const QHash<QString, QVariant> &backends, QWidget *parent = 0);
+    AuthenticationSelectionPage(const QVariantList &authInfos, QWidget *parent = 0);
     int nextId() const;
-    QString selectedBackend() const;
+    QString displayName() const;
+    QString authenticator() const;
     QVariantMap authProperties() const;
 
 private slots:
-    void on_backendList_currentIndexChanged();
+    void on_backendList_currentIndexChanged(int index);
+
 private:
     Ui::CoreConfigWizardAuthenticationSelectionPage ui;
-    QGroupBox *_connectionBox;
-    QHash<QString, QVariant> _backends;
+    QGroupBox *_fieldBox {nullptr};
+    std::vector<QVariantMap> _authProperties;
+    std::vector<std::vector<FieldInfo>> _authFields;
 };
 
 class StorageSelectionPage : public QWizardPage
 {
     Q_OBJECT
+    using FieldInfo = std::tuple<QString, QString, QVariant>;
 
 public:
-    StorageSelectionPage(const QHash<QString, QVariant> &backends, QWidget *parent = 0);
+    StorageSelectionPage(const QVariantList &backendInfos, QWidget *parent = 0);
     int nextId() const;
-    QString selectedBackend() const;
-    QVariantMap connectionProperties() const;
+    QString displayName() const;
+    QString backend() const;
+    QVariantMap backendProperties() const;
 
 private slots:
-    void on_backendList_currentIndexChanged();
+    void on_backendList_currentIndexChanged(int index);
+
 private:
     Ui::CoreConfigWizardStorageSelectionPage ui;
-    QGroupBox *_connectionBox;
-    QHash<QString, QVariant> _backends;
+    QGroupBox *_fieldBox {nullptr};
+    std::vector<QVariantMap> _backendProperties;
+    std::vector<std::vector<FieldInfo>> _backendFields;
 };
 
 class SyncPage : public QWizardPage
@@ -167,8 +171,8 @@ signals:
 
 private:
     Ui::CoreConfigWizardSyncPage ui;
-    bool complete;
-    bool hasError;
+    bool _complete {false};
+    bool _hasError {false};
 };
 
 
@@ -191,5 +195,3 @@ private:
     Mode mode;
 };
 }
-
-#endif
index 4d0ed79..d00c8f0 100644 (file)
@@ -1,7 +1,8 @@
-<ui version="4.0" >
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
  <class>CoreConfigWizardAuthenticationSelectionPage</class>
- <widget class="QWidget" name="CoreConfigWizardAuthenticationSelectionPage" >
-  <property name="geometry" >
+ <widget class="QWidget" name="CoreConfigWizardAuthenticationSelectionPage">
+  <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <height>168</height>
    </rect>
   </property>
-  <property name="windowTitle" >
+  <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" >
+  <layout class="QVBoxLayout">
    <item>
-    <layout class="QHBoxLayout" >
+    <layout class="QHBoxLayout">
      <item>
-      <widget class="QLabel" name="label" >
-       <property name="text" >
+      <widget class="QLabel" name="label">
+       <property name="text">
         <string>Authentication Backend:</string>
        </property>
       </widget>
      </item>
      <item>
-      <widget class="QComboBox" name="backendList" >
-       <property name="sizePolicy" >
-        <sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
+      <widget class="QComboBox" name="backendList">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
-       <property name="insertPolicy" >
+       <property name="insertPolicy">
         <enum>QComboBox::InsertAtBottom</enum>
        </property>
       </widget>
      </item>
      <item>
       <spacer>
-       <property name="orientation" >
+       <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
-       <property name="sizeHint" stdset="0" >
+       <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
     </layout>
    </item>
    <item>
-    <widget class="QGroupBox" name="descriptionBox" >
-     <property name="title" >
+    <widget class="QGroupBox" name="descriptionBox">
+     <property name="title">
       <string>Description</string>
      </property>
-     <layout class="QVBoxLayout" >
+     <layout class="QVBoxLayout">
       <item>
-       <widget class="QLabel" name="description" >
-        <property name="text" >
-         <string>Foobar</string>
+       <widget class="QLabel" name="description">
+        <property name="text">
+         <string notr="true">Foobar</string>
         </property>
-        <property name="alignment" >
+        <property name="alignment">
          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
         </property>
-        <property name="wordWrap" >
+        <property name="wordWrap">
          <bool>true</bool>
         </property>
        </widget>
     </widget>
    </item>
    <item>
-    <spacer name="verticalSpacer" >
-     <property name="orientation" >
+    <spacer name="verticalSpacer">
+     <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
-     <property name="sizeHint" stdset="0" >
+     <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
index 75f45e9..6e71837 100644 (file)
@@ -1,7 +1,8 @@
-<ui version="4.0" >
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
  <class>CoreConfigWizardStorageSelectionPage</class>
- <widget class="QWidget" name="CoreConfigWizardStorageSelectionPage" >
-  <property name="geometry" >
+ <widget class="QWidget" name="CoreConfigWizardStorageSelectionPage">
+  <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <height>168</height>
    </rect>
   </property>
-  <property name="windowTitle" >
+  <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" >
+  <layout class="QVBoxLayout">
    <item>
-    <layout class="QHBoxLayout" >
+    <layout class="QHBoxLayout">
      <item>
-      <widget class="QLabel" name="label" >
-       <property name="text" >
+      <widget class="QLabel" name="label">
+       <property name="text">
         <string>Storage Backend:</string>
        </property>
       </widget>
      </item>
      <item>
-      <widget class="QComboBox" name="backendList" >
-       <property name="sizePolicy" >
-        <sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
+      <widget class="QComboBox" name="backendList">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
-       <property name="insertPolicy" >
+       <property name="insertPolicy">
         <enum>QComboBox::InsertAtBottom</enum>
        </property>
       </widget>
      </item>
      <item>
       <spacer>
-       <property name="orientation" >
+       <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
-       <property name="sizeHint" stdset="0" >
+       <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
     </layout>
    </item>
    <item>
-    <widget class="QGroupBox" name="descriptionBox" >
-     <property name="title" >
+    <widget class="QGroupBox" name="descriptionBox">
+     <property name="title">
       <string>Description</string>
      </property>
-     <layout class="QVBoxLayout" >
+     <layout class="QVBoxLayout">
       <item>
-       <widget class="QLabel" name="description" >
-        <property name="text" >
-         <string>Foobar</string>
+       <widget class="QLabel" name="description">
+        <property name="text">
+         <string notr="true">Foobar</string>
         </property>
-        <property name="alignment" >
+        <property name="alignment">
          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
         </property>
-        <property name="wordWrap" >
+        <property name="wordWrap">
          <bool>true</bool>
         </property>
        </widget>
     </widget>
    </item>
    <item>
-    <spacer name="verticalSpacer" >
-     <property name="orientation" >
+    <spacer name="verticalSpacer">
+     <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
-     <property name="sizeHint" stdset="0" >
+     <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
index 6345ef7..9894e97 100644 (file)
@@ -1,7 +1,8 @@
-<ui version="4.0" >
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
  <class>CoreConfigWizardSyncPage</class>
- <widget class="QWidget" name="CoreConfigWizardSyncPage" >
-  <property name="geometry" >
+ <widget class="QWidget" name="CoreConfigWizardSyncPage">
+  <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <height>300</height>
    </rect>
   </property>
-  <property name="windowTitle" >
+  <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" >
+  <layout class="QVBoxLayout">
    <item>
-    <widget class="QGroupBox" name="groupBox" >
-     <property name="title" >
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
       <string>Your Choices</string>
      </property>
-     <layout class="QVBoxLayout" >
+     <layout class="QVBoxLayout">
       <item>
-       <layout class="QHBoxLayout" >
+       <layout class="QHBoxLayout">
         <item>
-         <layout class="QGridLayout" >
-          <item row="0" column="0" >
-           <widget class="QLabel" name="label" >
-            <property name="font" >
+         <layout class="QGridLayout">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label">
+            <property name="font">
              <font>
               <weight>75</weight>
               <bold>true</bold>
              </font>
             </property>
-            <property name="text" >
+            <property name="text">
              <string>Admin User:</string>
             </property>
            </widget>
           </item>
-          <item row="0" column="1" >
-           <widget class="QLabel" name="user" >
-            <property name="text" >
-             <string>foo</string>
+          <item row="0" column="1">
+           <widget class="QLabel" name="user">
+            <property name="text">
+             <string notr="true">foo</string>
             </property>
            </widget>
           </item>
-          <item row="1" column="0" >
-           <widget class="QLabel" name="label_2" >
-            <property name="font" >
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_2">
+            <property name="font">
              <font>
               <weight>75</weight>
               <bold>true</bold>
              </font>
             </property>
-            <property name="text" >
+            <property name="text">
              <string>Storage Backend:</string>
             </property>
            </widget>
           </item>
-          <item row="1" column="1" >
-           <widget class="QLabel" name="backend" >
-            <property name="text" >
-             <string>bar</string>
+          <item row="1" column="1">
+           <widget class="QLabel" name="backend">
+            <property name="text">
+             <string notr="true">bar</string>
             </property>
            </widget>
           </item>
-          <item row="2" column="0" >
-           <widget class="QLabel" name="label_3" >
-            <property name="font" >
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_3">
+            <property name="font">
              <font>
               <weight>75</weight>
               <bold>true</bold>
              </font>
             </property>
-            <property name="text" >
+            <property name="text">
              <string>Authentication Backend:</string>
             </property>
            </widget>
           </item>
-          <item row="2" column="1" >
-           <widget class="QLabel" name="authenticator" >
-            <property name="text" >
-             <string>bar</string>
+          <item row="2" column="1">
+           <widget class="QLabel" name="authenticator">
+            <property name="text">
+             <string notr="true">bar</string>
             </property>
            </widget>
           </item>
         </item>
         <item>
          <spacer>
-          <property name="orientation" >
+          <property name="orientation">
            <enum>Qt::Horizontal</enum>
           </property>
-          <property name="sizeHint" >
+          <property name="sizeHint" stdset="0">
            <size>
             <width>40</width>
             <height>20</height>
    </item>
    <item>
     <spacer>
-     <property name="orientation" >
+     <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
-     <property name="sizeHint" >
+     <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
     </spacer>
    </item>
    <item>
-    <widget class="QLabel" name="status" >
-     <property name="text" >
+    <widget class="QLabel" name="status">
+     <property name="text">
       <string>Please wait while your settings are being transmitted to the core...</string>
      </property>
-     <property name="alignment" >
+     <property name="alignment">
       <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
      </property>
-     <property name="wordWrap" >
+     <property name="wordWrap">
       <bool>true</bool>
      </property>
     </widget>