diff --git a/pglab/ConnectionConfigurationWidget.cpp b/pglab/ConnectionConfigurationWidget.cpp index a5837d0..2230c40 100644 --- a/pglab/ConnectionConfigurationWidget.cpp +++ b/pglab/ConnectionConfigurationWidget.cpp @@ -3,21 +3,27 @@ #include "ConnectionConfig.h" #include "ConnectionController.h" #include "ConnectionListModel.h" +#include "Pgsql_Connection.h" +#include "Pgsql_PgException.h" #include "util.h" #include +#include +#include #include +#include +#include #include #include +#include #include #include #include #include -#include -#include -#include +#include #define SET_OBJECT_NAME(var) var->setObjectName(#var) + void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg) { try { @@ -25,7 +31,6 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c w->setData(cfg); auto btn_hbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); - //auto btn_test = btn_hbox->addButton(tr("Test"), QDialogButtonBox::ActionRole); auto vbox = new QVBoxLayout; vbox->addWidget(w); @@ -37,7 +42,8 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c win->setAttribute( Qt::WA_DeleteOnClose, true ); QObject::connect(btn_hbox, &QDialogButtonBox::accepted, [ctrl, w, win] () { - auto [grp, cc] = w->data(); + auto cc = w->data(); + auto grp = w->group(); ctrl->getConnectionTreeModel()->save(grp, cc); win->accept(); }); @@ -55,7 +61,7 @@ void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *c ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent) : QWidget(parent) - , m_connectionModel(connection_model) + , m_connectionModel(connection_model) { lblGroup = new QLabel; cmbbxGroup = new QComboBox; @@ -88,11 +94,20 @@ ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel SET_OBJECT_NAME(edtUser); lblUser->setBuddy(edtUser); + lblPassword = new QLabel; + SET_OBJECT_NAME(lblPassword); + edtPassword = new QLineEdit; + SET_OBJECT_NAME(edtPassword); + edtPassword->setEchoMode(QLineEdit::PasswordEchoOnEdit); + + cbSavePassword = new QCheckBox; + SET_OBJECT_NAME(cbSavePassword); + lblDbName = new QLabel; SET_OBJECT_NAME(lblDbName); - edtDbname = new QLineEdit; - SET_OBJECT_NAME(edtDbname); - lblDbName->setBuddy(edtDbname); + cmbDbname = new QComboBox; + cmbDbname->setEditable(true); // allows to enter the database name even if test failed. + cmbDbname->setInsertPolicy(QComboBox::InsertAlphabetically); lblSsl = new QLabel; SET_OBJECT_NAME(lblSsl); @@ -127,6 +142,11 @@ ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel SET_OBJECT_NAME(edtCrl); lblCrl->setBuddy(edtCrl); + btnTest = new QPushButton; + connect(btnTest, &QPushButton::clicked, this, &ConnectionConfigurationWidget::testConnection); + + lblResult = new QLabel; + formLayout = new QFormLayout; formLayout->addRow(lblGroup, cmbbxGroup); @@ -134,13 +154,19 @@ ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel formLayout->addRow(lblHost, edtHost); formLayout->addRow(lblPort, spinPort); formLayout->addRow(lblUser, edtUser); - formLayout->addRow(lblDbName, edtDbname); + formLayout->addRow(lblPassword, edtPassword); + formLayout->addRow(nullptr, cbSavePassword); + formLayout->addRow(lblDbName, cmbDbname); formLayout->addRow(lblSsl, cmbbxSsl); formLayout->addRow(lblCert, edtCert); formLayout->addRow(lblKey, edtKey); formLayout->addRow(lblRootCert, edtRootCert); formLayout->addRow(lblCrl, edtCrl); - setLayout(formLayout); + formLayout->addRow(btnTest, lblResult); + setLayout(formLayout); + + connect(&TestWatcher, &QFutureWatcher::finished, this, + &ConnectionConfigurationWidget::handleTestResult); retranslateUi(); } @@ -152,12 +178,15 @@ void ConnectionConfigurationWidget::retranslateUi() lblHost->setText(QApplication::translate("ConnectionConfigurationWidget", "&Host", nullptr)); lblPort->setText(QApplication::translate("ConnectionConfigurationWidget", "&Port", nullptr)); lblUser->setText(QApplication::translate("ConnectionConfigurationWidget", "&Username", nullptr)); - lblDbName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Database", nullptr)); + lblPassword->setText(QApplication::translate("ConnectionConfigurationWidget", "Password", nullptr)); + cbSavePassword->setText(QApplication::translate("ConnectionConfigurationWidget", "&Save password", nullptr)); + lblDbName->setText(QApplication::translate("ConnectionConfigurationWidget", "Database", nullptr)); lblSsl->setText(QApplication::translate("ConnectionConfigurationWidget", "&SSL mode", nullptr)); lblCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Certificate", nullptr)); lblKey->setText(QApplication::translate("ConnectionConfigurationWidget", "&Key", nullptr)); lblRootCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Root cert.", nullptr)); lblCrl->setText(QApplication::translate("ConnectionConfigurationWidget", "Revocation &list", nullptr)); + btnTest->setText(QApplication::translate("ConnectionConfigurationWidget", "Test", nullptr)); } void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) @@ -173,7 +202,7 @@ void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) edtHost->setText(cfg.host()); spinPort->setValue(cfg.port()); edtUser->setText(cfg.user()); - edtDbname->setText(cfg.dbname()); + cmbDbname->setCurrentText(cfg.dbname()); cmbbxSsl->setCurrentIndex(static_cast(cfg.sslMode())); edtCert->setText(cfg.sslCert()); edtKey->setText(cfg.sslKey()); @@ -181,23 +210,46 @@ void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg) edtCrl->setText(cfg.sslCrl()); } -std::tuple ConnectionConfigurationWidget::data() const +ConnectionConfig ConnectionConfigurationWidget::data() const { - QString group; - group = cmbbxGroup->currentText(); - ConnectionConfig cfg; cfg.setUuid(m_uuid); cfg.setName(edtName->text()); cfg.setHost(edtHost->text()); cfg.setPort(static_cast(spinPort->value())); cfg.setUser(edtUser->text()); - cfg.setDbname(edtDbname->text()); + cfg.setPassword(edtPassword->text()); + cfg.setDbname(cmbDbname->currentText()); cfg.setSslMode(static_cast(cmbbxSsl->currentIndex())); cfg.setSslCert(edtCert->text()); cfg.setSslKey(edtKey->text()); cfg.setSslRootCert(edtRootCert->text()); cfg.setSslCrl(edtCrl->text()); - return { group, cfg }; + return cfg; +} + +QString ConnectionConfigurationWidget::group() const +{ + return cmbbxGroup->currentText(); +} + +void ConnectionConfigurationWidget::testConnection() +{ + auto cc = data(); + QFuture result = QtConcurrent::run([cc] { return TestConnection(cc); }); + TestWatcher.setFuture(result); +} + +void ConnectionConfigurationWidget::handleTestResult() +{ + auto f = TestWatcher.future(); + auto result = f.result(); + + lblResult->setText(result.message()); + QString current = cmbDbname->currentText(); + cmbDbname->clear(); + for (auto &dbn : result.databases()) + cmbDbname->addItem(dbn); + cmbDbname->setCurrentText(current); } diff --git a/pglab/ConnectionConfigurationWidget.h b/pglab/ConnectionConfigurationWidget.h index e7b9ab2..ca2b4f0 100644 --- a/pglab/ConnectionConfigurationWidget.h +++ b/pglab/ConnectionConfigurationWidget.h @@ -3,19 +3,24 @@ #include #include +#include #include +#include "TestConnection.h" class ConnectionController; class ConnectionConfig; +class QCheckBox; +class QComboBox; +class QDataWidgetMapper; class QFormLayout; class QLabel; class QLineEdit; +class QPushButton; class QSpinBox; -class QComboBox; -class QDataWidgetMapper; class ConnectionTreeModel; + class ConnectionConfigurationWidget : public QWidget { Q_OBJECT @@ -26,11 +31,15 @@ public: void retranslateUi(); void setData(const ConnectionConfig &cfg); - std::tuple data() const; + ConnectionConfig data() const; + QString group() const; signals: +public slots: + void testConnection(); + private: - ConnectionTreeModel *m_connectionModel; + ConnectionTreeModel *m_connectionModel; QUuid m_uuid; @@ -44,8 +53,11 @@ private: QSpinBox *spinPort; QLabel *lblUser; QLineEdit *edtUser; - QLabel *lblDbName; - QLineEdit *edtDbname; + QLabel *lblPassword; + QLineEdit *edtPassword; + QCheckBox *cbSavePassword; + QLabel *lblDbName; + QComboBox *cmbDbname; QLabel *lblSsl; QComboBox *cmbbxSsl; QLabel *lblCert; @@ -56,9 +68,14 @@ private: QLineEdit *edtRootCert; QLabel *lblCrl; QLineEdit *edtCrl; - QFormLayout *formLayout; -public slots: + QPushButton *btnTest; + QLabel *lblResult; + QFormLayout *formLayout; + + QFutureWatcher TestWatcher; +private slots: + void handleTestResult(); }; diff --git a/pglab/CrudModel.cpp b/pglab/CrudModel.cpp index bc1efe0..9a2fb9d 100644 --- a/pglab/CrudModel.cpp +++ b/pglab/CrudModel.cpp @@ -173,9 +173,6 @@ void CrudModel::loadData() Q_ARG(std::shared_ptr, dbres)); } } - else { -// emit onQueryError(); - } }); } diff --git a/pglablib/ASyncDBConnection.h b/pglablib/ASyncDBConnection.h index fc8788e..cdd6965 100644 --- a/pglablib/ASyncDBConnection.h +++ b/pglablib/ASyncDBConnection.h @@ -51,25 +51,25 @@ public: /** This version of send uses the signal onQueryResult and onQueryError to report back * the completion of the query. */ - bool send(const std::string &command, Pgsql::Params params = Pgsql::Params()) - { - return send(command, params, [this] (Expected> res, qint64) { - if (res.valid()) { - emit onQueryResult(res.get()); - } - else { - emit onQueryError(); - } - }); - } +// bool send(const std::string &command, Pgsql::Params params = Pgsql::Params()) +// { +// return send(command, params, [this] (Expected> res, qint64) { +// if (res.valid()) { +// emit onQueryResult(res.get()); +// } +// else { +// emit onQueryError(); +// } +// }); +// } bool cancel(); signals: void onStateChanged(ASyncDBConnection::State state); void onNotice(Pgsql::ErrorDetails notice); - void onQueryResult(std::shared_ptr result); - void onQueryError(); +// void onQueryResult(std::shared_ptr result); +// void onQueryError(); private: Pgsql::Connection m_connection; diff --git a/pglablib/TestConnection.cpp b/pglablib/TestConnection.cpp new file mode 100644 index 0000000..12c031c --- /dev/null +++ b/pglablib/TestConnection.cpp @@ -0,0 +1,58 @@ +#include "TestConnection.h" +#include "ConnectionConfig.h" +#include "Pgsql_Connection.h" +#include "Pgsql_PgException.h" + +TestConnectionResult TestConnectionResult::Failed(QString msg) +{ + return TestConnectionResult(false, msg, {}); +} + +TestConnectionResult TestConnectionResult::Success(QStringList databases) +{ + return TestConnectionResult(true, "Test succesfull", databases); +} + +bool TestConnectionResult::ok() const +{ + return Ok; +} + +QString TestConnectionResult::message() const +{ + return Message; +} + +const QStringList &TestConnectionResult::databases() const +{ + return Databases; +} + +TestConnectionResult::TestConnectionResult(bool ok, QString msg, QStringList databases) + : Ok(ok) + , Message(msg) + , Databases(databases) +{} + +TestConnectionResult TestConnection(const ConnectionConfig &cc) +{ + try { + Pgsql::Connection conn; + conn.connect(cc.connectionString()); + + auto result = conn.query( + "SELECT datname\n" + " FROM pg_database\n" + " WHERE datallowconn AND has_database_privilege(datname, 'connect')\n" + " ORDER BY 1"); + + QStringList dbs; + for (auto row : result) + dbs.append(row.get(0)); + + return TestConnectionResult::Success(dbs); + } + catch (const Pgsql::PgException &ex) { + return TestConnectionResult::Failed(QString::fromUtf8(ex.what())); + } +} diff --git a/pglablib/TestConnection.h b/pglablib/TestConnection.h new file mode 100644 index 0000000..45b3cb8 --- /dev/null +++ b/pglablib/TestConnection.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +class ConnectionConfig; + +class TestConnectionResult { +public: + /// Creates an instance signalling failure + static TestConnectionResult Failed(QString msg); + /// Creates a result instance signalling success + static TestConnectionResult Success(QStringList databases); + + TestConnectionResult() = default; + + bool ok() const; + QString message() const; + const QStringList& databases() const; +private: + bool Ok; + QString Message; + QStringList Databases; + + TestConnectionResult(bool ok, QString msg, QStringList databases); +}; + +/// Test if a connection can be opened using the details in cc +/// +/// It returns either the error message from the string on failure +/// or a list of databases the details give access to. +TestConnectionResult TestConnection(const ConnectionConfig &cc); diff --git a/pglablib/pglablib.pro b/pglablib/pglablib.pro index 82a858a..a835755 100644 --- a/pglablib/pglablib.pro +++ b/pglablib/pglablib.pro @@ -23,6 +23,7 @@ SOURCES += \ Pglablib.cpp \ ASyncDBConnection.cpp \ ConnectionConfig.cpp \ + TestConnection.cpp \ WaitHandleList.cpp \ catalog/PgType.cpp \ catalog/PgTypeContainer.cpp \ @@ -89,6 +90,7 @@ HEADERS += \ Pglablib.h \ ASyncDBConnection.h \ ConnectionConfig.h \ + TestConnection.h \ WaitHandleList.h \ Win32Event.h \ catalog/PgType.h \