#include "BackupDialog.h" #include "BackupFormatModel.h" #include "PgDumpOutputHighlighter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 # include // for CreateProcess flags #endif 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()); return stdOutput; } BackupDialog::BackupDialog(QWidget *parent) : QDialog(parent) { // Options tab contents // Filename labelFileName = new QLabel(this); labelFileName->setObjectName(QString::fromUtf8("labelFileName")); // Filename edit+button editFilename = new QLineEdit(this); editFilename->setObjectName(QString::fromUtf8("editFilename")); selectDestination = new QPushButton; 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; backupFormat->setObjectName(QString::fromUtf8("backupFormat")); backupFormat->setModelColumn(0); // Jobs labelJobs = new QLabel; labelJobs->setObjectName(QString::fromUtf8("labelJobs")); jobs = new QSpinBox; jobs->setObjectName(QString::fromUtf8("jobs")); // Verbose chkbxVerbose = new QCheckBox; chkbxVerbose->setObjectName(QString::fromUtf8("chkbxVerbose")); // Compression labelCompression = new QLabel; labelCompression->setObjectName(QString::fromUtf8("labelCompression")); compression = new QSpinBox; compression->setObjectName(QString::fromUtf8("compression")); compression->setMinimum(-1); compression->setMaximum(9); compression->setValue(-1); // Include BLOBs chkbxIncludeBlobs = new QCheckBox(this); chkbxIncludeBlobs->setObjectName(QString::fromUtf8("chkbxIncludeBlobs")); // Clean chkbxClean = new QCheckBox(this); 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); chkbxCreate->setObjectName(QString::fromUtf8("chkbxCreate")); // owner noOwner = new QCheckBox(this); 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")); // 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")); auto verticalLayout = new QVBoxLayout; verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); verticalLayout->addLayout(formLayout); verticalLayout->addWidget(btnStart); optionsView = new QWidget(); optionsView->setObjectName(QString::fromUtf8("tabOptions")); optionsView->setLayout(verticalLayout); // Progress tab stdOutput = createStdOutput(this); 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); btnBack = new QPushButton; btnBack->setObjectName("btnBack"); btnClose = new QPushButton; btnClose->setObjectName("btnClose"); auto layoutButtons = new QHBoxLayout; layoutButtons->addWidget(btnBack); layoutButtons->addWidget(btnClose); auto progressMainLayout = new QVBoxLayout; progressMainLayout->setObjectName(QString::fromUtf8("progressMainLayout")); progressMainLayout->addWidget(stdOutput); progressMainLayout->addLayout(layoutButtons); progressView = new QWidget; progressView->setObjectName(QString::fromUtf8("progressTab")); progressView->setLayout(progressMainLayout); viewStack = new QStackedLayout; viewStack->setObjectName(QString::fromUtf8("viewStack")); viewStack->addWidget(optionsView); viewStack->addWidget(progressView); viewStack->setCurrentWidget(optionsView); setLayout(viewStack); QMetaObject::connectSlotsByName(this); connect(btnClose, &QPushButton::clicked, this, &QDialog::close); auto format_model = new BackupFormatModel(this); backupFormat->setModel(format_model); retranslateUi(); } BackupDialog::~BackupDialog() { } void BackupDialog::retranslateUi() { setWindowTitle(QApplication::translate("BackupDialog", "Backup database", nullptr)); labelFileName->setText(QApplication::translate("BackupDialog", "Filename", nullptr)); selectDestination->setText(QApplication::translate("BackupDialog", "...", nullptr)); labelFormat->setText(QApplication::translate("BackupDialog", "Format", nullptr)); labelJobs->setText(QApplication::translate("BackupDialog", "Jobs:", nullptr)); chkbxVerbose->setText(QApplication::translate("BackupDialog", "Verbose (-v)", nullptr)); labelCompression->setText(QApplication::translate("BackupDialog", "Compression (-Z)", nullptr)); #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)); 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)); } // 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("" + s.toHtmlEscaped() + ""); break; case Format::Success: stdOutput->appendHtml("" + s.toHtmlEscaped() + ""); break; } 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("-"); if (chkbxVerbose->checkState() == Qt::Checked) short_args += "v"; if (chkbxClean->checkState() == Qt::Checked) short_args += "c"; if (chkbxCreate->checkState() == Qt::Checked) short_args += "C"; if (chkbxIncludeBlobs->checkState() == Qt::Checked) short_args += "b"; switch (what->currentIndex()) { case 1: short_args += "a"; break; case 2: short_args += "s"; break; } if (oids->checkState() == Qt::Checked) short_args += "o"; 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? QFileInfo fi(QDir::fromNativeSeparators(editFilename->text())); QDir dir(fi.absolutePath()); if (!dir.exists()) dir.mkdir(fi.absolutePath()); args << "-f" << editFilename->text(); // R"-(c:\temp\backup.sql)-"; int format_index = backupFormat->currentIndex(); auto format_model_base = backupFormat->model(); auto *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 j = jobs->value(); if (j > 0) args << "-j" << QString::number(j); int comp = compression->value(); if (comp >= 0) args << "-Z" << QString::number(comp); } void BackupDialog::on_btnStart_clicked() { 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 // 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*/) { int format_index = backupFormat->currentIndex(); auto format_model_base = backupFormat->model(); auto *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"); compression->setEnabled(comp_enable); if (!comp_enable) compression->setValue(-1); bool jobs_enable = (format == "d"); jobs->setEnabled(jobs_enable); if (!jobs_enable) 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()) { editFilename->setText(QDir::toNativeSeparators(fn)); } } void BackupDialog::on_btnBack_clicked() { viewStack->setCurrentWidget(optionsView); }