Use QStringLiteral
[kdepim.git] / kleopatra / utils / output.cpp
blob62d364deb75be97cf90e025e52e65864945447c1
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 <kleo/exception.h>
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 /* reimp */ void close()
116 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 /* reimp */ QString label() const
198 return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel;
200 /* reimp */ void setLabel(const QString &label)
202 m_customLabel = label;
204 void setDefaultLabel(const QString &l)
206 m_defaultLabel = l;
208 /* reimp */ void setBinaryOpt(bool value)
210 m_binaryOpt = value;
212 /* reimp */ bool binaryOpt() const
214 return m_binaryOpt;
217 /* reimp */ QString errorString() const
219 if (m_errorString.dirty()) {
220 m_errorString = doErrorString();
222 return m_errorString;
225 /* reimp */ bool isFinalized() const
227 return m_isFinalized;
229 /* reimp */ void finalize()
231 qCDebug(KLEOPATRA_LOG) << this;
232 if (m_isFinalized || m_isFinalizing) {
233 return;
235 m_isFinalizing = true;
236 try {
237 doFinalize();
238 } catch (...) {
239 m_isFinalizing = false;
240 throw;
242 m_isFinalizing = false;
243 m_isFinalized = true;
244 if (m_cancelPending) {
245 cancel();
249 /* reimp */ void cancel()
251 qCDebug(KLEOPATRA_LOG) << this;
252 if (m_isFinalizing) {
253 m_cancelPending = true;
254 } else if (!m_canceled) {
255 m_isFinalizing = true;
256 try {
257 doCancel();
258 } catch (...) {}
259 m_isFinalizing = false;
260 m_isFinalized = true;
261 m_canceled = true;
264 private:
265 virtual QString doErrorString() const
267 if (shared_ptr<QIODevice> io = ioDevice()) {
268 return io->errorString();
269 } else {
270 return i18n("No output device");
273 virtual void doFinalize() = 0;
274 virtual void doCancel() = 0;
275 private:
276 QString m_defaultLabel;
277 QString m_customLabel;
278 mutable cached<QString> m_errorString;
279 bool m_isFinalized : 1;
280 bool m_isFinalizing : 1;
281 bool m_cancelPending : 1;
282 bool m_canceled : 1;
283 bool m_binaryOpt : 1;
286 class PipeOutput : public OutputImplBase
288 public:
289 explicit PipeOutput(assuan_fd_t fd);
291 /* reimp */ shared_ptr<QIODevice> ioDevice() const
293 return m_io;
295 /* reimp */ void doFinalize()
297 m_io->reallyClose();
299 /* reimp */ void doCancel()
301 doFinalize();
303 private:
304 shared_ptr< inhibit_close<KDPipeIODevice> > m_io;
307 class ProcessStdInOutput : public OutputImplBase
309 public:
310 explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd);
312 /* reimp */ shared_ptr<QIODevice> ioDevice() const
314 return m_proc;
316 /* reimp */ void doFinalize()
319 Make sure the data is written in the output here. If this
320 is not done the output will be written in small chunks
321 trough the eventloop causing an uncessary delay before
322 the process has even a chance to work and finish.
323 This delay is mainly noticabe on Windows where it can
324 take ~30 seconds to write out a 10MB file in the 512 byte
325 chunks gpgme serves. */
326 qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite()
327 << " Bytes to be written";
328 while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT));
330 if (!m_proc->isClosed()) {
331 m_proc->close();
333 m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT);
335 /* reimp */ void doCancel()
337 m_proc->terminate();
338 QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), SLOT(kill()));
340 /* reimp */ QString label() const;
342 private:
343 /* reimp */ QString doErrorString() const;
345 private:
346 const QString m_command;
347 const QStringList m_arguments;
348 const shared_ptr< redirect_close<QProcess> > m_proc;
351 class FileOutput : public OutputImplBase
353 public:
354 explicit FileOutput(const QString &fileName, const shared_ptr<OverwritePolicy> &policy);
355 ~FileOutput()
357 qCDebug(KLEOPATRA_LOG) << this;
360 /* reimp */ QString label() const
362 return QFileInfo(m_fileName).fileName();
364 /* reimp */ shared_ptr<QIODevice> ioDevice() const
366 return m_tmpFile;
368 /* reimp */ void doFinalize();
369 /* reimp */ void doCancel()
371 qCDebug(KLEOPATRA_LOG) << this;
373 private:
374 bool obtainOverwritePermission();
376 private:
377 const QString m_fileName;
378 shared_ptr< TemporaryFile > m_tmpFile;
379 const shared_ptr<OverwritePolicy> m_policy;
382 #ifndef QT_NO_CLIPBOARD
383 class ClipboardOutput : public OutputImplBase
385 public:
386 explicit ClipboardOutput(QClipboard::Mode mode);
388 /* reimp */ QString label() const;
389 /* reimp */ shared_ptr<QIODevice> ioDevice() const
391 return m_buffer;
393 /* reimp */ void doFinalize();
394 /* reimp */ void doCancel() {}
396 private:
397 /* reimp */ QString doErrorString() const
399 return QString();
401 private:
402 const QClipboard::Mode m_mode;
403 shared_ptr<QBuffer> m_buffer;
405 #endif // QT_NO_CLIPBOARD
409 shared_ptr<Output> Output::createFromPipeDevice(assuan_fd_t fd, const QString &label)
411 shared_ptr<PipeOutput> po(new PipeOutput(fd));
412 po->setDefaultLabel(label);
413 return po;
416 PipeOutput::PipeOutput(assuan_fd_t fd)
417 : OutputImplBase(),
418 m_io(new inhibit_close<KDPipeIODevice>)
420 errno = 0;
421 if (!m_io->open(fd, QIODevice::WriteOnly))
422 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
423 i18n("Could not open FD %1 for writing",
424 assuanFD2int(fd)));
427 shared_ptr<Output> Output::createFromFile(const QString &fileName, bool forceOverwrite)
429 return createFromFile(fileName, shared_ptr<OverwritePolicy>(new OverwritePolicy(0, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny)));
432 shared_ptr<Output> Output::createFromFile(const QString &fileName, const shared_ptr<OverwritePolicy> &policy)
434 shared_ptr<FileOutput> fo(new FileOutput(fileName, policy));
435 qCDebug(KLEOPATRA_LOG) << fo.get();
436 return fo;
439 FileOutput::FileOutput(const QString &fileName, const shared_ptr<OverwritePolicy> &policy)
440 : OutputImplBase(),
441 m_fileName(fileName),
442 m_tmpFile(new TemporaryFile(fileName)),
443 m_policy(policy)
445 assert(m_policy);
446 errno = 0;
447 if (!m_tmpFile->openNonInheritable())
448 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
449 i18n("Could not create temporary file for output \"%1\"", fileName));
452 bool FileOutput::obtainOverwritePermission()
454 if (m_policy->policy() != OverwritePolicy::Ask) {
455 return m_policy->policy() == OverwritePolicy::Allow;
457 const int sel = KMessageBox::questionYesNoCancel(m_policy->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
458 "Overwrite?", m_fileName),
459 i18n("Overwrite Existing File?"),
460 KStandardGuiItem::overwrite(),
461 KGuiItem(i18n("Overwrite All")),
462 KStandardGuiItem::cancel());
463 if (sel == KMessageBox::No) { //Overwrite All
464 m_policy->setPolicy(OverwritePolicy::Allow);
466 return sel == KMessageBox::Yes || sel == KMessageBox::No;
469 void FileOutput::doFinalize()
471 qCDebug(KLEOPATRA_LOG) << this;
473 struct Remover {
474 QString file;
475 ~Remover()
477 if (QFile::exists(file)) {
478 QFile::remove(file);
481 } remover;
483 kleo_assert(m_tmpFile);
485 if (m_tmpFile->isOpen()) {
486 m_tmpFile->close();
489 const QString tmpFileName = remover.file = m_tmpFile->oldFileName();
491 m_tmpFile->setAutoRemove(false);
492 QPointer<QObject> guard = m_tmpFile.get();
493 m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/
494 kleo_assert(!guard); // if this triggers, we need to audit for holder of shared_ptr<QIODevice>s.
496 qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName ;
498 if (QFile::rename(tmpFileName, m_fileName)) {
499 qCDebug(KLEOPATRA_LOG) << this << "succeeded";
500 return;
503 qCDebug(KLEOPATRA_LOG) << this << "failed";
505 if (!obtainOverwritePermission())
506 throw Exception(gpg_error(GPG_ERR_CANCELED),
507 i18n("Overwriting declined"));
509 qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName ;
511 if (!QFile::remove(m_fileName))
512 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
513 i18n("Could not remove file \"%1\" for overwriting.", m_fileName));
515 qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName;
517 if (QFile::rename(tmpFileName, m_fileName)) {
518 qCDebug(KLEOPATRA_LOG) << this << "succeeded";
519 return;
522 qCDebug(KLEOPATRA_LOG) << this << "failed";
524 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
525 i18n("Could not rename file \"%1\" to \"%2\"",
526 tmpFileName, m_fileName));
529 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command)
531 return shared_ptr<Output>(new ProcessStdInOutput(command, QStringList(), QDir::current()));
534 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args)
536 return shared_ptr<Output>(new ProcessStdInOutput(command, args, QDir::current()));
539 shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd)
541 return shared_ptr<Output>(new ProcessStdInOutput(command, args, wd));
544 ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd)
545 : OutputImplBase(),
546 m_command(cmd),
547 m_arguments(args),
548 m_proc(new redirect_close<QProcess>)
550 qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << endl << cmd << args;
551 if (cmd.isEmpty())
552 throw Exception(gpg_error(GPG_ERR_INV_ARG),
553 i18n("Command not specified"));
554 m_proc->setWorkingDirectory(wd.absolutePath());
555 m_proc->start(cmd, args);
556 m_proc->setReadChannel(QProcess::StandardError);
557 if (!m_proc->waitForStarted())
558 throw Exception(gpg_error(GPG_ERR_EIO),
559 i18n("Could not start %1 process: %2", cmd, m_proc->errorString()));
562 QString ProcessStdInOutput::label() const
564 if (!m_proc) {
565 return OutputImplBase::label();
567 // output max. 3 arguments
568 const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QStringLiteral(" "));
569 if (m_arguments.size() > 3) {
570 return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline);
571 } else {
572 return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline);
576 QString ProcessStdInOutput::doErrorString() const
578 kleo_assert(m_proc);
579 if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) {
580 return QString();
582 if (m_proc->error() == QProcess::UnknownError)
583 return i18n("Error while running %1: %2", m_command,
584 QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData()));
585 else {
586 return i18n("Failed to execute %1: %2", m_command, m_proc->errorString());
590 #ifndef QT_NO_CLIPBOARD
591 shared_ptr<Output> Output::createFromClipboard()
593 return shared_ptr<Output>(new ClipboardOutput(QClipboard::Clipboard));
596 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode)
597 : OutputImplBase(),
598 m_mode(mode),
599 m_buffer(new QBuffer)
601 errno = 0;
602 if (!m_buffer->open(QIODevice::WriteOnly))
603 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
604 i18n("Could not write to clipboard"));
607 QString ClipboardOutput::label() const
609 switch (m_mode) {
610 case QClipboard::Clipboard:
611 return i18n("Clipboard");
612 case QClipboard::FindBuffer:
613 return i18n("Find buffer");
614 case QClipboard::Selection:
615 return i18n("Selection");
617 return QString();
620 void ClipboardOutput::doFinalize()
622 if (m_buffer->isOpen()) {
623 m_buffer->close();
625 if (QClipboard *const cb = QApplication::clipboard()) {
626 cb->setText(QString::fromUtf8(m_buffer->data()));
627 } else
628 throw Exception(gpg_error(GPG_ERR_EIO),
629 i18n("Could not find clipboard"));
631 #endif // QT_NO_CLIPBOARD
633 Output::~Output() {}