diff --git a/core/core.pro b/core/core.pro index 1456d6d..4511739 100644 --- a/core/core.pro +++ b/core/core.pro @@ -30,13 +30,15 @@ SOURCES += Core.cpp \ my_boost_assert_handler.cpp \ SqlLexer.cpp \ PasswordManager.cpp \ - CsvWriter.cpp + CsvWriter.cpp \ + BackupFormatModel.cpp HEADERS += Core.h \ PasswordManager.h \ SqlLexer.h \ ScopeGuard.h \ - CsvWriter.h + CsvWriter.h \ + BackupFormatModel.h unix { target.path = /usr/lib INSTALLS += target diff --git a/src/BackupDialog.cpp b/src/BackupDialog.cpp index 76fd0a3..882272f 100644 --- a/src/BackupDialog.cpp +++ b/src/BackupDialog.cpp @@ -1,14 +1,223 @@ -#include "backupdialog.h" +#include "backupdialog.h" #include "ui_backupdialog.h" +#include "BackupFormatModel.h" + +#include +#include +#include +#include +#include + BackupDialog::BackupDialog(QWidget *parent) : QDialog(parent), ui(new Ui::BackupDialog) { ui->setupUi(this); + + auto format_model = new BackupFormatModel(this); + ui->backupFormat->setModel(format_model); } BackupDialog::~BackupDialog() { delete ui; } + +void BackupDialog::setConfig(const ConnectionConfig &cfg) +{ + m_config = cfg; +} + + +void BackupDialog::ConnectTo(QProcess *process) +{ + disconnectCurrentProcess(); + m_process = process; + if (process) { + process->setProcessChannelMode(QProcess::MergedChannels); + process->setReadChannel(QProcess::StandardOutput); + + connect(process, SIGNAL(readyRead()), this, SLOT(on_process_readyRead())); + connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, + SLOT(on_process_errorOccurred(QProcess::ProcessError))); + connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(on_process_finished(int, QProcess::ExitStatus))); + } +} + +void BackupDialog::disconnectCurrentProcess() +{ + if (m_process) { + disconnect(m_process, SIGNAL(readyRead()), this, SLOT(on_process_readyRead())); + disconnect(m_process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, + SLOT(on_process_errorOccurred(QProcess::ProcessError))); + disconnect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(on_process_finished(int, QProcess::ExitStatus))); + m_process = nullptr; + } + +} + + +void BackupDialog::writeOutput(QString s) +{ + ui->stdOutput->appendPlainText(s); + QScrollBar *bar = ui->stdOutput->verticalScrollBar(); + bar->setValue(bar->maximum()); +} + +void BackupDialog::on_process_readyRead() +{ + QByteArray data = m_process->readAllStandardOutput(); + writeOutput(QString::fromUtf8(data)); +} + +void BackupDialog::on_process_errorOccurred(QProcess::ProcessError error) +{ + QString msg; + switch (error) { + case 0: + msg = tr("Failed to start"); + break; + case 1: + msg = tr("Crasged"); + break; + case 2: + msg = tr("Timedout"); + break; + case 3: + msg = tr("Read error"); + break; + case 4: + msg = tr("Write error"); + break; + case 5: + msg = tr("Unknown error"); + break; + default: + msg = tr("Unexpected error"); + } + + + auto res = QMessageBox::critical(this, "pglab", msg, QMessageBox::Close); + if (res == QMessageBox::Yes) { + QApplication::quit(); + } + +} + +void BackupDialog::on_process_finished(int exitCode, QProcess::ExitStatus ) +{ + writeOutput(tr("Completed, with exitcode %1\n").arg(exitCode)); +} + +void BackupDialog::setParams(QStringList &args) +{ + QString short_args("-"); + if (ui->chkbxVerbose->checkState() == Qt::Checked) + short_args += "v"; + if (ui->chkbxClean->checkState() == Qt::Checked) + short_args += "c"; + if (ui->chkbxCreate->checkState() == Qt::Checked) + short_args += "C"; + if (ui->chkbxIncludeBlobs->checkState() == Qt::Checked) + short_args += "b"; + switch (ui->what->currentIndex()) { + case 1: + short_args += "a"; + break; + case 2: + short_args += "s"; + break; + } + if (ui->oids->checkState() == Qt::Checked) + short_args += "o"; + if (ui->noAcl->checkState() == Qt::Checked) + short_args += "x"; + if (short_args.length() > 1) // larger then one because always includes '-' + args << short_args; + + // Todo check path exists and name is valid? + + QFileInfo fi(QDir::fromNativeSeparators(ui->editFilename->text())); + QDir dir(fi.absolutePath()); + if (!dir.exists()) + dir.mkdir(fi.absolutePath()); + + args << "-f" << ui->editFilename->text(); // R"-(c:\temp\backup.sql)-"; + + int format_index = ui->backupFormat->currentIndex(); + auto format_model_base = ui->backupFormat->model(); + BackupFormatModel *bfm = dynamic_cast(format_model_base); + if (bfm) { + QVariant v = bfm->data(bfm->index(format_index, 1)); + QString format("-F"); + format += v.toString(); + args << format; + } + + int jobs = ui->jobs->value(); + if (jobs > 0) + args << "-j" << QString::number(jobs); + + int compression = ui->compression->value(); + if (compression >= 0) + args << "-Z" << QString::number(compression); +} + +void BackupDialog::on_btnStart_clicked() +{ + ui->stdOutput->clear(); + + QString program = R"-(C:\Prog\bigsql\pg96\bin\pg_dump.exe)-"; + QStringList arguments; + setParams(arguments); + + +// QString program = R"_(C:\prog\build-conoutputtest-Desktop_Qt_5_8_0_MSVC2015_32bit2-Debug\debug\conoutputtest.exe)_"; +// QStringList arguments; +// arguments << "/C" << "DIR /S c:\\"; + + // Database connection paramters are passed through the environment as this is far less visible to others. Commandline + // parameters can often be viewed even if the user is not the owner of the process. + // We use the systemEnvironment as a sane default. Then we let the connection overwrite all PG variables in it. + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + m_config.writeToEnvironment(env); + env.insert("SESSIONNAME", "Console"); + auto p = new QProcess(this); + ConnectTo(p); + p->setProcessEnvironment(env); + p->start(program, arguments); +} + +void BackupDialog::on_backupFormat_currentIndexChanged(int index) +{ + int format_index = ui->backupFormat->currentIndex(); + auto format_model_base = ui->backupFormat->model(); + BackupFormatModel *bfm = dynamic_cast(format_model_base); + if (bfm) { + QVariant v = bfm->data(bfm->index(format_index, 1)); + QString format = v.toString(); + + bool comp_enable = (format == "c" || format == "d"); + ui->compression->setEnabled(comp_enable); + if (!comp_enable) + ui->compression->setValue(-1); + + bool jobs_enable = (format == "d"); + ui->jobs->setEnabled(jobs_enable); + if (!jobs_enable) + ui->jobs->setValue(0); + } +} + +void BackupDialog::on_selectDestination_clicked() +{ + QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory); + QString fn = QFileDialog::getSaveFileName(this, tr("Save backup"), home_dir, + tr("Backup file (*.backup)")); + if (!fn.isEmpty()) { + ui->editFilename->setText(QDir::toNativeSeparators(fn)); + } +} diff --git a/src/BackupDialog.h b/src/BackupDialog.h index c8f85ab..6ce44dd 100644 --- a/src/BackupDialog.h +++ b/src/BackupDialog.h @@ -1,12 +1,16 @@ -#ifndef BACKUPDIALOG_H +#ifndef BACKUPDIALOG_H #define BACKUPDIALOG_H #include +#include "ConnectionConfig.h" +#include namespace Ui { -class BackupDialog; + class BackupDialog; } +class QStringList; + class BackupDialog : public QDialog { Q_OBJECT @@ -15,8 +19,26 @@ public: explicit BackupDialog(QWidget *parent = 0); ~BackupDialog(); + void ConnectTo(QProcess *process); + + void disconnectCurrentProcess(); + void setConfig(const ConnectionConfig &cfg); private: Ui::BackupDialog *ui; + QProcess *m_process = nullptr; + ConnectionConfig m_config; + + void writeOutput(QString s); + + void setParams(QStringList &args); +private slots: + + void on_process_readyRead(); + void on_process_errorOccurred(QProcess::ProcessError error); + void on_process_finished(int exitCode, QProcess::ExitStatus exitStatus); + void on_btnStart_clicked(); + void on_backupFormat_currentIndexChanged(int index); + void on_selectDestination_clicked(); }; #endif // BACKUPDIALOG_H diff --git a/src/BackupDialog.ui b/src/BackupDialog.ui index 6fadbff..3138428 100644 --- a/src/BackupDialog.ui +++ b/src/BackupDialog.ui @@ -6,204 +6,299 @@ 0 0 - 645 + 650 745 Dialog - + - 76 - 9 - 232 - 41 - - - - - - - - - - PushButton - - - - - - - - - 9 - 98 - 34 - 16 + 270 + 630 + 75 + 23 - Format + START - + - 76 - 98 - 69 - 20 + 30 + 90 + 601 + 501 - - - - - 9 - 9 - 42 - 16 - - - - Filename - - - - - - 9 - 124 - 26 - 16 - - - - Jobs: - - - - - - 76 - 124 - 37 - 20 - - - - - - - 76 - 150 - 70 - 17 - - - - Verbose - - - - - - 9 - 173 - 61 - 16 - - - - Compression - - - - - - 76 - 173 - 37 - 20 - - - - - - - 70 - 210 - 70 - 17 - - - - Data only - - - - - - 70 - 230 - 171 - 17 - - - - Include blobs - - - - - - 70 - 260 - 70 - 17 - - - - Clean - - - - - - 70 - 280 - 70 - 17 - - - - Create - - - - - - 20 - 320 - 47 - 13 - - - - Encoding - - - - - - 70 - 320 - 69 - 22 - + + 0 + + + Tab 1 + + + + + + + + Filename + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + ... + + + + + + + + + + Format + + + + + + + 0 + + + + + + + Jobs: + + + + + + + + + + Verbose + + + + + + + Compression + + + + + + + -1 means default, 0-9 is no compression to max compression + + + -1 + + + 9 + + + -1 + + + + + + + Include blobs + + + + + + + Clean + + + + + + + Create + + + + + + + No owner + + + + + + + Oids + + + + + + + + data + schema + + + + + data only (-a) + + + + + schema-only (-s) + + + + + + + + What + + + + + + + No privileges/acl + + + + + + + + + + Tab 2 + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + 240 + 240 + 240 + + + + + + + + + Source Code Pro + 10 + + + + QPlainTextEdit::NoWrap + + + true + + + Test text. + + + + + diff --git a/src/ConnectionConfig.cpp b/src/ConnectionConfig.cpp index ae7fe88..7b14dbb 100644 --- a/src/ConnectionConfig.cpp +++ b/src/ConnectionConfig.cpp @@ -1,5 +1,7 @@ #include "connectionconfig.h" +#include "util.h" #include +#include namespace { @@ -259,3 +261,60 @@ void ConnectionConfig::clean() { m_dirty = false; } + +/* + + PGHOST behaves the same as the host connection parameter. + PGHOSTADDR behaves the same as the hostaddr connection parameter. This can be set instead of or in addition to PGHOST to avoid DNS lookup overhead. + PGPORT behaves the same as the port connection parameter. + PGDATABASE behaves the same as the dbname connection parameter. + PGUSER behaves the same as the user connection parameter. + PGPASSWORD behaves the same as the password connection parameter. Use of this environment variable is not recommended for security reasons, as some operating systems allow non-root users to see process environment variables via ps; instead consider using the ~/.pgpass file (see Section 31.15). + PGPASSFILE specifies the name of the password file to use for lookups. If not set, it defaults to ~/.pgpass (see Section 31.15). + PGSERVICE behaves the same as the service connection parameter. + PGSERVICEFILE specifies the name of the per-user connection service file. If not set, it defaults to ~/.pg_service.conf (see Section 31.16). + PGREALM sets the Kerberos realm to use with PostgreSQL, if it is different from the local realm. If PGREALM is set, libpq applications will attempt authentication with servers for this realm and use separate ticket files to avoid conflicts with local ticket files. This environment variable is only used if GSSAPI authentication is selected by the server. + PGOPTIONS behaves the same as the options connection parameter. + PGAPPNAME behaves the same as the application_name connection parameter. + PGSSLMODE behaves the same as the sslmode connection parameter. + PGREQUIRESSL behaves the same as the requiressl connection parameter. + PGSSLCOMPRESSION behaves the same as the sslcompression connection parameter. + PGSSLCERT behaves the same as the sslcert connection parameter. + PGSSLKEY behaves the same as the sslkey connection parameter. + PGSSLROOTCERT behaves the same as the sslrootcert connection parameter. + PGSSLCRL behaves the same as the sslcrl connection parameter. + PGREQUIREPEER behaves the same as the requirepeer connection parameter. + PGKRBSRVNAME behaves the same as the krbsrvname connection parameter. + PGGSSLIB behaves the same as the gsslib connection parameter. + PGCONNECT_TIMEOUT behaves the same as the connect_timeout connection parameter. + PGCLIENTENCODING behaves the same as the client_encoding connection parameter. + + */ + +void ConnectionConfig::writeToEnvironment(QProcessEnvironment &env) const +{ + strToEnv(env, "PGHOST", m_host); + strToEnv(env, "PGHOSTADDR", m_hostaddr); + strToEnv(env, "PGPORT", m_port); + strToEnv(env, "PGDATABASE", m_dbname); + strToEnv(env, "PGUSER", m_user); + strToEnv(env, "PGPASSWORD", m_password); + strToEnv(env, "PGSSLMODE", m_sslMode); + strToEnv(env, "PGSSLCERT", m_sslCert); + strToEnv(env, "PGSSLKEY", m_sslKey); + strToEnv(env, "PGSSLROOTCERT", m_sslRootCert); + strToEnv(env, "PGSSLCRL", m_sslCrl); + strToEnv(env, "PGSSLCOMPRESSION", "0"); + strToEnv(env, "PGCONNECT_TIMEOUT", "10"); + env.insert("PGCLIENTENCODING", "utf8"); + + env.remove("PGREQUIRESSL"); +} + +void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val) +{ + if (val.empty()) + env.remove(var); + else + env.insert(var, stdStrToQ(val)); +} diff --git a/src/ConnectionConfig.h b/src/ConnectionConfig.h index 831ca76..b451a6a 100644 --- a/src/ConnectionConfig.h +++ b/src/ConnectionConfig.h @@ -4,6 +4,7 @@ #include #include + enum class SslMode { disable=0, allow=1, @@ -19,6 +20,9 @@ enum class PasswordMode { DontSave }; +class QProcessEnvironment; +class QString; + class ConnectionConfig { public: ConnectionConfig(); @@ -64,6 +68,8 @@ public: bool isSameDatabase(const ConnectionConfig &rhs) const; + void writeToEnvironment(QProcessEnvironment &env) const; + bool dirty() const; void clean(); private: @@ -86,6 +92,8 @@ private: bool m_dirty = false; + static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val); + static std::vector s_keywords; mutable std::vector m_values; }; diff --git a/src/ConnectionListModel.cpp b/src/ConnectionListModel.cpp index fef8bda..597ea13 100644 --- a/src/ConnectionListModel.cpp +++ b/src/ConnectionListModel.cpp @@ -14,7 +14,6 @@ ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent) ConnectionListModel::~ConnectionListModel() { - delete m_connections; } int ConnectionListModel::rowCount(const QModelIndex &parent) const diff --git a/src/ConnectionManagerWindow.cpp b/src/ConnectionManagerWindow.cpp index f4a9529..5e431a5 100644 --- a/src/ConnectionManagerWindow.cpp +++ b/src/ConnectionManagerWindow.cpp @@ -102,7 +102,8 @@ void ConnectionManagerWindow::on_actionQuit_application_triggered() void ConnectionManagerWindow::on_actionBackup_database_triggered() { - // BACKUP + auto ci = ui->listView->selectionModel()->currentIndex(); + m_masterController->openBackupDlgForConnection(ci.row()); } void ConnectionManagerWindow::on_actionManage_server_triggered() @@ -111,6 +112,8 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() m_masterController->openServerWindowForConnection(ci.row()); } + + #include //#include //#include @@ -118,6 +121,7 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() //#include #include + void ConnectionManagerWindow::on_testButton_clicked() { std::string error = Botan::runtime_version_check(BOTAN_VERSION_MAJOR, diff --git a/src/MasterController.cpp b/src/MasterController.cpp index 7e523f2..05b1319 100644 --- a/src/MasterController.cpp +++ b/src/MasterController.cpp @@ -4,6 +4,7 @@ #include "ConnectionListModel.h" #include "MainWindow.h" #include "ServerWindow.h" +#include "BackupDialog.h" MasterController::MasterController(QObject *parent) : QObject(parent) @@ -45,6 +46,18 @@ void MasterController::openSqlWindowForConnection(int connection_index) } +void MasterController::openBackupDlgForConnection(int connection_index) +{ + auto cc = m_connectionListModel->get(connection_index); + m_connectionListModel->save(connection_index); + if (cc.valid()) { + auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc.get()); + w->show(); + } +} + void MasterController::openServerWindowForConnection(int connection_index) { auto cc = m_connectionListModel->get(connection_index); diff --git a/src/MasterController.h b/src/MasterController.h index 7157673..68b4bd1 100644 --- a/src/MasterController.h +++ b/src/MasterController.h @@ -29,6 +29,7 @@ public: void showConnectionManager(); void openSqlWindowForConnection(int connection_index); void openServerWindowForConnection(int connection_index); + void openBackupDlgForConnection(int connection_index); signals: diff --git a/src/src.pro b/src/src.pro index 99a7152..26ad4c4 100644 --- a/src/src.pro +++ b/src/src.pro @@ -73,7 +73,8 @@ SOURCES += main.cpp\ Pgsql_Result.cpp \ Pgsql_Row.cpp \ Pgsql_Value.cpp \ - ConnectionList.cpp + ConnectionList.cpp \ + ProcessStdioWidget.cpp HEADERS += \ sqlparser.h \ @@ -108,7 +109,6 @@ HEADERS += \ ParamListModel.h \ MainWindow.h \ SqlSyntaxHighlighter.h \ - SqlLexer.h \ ServerWindow.h \ ASyncWindow.h \ DatabasesTableModel.h \ @@ -123,7 +123,8 @@ HEADERS += \ Pgsql_Result.h \ Pgsql_Row.h \ Pgsql_Value.h \ - ConnectionList.h + ConnectionList.h \ + ProcessStdioWidget.h FORMS += mainwindow.ui \ DatabaseWindow.ui \ @@ -132,7 +133,8 @@ FORMS += mainwindow.ui \ TuplesResultWidget.ui \ QueryTab.ui \ BackupDialog.ui \ - ServerWindow.ui + ServerWindow.ui \ + ProcessStdioWidget.ui RESOURCES += \ resources.qrc diff --git a/src/typeselectionitemmodel.h b/src/typeselectionitemmodel.h index ce43ff3..25055b9 100644 --- a/src/typeselectionitemmodel.h +++ b/src/typeselectionitemmodel.h @@ -6,10 +6,8 @@ class PgTypeContainer; -class TypeSelectionItemModel : public QAbstractListModel -{ +class TypeSelectionItemModel : public QAbstractListModel { Q_OBJECT - public: explicit TypeSelectionItemModel(QObject *parent = 0);