File indexing completed on 2024-06-23 05:13:41
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 commands/gnupgprocesscommand.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include <config-kleopatra.h> 0011 0012 #include "gnupgprocesscommand.h" 0013 0014 #include "command_p.h" 0015 0016 #include <Libkleo/GnuPG> 0017 0018 #include "kleopatra_debug.h" 0019 #include <KLocalizedString> 0020 #include <KWindowSystem> 0021 0022 #include <QByteArray> 0023 #include <QDialog> 0024 #include <QDialogButtonBox> 0025 #include <QPointer> 0026 #include <QProcess> 0027 #include <QPushButton> 0028 #include <QString> 0029 #include <QTextEdit> 0030 #include <QTimer> 0031 #include <QVBoxLayout> 0032 0033 static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds 0034 0035 using namespace Kleo; 0036 using namespace Kleo::Commands; 0037 0038 namespace 0039 { 0040 0041 class OutputDialog : public QDialog 0042 { 0043 Q_OBJECT 0044 public: 0045 explicit OutputDialog(QWidget *parent = nullptr) 0046 : QDialog(parent) 0047 , vlay(this) 0048 , logTextWidget(this) 0049 , buttonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close, Qt::Horizontal, this) 0050 { 0051 KDAB_SET_OBJECT_NAME(vlay); 0052 KDAB_SET_OBJECT_NAME(logTextWidget); 0053 KDAB_SET_OBJECT_NAME(buttonBox); 0054 0055 logTextWidget.setReadOnly(true); 0056 0057 vlay.addWidget(&logTextWidget, 1); 0058 vlay.addWidget(&buttonBox); 0059 0060 connect(closeButton(), &QAbstractButton::clicked, this, &QWidget::close); 0061 connect(cancelButton(), &QAbstractButton::clicked, this, &OutputDialog::slotCancelClicked); 0062 0063 resize(600, 500); 0064 } 0065 0066 Q_SIGNALS: 0067 void cancelRequested(); 0068 0069 public Q_SLOTS: 0070 void message(const QString &s) 0071 { 0072 logTextWidget.append(s); 0073 logTextWidget.ensureCursorVisible(); 0074 } 0075 void setComplete(bool complete) 0076 { 0077 cancelButton()->setVisible(!complete); 0078 } 0079 0080 private Q_SLOTS: 0081 void slotCancelClicked() 0082 { 0083 cancelButton()->hide(); 0084 Q_EMIT cancelRequested(); 0085 } 0086 0087 private: 0088 QAbstractButton *closeButton() const 0089 { 0090 return buttonBox.button(QDialogButtonBox::Close); 0091 } 0092 QAbstractButton *cancelButton() const 0093 { 0094 return buttonBox.button(QDialogButtonBox::Cancel); 0095 } 0096 0097 private: 0098 QVBoxLayout vlay; 0099 QTextEdit logTextWidget; 0100 QDialogButtonBox buttonBox; 0101 }; 0102 0103 } 0104 0105 class GnuPGProcessCommand::Private : Command::Private 0106 { 0107 friend class ::Kleo::Commands::GnuPGProcessCommand; 0108 GnuPGProcessCommand *q_func() const 0109 { 0110 return static_cast<GnuPGProcessCommand *>(q); 0111 } 0112 0113 public: 0114 explicit Private(GnuPGProcessCommand *qq, KeyListController *c); 0115 ~Private() override; 0116 0117 private: 0118 void init(); 0119 void ensureDialogCreated() 0120 { 0121 if (!showsOutputWindow) { 0122 return; 0123 } 0124 if (!dialog) { 0125 dialog = new OutputDialog; 0126 dialog->setAttribute(Qt::WA_DeleteOnClose); 0127 applyWindowID(dialog); 0128 connect(dialog.data(), &OutputDialog::cancelRequested, q, &Command::cancel); 0129 dialog->setWindowTitle(i18nc("@title:window", "Subprocess Diagnostics")); 0130 } 0131 } 0132 void ensureDialogVisible() 0133 { 0134 if (!showsOutputWindow) { 0135 return; 0136 } 0137 ensureDialogCreated(); 0138 if (dialog->isVisible()) { 0139 dialog->raise(); 0140 } else { 0141 dialog->show(); 0142 } 0143 } 0144 void message(const QString &msg) 0145 { 0146 if (dialog) { 0147 dialog->message(msg); 0148 } else { 0149 qCDebug(KLEOPATRA_LOG) << msg; 0150 } 0151 } 0152 0153 private: 0154 void slotProcessFinished(int, QProcess::ExitStatus); 0155 void slotProcessReadyReadStandardError(); 0156 0157 private: 0158 QProcess process; 0159 QPointer<OutputDialog> dialog; 0160 QStringList arguments; 0161 QByteArray errorBuffer; 0162 bool ignoresSuccessOrFailure; 0163 bool showsOutputWindow; 0164 bool canceled; 0165 }; 0166 0167 GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() 0168 { 0169 return static_cast<Private *>(d.get()); 0170 } 0171 const GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() const 0172 { 0173 return static_cast<const Private *>(d.get()); 0174 } 0175 0176 #define d d_func() 0177 #define q q_func() 0178 0179 GnuPGProcessCommand::Private::Private(GnuPGProcessCommand *qq, KeyListController *c) 0180 : Command::Private(qq, c) 0181 , process() 0182 , dialog() 0183 , errorBuffer() 0184 , ignoresSuccessOrFailure(false) 0185 , showsOutputWindow(false) 0186 , canceled(false) 0187 { 0188 process.setReadChannel(QProcess::StandardError); 0189 } 0190 0191 GnuPGProcessCommand::Private::~Private() 0192 { 0193 } 0194 0195 GnuPGProcessCommand::GnuPGProcessCommand(KeyListController *c) 0196 : Command(new Private(this, c)) 0197 { 0198 d->init(); 0199 } 0200 0201 GnuPGProcessCommand::GnuPGProcessCommand(QAbstractItemView *v, KeyListController *c) 0202 : Command(v, new Private(this, c)) 0203 { 0204 d->init(); 0205 } 0206 0207 GnuPGProcessCommand::GnuPGProcessCommand(const GpgME::Key &key) 0208 : Command(key, new Private(this, nullptr)) 0209 { 0210 d->init(); 0211 } 0212 0213 GnuPGProcessCommand::GnuPGProcessCommand(const std::vector<GpgME::Key> &keys) 0214 : Command(keys, new Private(this, nullptr)) 0215 { 0216 d->init(); 0217 } 0218 0219 void GnuPGProcessCommand::Private::init() 0220 { 0221 connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) { 0222 slotProcessFinished(exitCode, status); 0223 }); 0224 q->m_procReadyReadStdErrConnection = connect(&process, &QProcess::readyReadStandardError, q, [this]() { 0225 slotProcessReadyReadStandardError(); 0226 }); 0227 } 0228 0229 GnuPGProcessCommand::~GnuPGProcessCommand() 0230 { 0231 } 0232 0233 QDialog *GnuPGProcessCommand::dialog() const 0234 { 0235 return d->dialog; 0236 } 0237 0238 bool GnuPGProcessCommand::preStartHook(QWidget *) const 0239 { 0240 return true; 0241 } 0242 0243 void GnuPGProcessCommand::postSuccessHook(QWidget *) 0244 { 0245 } 0246 0247 void GnuPGProcessCommand::doStart() 0248 { 0249 if (!preStartHook(d->parentWidgetOrView())) { 0250 d->finished(); 0251 return; 0252 } 0253 0254 d->arguments = arguments(); 0255 0256 d->process.setProgram(d->arguments.takeFirst()); 0257 0258 d->process.setArguments(d->arguments); 0259 0260 // Historically code using this expects arguments first to be the program. 0261 d->arguments.prepend(d->process.program()); 0262 0263 d->process.start(); 0264 0265 if (!d->process.waitForStarted()) { 0266 d->error(i18n("Unable to start process %1. " 0267 "Please check your installation.", 0268 d->arguments[0]), 0269 errorCaption()); 0270 d->finished(); 0271 } else { 0272 d->ensureDialogVisible(); 0273 d->message(i18n("Starting %1...", d->arguments.join(QLatin1Char(' ')))); 0274 } 0275 } 0276 0277 void GnuPGProcessCommand::doCancel() 0278 { 0279 d->canceled = true; 0280 if (d->process.state() != QProcess::NotRunning) { 0281 d->process.terminate(); 0282 QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill); 0283 } 0284 } 0285 0286 void GnuPGProcessCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status) 0287 { 0288 if (!canceled) { 0289 if (status == QProcess::CrashExit) { 0290 const QString msg = q->crashExitMessage(arguments); 0291 if (!msg.isEmpty()) { 0292 error(msg, q->errorCaption()); 0293 } 0294 } else if (ignoresSuccessOrFailure) { 0295 if (dialog) { 0296 message(i18n("Process finished")); 0297 } else { 0298 ; 0299 } 0300 } else if (code) { 0301 const QString msg = q->errorExitMessage(arguments); 0302 if (!msg.isEmpty()) { 0303 error(q->errorExitMessage(arguments), q->errorCaption()); 0304 } 0305 } else { 0306 q->postSuccessHook(parentWidgetOrView()); 0307 const QString successMessage = q->successMessage(arguments); 0308 if (!successMessage.isNull()) { 0309 if (dialog) { 0310 message(successMessage); 0311 } else { 0312 information(successMessage, q->successCaption()); 0313 } 0314 } 0315 } 0316 } 0317 0318 if (dialog) { 0319 dialog->setComplete(true); 0320 } 0321 finished(); 0322 } 0323 0324 void GnuPGProcessCommand::Private::slotProcessReadyReadStandardError() 0325 { 0326 auto ba = process.readAllStandardError(); 0327 errorBuffer += ba; 0328 while (ba.endsWith('\n') || ba.endsWith('\r')) { 0329 ba.chop(1); 0330 } 0331 message(Kleo::stringFromGpgOutput(ba)); 0332 } 0333 0334 QString GnuPGProcessCommand::errorString() const 0335 { 0336 return Kleo::stringFromGpgOutput(d->errorBuffer); 0337 } 0338 0339 void GnuPGProcessCommand::setIgnoresSuccessOrFailure(bool ignores) 0340 { 0341 d->ignoresSuccessOrFailure = ignores; 0342 } 0343 0344 bool GnuPGProcessCommand::ignoresSuccessOrFailure() const 0345 { 0346 return d->ignoresSuccessOrFailure; 0347 } 0348 0349 void GnuPGProcessCommand::setShowsOutputWindow(bool show) 0350 { 0351 if (show == d->showsOutputWindow) { 0352 return; 0353 } 0354 d->showsOutputWindow = show; 0355 if (show) { 0356 d->ensureDialogCreated(); 0357 } else { 0358 if (d->dialog) { 0359 d->dialog->deleteLater(); 0360 } 0361 d->dialog = nullptr; 0362 } 0363 } 0364 0365 bool GnuPGProcessCommand::showsOutputWindow() const 0366 { 0367 return d->showsOutputWindow; 0368 } 0369 0370 QProcess *GnuPGProcessCommand::process() 0371 { 0372 return &d->process; 0373 } 0374 0375 QString GnuPGProcessCommand::successCaption() const 0376 { 0377 return QString(); 0378 } 0379 0380 QString GnuPGProcessCommand::successMessage(const QStringList &args) const 0381 { 0382 Q_UNUSED(args) 0383 return QString(); 0384 } 0385 0386 #undef d 0387 #undef q 0388 0389 #include "gnupgprocesscommand.moc" 0390 #include "moc_gnupgprocesscommand.cpp"