pgLab/pglab/BackupDialog.cpp

518 lines
16 KiB
C++
Raw Normal View History

#include "BackupDialog.h"
#include "BackupFormatModel.h"
#include "PgDumpOutputHighlighter.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QScrollBar>
#include <QStandardPaths>
2019-07-13 08:45:32 +02:00
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QInputDialog>
2019-07-13 08:45:32 +02:00
#include <QLabel>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSettings>
2019-07-13 08:45:32 +02:00
#include <QSpinBox>
#include <QStackedLayout>
2019-07-13 08:45:32 +02:00
#include <QTabWidget>
#include <QVBoxLayout>
#include <QWidget>
2017-11-26 13:07:21 +01:00
#ifdef WIN32
# include <windows.h> // for CreateProcess flags
#endif
2019-07-13 08:45:32 +02:00
QPlainTextEdit* createStdOutput(QWidget *parent)
{
auto stdOutput = new QPlainTextEdit(parent);
stdOutput->setObjectName(QString::fromUtf8("stdOutput"));
QPalette palette;
QBrush brush(QColor(255, 255, 255, 255));
brush.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Active, QPalette::Text, brush);
QBrush brush1(QColor(0, 0, 0, 255));
brush1.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Active, QPalette::Base, brush1);
palette.setBrush(QPalette::Inactive, QPalette::Text, brush);
palette.setBrush(QPalette::Inactive, QPalette::Base, brush1);
QBrush brush2(QColor(120, 120, 120, 255));
brush2.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Disabled, QPalette::Text, brush2);
QBrush brush3(QColor(240, 240, 240, 255));
brush3.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Disabled, QPalette::Base, brush3);
stdOutput->setPalette(palette);
QFont font;
font.setFamily(QString::fromUtf8("Source Code Pro"));
font.setPointSize(10);
stdOutput->setFont(font);
stdOutput->setLineWrapMode(QPlainTextEdit::NoWrap);
stdOutput->setReadOnly(true);
new PgDumpOutputHighlighter(stdOutput->document());
2019-07-13 08:45:32 +02:00
return stdOutput;
}
BackupDialog::BackupDialog(QWidget *parent)
: QDialog(parent)
{
// Options tab contents
// Filename
labelFileName = new QLabel(this);
labelFileName->setObjectName(QString::fromUtf8("labelFileName"));
2019-07-13 08:45:32 +02:00
// Filename edit+button
editFilename = new QLineEdit(this);
2019-07-13 08:45:32 +02:00
editFilename->setObjectName(QString::fromUtf8("editFilename"));
selectDestination = new QPushButton;
2019-07-13 08:45:32 +02:00
selectDestination->setObjectName(QString::fromUtf8("selectDestination"));
layoutDestination = new QHBoxLayout;
layoutDestination->setSpacing(0);
layoutDestination->setObjectName(QString::fromUtf8("layoutDestination"));
layoutDestination->setContentsMargins(0, 0, 0, 0);
layoutDestination->addWidget(editFilename);
layoutDestination->addWidget(selectDestination);
widgetDestination = new QWidget;
widgetDestination->setObjectName(QString::fromUtf8("widgetDestination"));
widgetDestination->setLayout(layoutDestination);
// Backup format
labelFormat = new QLabel;
labelFormat->setObjectName(QString::fromUtf8("labelFormat"));
backupFormat = new QComboBox;
2019-07-13 08:45:32 +02:00
backupFormat->setObjectName(QString::fromUtf8("backupFormat"));
backupFormat->setModelColumn(0);
// Jobs
labelJobs = new QLabel;
labelJobs->setObjectName(QString::fromUtf8("labelJobs"));
jobs = new QSpinBox;
2019-07-13 08:45:32 +02:00
jobs->setObjectName(QString::fromUtf8("jobs"));
// Verbose
chkbxVerbose = new QCheckBox;
2019-07-13 08:45:32 +02:00
chkbxVerbose->setObjectName(QString::fromUtf8("chkbxVerbose"));
// Compression
labelCompression = new QLabel;
labelCompression->setObjectName(QString::fromUtf8("labelCompression"));
compression = new QSpinBox;
2019-07-13 08:45:32 +02:00
compression->setObjectName(QString::fromUtf8("compression"));
compression->setMinimum(-1);
compression->setMaximum(9);
compression->setValue(-1);
// Include BLOBs
chkbxIncludeBlobs = new QCheckBox(this);
2019-07-13 08:45:32 +02:00
chkbxIncludeBlobs->setObjectName(QString::fromUtf8("chkbxIncludeBlobs"));
// Clean
chkbxClean = new QCheckBox(this);
2019-07-13 08:45:32 +02:00
chkbxClean->setObjectName(QString::fromUtf8("chkbxClean"));
// Data and/or Schema
labelDataOrSchema = new QLabel(this);
labelDataOrSchema->setObjectName(QString::fromUtf8("labelDataOrSchema"));
what = new QComboBox(this);
what->addItem(QString());
what->addItem(QString());
what->addItem(QString());
what->setObjectName(QString::fromUtf8("what"));
// create
chkbxCreate = new QCheckBox(this);
2019-07-13 08:45:32 +02:00
chkbxCreate->setObjectName(QString::fromUtf8("chkbxCreate"));
// owner
noOwner = new QCheckBox(this);
2019-07-13 08:45:32 +02:00
noOwner->setObjectName(QString::fromUtf8("noOwner"));
// with oids
oids = new QCheckBox(this);
oids->setObjectName(QString::fromUtf8("oids"));
// without acl (permissions)
noAcl = new QCheckBox(this);
noAcl->setObjectName(QString::fromUtf8("noAcl"));
2019-07-13 08:45:32 +02:00
// Options layout
auto formLayout = new QFormLayout;
formLayout->setObjectName(QString::fromUtf8("formLayout"));
int form_row = 0;
formLayout->setWidget(form_row, QFormLayout::LabelRole, labelFileName);
formLayout->setWidget(form_row, QFormLayout::FieldRole, widgetDestination);
++form_row;
formLayout->setWidget(form_row, QFormLayout::LabelRole, labelFormat);
formLayout->setWidget(form_row, QFormLayout::FieldRole, backupFormat);
++form_row;
formLayout->setWidget(form_row, QFormLayout::LabelRole, labelJobs);
formLayout->setWidget(form_row, QFormLayout::FieldRole, jobs);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxVerbose);
++form_row;
formLayout->setWidget(form_row, QFormLayout::LabelRole, labelCompression);
formLayout->setWidget(form_row, QFormLayout::FieldRole, compression);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, what);
formLayout->setWidget(form_row, QFormLayout::LabelRole, labelDataOrSchema);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxIncludeBlobs);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxClean);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxCreate);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, noOwner);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, oids);
++form_row;
formLayout->setWidget(form_row, QFormLayout::FieldRole, noAcl);
btnStart = new QPushButton;
btnStart->setObjectName(QString::fromUtf8("btnStart"));
2019-07-13 08:45:32 +02:00
auto verticalLayout = new QVBoxLayout;
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
verticalLayout->addLayout(formLayout);
verticalLayout->addWidget(btnStart);
2019-07-13 08:45:32 +02:00
optionsView = new QWidget();
optionsView->setObjectName(QString::fromUtf8("tabOptions"));
optionsView->setLayout(verticalLayout);
2019-07-13 08:45:32 +02:00
// Progress tab
stdOutput = createStdOutput(this);
2019-07-13 08:45:32 +02:00
m_OutputOkFormat.setForeground(Qt::white);
m_OutputOkFormat.setBackground(QBrush(Qt::darkGreen));
m_OutputOkFormat.setFontWeight(QFont::Bold);
m_OutputErrorFormat.setForeground(Qt::white);
m_OutputErrorFormat.setBackground(QBrush(Qt::darkRed));
m_OutputErrorFormat.setFontWeight(QFont::Bold);
2019-07-13 08:45:32 +02:00
btnBack = new QPushButton;
btnBack->setObjectName("btnBack");
btnClose = new QPushButton;
btnClose->setObjectName("btnClose");
2019-07-13 08:45:32 +02:00
auto layoutButtons = new QHBoxLayout;
layoutButtons->addWidget(btnBack);
layoutButtons->addWidget(btnClose);
2019-07-13 08:45:32 +02:00
auto progressMainLayout = new QVBoxLayout;
progressMainLayout->setObjectName(QString::fromUtf8("progressMainLayout"));
progressMainLayout->addWidget(stdOutput);
progressMainLayout->addLayout(layoutButtons);
2019-07-13 08:45:32 +02:00
progressView = new QWidget;
progressView->setObjectName(QString::fromUtf8("progressTab"));
progressView->setLayout(progressMainLayout);
2019-07-13 08:45:32 +02:00
viewStack = new QStackedLayout;
viewStack->setObjectName(QString::fromUtf8("viewStack"));
viewStack->addWidget(optionsView);
viewStack->addWidget(progressView);
viewStack->setCurrentWidget(optionsView);
2019-07-13 08:45:32 +02:00
setLayout(viewStack);
2019-07-13 08:45:32 +02:00
QMetaObject::connectSlotsByName(this);
connect(btnClose, &QPushButton::clicked, this, &QDialog::close);
auto format_model = new BackupFormatModel(this);
2019-07-13 08:45:32 +02:00
backupFormat->setModel(format_model);
retranslateUi();
}
BackupDialog::~BackupDialog()
{
}
2019-07-13 08:45:32 +02:00
void BackupDialog::retranslateUi()
{
setWindowTitle(QApplication::translate("BackupDialog", "Backup database", nullptr));
labelFileName->setText(QApplication::translate("BackupDialog", "Filename", nullptr));
2019-07-13 08:45:32 +02:00
selectDestination->setText(QApplication::translate("BackupDialog", "...", nullptr));
labelFormat->setText(QApplication::translate("BackupDialog", "Format", nullptr));
labelJobs->setText(QApplication::translate("BackupDialog", "Jobs:", nullptr));
2019-07-13 08:45:32 +02:00
chkbxVerbose->setText(QApplication::translate("BackupDialog", "Verbose (-v)", nullptr));
labelCompression->setText(QApplication::translate("BackupDialog", "Compression (-Z)", nullptr));
2019-07-13 08:45:32 +02:00
#ifndef QT_NO_TOOLTIP
compression->setToolTip(QApplication::translate("BackupDialog", "-1 means default, 0-9 is no compression to max compression", nullptr));
#endif // QT_NO_TOOLTIP
labelDataOrSchema->setText(QApplication::translate("BackupDialog", "What", nullptr));
2019-07-13 08:45:32 +02:00
what->setItemText(0, QApplication::translate("BackupDialog", "data + schema", nullptr));
what->setItemText(1, QApplication::translate("BackupDialog", "data only (-a)", nullptr));
what->setItemText(2, QApplication::translate("BackupDialog", "schema-only (-s)", nullptr));
chkbxIncludeBlobs->setText(QApplication::translate("BackupDialog", "Include blobs (-b)", nullptr));
chkbxClean->setText(QApplication::translate("BackupDialog", "Clean (-c)", nullptr));
chkbxCreate->setText(QApplication::translate("BackupDialog", "Create (-C)", nullptr));
noOwner->setText(QApplication::translate("BackupDialog", "No owner (-O)", nullptr));
oids->setText(QApplication::translate("BackupDialog", "Oids (-o)", nullptr));
noAcl->setText(QApplication::translate("BackupDialog", "No privileges/acl (-x)", nullptr));
btnStart->setText(QApplication::translate("BackupDialog", "START", nullptr));
btnBack->setText(QApplication::translate("BackupDialog", "Back", nullptr));
btnClose->setText(QApplication::translate("BackupDialog", "Close", nullptr));
2019-07-13 08:45:32 +02:00
} // retranslateUi
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(process_readyRead()));
connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(process_errorOccurred(QProcess::ProcessError)));
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
SLOT(process_finished(int, QProcess::ExitStatus)));
}
}
void BackupDialog::disconnectCurrentProcess()
{
if (m_process) {
disconnect(m_process, SIGNAL(readyRead()), this, SLOT(process_readyRead()));
disconnect(m_process, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(process_errorOccurred(QProcess::ProcessError)));
disconnect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this,
SLOT(process_finished(int, QProcess::ExitStatus)));
m_process = nullptr;
}
}
void BackupDialog::writeOutput(const QString &s, Format f)
{
switch (f) {
case Format::Normal:
stdOutput->appendPlainText(s);
break;
case Format::Error:
stdOutput->appendHtml("<span style='background-color: maroon; color: white; font-weight: bold'>" + s.toHtmlEscaped() + "</span>");
break;
case Format::Success:
stdOutput->appendHtml("<span style='background-color: green; color: white; font-weight: bold'>" + s.toHtmlEscaped() + "</span>");
break;
}
2019-07-13 08:45:32 +02:00
QScrollBar *bar = stdOutput->verticalScrollBar();
bar->setValue(bar->maximum());
}
void BackupDialog::process_readyRead()
{
QByteArray data = m_process->readAllStandardOutput();
writeOutput(QString::fromUtf8(data));
}
void BackupDialog::process_errorOccurred(QProcess::ProcessError error)
{
QString msg;
switch (error) {
case 0:
msg = tr("Failed to start. Possibly the executable does not exist or you have insufficient privileges to invoke the program.");
break;
case 1:
msg = tr("Crashed");
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();
// }
writeOutput(msg, Format::Error);
btnBack->setEnabled(true);
btnClose->setEnabled(true);
}
void BackupDialog::process_finished(int exitCode, QProcess::ExitStatus)
{
btnBack->setEnabled(true);
btnClose->setEnabled(true);
QString msg = tr("Completed, with exitcode %1\n").arg(exitCode);
writeOutput(msg, exitCode == 0 ? Format::Success : Format::Error);
}
void BackupDialog::setParams(QStringList &args)
{
QString short_args("-");
2019-07-13 08:45:32 +02:00
if (chkbxVerbose->checkState() == Qt::Checked)
short_args += "v";
2019-07-13 08:45:32 +02:00
if (chkbxClean->checkState() == Qt::Checked)
short_args += "c";
2019-07-13 08:45:32 +02:00
if (chkbxCreate->checkState() == Qt::Checked)
short_args += "C";
2019-07-13 08:45:32 +02:00
if (chkbxIncludeBlobs->checkState() == Qt::Checked)
short_args += "b";
2019-07-13 08:45:32 +02:00
switch (what->currentIndex()) {
case 1:
short_args += "a";
break;
case 2:
short_args += "s";
break;
}
2019-07-13 08:45:32 +02:00
if (oids->checkState() == Qt::Checked)
short_args += "o";
2019-07-13 08:45:32 +02:00
if (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?
2019-07-13 08:45:32 +02:00
QFileInfo fi(QDir::fromNativeSeparators(editFilename->text()));
QDir dir(fi.absolutePath());
if (!dir.exists())
dir.mkdir(fi.absolutePath());
2019-07-13 08:45:32 +02:00
args << "-f" << editFilename->text(); // R"-(c:\temp\backup.sql)-";
2019-07-13 08:45:32 +02:00
int format_index = backupFormat->currentIndex();
auto format_model_base = backupFormat->model();
auto *bfm = dynamic_cast<BackupFormatModel *>(format_model_base);
if (bfm) {
QVariant v = bfm->data(bfm->index(format_index, 1));
QString format("-F");
format += v.toString();
args << format;
}
2019-07-13 08:45:32 +02:00
int j = jobs->value();
if (j > 0)
args << "-j" << QString::number(j);
2019-07-13 08:45:32 +02:00
int comp = compression->value();
if (comp >= 0)
args << "-Z" << QString::number(comp);
}
void BackupDialog::on_btnStart_clicked()
{
2019-07-13 08:45:32 +02:00
stdOutput->clear();
btnBack->setEnabled(false);
btnClose->setEnabled(false);
viewStack->setCurrentWidget(progressView);
QSettings user_settings; // Default constructor is for UserScope
QVariant var = user_settings.value("pg_tools_path", {});
QDir dir;
if (var.type() == QMetaType::QString) {
dir.setPath(var.toString());
}
if (!dir.exists() || !dir.exists("pg_dump.exe")) {
QString result = QInputDialog::getText(this, tr("Postgres tools location"), tr("Please specify the folder that contains the postgresql tools executables"));
if (!result.isEmpty()) {
QDir d(result);
if (d.exists() && d.exists("pg_dump.exe")) {
user_settings.setValue("pg_tools_path", result);
dir = d;
}
else {
return;
}
}
}
//QString program = R"-(C:\Prog\bigsql\pg96\bin\pg_dump.exe)-";
QString program = dir.path() + "/pg_dump.exe";
QStringList arguments;
setParams(arguments);
// 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);
#ifdef WIN32
2019-07-13 08:45:32 +02:00
// p->setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args)
// {
// args->flags |= CREATE_NEW_CONSOLE;
// args->flags &= ~DETACHED_PROCESS;
// args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
// });
#endif
p->start(program, arguments);
}
void BackupDialog::on_backupFormat_currentIndexChanged(int /*index*/)
{
2019-07-13 08:45:32 +02:00
int format_index = backupFormat->currentIndex();
auto format_model_base = backupFormat->model();
auto *bfm = dynamic_cast<BackupFormatModel *>(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");
2019-07-13 08:45:32 +02:00
compression->setEnabled(comp_enable);
if (!comp_enable)
2019-07-13 08:45:32 +02:00
compression->setValue(-1);
bool jobs_enable = (format == "d");
2019-07-13 08:45:32 +02:00
jobs->setEnabled(jobs_enable);
if (!jobs_enable)
2019-07-13 08:45:32 +02:00
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()) {
2019-07-13 08:45:32 +02:00
editFilename->setText(QDir::toNativeSeparators(fn));
}
}
void BackupDialog::on_btnBack_clicked()
{
viewStack->setCurrentWidget(optionsView);
}