gnupgprocesscommand.cpp 10.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
/* -*- mode: c++; c-basic-offset:4 -*-
    commands/gnupgprocesscommand.cpp

    This file is part of Kleopatra, the KDE keymanager
    Copyright (c) 2008 Klarälvdalens Datakonsult AB

    Kleopatra is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Kleopatra is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the Qt library by Trolltech AS, Norway (or with modified versions
    of Qt that use the same license as Qt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    Qt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include <config-kleopatra.h>

#include "gnupgprocesscommand.h"

#include "command_p.h"

Allen Winter's avatar
Allen Winter committed
39
#include <utils/kdlogtextwidget.h>
40

Laurent Montel's avatar
Laurent Montel committed
41
#include "kleopatra_debug.h"
Laurent Montel's avatar
Laurent Montel committed
42
#include <KLocalizedString>
Allen Winter's avatar
Allen Winter committed
43 44 45 46 47 48 49 50 51 52 53
#include <KWindowSystem>

#include <QString>
#include <QStringList>
#include <QByteArray>
#include <QTimer>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QPointer>
54
#include <QProcess>
55 56 57 58 59 60

static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds

using namespace Kleo;
using namespace Kleo::Commands;

Laurent Montel's avatar
Laurent Montel committed
61 62
namespace
{
Allen Winter's avatar
Allen Winter committed
63

Laurent Montel's avatar
Laurent Montel committed
64 65 66 67
class OutputDialog : public QDialog
{
    Q_OBJECT
public:
Laurent Montel's avatar
Laurent Montel committed
68
    explicit OutputDialog(QWidget *parent = nullptr)
Laurent Montel's avatar
Laurent Montel committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
        : QDialog(parent),
          vlay(this),
          logTextWidget(this),
          buttonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close, Qt::Horizontal, this)
    {
        KDAB_SET_OBJECT_NAME(vlay);
        KDAB_SET_OBJECT_NAME(logTextWidget);
        KDAB_SET_OBJECT_NAME(buttonBox);

        logTextWidget.setMinimumVisibleLines(20);
        logTextWidget.setMinimumVisibleColumns(80);

        vlay.addWidget(&logTextWidget, 1);
        vlay.addWidget(&buttonBox);

84 85
        connect(closeButton(), &QAbstractButton::clicked, this, &QWidget::close);
        connect(cancelButton(), &QAbstractButton::clicked, this, &OutputDialog::slotCancelClicked);
Laurent Montel's avatar
Laurent Montel committed
86
    }
Allen Winter's avatar
Allen Winter committed
87

Laurent Montel's avatar
Laurent Montel committed
88 89
Q_SIGNALS:
    void cancelRequested();
Allen Winter's avatar
Allen Winter committed
90

Laurent Montel's avatar
Laurent Montel committed
91 92 93 94 95 96 97 98 99
public Q_SLOTS:
    void message(const QString &s)
    {
        logTextWidget.message(s);
    }
    void setComplete(bool complete)
    {
        cancelButton()->setVisible(!complete);
    }
Allen Winter's avatar
Allen Winter committed
100

Laurent Montel's avatar
Laurent Montel committed
101 102 103 104
private Q_SLOTS:
    void slotCancelClicked()
    {
        cancelButton()->hide();
Laurent Montel's avatar
Laurent Montel committed
105
        Q_EMIT cancelRequested();
Laurent Montel's avatar
Laurent Montel committed
106
    }
Allen Winter's avatar
Allen Winter committed
107

Laurent Montel's avatar
Laurent Montel committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
private:
    QAbstractButton *closeButton() const
    {
        return buttonBox.button(QDialogButtonBox::Close);
    }
    QAbstractButton *cancelButton() const
    {
        return buttonBox.button(QDialogButtonBox::Cancel);
    }

private:
    QVBoxLayout vlay;
    KDLogTextWidget logTextWidget;
    QDialogButtonBox buttonBox;
};
Allen Winter's avatar
Allen Winter committed
123 124 125

}

Laurent Montel's avatar
Laurent Montel committed
126 127
class GnuPGProcessCommand::Private : Command::Private
{
128
    friend class ::Kleo::Commands::GnuPGProcessCommand;
Laurent Montel's avatar
Laurent Montel committed
129 130 131 132
    GnuPGProcessCommand *q_func() const
    {
        return static_cast<GnuPGProcessCommand *>(q);
    }
133
public:
Laurent Montel's avatar
Laurent Montel committed
134
    explicit Private(GnuPGProcessCommand *qq, KeyListController *c);
135 136 137 138
    ~Private();

private:
    void init();
Laurent Montel's avatar
Laurent Montel committed
139 140 141
    void ensureDialogCreated()
    {
        if (!showsOutputWindow) {
Allen Winter's avatar
Allen Winter committed
142
            return;
Laurent Montel's avatar
Laurent Montel committed
143 144
        }
        if (!dialog) {
Allen Winter's avatar
Allen Winter committed
145
            dialog = new OutputDialog;
Laurent Montel's avatar
Laurent Montel committed
146 147
            dialog->setAttribute(Qt::WA_DeleteOnClose);
            applyWindowID(dialog);
148
            connect(dialog.data(), &OutputDialog::cancelRequested, q, &Command::cancel);
Laurent Montel's avatar
Laurent Montel committed
149
            dialog->setWindowTitle(i18n("Subprocess Diagnostics"));
Allen Winter's avatar
Allen Winter committed
150
        }
Allen Winter's avatar
Allen Winter committed
151
    }
Laurent Montel's avatar
Laurent Montel committed
152 153 154
    void ensureDialogVisible()
    {
        if (!showsOutputWindow) {
Allen Winter's avatar
Allen Winter committed
155
            return;
Laurent Montel's avatar
Laurent Montel committed
156
        }
Allen Winter's avatar
Allen Winter committed
157
        ensureDialogCreated();
Laurent Montel's avatar
Laurent Montel committed
158
        if (dialog->isVisible()) {
Allen Winter's avatar
Allen Winter committed
159
            dialog->raise();
Laurent Montel's avatar
Laurent Montel committed
160
        } else {
Allen Winter's avatar
Allen Winter committed
161
            dialog->show();
Laurent Montel's avatar
Laurent Montel committed
162
        }
Allen Winter's avatar
Allen Winter committed
163
#ifdef Q_OS_WIN
Laurent Montel's avatar
Laurent Montel committed
164
        KWindowSystem::forceActiveWindow(dialog->winId());
Allen Winter's avatar
Allen Winter committed
165 166
#endif
    }
Laurent Montel's avatar
Laurent Montel committed
167 168 169 170 171
    void message(const QString &msg)
    {
        if (dialog) {
            dialog->message(msg);
        } else {
Laurent Montel's avatar
Laurent Montel committed
172
            qCDebug(KLEOPATRA_LOG) << msg;
Laurent Montel's avatar
Laurent Montel committed
173
        }
Allen Winter's avatar
Allen Winter committed
174
    }
175 176

private:
Laurent Montel's avatar
Laurent Montel committed
177
    void slotProcessFinished(int, QProcess::ExitStatus);
178 179 180
    void slotProcessReadyReadStandardError();

private:
181
    QProcess process;
Allen Winter's avatar
Allen Winter committed
182
    QPointer<OutputDialog> dialog;
183 184
    QStringList arguments;
    QByteArray errorBuffer;
Thomas McGuire's avatar
Thomas McGuire committed
185
    bool ignoresSuccessOrFailure;
Allen Winter's avatar
Allen Winter committed
186
    bool showsOutputWindow;
187 188 189
    bool canceled;
};

Laurent Montel's avatar
Laurent Montel committed
190 191 192 193 194 195 196 197
GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func()
{
    return static_cast<Private *>(d.get());
}
const GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() const
{
    return static_cast<const Private *>(d.get());
}
198 199 200 201

#define d d_func()
#define q q_func()

Laurent Montel's avatar
Laurent Montel committed
202 203
GnuPGProcessCommand::Private::Private(GnuPGProcessCommand *qq, KeyListController *c)
    : Command::Private(qq, c),
204
      process(),
Allen Winter's avatar
Allen Winter committed
205
      dialog(),
206
      errorBuffer(),
Laurent Montel's avatar
Laurent Montel committed
207 208 209
      ignoresSuccessOrFailure(false),
      showsOutputWindow(false),
      canceled(false)
210
{
211
    process.setReadChannel(QProcess::StandardError);
212 213 214 215
}

GnuPGProcessCommand::Private::~Private() {}

Laurent Montel's avatar
Laurent Montel committed
216 217
GnuPGProcessCommand::GnuPGProcessCommand(KeyListController *c)
    : Command(new Private(this, c))
218 219 220 221
{
    d->init();
}

Laurent Montel's avatar
Laurent Montel committed
222 223
GnuPGProcessCommand::GnuPGProcessCommand(QAbstractItemView *v, KeyListController *c)
    : Command(v, new Private(this, c))
224 225 226 227
{
    d->init();
}

Laurent Montel's avatar
Laurent Montel committed
228
GnuPGProcessCommand::GnuPGProcessCommand(const GpgME::Key &key)
Laurent Montel's avatar
Laurent Montel committed
229
    : Command(key, new Private(this, nullptr))
230 231 232 233
{
    d->init();
}

Laurent Montel's avatar
Laurent Montel committed
234 235 236 237 238 239
void GnuPGProcessCommand::Private::init()
{
    connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)),
            q, SLOT(slotProcessFinished(int,QProcess::ExitStatus)));
    connect(&process, SIGNAL(readyReadStandardError()),
            q, SLOT(slotProcessReadyReadStandardError()));
240 241 242 243
}

GnuPGProcessCommand::~GnuPGProcessCommand() {}

Laurent Montel's avatar
Laurent Montel committed
244 245
QDialog *GnuPGProcessCommand::dialog() const
{
Allen Winter's avatar
Allen Winter committed
246 247 248
    return d->dialog;
}

Laurent Montel's avatar
Laurent Montel committed
249 250
bool GnuPGProcessCommand::preStartHook(QWidget *) const
{
251 252 253
    return true;
}

Laurent Montel's avatar
Laurent Montel committed
254 255
void GnuPGProcessCommand::postSuccessHook(QWidget *)
{
Thomas McGuire's avatar
Thomas McGuire committed
256 257 258

}

Laurent Montel's avatar
Laurent Montel committed
259 260
void GnuPGProcessCommand::doStart()
{
261

Laurent Montel's avatar
Laurent Montel committed
262
    if (!preStartHook(d->parentWidgetOrView())) {
263 264 265 266
        d->finished();
        return;
    }

267 268
    d->arguments = arguments();

269 270
    d->process.setProgram(d->arguments.takeFirst());
    d->process.setArguments(d->arguments);
271

272 273 274
    // Historically code using this expects arguments first to be the program.
    d->arguments.prepend(d->process.program());

275 276
    d->process.start();

Laurent Montel's avatar
Laurent Montel committed
277 278 279 280
    if (!d->process.waitForStarted()) {
        d->error(i18n("Unable to start process %1. "
                      "Please check your installation.", d->arguments[0]),
                 errorCaption());
281
        d->finished();
Allen Winter's avatar
Allen Winter committed
282 283
    } else {
        d->ensureDialogVisible();
Laurent Montel's avatar
Laurent Montel committed
284
        d->message(i18n("Starting %1...", d->arguments.join(QLatin1Char(' '))));
285 286 287
    }
}

Laurent Montel's avatar
Laurent Montel committed
288 289
void GnuPGProcessCommand::doCancel()
{
290
    d->canceled = true;
Laurent Montel's avatar
Laurent Montel committed
291
    if (d->process.state() != QProcess::NotRunning) {
292
        d->process.terminate();
293
        QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
294 295 296
    }
}

Laurent Montel's avatar
Laurent Montel committed
297 298
void GnuPGProcessCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
{
Sergio Martins's avatar
Sergio Martins committed
299
    if (!canceled) {
Laurent Montel's avatar
Laurent Montel committed
300
        if (status == QProcess::CrashExit) {
301 302 303 304
            const QString msg = q->crashExitMessage(arguments);
            if (!msg.isEmpty()) {
                error(msg, q->errorCaption());
            }
Laurent Montel's avatar
Laurent Montel committed
305 306 307
        } else if (ignoresSuccessOrFailure) {
            if (dialog) {
                message(i18n("Process finished"));
308
            } else {
Allen Winter's avatar
Allen Winter committed
309
                ;
310
            }
Laurent Montel's avatar
Laurent Montel committed
311
        } else if (code) {
312 313 314 315
            const QString msg = q->errorExitMessage(arguments);
            if (!msg.isEmpty()) {
                error(q->errorExitMessage(arguments), q->errorCaption());
            }
316
        } else {
Laurent Montel's avatar
Laurent Montel committed
317 318
            q->postSuccessHook(parentWidgetOrView());
            const QString successMessage = q->successMessage(arguments);
319 320 321 322 323 324
            if (!successMessage.isNull()) {
                if (dialog) {
                    message(successMessage);
                } else {
                    information(successMessage, q->successCaption());
                }
325
            }
Thomas McGuire's avatar
Thomas McGuire committed
326
        }
Sergio Martins's avatar
Sergio Martins committed
327 328
    }

Laurent Montel's avatar
Laurent Montel committed
329 330 331
    if (dialog) {
        dialog->setComplete(true);
    }
332 333 334
    finished();
}

Laurent Montel's avatar
Laurent Montel committed
335 336
void GnuPGProcessCommand::Private::slotProcessReadyReadStandardError()
{
337 338 339 340
    auto ba = process.readAllStandardError();
    errorBuffer += ba;
    while (ba.endsWith('\n') || ba.endsWith('\r')) {
        ba.chop(1);
Allen Winter's avatar
Allen Winter committed
341
    }
342
    message(QString::fromLocal8Bit(ba.constData(), ba.size()));
343 344
}

Laurent Montel's avatar
Laurent Montel committed
345 346 347
QString GnuPGProcessCommand::errorString() const
{
    return QString::fromLocal8Bit(d->errorBuffer);
348 349
}

Laurent Montel's avatar
Laurent Montel committed
350 351
void GnuPGProcessCommand::setIgnoresSuccessOrFailure(bool ignores)
{
Thomas McGuire's avatar
Thomas McGuire committed
352 353 354
    d->ignoresSuccessOrFailure = ignores;
}

Laurent Montel's avatar
Laurent Montel committed
355 356
bool GnuPGProcessCommand::ignoresSuccessOrFailure() const
{
Thomas McGuire's avatar
Thomas McGuire committed
357 358 359
    return d->ignoresSuccessOrFailure;
}

Laurent Montel's avatar
Laurent Montel committed
360 361 362
void GnuPGProcessCommand::setShowsOutputWindow(bool show)
{
    if (show == d->showsOutputWindow) {
Allen Winter's avatar
Allen Winter committed
363
        return;
Laurent Montel's avatar
Laurent Montel committed
364
    }
Allen Winter's avatar
Allen Winter committed
365
    d->showsOutputWindow = show;
Laurent Montel's avatar
Laurent Montel committed
366
    if (show) {
Allen Winter's avatar
Allen Winter committed
367 368
        d->ensureDialogCreated();
    } else {
Laurent Montel's avatar
Laurent Montel committed
369
        if (d->dialog) {
Allen Winter's avatar
Allen Winter committed
370
            d->dialog->deleteLater();
Laurent Montel's avatar
Laurent Montel committed
371
        }
Laurent Montel's avatar
Laurent Montel committed
372
        d->dialog = nullptr;
Allen Winter's avatar
Allen Winter committed
373
    }
Allen Winter's avatar
Allen Winter committed
374 375
}

Laurent Montel's avatar
Laurent Montel committed
376 377
bool GnuPGProcessCommand::showsOutputWindow() const
{
Allen Winter's avatar
Allen Winter committed
378 379 380
    return d->showsOutputWindow;
}

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
QProcess *GnuPGProcessCommand::process()
{
    return &d->process;
}

QString GnuPGProcessCommand::successCaption() const
{
    return QString();
}

QString GnuPGProcessCommand::successMessage(const QStringList &args) const
{
    Q_UNUSED(args);
    return QString();
}

397 398 399 400
#undef d
#undef q

#include "moc_gnupgprocesscommand.cpp"
Allen Winter's avatar
Allen Winter committed
401
#include "gnupgprocesscommand.moc"