SVN_SILENT made messages (.desktop file) - always resolve ours
[kdepim.git] / kleopatra / utils / output.cpp
blob2b9d080a43420e90ccc723893d3278e051b8db32
1 /* -*- mode: c++; c-basic-offset:4 -*-
2 utils/output.cpp
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2007 Klarälvdalens Datakonsult AB
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
30 your version.
33 #include <config-kleopatra.h>
35 #include "output.h"
37 #include "detail_p.h"
38 #include "kleo_assert.h"
39 #include "kdpipeiodevice.h"
40 #include "log.h"
41 #include "cached.h"
43 #include <Libkleo/Exception>
45 #include <KLocalizedString>
46 #include <KMessageBox>
47 #include "kleopatra_debug.h"
49 #include <QFileInfo>
50 #include <QTemporaryFile>
51 #include <QString>
52 #include <QClipboard>
53 #include <QApplication>
54 #include <QBuffer>
55 #include <QPointer>
56 #include <QWidget>
57 #include <QDir>
58 #include <QProcess>
59 #include <QTimer>
61 #ifdef Q_OS_WIN
62 # include <windows.h>
63 #endif
65 #include <errno.h>
67 using namespace Kleo;
68 using namespace Kleo::_detail;
69 using namespace boost;
71 static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1; // no timeout
72 static const int PROCESS_TERMINATE_TIMEOUT = 5 * 1000; // 5s
74 class OverwritePolicy::Private
76 public:
77 Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {}
78 OverwritePolicy::Policy policy;
79 QWidget *widget;
82 OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy))
86 OverwritePolicy::~OverwritePolicy() {}
88 OverwritePolicy::Policy OverwritePolicy::policy() const
90 return d->policy;
93 void OverwritePolicy::setPolicy(Policy policy)
95 d->policy = policy;
98 QWidget *OverwritePolicy::parentWidget() const
100 return d->widget;
103 namespace
106 class TemporaryFile : public QTemporaryFile
108 public:
109 explicit TemporaryFile() : QTemporaryFile() {}
110 explicit TemporaryFile(const QString &templateName) : QTemporaryFile(templateName) {}
111 explicit TemporaryFile(QObject *parent) : QTemporaryFile(parent) {}
112 explicit TemporaryFile(const QString &templateName, QObject *parent) : QTemporaryFile(templateName, parent) {}
114 void close() Q_DECL_OVERRIDE {
115 if (isOpen())
117 m_oldFileName = fileName();
119 QTemporaryFile::close();
122 bool openNonInheritable()
124 if (!QTemporaryFile::open()) {
125 return false;
127 #if defined(Q_OS_WIN)
128 //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable.
129 //The handle is then inherited by gpgsm, which prevents deletion of the temp file
130 //in FileOutput::doFinalize()
131 //There are no inheritable handles under wince
132 return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0);
133 #endif
134 return true;
137 QString oldFileName() const
139 return m_oldFileName;
142 private:
143 QString m_oldFileName;
146 template <typename T_IODevice>
147 struct inhibit_close : T_IODevice {
148 explicit inhibit_close() : T_IODevice() {}
149 template <typename T1>
150 explicit inhibit_close(T1 &t1) : T_IODevice(t1) {}
152 /* reimp */ void close() {}
153 void reallyClose()
155 T_IODevice::close();
159 template <typename T_IODevice>
160 struct redirect_close : T_IODevice {
161 explicit redirect_close() : T_IODevice(), m_closed(false) {}
162 template <typename T1>
163 explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {}
165 /* reimp */ void close()
167 this->closeWriteChannel();
168 m_closed = true;
171 bool isClosed() const
173 return m_closed;
175 private:
176 bool m_closed;
179 class OutputImplBase : public Output
181 public:
182 OutputImplBase()
183 : Output(),
184 m_defaultLabel(),
185 m_customLabel(),
186 m_errorString(),
187 m_isFinalized(false),
188 m_isFinalizing(false),
189 m_cancelPending(false),
190 m_canceled(false),
191 m_binaryOpt(false)
196 QString label() const Q_DECL_OVERRIDE
198 return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel;
200 void setLabel(const QString &label) Q_DECL_OVERRIDE {
201 m_customLabel = label;
203 void setDefaultLabel(const QString &l)
205 m_defaultLabel = l;
207 void setBinaryOpt(bool value) Q_DECL_OVERRIDE {
208 m_binaryOpt = value;
210 bool binaryOpt() const Q_DECL_OVERRIDE
212 return m_binaryOpt;
215 QString errorString() const Q_DECL_OVERRIDE
217 if (m_errorString.dirty()) {
218 m_errorString = doErrorString();
220 return m_errorString;
223 bool isFinalized() const Q_DECL_OVERRIDE
225 return m_isFinalized;
227 void finalize() Q_DECL_OVERRIDE {
228 qCDebug(KLEOPATRA_LOG) << this;
229 if (m_isFinalized || m_isFinalizing)
231 return;
233 m_isFinalizing = true;
234 try {
235 doFinalize();
236 } catch (...)
238 m_isFinalizing = false;
239 throw;
241 m_isFinalizing = false;
242 m_isFinalized = true;
243 if (m_cancelPending)
245 cancel();
249 void cancel() Q_DECL_OVERRIDE {
250 qCDebug(KLEOPATRA_LOG) << this;
251 if (m_isFinalizing)
253 m_cancelPending = true;
254 } else if (!m_canceled)
256 m_isFinalizing = true;
257 try {
258 doCancel();
259 } catch (...) {}
260 m_isFinalizing = false;
261 m_isFinalized = true;
262 m_canceled = true;
265 private:
266 virtual QString doErrorString() const
268 if (shared_ptr<QIODevice> io = ioDevice()) {
269 return io->errorString();
270 } else {
271 return i18n("No output device");
274 virtual void doFinalize() = 0;
275 virtual void doCancel() = 0;
276 private:
277 QString m_defaultLabel;
278 QString m_customLabel;
279 mutable cached<QString> m_errorString;
280 bool m_isFinalized : 1;
281 bool m_isFinalizing : 1;
282 bool m_cancelPending : 1;
283 bool m_canceled : 1;
284 bool m_binaryOpt : 1;
287 class PipeOutput : public OutputImplBase
289 public:
290 explicit PipeOutput(assuan_fd_t fd);
292 shared_ptr<QIODevice> ioDevice() const Q_DECL_OVERRIDE
294 return m_io;
296 void doFinalize() Q_DECL_OVERRIDE {
297 m_io->reallyClose();
299 void doCancel() Q_DECL_OVERRIDE {
300 doFinalize();
302 private:
303 shared_ptr< inhibit_close<KDPipeIODevice> > m_io;
306 class ProcessStdInOutput : public OutputImplBase
308 public:
309 explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd);
311 shared_ptr<QIODevice> ioDevice() const Q_DECL_OVERRIDE
313 return m_proc;
315 void doFinalize() Q_DECL_OVERRIDE {
317 Make sure the data is written in the output here. If this
318 is not done the output will be written in small chunks
319 trough the eventloop causing an uncessary delay before
320 the process has even a chance to work and finish.
321 This delay is mainly noticabe on Windows where it can
322 take ~30 seconds to write out a 10MB file in the 512 byte
323 chunks gpgme serves. */
324 qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite()
325 << " Bytes to be written";
326 while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT));
328 if (!m_proc->isClosed())
330 m_proc->close();
332 m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT);
334 void doCancel() Q_DECL_OVERRIDE {
335 m_proc->terminate();
336 QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill);
338 QString label() const Q_DECL_OVERRIDE;
340 private:
341 QString doErrorString() const Q_DECL_OVERRIDE;
343 private:
344 const QString m_command;
345 const QStringList m_arguments;
346 const shared_ptr< redirect_close<QProcess> > m_proc;
349 class FileOutput : public OutputImplBase
351 public:
352 explicit FileOutput(const QString &fileName, const shared_ptr<OverwritePolicy> &policy);
353 ~FileOutput()
355 qCDebug(KLEOPATRA_LOG) << this;
358 QString label() const Q_DECL_OVERRIDE
360 return QFileInfo(m_fileName).fileName();
362 shared_ptr<QIODevice> ioDevice() const Q_DECL_OVERRIDE
364 return m_tmpFile;
366 void doFinalize() Q_DECL_OVERRIDE;
367 void doCancel() Q_DECL_OVERRIDE {
368 qCDebug(KLEOPATRA_LOG) << this;
370 private:
371 bool obtainOverwritePermission();
373 private:
374 const QString m_fileName;
375 shared_ptr< TemporaryFile > m_tmpFile;
376 const shared_ptr<OverwritePolicy> m_policy;
379 #ifndef QT_NO_CLIPBOARD
380 class ClipboardOutput : public OutputImplBase
382 public:
383 explicit ClipboardOutput(QClipboard::Mode mode);
385 QString label() const Q_DECL_OVERRIDE;
386 shared_ptr<QIODevice> ioDevice() const Q_DECL_OVERRIDE
388 return m_buffer;
390 void doFinalize() Q_DECL_OVERRIDE;
391 void doCancel() Q_DECL_OVERRIDE {}
393 private:
394 QString doErrorString() const Q_DECL_OVERRIDE
396 return QString();
398 private:
399 const QClipboard::Mode m_mode;
400 shared_ptr<QBuffer> m_buffer;
402 #endif // QT_NO_CLIPBOARD
406 shared_ptr<Output> Output::createFromPipeDevice(assuan_fd_t fd, const QString &label)
408 shared_ptr<PipeOutput> po(new PipeOutput(fd));
409 po->setDefaultLabel(label);
410 return po;
413 PipeOutput::PipeOutput(assuan_fd_t fd)
414 : OutputImplBase(),
415 m_io(new inhibit_close<KDPipeIODevice>)
417 errno = 0;
418 if (!m_io->open(fd, QIODevice::WriteOnly))
419 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
420 i18n("Could not open FD %1 for writing",
421 assuanFD2int(fd)));
424 shared_ptr<Output> Output::createFromFile(const QString &fileName, bool forceOverwrite)
426 return createFromFile(fileName, shared_ptr<OverwritePolicy>(new OverwritePolicy(0, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny)));
429 shared_ptr<Output> Output::createFromFile(const QString &fileName, const shared_ptr<OverwritePolicy> &policy)
431 shared_ptr<FileOutput> fo(new FileOutput(fileName, policy));
432 qCDebug(KLEOPATRA_LOG) << fo.get();
433 return fo;
436 FileOutput::FileOutput(const QString &fileName, const shared_ptr<OverwritePolicy> &policy)
437 : OutputImplBase(),
438 m_fileName(fileName),
439 m_tmpFile(new TemporaryFile(fileName)),
440 m_policy(policy)
442 assert(m_policy);
443 errno = 0;
444 if (!m_tmpFile->openNonInheritable())
445 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
446 i18n("Could not create temporary file for output \"%1\"", fileName));
449 bool FileOutput::obtainOverwritePermission()
451 if (m_policy->policy() != OverwritePolicy::Ask) {
452 return m_policy->policy() == OverwritePolicy::Allow;
454 const int sel = KMessageBox::questionYesNoCancel(m_policy->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
455 "Overwrite?", m_fileName),
456 i18n("Overwrite Existing File?"),
457 KStandardGuiItem::overwrite(),
458 KGuiItem(i18n("Overwrite All")),
459 KStandardGuiItem::cancel());
460 if (sel == KMessageBox::No) { //Overwrite All
461 m_policy->setPolicy(OverwritePolicy::Allow);
463 return sel == KMessageBox::Yes || sel == KMessageBox::No;
466 void FileOutput::doFinalize()
468 qCDebug(KLEOPATRA_LOG) << this;
470 struct Remover {
471 QString file;
472 ~Remover()
474 if (QFile::exists(file)) {
475 QFile::remove(file);
478 } remover;
480 kleo_assert(m_tmpFile);
482 if (m_tmpFile->isOpen()) {
483 m_tmpFile->close();
486 const QString tmpFileName = remover.file = m_tmpFile->oldFileName();
488 m_tmpFile->setAutoRemove(false);
489 QPointer<QObject> guard = m_tmpFile.get();
490 m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/
491 kleo_assert(!guard); // if this triggers, we need to audit for holder of shared_ptr<QIODevice>s.
493 qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName;
495 if (QFile::rename(tmpFileName, m_fileName)) {
496 qCDebug(KLEOPATRA_LOG) << this << "succeeded";
497 return;
500 qCDebug(KLEOPATRA_LOG) << this << "failed";
502 if (!obtainOverwritePermission())
503 throw Exception(gpg_error(GPG_ERR_CANCELED),
504 i18n("Overwriting declined"));
506 qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName;
508 if (!QFile::remove(m_fileName))
509 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
510 i18n("Could not remove file \"%1\" for overwriting.", m_fileName));
512 qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName;
514 if (QFile::rename(tmpFileName, m_fileName)) {
515 qCDebug(KLEOPATRA_LOG) << this << "succeeded";
516 return;
519 qCDebug(KLEOPATRA_LOG) << this << "failed";
521 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
522 i18n("Could not rename file \"%1\" to \"%2\"",
523 tmpFileName, m_fileName));
526 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command)
528 return shared_ptr<Output>(new ProcessStdInOutput(command, QStringList(), QDir::current()));
531 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args)
533 return shared_ptr<Output>(new ProcessStdInOutput(command, args, QDir::current()));
536 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd)
538 return shared_ptr<Output>(new ProcessStdInOutput(command, args, wd));
541 ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd)
542 : OutputImplBase(),
543 m_command(cmd),
544 m_arguments(args),
545 m_proc(new redirect_close<QProcess>)
547 qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << endl << cmd << args;
548 if (cmd.isEmpty())
549 throw Exception(gpg_error(GPG_ERR_INV_ARG),
550 i18n("Command not specified"));
551 m_proc->setWorkingDirectory(wd.absolutePath());
552 m_proc->start(cmd, args);
553 m_proc->setReadChannel(QProcess::StandardError);
554 if (!m_proc->waitForStarted())
555 throw Exception(gpg_error(GPG_ERR_EIO),
556 i18n("Could not start %1 process: %2", cmd, m_proc->errorString()));
559 QString ProcessStdInOutput::label() const
561 if (!m_proc) {
562 return OutputImplBase::label();
564 // output max. 3 arguments
565 const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QStringLiteral(" "));
566 if (m_arguments.size() > 3) {
567 return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline);
568 } else {
569 return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline);
573 QString ProcessStdInOutput::doErrorString() const
575 kleo_assert(m_proc);
576 if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) {
577 return QString();
579 if (m_proc->error() == QProcess::UnknownError)
580 return i18n("Error while running %1: %2", m_command,
581 QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData()));
582 else {
583 return i18n("Failed to execute %1: %2", m_command, m_proc->errorString());
587 #ifndef QT_NO_CLIPBOARD
588 shared_ptr<Output> Output::createFromClipboard()
590 return shared_ptr<Output>(new ClipboardOutput(QClipboard::Clipboard));
593 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode)
594 : OutputImplBase(),
595 m_mode(mode),
596 m_buffer(new QBuffer)
598 errno = 0;
599 if (!m_buffer->open(QIODevice::WriteOnly))
600 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
601 i18n("Could not write to clipboard"));
604 QString ClipboardOutput::label() const
606 switch (m_mode) {
607 case QClipboard::Clipboard:
608 return i18n("Clipboard");
609 case QClipboard::FindBuffer:
610 return i18n("Find buffer");
611 case QClipboard::Selection:
612 return i18n("Selection");
614 return QString();
617 void ClipboardOutput::doFinalize()
619 if (m_buffer->isOpen()) {
620 m_buffer->close();
622 if (QClipboard *const cb = QApplication::clipboard()) {
623 cb->setText(QString::fromUtf8(m_buffer->data()));
624 } else
625 throw Exception(gpg_error(GPG_ERR_EIO),
626 i18n("Could not find clipboard"));
628 #endif // QT_NO_CLIPBOARD
630 Output::~Output() {}