diff options
author | Rainer Keller <rainer.keller@digia.com> | 2013-03-01 13:17:43 +0100 |
---|---|---|
committer | Rainer Keller <rainer.keller@digia.com> | 2013-03-01 13:17:43 +0100 |
commit | 430f6fb5346529be7760432097277bb6822c7ba2 (patch) | |
tree | 2543fa7c9d94bc4ce53136ef488d1f9cf91a0d1f |
Intial checkin
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | README | 87 | ||||
-rw-r--r-- | app.cpp | 116 | ||||
-rw-r--r-- | app.h | 42 | ||||
-rw-r--r-- | appdaemon.pro | 13 | ||||
-rw-r--r-- | daemon.cpp | 196 | ||||
-rw-r--r-- | daemon.h | 46 | ||||
-rw-r--r-- | main.cpp | 9 | ||||
-rw-r--r-- | protocol.cpp | 90 | ||||
-rw-r--r-- | protocol.h | 29 |
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 @@ -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. @@ -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"; +} + @@ -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 |