KProcess Line Mode Patch
Ralf Habacker <ralf.habacker <at> freenet.de>
2008-12-01 11:32:08 GMT
Hi,
based on the work described on
http://lists.kde.org/?l=kde-core-devel&m=120522889930459&w=2 there is a
patch appended which implements a line buffering mode directly in KProcess.
The advantage of this approach is that this feature is directly
available from kdecore and therefore no additional class located
somewhere in the related package source is required.
How it works:
1.Line buffering mode ist enabled with the
void setOutputLineMode(bool mode);
2.There are new KProcess signals available which indicates available lines.
void readyReadLineStandardOutput();
void readyReadLineStandardError();
3. The lines could be fetched with
QStringList &readLinesStandardOutput(QStringList &out=QStringList());
QStringList &readLinesStandardError(QStringList &out=QStringList());
4. non standard line delimiters could be set by
void setOutputLineDelimiter(QString &delim=QString());
Any comments or objections ?
Ralf
Index: kdecore/io/kprocess.cpp
===================================================================
--- kdecore/io/kprocess.cpp (revision 890832)
+++ kdecore/io/kprocess.cpp (working copy)
<at> <at> -41,6 +41,15 <at> <at>
# define STD_ERROR_HANDLE 2
#endif
+KProcessPrivate::KProcessPrivate() : openMode(QIODevice::ReadWrite), outputLineMode(false)
+{
+#ifdef Q_OS_WIN32
+ outputDelimiter = "\r\n";
+#else
+ outputDelimiter = "\n";
+#endif
+}
+
void KProcessPrivate::writeAll(const QByteArray &buf, int fd)
{
#ifdef Q_OS_WIN
<at> <at> -83,6 +92,67 <at> <at>
forwardStd(KProcess::StandardError, STD_ERROR_HANDLE);
}
+void KProcessPrivate::_k_receivedStdout()
+{
+ if (!outputLineMode)
+ return;
+
+ Q_Q(KProcess);
+ QByteArray ndata = q->readAllStandardOutput();
+ int oldBufferSize = stdoutBuffer.size();
+ stdoutBuffer.append(ndata);
+ if (newlineInStdout < 0) {
+ newlineInStdout = ndata.indexOf(outputDelimiter);
+ if (newlineInStdout >= 0) {
+ newlineInStdout += oldBufferSize;
+ emit q->readyReadLineStandardOutput();
+ }
+ }
+}
+
+void KProcessPrivate::_k_receivedStderr()
+{
+ if (!outputLineMode)
+ return;
+
+ Q_Q(KProcess);
+ QByteArray ndata = q->readAllStandardError();
+ int oldBufferSize = stderrBuffer.size();
+ stderrBuffer.append(ndata);
+ if (newlineInStderr < 0) {
+ newlineInStderr = ndata.indexOf(outputDelimiter);
+ if (newlineInStderr >= 0) {
+ newlineInStderr += oldBufferSize;
+ emit q->readyReadLineStandardError();
+ }
+ }
+}
+
+void KProcessPrivate::_k_finished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ if (!outputLineMode)
+ return;
+
+ // check if there is unprocessed data in output channel
+ Q_Q(KProcess);
+
+ QByteArray ndata = q->readAllStandardOutput();
+ stdoutBuffer.append(ndata);
+ if (stdoutBuffer.size() > 0) {
+ stdoutBuffer.append(outputDelimiter);
+ newlineInStdout = stdoutBuffer.indexOf(outputDelimiter);
+ emit q->readyReadLineStandardOutput();
+ }
+ ndata = q->readAllStandardError();
+ stderrBuffer.append(ndata);
+ if (stderrBuffer.size() > 0) {
+ stderrBuffer.append(outputDelimiter);
+ newlineInStderr = stderrBuffer.indexOf(outputDelimiter);
+ emit q->readyReadLineStandardError();
+ }
+}
+
+
/////////////////////////////
// public member functions //
/////////////////////////////
<at> <at> -115,18 +185,36 <at> <at>
d->outputChannelMode = mode;
disconnect(this, SIGNAL(readyReadStandardOutput()));
disconnect(this, SIGNAL(readyReadStandardError()));
+ disconnect(this, SIGNAL(finished(int,QProcess::ExitStatus)));
switch (mode) {
case OnlyStdoutChannel:
connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr()));
+ QProcess::setProcessChannelMode(QProcess::SeparateChannels);
break;
case OnlyStderrChannel:
connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout()));
+ QProcess::setProcessChannelMode(QProcess::SeparateChannels);
break;
+ case MergedChannels:
+ if (d->outputLineMode) {
+ connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_receivedStdout()));
+ connect(this, SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(_k_finished(int,QProcess::ExitStatus)));
+ }
+ QProcess::setProcessChannelMode(QProcess::MergedChannels);
+ break;
+
+ case SeparateChannels:
+ if (d->outputLineMode) {
+ connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_receivedStdout()));
+ connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_receivedStderr()));
+ connect(this, SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(_k_finished(int,QProcess::ExitStatus)));
+ }
+ QProcess::setProcessChannelMode(QProcess::SeparateChannels);
+ break;
default:
QProcess::setProcessChannelMode((ProcessChannelMode)mode);
- return;
+ break;
}
- QProcess::setProcessChannelMode(QProcess::SeparateChannels);
}
KProcess::OutputChannelMode KProcess::outputChannelMode() const
<at> <at> -136,6 +224,68 <at> <at>
return d->outputChannelMode;
}
+void KProcess::setOutputLineMode(bool mode)
+{
+ Q_D(KProcess);
+
+ d->outputLineMode = mode;
+ // make sure slots are setup right
+ setOutputChannelMode(d->outputChannelMode);
+}
+
+bool KProcess::outputLineMode() const
+{
+ Q_D(const KProcess);
+
+ return d->outputLineMode;
+}
+
+void KProcess::setOutputLineDelimiter(QString &delim)
+{
+ Q_D(KProcess);
+
+ d->outputDelimiter = delim.toAscii();
+}
+
+QString KProcess::outputLineDelimiter() const
+{
+ Q_D(const KProcess);
+
+ return d->outputDelimiter;
+}
+
+QStringList &KProcess::readLinesStandardOutput(QStringList &out)
+{
+ Q_D(KProcess);
+
+ if (d->newlineInStdout < 0)
+ return out;
+
+ while (d->newlineInStdout > -1) {
+ out << d->stdoutBuffer.left(d->newlineInStdout);
+ d->stdoutBuffer.remove(0, d->newlineInStdout + d->outputDelimiter.size());
+ d->newlineInStdout = d->stdoutBuffer.indexOf(d->outputDelimiter);
+ }
+
+ return out;
+}
+
+QStringList &KProcess::readLinesStandardError(QStringList &out)
+{
+ Q_D(KProcess);
+
+ if (d->newlineInStderr < 0)
+ return out;
+
+ while (d->newlineInStderr > -1) {
+ out << d->stdoutBuffer.left(d->newlineInStderr);
+ d->stderrBuffer.remove(0, d->newlineInStderr + d->outputDelimiter.size());
+ d->newlineInStderr = d->stderrBuffer.indexOf(d->outputDelimiter);
+ }
+
+ return out;
+}
+
void KProcess::setNextOpenMode(QIODevice::OpenMode mode)
{
Q_D(KProcess);
Index: kdecore/io/kprocess.h
===================================================================
--- kdecore/io/kprocess.h (revision 890832)
+++ kdecore/io/kprocess.h (working copy)
<at> <at> -97,6 +97,50 <at> <at>
OutputChannelMode outputChannelMode() const;
/**
+ * set the output line mode. When activated output data from the process is returned as complete
+ * lines. The line data can be retrieved by calling <at> ref readLinesStandardOutput() for stdout and
+ * <at> ref readLinesStandardError() for stderr.
+ *
+ * Available line data is indicated by the signal <at> ref readReadLineStandardOutput() for stdout
+ * and <at> ref readReadLineStandardError() for stderr .
+ *
+ * As line delimiter the operating systems default line delimiter is used (\\r\\n on win32, \\n on unix, ??
on mac).
+ * Non standard line delimiters could be set with <at> ref setOutputLineDelimiter().
+ *
+ * The output line mode is disabled by default.
+
+ * <at> note When using output line mode, the readAllStandardOutput() and readAllStandardError() methods
should not
+ * be used, otherwise there will be no line output.
+ *
+ * <at> param mode of the output line mode
+ */
+ void setOutputLineMode(bool mode);
+
+ /**
+ * Query the state of the output line mode. See <at> ref setOutputLineMode() for more details for the output
line mode.
+ *
+ * <at> return the state of the output line mode
+ */
+ bool outputLineMode() const;
+
+ /**
+ * Set non standard line delimiter for output data in case the default line delimiter (\\r\\n on win32, \\n
on unix, ?? on mac)
+ * is not appliable.
+ *
+ * This function must be called before starting the process.
+ *
+ * <at> param string with line delimiter
+ */
+ void setOutputLineDelimiter(QString &delim=QString());
+
+ /**
+ * Query the currently used output delimiter
+ *
+ * <at> return the output line delimiter
+ */
+ QString outputLineDelimiter() const;
+
+ /**
* Set the QIODevice open mode the process will be opened in.
*
* This function must be called before starting the process, obviously.
<at> <at> -315,6 +359,38 <at> <at>
*/
int pid() const;
+ /**
+ * When a line delimiter is set with setOutputLineDelimiter(), this function returns
+ * all data lines available from the standard output of the process as a QStringList,
+ * regardless of the current read channel,
+ *
+ * Available data lines are indicated by the readyReadLineStandardOutput() signal.
+ */
+ QStringList &readLinesStandardOutput(QStringList &out=QStringList());
+
+ /**
+ * When a line delimiter is set with setOutputLineDelimiter(), this function returns
+ * all data lines available from the standard error of the process as a QStringList,
+ * regardless of the current read channel,
+ *
+ * Available data lines are indicated by the readyReadLineStandardOutput() signal.
+ */
+ QStringList &readLinesStandardError(QStringList &out=QStringList());
+
+signals:
+ /**
+ * This signal is emitted when an output delimiter is set with setOutputLineDelimiter() and
+ * the process has made new data lines available through its standard output channel (stdout).
+ * It is emitted regardless of the current read channel.
+ */
+ void readyReadLineStandardOutput();
+ /**
+ * This signal is emitted when an output delimiter is set with setOutputLineDelimiter() and
+ * the process has made new data lines available through its standard error channel (stderr).
+ * It is emitted regardless of the current read channel.
+ */
+ void readyReadLineStandardError();
+
protected:
/**
* <at> internal
<at> <at> -335,6 +411,9 <at> <at>
Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout())
Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr())
+ Q_PRIVATE_SLOT(d_func(), void _k_receivedStdout())
+ Q_PRIVATE_SLOT(d_func(), void _k_receivedStderr())
+ Q_PRIVATE_SLOT(d_func(), void _k_finished(int, QProcess::ExitStatus))
};
#endif
Index: kdecore/io/kprocess_p.h
===================================================================
--- kdecore/io/kprocess_p.h (revision 890832)
+++ kdecore/io/kprocess_p.h (working copy)
<at> <at> -27,21 +27,28 <at> <at>
class KProcessPrivate {
Q_DECLARE_PUBLIC(KProcess)
protected:
- KProcessPrivate() :
- openMode(QIODevice::ReadWrite)
- {
- }
+ KProcessPrivate();
void writeAll(const QByteArray &buf, int fd);
void forwardStd(KProcess::ProcessChannel good, int fd);
void _k_forwardStdout();
void _k_forwardStderr();
+ void _k_receivedStdout();
+ void _k_receivedStderr();
+ void _k_finished(int, QProcess::ExitStatus);
QString prog;
QStringList args;
KProcess::OutputChannelMode outputChannelMode;
QIODevice::OpenMode openMode;
+ KProcess *q_ptr;
- KProcess *q_ptr;
+
+ bool outputLineMode;
+ QByteArray outputDelimiter;
+ QByteArray stdoutBuffer;
+ QByteArray stderrBuffer;
+ int newlineInStdout;
+ int newlineInStderr;
};
Index: kdecore/tests/CMakeLists.txt
===================================================================
--- kdecore/tests/CMakeLists.txt (revision 890832)
+++ kdecore/tests/CMakeLists.txt (working copy)
<at> <at> -105,3 +105,10 <at> <at>
set_target_properties(klibloadertestmodule4 PROPERTIES SKIP_BUILD_RPATH FALSE
BUILD_WITH_INSTALL_RPATH FALSE)
endif (NOT WIN32)
+
+########### next target ###############
+
+kde4_add_executable(kprocesstesthelper NOGUI kprocesstesthelper.cpp)
+target_link_libraries(kprocesstesthelper ${KDE4_KDECORE_LIBS})
+
+add_dependencies(kprocesstest kprocesstesthelper)
Index: kdecore/tests/kprocesstest.cpp
===================================================================
--- kdecore/tests/kprocesstest.cpp (revision 890832)
+++ kdecore/tests/kprocesstest.cpp (working copy)
<at> <at> -2,6 +2,7 <at> <at>
This file is part of the KDE libraries
Copyright (C) 2007 Oswald Buddenhagen <ossi <at> kde.org>
+ Copyright (C) 2008 Ralf Habacker <ralf.habacker <at> freenet.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
<at> <at> -26,12 +27,40 <at> <at>
#include <stdlib.h>
#include <signal.h>
+#ifdef Q_OS_WIN32
+#define NL "\r\n"
+#else
+#ifdef Q_OS_UNIX
+#define NL "\n"
+#else
+# warning define NL for this os
+#endif
+#endif
+
class KProcessTest : public QObject {
Q_OBJECT
+public:
+ KProcessTest();
+
private Q_SLOTS:
void test_channels();
void test_setShellCommand();
+ void test_signals();
+ void test_outputLineMode();
+
+ void slotPrintError();
+ void slotPrintOutput();
+ void slotFinished(int exitCode, QProcess::ExitStatus exitStatus);
+
+ void slotOutputLines();
+ void slotOutputLinesFinished(int exitCode, QProcess::ExitStatus exitStatus);
+
+ void cleanup();
+
+private:
+ QEventLoop loop;
+ QStringList outData;
};
// IOCCC nomination pending
<at> <at> -60,6 +89,11 <at> <at>
a = "mode: " ms "\n" + recurse(KProcess::me); \
QCOMPARE(a, e)
+
+KProcessTest::KProcessTest() : loop( this )
+{
+}
+
void KProcessTest::test_channels()
{
#ifdef Q_OS_UNIX
<at> <at> -89,6 +123,73 <at> <at>
#endif
}
+void KProcessTest::slotPrintOutput()
+{
+ KProcess *p = static_cast<KProcess*>(sender());
+ outData << p->readAllStandardOutput();
+}
+
+void KProcessTest::slotPrintError()
+{
+ KProcess *p = static_cast<KProcess*>(sender());
+ qDebug() << p->readAllStandardError();
+}
+
+void KProcessTest::slotFinished (int exitCode, QProcess::ExitStatus exitStatus)
+{
+ loop.exit();
+ QVERIFY(outData.size() == 1);
+ QString a = QLatin1String("line1" NL "line2" NL "line3");
+ QCOMPARE(outData.at(0),a);
+}
+
+void KProcessTest::test_signals()
+{
+ outData.clear();
+ KProcess *p = new KProcess;
+ p->setOutputChannelMode(KProcess::SeparateChannels);
+ connect(p,SIGNAL(readyReadStandardOutput()),this,SLOT(slotPrintOutput()));
+ connect(p,SIGNAL(readyReadStandardError()),this,SLOT(slotPrintError()));
+ connect(p,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(slotFinished(int,QProcess::ExitStatus)));
+ p->setProgram("kprocesstesthelper", QStringList() << "--partial-line" << "--stdout");
+ p->start();
+ loop.exec();
+}
+
+void KProcessTest::slotOutputLines()
+{
+ KProcess *p = static_cast<KProcess*>(sender());
+ p->readLinesStandardOutput(outData);
+}
+
+void KProcessTest::slotOutputLinesFinished (int exitCode, QProcess::ExitStatus exitStatus)
+{
+ loop.exit();
+ QVERIFY(outData.size() == 3);
+ QCOMPARE(outData.at(0), QLatin1String("line1"));
+ QCOMPARE(outData.at(1), QLatin1String("line2"));
+ QCOMPARE(outData.at(2), QLatin1String("line3"));
+}
+
+void KProcessTest::test_outputLineMode()
+{
+ outData.clear();
+ KProcess *p = new KProcess;
+ p->setOutputLineMode(true);
+ p->setOutputChannelMode(KProcess::SeparateChannels);
+ connect(p,SIGNAL(readyReadLineStandardOutput()),this,SLOT(slotOutputLines()));
+ connect(p,SIGNAL(readyReadStandardError()),this,SLOT(slotPrintError()));
+ connect(p,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(slotOutputLinesFinished(int,QProcess::ExitStatus)));
+ p->setProgram("kprocesstesthelper", QStringList() << "--partial-line" << "--stdout");
+ p->start();
+ loop.exec();
+}
+
+void KProcessTest::cleanup()
+{
+ loop.exit();
+}
+
static void recursor(char **argv)
{
if (argv[1]) {
Index: kdecore/tests/kprocesstesthelper.cpp
===================================================================
--- kdecore/tests/kprocesstesthelper.cpp (revision 0)
+++ kdecore/tests/kprocesstesthelper.cpp (revision 0)
<at> <at> -0,0 +1,50 <at> <at>
+/*
+ This file is part of the KDE libraries
+
+ Copyright (C) 2008 Ralf Habacker <ralf.habacker <at> freenet.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <stdio.h>
+#include <QCoreApplication>
+#include <QStringList>
+
+int main( int argc, char **argv )
+{
+ QCoreApplication app(argc, argv);
+ bool partialLine = false;
+ FILE *out = stderr;
+
+ QStringList args = QCoreApplication::arguments();
+ args.removeFirst();
+ Q_FOREACH(QString arg, args) {
+ if (arg == "--partial-line")
+ partialLine = true;
+ else if (arg == "--stdout")
+ out = stdout;
+ else if (arg == "--stderr")
+ out = stderr;
+ }
+ if (partialLine)
+ fprintf(out, "line1\nline2\nline3");
+ else
+ fprintf(out, "line1\nline2\nline3\n");
+
+ fflush(out);
+
+ return 0;
+}