summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRainer Keller <rainer.keller@digia.com>2013-03-01 13:17:43 +0100
committerRainer Keller <rainer.keller@digia.com>2013-03-01 13:17:43 +0100
commit430f6fb5346529be7760432097277bb6822c7ba2 (patch)
tree2543fa7c9d94bc4ce53136ef488d1f9cf91a0d1f
Intial checkin
-rw-r--r--.gitignore4
-rw-r--r--README87
-rw-r--r--app.cpp116
-rw-r--r--app.h42
-rw-r--r--appdaemon.pro13
-rw-r--r--daemon.cpp196
-rw-r--r--daemon.h46
-rw-r--r--main.cpp9
-rw-r--r--protocol.cpp90
-rw-r--r--protocol.h29
10 files changed, 632 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ebe01e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+appdaemon
+Makefile
+*.o
+moc_*.cpp
diff --git a/README b/README
new file mode 100644
index 0000000..d023633
--- /dev/null
+++ b/README
@@ -0,0 +1,87 @@
+General
+-------
+
+A transfer packet starts with '@' and ends with '@\n'.
+
+ @sample text@\n
+
+Fields are separated by ':'.
+
+ @sample text:field1:field2@\n
+
+Fields must not be empty.
+
+To include the characters ':' and '@' in texts these have to be escaped with an additional same character.
+':' becomes "::" and '@' becomes "@@".
+
+ @sample text:some::value:some@@value@\n
+
+Will be parsed to
+
+ sample text
+ some:value
+ some@value
+
+Packets must not be handled until they are complete.
+
+Commands
+---------
+
+Commands can be issued by QtCreator.
+
+@start:<binary>[:<arg>]@\n
+
+Starts the given process. All running apps will be stopped.
+The binary has to be an absolute path.
+Arguments have to be provided as fields. Multiple fields are possible.
+
+@stop@\n
+
+Kill the running app.
+
+@debug[:<binary>][:<arg>]@\n
+
+Starts debugging. If no binary given it attaches to the running app.
+Otherwise it starts a new app as described in '@start'.
+
+@stdin:<text>@\n
+
+A line to be sent to the apps stdin.
+
+@env:<key>[:<value>]@\n
+
+Set an environment variable.
+
+<key>=<value>
+
+If value is missing <key> will be removed from environment.
+
+Notifications
+-------------
+
+Notifications are sent to QtCreator.
+
+@started@\n
+
+An app was started successfully.
+
+@stopped:<exitstatus>:<exitcode>@\n
+
+An app was/had stopped.
+exitstatus: 0=NormalExit; 1=CrashExit
+exitcode: Exitcode in case the program exits normally. Otherwise undefined.
+
+@stdout:<text>@\n
+
+A line of the apps stdout
+
+@stderr:<text>@\n
+A line of the apps stderr
+
+@debugging:<port>@\n
+
+A debugger started on port.
+
+@error:<text>@\n
+
+Last command failed.
diff --git a/app.cpp b/app.cpp
new file mode 100644
index 0000000..16ea5cc
--- /dev/null
+++ b/app.cpp
@@ -0,0 +1,116 @@
+#include "app.h"
+
+#include <QProcess>
+#include <QProcessEnvironment>
+#include <QDebug>
+
+App::App(QObject *parent)
+ : QObject(parent)
+ , mProcess(new QProcess(this))
+ , mEnv(new QProcessEnvironment(QProcessEnvironment::systemEnvironment()))
+{
+ mProcess->setProcessChannelMode(QProcess::SeparateChannels);
+
+ connect(mProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
+ connect(mProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus)));
+ connect(mProcess, SIGNAL(readyReadStandardError()), this, SLOT(processStderr()));
+ connect(mProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(processStdout()));
+ connect(mProcess, SIGNAL(started()), this, SLOT(processStarted()));
+}
+
+App::~App()
+{
+ delete mEnv;
+}
+
+void App::start(const QString &binary, const QStringList &args)
+{
+ stop();
+ mProcess->setProcessEnvironment(*mEnv);
+ mProcess->start(binary, args);
+}
+
+void App::stop()
+{
+ if (mProcess->state() == QProcess::NotRunning)
+ return;
+
+ mProcess->terminate();
+ if (!mProcess->waitForFinished()) {
+ mProcess->kill();
+ if (!mProcess->waitForFinished()) {
+ emit error("Could not kill");
+ }
+ }
+}
+
+void App::debug()
+{
+ emit error("Debugging not implemented.");
+}
+
+void App::addEnv(const QString &key, const QString &value)
+{
+ mEnv->insert(key, value);
+}
+
+void App::delEnv(const QString &key)
+{
+ mEnv->remove(key);
+}
+
+void App::write(const QByteArray &data)
+{
+ if (mProcess->state() == QProcess::Running)
+ mProcess->write(data);
+ else
+ emit error("Could not write input: Process not running.");
+}
+
+void App::processError(QProcess::ProcessError err)
+{
+ switch ( err ) {
+ case QProcess::FailedToStart:
+ emit error("Process failed to start.");
+ break;
+ case QProcess::Crashed:
+ // no error
+ // will be handled by: processFinished(...)
+ break;
+ case QProcess::Timedout: emit error("Last waitFor... timed out."); break;
+ case QProcess::WriteError: emit error("Error during write to process."); break;
+ case QProcess::ReadError: emit error("Error during read from process."); break;
+ case QProcess::UnknownError: emit error("Process had an unknown error."); break;
+ }
+}
+
+void App::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ if (exitStatus == QProcess::NormalExit) {
+ qDebug() << "Process exited with exitcode" << exitCode;
+ } else {
+ qDebug() << "Process crashed";
+ }
+ emit stopped(exitStatus, exitCode);
+}
+
+void App::processStderr()
+{
+ QByteArray out = mProcess->readAllStandardError();
+ if (!out.isEmpty())
+ emit stdErr(out);
+}
+
+void App::processStdout()
+{
+ QByteArray out = mProcess->readAllStandardOutput();
+ if (!out.isEmpty())
+ emit stdOut(out);
+}
+
+void App::processStarted()
+{
+ emit started();
+ qDebug() << "Process started";
+}
+
diff --git a/app.h b/app.h
new file mode 100644
index 0000000..def1bdc
--- /dev/null
+++ b/app.h
@@ -0,0 +1,42 @@
+#ifndef APP_H
+#define APP_H
+
+#include <QObject>
+#include <QProcess>
+class QProcessEnvironment;
+
+class App : public QObject
+{
+Q_OBJECT
+
+public:
+ App(QObject *parent = 0);
+ virtual ~App();
+
+ void start(const QString &, const QStringList &);
+ void stop();
+ void debug();
+ void addEnv(const QString &, const QString &);
+ void delEnv(const QString &);
+ void write(const QByteArray &);
+
+signals:
+ void started();
+ void stopped(int, int);
+ void stdOut(const QByteArray &);
+ void stdErr(const QByteArray &);
+ void debugging(quint16);
+ void error(const QString &);
+
+private slots:
+ void processError(QProcess::ProcessError error);
+ void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void processStderr();
+ void processStdout();
+ void processStarted();
+
+private:
+ QProcess *mProcess;
+ QProcessEnvironment *mEnv;
+};
+#endif // APP_H
diff --git a/appdaemon.pro b/appdaemon.pro
new file mode 100644
index 0000000..5f57389
--- /dev/null
+++ b/appdaemon.pro
@@ -0,0 +1,13 @@
+QT-=gui
+QT+=network
+HEADERS=\
+ app.h \
+ daemon.h \
+ protocol.h \
+
+SOURCES=\
+ app.cpp \
+ daemon.cpp \
+ main.cpp \
+ protocol.cpp \
+
diff --git a/daemon.cpp b/daemon.cpp
new file mode 100644
index 0000000..424b491
--- /dev/null
+++ b/daemon.cpp
@@ -0,0 +1,196 @@
+#include "daemon.h"
+#include "protocol.h"
+#include "app.h"
+
+#include <QTcpServer>
+#include <QTcpSocket>
+#include <QFile>
+
+Daemon::Daemon(QObject *parent)
+ : QObject(parent)
+ , mServer(new QTcpServer(this))
+ , mClient(0)
+ , mProtocol(new Protocol(this))
+ , mApp(new App(this))
+ , mPort(10066)
+{
+ loadDefaults();
+
+ if (!mServer->listen(QHostAddress::Any, mPort))
+ qDebug() << "Could not listen:" << mServer->errorString();
+
+ connect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
+ connect(mProtocol, SIGNAL(commandReceived(const QStringList &)), this, SLOT(printCommand(const QStringList &)));
+ connect(mProtocol, SIGNAL(commandReceived(const QStringList &)), this, SLOT(handleCommands(const QStringList &)));
+ connect(mApp, SIGNAL(started()), this, SLOT(appStarted()));
+ connect(mApp, SIGNAL(stopped(int,int)), this, SLOT(appStopped(int,int)));
+ connect(mApp, SIGNAL(stdOut(const QByteArray&)), this, SLOT(appStdout(const QByteArray&)));
+ connect(mApp, SIGNAL(stdErr(const QByteArray&)), this, SLOT(appStderr(const QByteArray&)));
+ connect(mApp, SIGNAL(debugging(quint16)), this, SLOT(appDebugging(quint16)));
+ connect(mApp, SIGNAL(error(const QString&)), this, SLOT(appError(const QString&)));
+
+ if (!defaultApp.isEmpty())
+ mApp->start(defaultApp, defaultArgs);
+}
+
+Daemon::~Daemon()
+{
+}
+
+void Daemon::newConnection()
+{
+ if (!mServer->hasPendingConnections())
+ return;
+
+ clientDisconnect();
+ mClient = mServer->nextPendingConnection();
+ connect(mClient, SIGNAL(disconnected()), this, SLOT(clientDisconnect()));
+ connect(mClient, SIGNAL(readyRead()), this, SLOT(clientRead()));
+}
+
+void Daemon::printCommand(const QStringList &l)
+{
+ qDebug() << l;
+}
+
+void Daemon::clientRead()
+{
+ mProtocol->write(mClient->readAll());
+}
+
+void Daemon::clientDisconnect()
+{
+ delete mClient;
+ mClient = 0;
+}
+
+void Daemon::handleCommands(const QStringList &list)
+{
+ if (list.size() == 0) {
+ qDebug() << "no commands";
+ return;
+ }
+
+ const QString command(list.first());
+ if (command == "start") {
+ if (list.size() < 2) {
+ qDebug() << "too less options";
+ return;
+ }
+ mApp->start(list[1], defaultArgs + list.mid(2));
+ } else if (command == "stop") {
+ mApp->stop();
+ } else if (command == "debug") {
+ qDebug() << "debug not implemented";
+ } else if (command == "stdin") {
+ if(list.size() < 2) {
+ qDebug() << "too less options";
+ return;
+ }
+ mApp->write(list[1].toLocal8Bit());
+ } else if (command == "env") {
+ if(list.size() == 2) {
+ mApp->delEnv(list[1]);
+ } else if (list.size() == 3) {
+ mApp->addEnv(list[1], list[2]);
+ } else {
+ qDebug() << "Invalid arg count for env";
+ return;
+ }
+ } else {
+ qDebug() << "unknown command:" << list[0];
+ }
+
+}
+
+void Daemon::appStarted()
+{
+ if (!mClient)
+ return;
+ QStringList l("started");
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::appStopped(int exitStatus, int exitCode)
+{
+ if (!mClient)
+ return;
+ QStringList l("stopped");
+ l += QString::number(exitStatus);
+ l += QString::number(exitCode);
+
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::appStdout(const QByteArray &data)
+{
+ if (!mClient)
+ return;
+ QStringList l("stdout");
+ l += QString::fromLocal8Bit(data);
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::appStderr(const QByteArray &data)
+{
+ if (!mClient)
+ return;
+ QStringList l("stderr");
+ l += data;
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::appDebugging(quint16 port)
+{
+ if (!mClient)
+ return;
+ QStringList l("debugging");
+ l += QString::number(port);
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::appError(const QString &text)
+{
+ if (!mClient)
+ return;
+ QStringList l("error");
+ l += text;
+ mClient->write(mProtocol->sendToClient(l).toLocal8Bit());
+}
+
+void Daemon::loadDefaults()
+{
+ QFile f("/system/bin/appdaemon.conf");
+
+ if (!f.open(QFile::ReadOnly))
+ qFatal("Could not read config file.");
+
+ while (!f.atEnd()) {
+ QString line = f.readLine();
+ if (line.startsWith("start=")) {
+ defaultApp = line.mid(6).simplified();
+ qDebug() << "start=" << defaultApp;
+ } else if (line.startsWith("port=")) {
+ mPort = line.mid(5).simplified().toUInt();
+ if (mPort == 0)
+ qFatal("Invalid port");
+ } else if (line.startsWith("env=")) {
+ QString sub = line.mid(4).simplified();
+ int index = sub.indexOf('=');
+ if (index < 2) {
+ // ignore
+ } else {
+ mApp->addEnv(sub.left(index), sub.mid(index+1));
+ qDebug() << sub.left(index) << sub.mid(index+1);
+ }
+ } else if (line.startsWith("append=")) {
+ defaultArgs += line.mid(7).simplified();
+ qDebug() << defaultArgs;
+ }
+ }
+
+ // start=/usr/bin/launcher
+ // port=10066
+ // env=...
+ // append=...
+}
diff --git a/daemon.h b/daemon.h
new file mode 100644
index 0000000..40f55f3
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,46 @@
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include <QObject>
+#include <QStringList>
+class QTcpServer;
+class QTcpSocket;
+class Protocol;
+class App;
+
+class Daemon : public QObject
+{
+Q_OBJECT
+
+public:
+ Daemon(QObject *parent = 0);
+ virtual ~Daemon();
+
+private slots:
+ void newConnection();
+ void printCommand(const QStringList &);
+ void clientDisconnect();
+ void clientRead();
+ void handleCommands(const QStringList &);
+
+ void appStarted();
+ void appStopped(int, int);
+ void appStdout(const QByteArray &);
+ void appStderr(const QByteArray &);
+ void appDebugging(quint16);
+ void appError(const QString &);
+
+private:
+ void loadDefaults();
+
+ QTcpServer *mServer;
+ QTcpSocket *mClient;
+ Protocol *mProtocol;
+ App *mApp;
+
+ QString defaultApp;
+ QStringList defaultArgs;
+ quint16 mPort;
+};
+
+#endif // DAEMON_H
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..23e4330
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,9 @@
+#include <QCoreApplication>
+#include "daemon.h"
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ Daemon daemon;
+ return app.exec();
+}
diff --git a/protocol.cpp b/protocol.cpp
new file mode 100644
index 0000000..19d6c3d
--- /dev/null
+++ b/protocol.cpp
@@ -0,0 +1,90 @@
+#include "protocol.h"
+#include <QDebug>
+
+Protocol::Protocol(QObject *parent)
+ : QObject(parent)
+ , mState(Start)
+{
+}
+
+Protocol::~Protocol()
+{
+}
+
+void Protocol::reset()
+{
+ mState = Start;
+ mFields.clear();
+ mField.clear();
+}
+
+void Protocol::write(const QByteArray &data)
+{
+ QByteArray buffer(data);
+
+ while (!buffer.isEmpty()) {
+ unsigned int remove = 0;
+
+ if (buffer[0] == '@') {
+ if (buffer.size() > 1) {
+ if (buffer[1] == '@' && mState != Start) {
+ mField += '@';
+ remove += 2;
+ } else if (buffer[1] == '\n' || buffer[1] == '\r') {
+ remove += 2;
+ if (mState != Field) {
+ reset();
+ qDebug() << "End unexpected";
+ } else {
+ mFields += mField;
+ emit commandReceived(mFields);
+ reset();
+ }
+ } else {
+ ++remove;
+ if (mState != Start) {
+ qDebug() << "Start unexpected";
+ } else {
+ mState = Field;
+ mField.clear();
+ mFields.clear();
+ }
+ }
+ }
+ } else if (buffer[0] == ':') {
+ if (buffer.size() > 1) {
+ if (buffer[1] == ':') {
+ mField += ':';
+ remove += 2;
+ } else {
+ ++remove;
+ mFields += mField;
+ mField.clear();
+ }
+ }
+ } else {
+ ++remove;
+ mField += buffer[0];
+ }
+
+ buffer.remove(0, remove);
+ }
+}
+
+QString Protocol::sendToClient(const QStringList &data)
+{
+ bool first = true;
+ QString rc("@");
+ foreach (QString s, data) {
+ s.replace(":", "::");
+ s.replace("@", "@@");
+ if (!first)
+ rc += ":";
+ else
+ first = false;
+
+ rc += s;
+ }
+ rc += "@\n";
+ return rc;
+}
diff --git a/protocol.h b/protocol.h
new file mode 100644
index 0000000..aee6683
--- /dev/null
+++ b/protocol.h
@@ -0,0 +1,29 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+#include <QObject>
+#include <QStringList>
+
+class Protocol : public QObject
+{
+Q_OBJECT
+
+public:
+ Protocol(QObject *parent = 0);
+ virtual ~Protocol();
+
+ void write(const QByteArray &);
+ QString sendToClient(const QStringList &);
+
+signals:
+ void commandReceived(const QStringList &);
+
+private:
+ void reset();
+ enum State { Start, Field };
+ State mState;
+ QStringList mFields;
+ QString mField;
+};
+
+#endif // PROTOCOL_H