22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |  |
30 |
31 |
32 |
33 | $projectname
34 | $projectnumber
35 |
36 | $projectbrief
37 | |
38 |
39 |
40 |
41 |
42 | $projectbrief
43 | |
44 |
45 |
46 |
47 |
48 | $searchbox |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/backgroundprocess/terminal.cpp:
--------------------------------------------------------------------------------
1 | #include "terminal.h"
2 | #include "terminal_p.h"
3 | #include "app_p.h"
4 |
5 | using namespace QtBackgroundProcess;
6 |
7 | Terminal::Terminal(TerminalPrivate *d_ptr, QObject *parent) :
8 | QIODevice(parent),
9 | d(d_ptr)
10 | {
11 | d->setParent(this);
12 | open(QIODevice::ReadWrite | QIODevice::Unbuffered);
13 |
14 | connect(d->socket, &QLocalSocket::disconnected,
15 | this, &Terminal::terminalDisconnected);
16 | connect(d->socket, QOverload
::of(&QLocalSocket::error),
17 | this, [this](QLocalSocket::LocalSocketError e) {
18 | if(e != QLocalSocket::PeerClosedError) {
19 | setErrorString(d->socket->errorString());
20 | emit terminalError((int)e);
21 | }
22 | });
23 |
24 | connect(d->socket, &QLocalSocket::channelReadyRead,
25 | this, &Terminal::channelReadyRead);
26 | connect(d->socket, &QLocalSocket::readyRead,
27 | this, &Terminal::readyRead);
28 | }
29 |
30 | Terminal::~Terminal()
31 | {
32 | if(d->socket->isOpen())
33 | d->socket->close();
34 | }
35 |
36 | bool Terminal::isStarter() const
37 | {
38 | return d->status[QStringLiteral("isStarter")].toBool();
39 | }
40 |
41 | QSharedPointer Terminal::parser() const
42 | {
43 | return d->parser;
44 | }
45 |
46 | bool Terminal::isAutoDelete() const
47 | {
48 | return d->autoDelete;
49 | }
50 |
51 | bool Terminal::isSequential() const
52 | {
53 | return true;
54 | }
55 |
56 | void Terminal::close()
57 | {
58 | d->socket->close();
59 | QIODevice::close();
60 | }
61 |
62 | qint64 Terminal::bytesAvailable() const
63 | {
64 | return QIODevice::bytesAvailable() + d->socket->bytesAvailable();
65 | }
66 |
67 | void Terminal::disconnectTerminal()
68 | {
69 | d->beginSoftDisconnect();
70 | }
71 |
72 | void Terminal::setAutoDelete(bool autoDelete)
73 | {
74 | d->autoDelete = autoDelete;
75 | }
76 |
77 | void Terminal::writeLine(const QByteArray &line, bool flush)
78 | {
79 | d->socket->write(line + '\n');
80 | if(flush)
81 | d->socket->flush();
82 | }
83 |
84 | void Terminal::flush()
85 | {
86 | d->socket->flush();
87 | }
88 |
89 | qint64 Terminal::readData(char *data, qint64 maxlen)
90 | {
91 | return d->socket->read(data, maxlen);
92 | }
93 |
94 | qint64 Terminal::writeData(const char *data, qint64 len)
95 | {
96 | return d->socket->write(data, len);
97 | }
98 |
99 | bool Terminal::open(QIODevice::OpenMode mode)
100 | {
101 | return QIODevice::open(mode);
102 | }
103 |
--------------------------------------------------------------------------------
/src/backgroundprocess/masterconnecter.cpp:
--------------------------------------------------------------------------------
1 | #include "masterconnecter_p.h"
2 | #include "app_p.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | using namespace QtBackgroundProcess;
13 |
14 | MasterConnecter::MasterConnecter(const QString &socketName, const QStringList &arguments, bool isStarter, QObject *parent) :
15 | QObject(parent),
16 | arguments(arguments),
17 | isStarter(isStarter),
18 | socket(new QLocalSocket(this)),
19 | console(new QConsole(this)),
20 | outFile(QConsole::qStdOut(this))
21 | {
22 | connect(socket, &QLocalSocket::connected,
23 | this, &MasterConnecter::connected);
24 | connect(socket, &QLocalSocket::disconnected,
25 | this, &MasterConnecter::disconnected);
26 | connect(socket, QOverload::of(&QLocalSocket::error),
27 | this, &MasterConnecter::error);
28 | connect(socket, &QLocalSocket::readyRead,
29 | this, &MasterConnecter::socketReady,
30 | Qt::QueuedConnection); //queued connection, because of "socket not ready" errors on win
31 |
32 | connect(console, &QConsole::readyRead,
33 | this, &MasterConnecter::stdinReady);
34 |
35 | socket->connectToServer(socketName);
36 | }
37 |
38 | MasterConnecter::~MasterConnecter()
39 | {
40 | console->close();
41 | }
42 |
43 | void MasterConnecter::connected()
44 | {
45 | //send over the status
46 | QJsonObject status;
47 | status[QStringLiteral("isStarter")] = isStarter;
48 | status[QStringLiteral("arguments")] = QJsonArray::fromStringList(arguments);
49 |
50 | QDataStream connectData(socket);
51 | connectData << QJsonDocument(status).toBinaryData();
52 | socket->flush();
53 |
54 | //begin stdin reading
55 | console->open();
56 | }
57 |
58 | void MasterConnecter::disconnected()
59 | {
60 | socket->close();
61 | qApp->quit();
62 | }
63 |
64 | void MasterConnecter::error(QLocalSocket::LocalSocketError socketError)
65 | {
66 | if(socketError != QLocalSocket::PeerClosedError) {
67 | qCritical() << tr("Connection to Master process failed with error:")
68 | << socket->errorString();
69 | socket->disconnectFromServer();
70 | qApp->exit(EXIT_FAILURE);
71 | }
72 | }
73 |
74 | void MasterConnecter::socketReady()
75 | {
76 | auto data = socket->readAll();
77 | outFile->write(data);
78 | outFile->flush();
79 | }
80 |
81 | void MasterConnecter::stdinReady()
82 | {
83 | auto bytes = console->bytesAvailable();
84 | auto data = console->read(bytes);
85 | socket->write(data);
86 | socket->flush();
87 | }
88 |
--------------------------------------------------------------------------------
/src/backgroundprocess/terminal.h:
--------------------------------------------------------------------------------
1 | #ifndef QTBACKGROUNDPROCESS_TERMINAL_H
2 | #define QTBACKGROUNDPROCESS_TERMINAL_H
3 |
4 | #include "QtBackgroundProcess/qtbackgroundprocess_global.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | class QLocalSocket;
11 |
12 | namespace QtBackgroundProcess {
13 |
14 | class TerminalPrivate;
15 | //! Represents a connection to a terminal in the master process
16 | class Q_BACKGROUNDPROCESS_EXPORT Terminal : public QIODevice
17 | {
18 | Q_OBJECT
19 |
20 | //! Specifies, whether this terminal is the one that started the master process
21 | Q_PROPERTY(bool starter READ isStarter CONSTANT)
22 | //! Returns a reference to the parser that parsed this terminals arguments
23 | Q_PROPERTY(QSharedPointer parser READ parser CONSTANT)
24 | //! If true, the terminal will delete itself as soon as the connection has been closed
25 | Q_PROPERTY(bool autoDelete READ isAutoDelete WRITE setAutoDelete)
26 |
27 | public:
28 | //! Creates a new terminal from it's private implementation
29 | explicit Terminal(TerminalPrivate *d_ptr, QObject *parent = nullptr);
30 | //! Destructor
31 | ~Terminal();
32 |
33 | //! @readAcFn{App::starter}
34 | bool isStarter() const;
35 | //! @readAcFn{App::parser}
36 | QSharedPointer parser() const;
37 | //! @readAcFn{App::autoDelete}
38 | bool isAutoDelete() const;
39 |
40 | //! @inherit{QIODevice::isSequential}
41 | bool isSequential() const override;
42 | //! @inherit{QIODevice::close} @sa Terminal::disconnectTerminal
43 | void close() override;
44 | //! @inherit{QIODevice::bytesAvailable}
45 | qint64 bytesAvailable() const override;
46 |
47 | public Q_SLOTS:
48 | //! Disconnects the terminal from the master
49 | void disconnectTerminal();
50 | //! @writeAcFn{App::autoDelete}
51 | void setAutoDelete(bool autoDelete);
52 |
53 | //! Writes the given line, appends a newline and optionally flushes
54 | void writeLine(const QByteArray &line, bool flush = true);
55 | //! Flushes the terminal
56 | void flush();
57 |
58 | Q_SIGNALS:
59 | //! Will be emitted after the terminal has been disconnected
60 | void terminalDisconnected();
61 | //! Will be emitted if an error occured
62 | void terminalError(int errorCode);
63 |
64 | protected:
65 | //! @inherit{QIODevice::readData}
66 | qint64 readData(char *data, qint64 maxlen) override;
67 | //! @inherit{QIODevice::writeData}
68 | qint64 writeData(const char *data, qint64 len) override;
69 |
70 | private:
71 | TerminalPrivate *d;
72 |
73 | bool open(OpenMode mode) override;
74 | };
75 |
76 | }
77 |
78 | #endif // QTBACKGROUNDPROCESS_TERMINAL_H
79 |
--------------------------------------------------------------------------------
/src/backgroundprocess/terminal_p.cpp:
--------------------------------------------------------------------------------
1 | #include "terminal_p.h"
2 | #include "app_p.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | using namespace QtBackgroundProcess;
9 |
10 | TerminalPrivate::TerminalPrivate(QLocalSocket *socket, QObject *parent) :
11 | QObject(parent),
12 | socket(socket),
13 | status(),
14 | parser(),
15 | autoDelete(false),
16 | isLoading(true),
17 | disconnecting(false),
18 | stream(socket)
19 | {
20 | socket->setParent(this);
21 |
22 | connect(socket, &QLocalSocket::disconnected,
23 | this, &TerminalPrivate::disconnected);
24 | connect(socket, QOverload::of(&QLocalSocket::error),
25 | this, &TerminalPrivate::error);
26 | connect(socket, &QLocalSocket::readyRead,
27 | this, &TerminalPrivate::readyRead);
28 | connect(socket, &QLocalSocket::bytesWritten,
29 | this, &TerminalPrivate::writeReady);
30 | }
31 |
32 | bool TerminalPrivate::loadParser()
33 | {
34 | auto array = status[QStringLiteral("arguments")].toArray();
35 | QStringList lst(QCoreApplication::applicationFilePath());
36 | foreach(auto value, array)
37 | lst.append(value.toString());
38 | return parser->parse(lst);
39 | }
40 |
41 | void TerminalPrivate::beginSoftDisconnect()
42 | {
43 | disconnecting = true;
44 | if(socket->bytesToWrite() > 0) {
45 | QTimer::singleShot(500, this, [=](){
46 | if(socket->state() == QLocalSocket::ConnectedState)
47 | socket->disconnectFromServer();
48 | });
49 | socket->flush();
50 | } else
51 | socket->disconnectFromServer();
52 | }
53 |
54 | void TerminalPrivate::disconnected()
55 | {
56 | if(isLoading) {
57 | emit statusLoadComplete(this, false);
58 | socket->close();
59 | } else if(autoDelete && parent())
60 | parent()->deleteLater();
61 | }
62 |
63 | void TerminalPrivate::error(QLocalSocket::LocalSocketError socketError)
64 | {
65 | if(socketError != QLocalSocket::PeerClosedError) {
66 | if(isLoading) {
67 | qWarning() << tr("Terminal closed due to connection error while loading terminal status:")
68 | << socket->errorString();
69 | }
70 | socket->disconnectFromServer();
71 | }
72 | }
73 |
74 | void TerminalPrivate::readyRead()
75 | {
76 | if(isLoading) {
77 | stream.startTransaction();
78 | QByteArray data;
79 | stream >> data;
80 | if(stream.commitTransaction()) {
81 | auto doc = QJsonDocument::fromBinaryData(data);
82 | if(doc.isNull()) {
83 | qWarning() << tr("Invalid Terminal status received. Data is corrupted. Terminal will be disconnected");
84 | socket->disconnectFromServer();
85 | } else {
86 | status = doc.object();
87 | isLoading = false;
88 | emit statusLoadComplete(this, true);
89 | }
90 | }
91 | }
92 | }
93 |
94 | void TerminalPrivate::writeReady()
95 | {
96 | if(disconnecting && socket->bytesToWrite() == 0) {
97 | QTimer::singleShot(500, this, [=](){
98 | if(socket->state() == QLocalSocket::ConnectedState)
99 | socket->disconnectFromServer();
100 | });
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/backgroundprocess/globalterminal.cpp:
--------------------------------------------------------------------------------
1 | #include "globalterminal.h"
2 | #include "app_p.h"
3 |
4 | #include
5 | #include
6 |
7 | namespace QtBackgroundProcess {
8 |
9 | class GlobalTerminalPrivate {
10 | public:
11 | App *app;
12 | QBuffer *buffer;
13 |
14 | GlobalTerminalPrivate(bool enableBootBuffer, GlobalTerminal *q_ptr) :
15 | app(qApp),
16 | buffer(enableBootBuffer ? new QBuffer(q_ptr) : nullptr)
17 | {}
18 | };
19 |
20 | }
21 |
22 | using namespace QtBackgroundProcess;
23 |
24 | GlobalTerminal::GlobalTerminal(QObject *parent, bool enableBootBuffer) :
25 | QIODevice(parent),
26 | d(new GlobalTerminalPrivate(enableBootBuffer, this))
27 | {
28 | if(enableBootBuffer) {
29 | connect(d->app, &App::connectedTerminalsChanged,
30 | this, &GlobalTerminal::tryPushBuffer);
31 | d->buffer->open(QIODevice::WriteOnly);
32 |
33 | QTimer::singleShot(10*1000, this, [this](){//wait up to 10 seconds for the first terminal to connect
34 | if(d->buffer && !tryPushBuffer(d->app->connectedTerminals()))
35 | discardBuffer();
36 | });
37 | }
38 |
39 | open(QIODevice::WriteOnly | QIODevice::Unbuffered);
40 | }
41 |
42 | GlobalTerminal::~GlobalTerminal()
43 | {
44 | QIODevice::close();
45 | }
46 |
47 | bool GlobalTerminal::isSequential() const
48 | {
49 | return true;
50 | }
51 |
52 | bool GlobalTerminal::canReadLine() const
53 | {
54 | return false;
55 | }
56 |
57 | void GlobalTerminal::writeLine(const QByteArray &line, bool doFlush)
58 | {
59 | write(line + '\n');
60 | if(doFlush)
61 | flush();
62 | }
63 |
64 | void GlobalTerminal::flush()
65 | {
66 | auto terms = d->app->connectedTerminals();
67 | if(d->buffer && !terms.isEmpty())
68 | pushBuffer(terms);
69 | foreach(auto term, terms)
70 | term->flush();
71 | }
72 |
73 | qint64 GlobalTerminal::readData(char *data, qint64 maxlen)
74 | {
75 | Q_UNUSED(data);
76 | Q_UNUSED(maxlen);
77 | return 0;
78 | }
79 |
80 | qint64 GlobalTerminal::writeData(const char *data, qint64 len)
81 | {
82 | auto terms = d->app->connectedTerminals();
83 | if(d->buffer) {
84 | if(terms.isEmpty())
85 | return d->buffer->write(data, len);
86 | else
87 | pushBuffer(terms);
88 | }
89 |
90 | foreach(auto term, terms)
91 | term->write(data, len);
92 | return len;
93 | }
94 |
95 | bool GlobalTerminal::tryPushBuffer(QList terms)
96 | {
97 | if(d->buffer && !terms.isEmpty()) {
98 | pushBuffer(terms);
99 | return true;
100 | } else
101 | return false;
102 | }
103 |
104 | bool GlobalTerminal::open(QIODevice::OpenMode mode)
105 | {
106 | return QIODevice::open(mode);
107 | }
108 |
109 | void GlobalTerminal::close() {}
110 |
111 | void GlobalTerminal::pushBuffer(QList terms)
112 | {
113 | const auto &data = d->buffer->data();
114 | if(!data.isEmpty()) {
115 | foreach(auto term, terms)
116 | term->write(data);
117 | }
118 | discardBuffer();
119 | }
120 |
121 | void GlobalTerminal::discardBuffer()
122 | {
123 | d->buffer->close();
124 | d->buffer->deleteLater();
125 | d->buffer = nullptr;
126 | disconnect(d->app, &App::connectedTerminalsChanged,
127 | this, &GlobalTerminal::tryPushBuffer);
128 | }
129 |
--------------------------------------------------------------------------------
/src/backgroundprocess/app_p.h:
--------------------------------------------------------------------------------
1 | #ifndef QTBACKGROUNDPROCESS_APP_P_H
2 | #define QTBACKGROUNDPROCESS_APP_P_H
3 |
4 | #include "app.h"
5 |
6 | #include "masterconnecter_p.h"
7 | #include "terminal_p.h"
8 | #include "globalterminal.h"
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | #include
19 |
20 | namespace QtBackgroundProcess {
21 |
22 | class Q_BACKGROUNDPROCESS_EXPORT AppPrivate : public QObject
23 | {
24 | Q_OBJECT
25 |
26 | public:
27 | static const QString masterArgument;
28 | static const QString purgeArgument;
29 | static const QString startArgument;
30 | static const QString restartArgument;
31 |
32 | static const QString terminalMessageFormat;
33 | static const QString masterMessageFormat;
34 |
35 | static AppPrivate *p_ptr();
36 |
37 | static void qbackProcMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
38 |
39 | static bool p_valid;
40 |
41 | bool running;
42 | bool masterLogging;
43 | bool autoStart;
44 | bool ignoreExtraStart;
45 | bool autoDelete;
46 | bool autoKill;
47 |
48 | QString instanceId;
49 | bool globalInstance;
50 |
51 | QScopedPointer masterLock;
52 | QLocalServer *masterServer;
53 | QList activeTerminals;
54 | std::function parserFunc;
55 | std::function startupFunc;
56 | std::function shutdownFunc;
57 |
58 | MasterConnecter *master;
59 |
60 | QPointer debugTerm;
61 | QPointer logFile;
62 |
63 | AppPrivate(App *q_ptr);
64 |
65 | QString generateSingleId(const QString &seed = QString());
66 | void setInstanceId(const QString &id);
67 | QString socketName() const;
68 |
69 | void setupDefaultParser(QCommandLineParser &parser, bool useShortOptions = true);
70 | void updateLoggingMode(int level);
71 | void updateLoggingPath(const QString &path);
72 |
73 | public Q_SLOTS:
74 | int initControlFlow(const QCommandLineParser &parser);
75 |
76 | private Q_SLOTS:
77 | int makeMaster(const QCommandLineParser &parser);
78 | int startMaster(bool isAutoStart = false, bool isRestart = false);
79 | int restartMaster(const QCommandLineParser &parser);
80 | int commandMaster();
81 | int purgeMaster(const QCommandLineParser &parser);
82 |
83 | void newTerminalConnected();
84 | void terminalLoaded(TerminalPrivate *terminal, bool success);
85 | void stopMaster(Terminal *term);
86 | void doExit(int code);
87 |
88 | void beginMasterConnect(const QStringList &arguments, bool isStarter);
89 |
90 | private:
91 | App *q;
92 | };
93 |
94 | Q_DECLARE_LOGGING_CATEGORY(loggingCategory)
95 |
96 | //custom logging operators
97 | #undef qInfo
98 | #define qInfo() qCInfo(loggingCategory).noquote()
99 | #undef qDebug
100 | #define qDebug() qCDebug(loggingCategory).noquote()
101 | #undef qWarning
102 | #define qWarning() qCWarning(loggingCategory).noquote()
103 | #undef qCritical
104 | #define qCritical() qCCritical(loggingCategory).noquote()
105 |
106 | }
107 |
108 | #endif // QTBACKGROUNDPROCESS_APP_P_H
109 |
--------------------------------------------------------------------------------
/examples/backgroundprocess/DemoApp/testapp.cpp:
--------------------------------------------------------------------------------
1 | #include "testapp.h"
2 |
3 | #include
4 | using namespace QtBackgroundProcess;
5 |
6 | TestApp::TestApp(int &argc, char **argv) :
7 | App(argc, argv),
8 | statusTerm(nullptr)
9 | {}
10 |
11 | void TestApp::parseTerminalOptions()
12 | {
13 | QCommandLineParser parser;
14 | setupParser(parser, true);
15 | parser.process(*this);
16 | setAutoStartMaster(parser.isSet(QStringLiteral("a")));
17 | setIgnoreMultiStarts(parser.isSet(QStringLiteral("i")));
18 | }
19 |
20 | int TestApp::startupApp(const QCommandLineParser &parser)
21 | {
22 | doCommand(parser);
23 | qDebug() << "App Master started with arguments:"
24 | << parser.positionalArguments()
25 | << "and options:"
26 | << parser.optionNames();
27 |
28 | connect(this, &TestApp::commandReceived,
29 | this, &TestApp::handleCommand);
30 |
31 | if(parser.isSet(QStringLiteral("m"))) {
32 | if(parser.value(QStringLiteral("m")) == QStringLiteral("echo")) {
33 | connect(this, &TestApp::newTerminalConnected,
34 | this, &TestApp::addTerminal);
35 | qDebug() << "Master started in echo mode!";
36 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("status")){
37 | statusTerm = new GlobalTerminal(this);
38 | qDebug() << "Master started in status mode!";
39 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("scream")){
40 | auto term = new GlobalTerminal(this);
41 | auto timer = new QTimer(this);
42 | timer->setInterval(500);
43 | qsrand(QDateTime::currentMSecsSinceEpoch());
44 | connect(timer, &QTimer::timeout, this, [term](){
45 | static const QByteArray strings="qwertzuiopasdfghjklyxcvbnm\n ";
46 | auto idx = (qrand() / (double)RAND_MAX) * (strings.size() - 1);
47 | term->write(strings.mid(idx, 1));
48 | term->flush();
49 | });
50 | timer->start();
51 | qDebug() << "Master started in scream mode!";
52 | } else if(parser.value(QStringLiteral("m")) == QStringLiteral("pid")) {
53 | qDebug() << "Master started in pid mode!";
54 | connect(this, &TestApp::newTerminalConnected, this, [this](Terminal *term){
55 | disconnect(this, &TestApp::newTerminalConnected,
56 | this, nullptr);
57 | term->write(QByteArray::number(QCoreApplication::applicationPid()));
58 | term->flush();
59 | term->disconnectTerminal();
60 | qDebug() << "pid written";
61 | });
62 | auto term = new GlobalTerminal(this);
63 | term->writeLine(QByteArray::number(QCoreApplication::applicationPid()));
64 | term->flush();
65 | } else
66 | qWarning() << "Unknown mode! Will be ignored";
67 | }
68 |
69 | connect(this, &App::aboutToQuit, this, [](){
70 | qDebug() << "I am quitting!";
71 | });
72 |
73 | return EXIT_SUCCESS;
74 | }
75 |
76 | bool TestApp::requestAppShutdown(Terminal *terminal, int &)
77 | {
78 | qDebug() << "stop requested with"
79 | << terminal->parser()->positionalArguments()
80 | << "and options:"
81 | << terminal->parser()->optionNames();
82 | return true;
83 | }
84 |
85 | void TestApp::setupParser(QCommandLineParser &parser, bool useShortOptions)
86 | {
87 | App::setupParser(parser, useShortOptions);
88 |
89 | parser.addOption({
90 | {QStringLiteral("a"), QStringLiteral("autostart")},
91 | QStringLiteral("Starts the master automatically, if not already running.")
92 | });
93 | parser.addOption({
94 | {QStringLiteral("i"), QStringLiteral("ignoreStart")},
95 | QStringLiteral("If start is called a second time, the arguments will be omitted.")
96 | });
97 | parser.addOption({
98 | {QStringLiteral("f"), QStringLiteral("forward")},
99 | QStringLiteral("forwards master debug output to all terminals if = 1, disables it if = 0."),
100 | QStringLiteral("active"),
101 | QStringLiteral("1")
102 | });
103 | parser.addOption({
104 | {QStringLiteral("m"), QStringLiteral("mode")},
105 | QStringLiteral("Tells the master to run . Can be \"echo\" to simply echo all terminals, "
106 | "\"status\" to simply broadcast new arguments to all terminals, \"scream\" to permanently "
107 | "print stuff to all terminals, or \"pid\" to print the process id to all terminals. Unless explicitly set, nothing will be done"),
108 | QStringLiteral("mode")
109 | });
110 | }
111 |
112 | void TestApp::handleCommand(QSharedPointer parser, bool starter)
113 | {
114 | if(starter) {
115 | qDebug() << "skipping starter args:"
116 | << parser->positionalArguments()
117 | << "and options:"
118 | << parser->optionNames();
119 | } else {
120 | doCommand(*parser.data());
121 | qDebug() << "received new command:"
122 | << parser->positionalArguments()
123 | << "and options:"
124 | << parser->optionNames();
125 | }
126 |
127 | if(statusTerm)
128 | statusTerm->writeLine('[' + parser->positionalArguments().join(QStringLiteral(", ")).toUtf8() + ']');
129 | }
130 |
131 | void TestApp::addTerminal(Terminal *terminal)
132 | {
133 | //add a simple echo to all terminals
134 | connect(terminal, &Terminal::readyRead, terminal, [terminal](){
135 | terminal->write(terminal->readAll());
136 | });
137 | }
138 |
139 | void TestApp::doCommand(const QCommandLineParser &parser)
140 | {
141 | if(parser.isSet(QStringLiteral("f")))
142 | setForwardMasterLog(parser.value(QStringLiteral("f")).toInt());
143 | }
144 |
145 | int main(int argc, char *argv[])
146 | {
147 | TestApp a(argc, argv);
148 | TestApp::setApplicationVersion(QStringLiteral("4.2.0"));
149 |
150 | a.parseTerminalOptions();
151 | return a.exec();
152 | }
153 |
--------------------------------------------------------------------------------
/src/backgroundprocess/app.cpp:
--------------------------------------------------------------------------------
1 | #include "app.h"
2 | #include "app_p.h"
3 |
4 | #include
5 |
6 | using namespace QtBackgroundProcess;
7 |
8 | App::App(int &argc, char **argv, int flags) :
9 | QCoreApplication(argc, argv, flags),
10 | d(new AppPrivate(this))
11 | {
12 | AppPrivate::p_valid = true;
13 |
14 | qSetMessagePattern(AppPrivate::terminalMessageFormat);
15 | qInstallMessageHandler(AppPrivate::qbackProcMessageHandler);
16 |
17 | QCtrlSignalHandler::instance()->setAutoQuitActive(true);
18 | }
19 |
20 | App::~App()
21 | {
22 | if(d->logFile)
23 | d->logFile->close();
24 | AppPrivate::p_valid = false;
25 | }
26 |
27 | QString App::instanceID() const
28 | {
29 | return d->instanceId;
30 | }
31 |
32 | bool App::globalInstance() const
33 | {
34 | return d->globalInstance;
35 | }
36 |
37 | bool App::forwardMasterLog() const
38 | {
39 | return d->masterLogging;
40 | }
41 |
42 | bool App::autoStartMaster() const
43 | {
44 | return d->autoStart;
45 | }
46 |
47 | bool App::ignoreMultiStarts() const
48 | {
49 | return d->ignoreExtraStart;
50 | }
51 |
52 | bool App::autoDeleteTerminals() const
53 | {
54 | return d->autoDelete;
55 | }
56 |
57 | bool App::autoKillTerminals() const
58 | {
59 | return d->autoKill;
60 | }
61 |
62 | void App::setParserSetupFunction(const std::function &function)
63 | {
64 | d->parserFunc = function;
65 | }
66 |
67 | void App::setStartupFunction(const std::function &function)
68 | {
69 | d->startupFunc = function;
70 | }
71 |
72 | void App::setShutdownRequestFunction(const std::function &function)
73 | {
74 | d->shutdownFunc = [function](Terminal *t, int &r){
75 | return function(*t->parser().data(), r);
76 | };
77 | }
78 |
79 | void App::setShutdownRequestFunction(const std::function &function)
80 | {
81 | d->shutdownFunc = function;
82 | }
83 |
84 | int App::exec()
85 | {
86 | d->running = true;
87 |
88 | //process arguments
89 | QCommandLineParser parser;
90 | setupParser(parser);
91 | parser.process(*this);
92 |
93 | //update terminal logging
94 | d->updateLoggingMode(parser.value(QStringLiteral("terminallog")).toInt());
95 |
96 | //generate the single id (temporary disable running)
97 | d->running = false;
98 | createDefaultInstanceID(false);
99 | d->running = true;
100 |
101 | auto res = d->initControlFlow(parser);
102 | if(res == -1)//special case
103 | return EXIT_SUCCESS;
104 | else if(res != EXIT_SUCCESS)
105 | return res;
106 | res = QCoreApplication::exec();
107 | d->running = false;
108 | return res;
109 | }
110 |
111 | QList App::connectedTerminals() const
112 | {
113 | return d->activeTerminals;
114 | }
115 |
116 | void App::createDefaultInstanceID(bool overwrite)
117 | {
118 | if(overwrite || d->instanceId.isNull())
119 | d->setInstanceId(d->generateSingleId());
120 | }
121 |
122 | void App::setInstanceID(QString instanceID, bool useAsSeed)
123 | {
124 | if(useAsSeed)
125 | d->setInstanceId(d->generateSingleId(instanceID));
126 | else
127 | d->setInstanceId(instanceID);
128 | }
129 |
130 | void App::setGlobalInstance(bool globalInstance)
131 | {
132 | d->globalInstance = globalInstance;
133 | }
134 |
135 | void App::setForwardMasterLog(bool forwardMasterLog)
136 | {
137 | if(forwardMasterLog == d->masterLogging)
138 | return;
139 |
140 | d->masterLogging = forwardMasterLog;
141 | if(d->masterLock && d->masterLock->isLocked()) {//I am master
142 | if(forwardMasterLog)
143 | d->debugTerm = new GlobalTerminal(d, true);
144 | else {
145 | d->debugTerm->deleteLater();
146 | d->debugTerm.clear();
147 | }
148 | }
149 | }
150 |
151 | void App::setAutoStartMaster(bool autoStartMaster)
152 | {
153 | d->autoStart = autoStartMaster;
154 | }
155 |
156 | void App::setIgnoreMultiStarts(bool ignoreMultiStarts)
157 | {
158 | d->ignoreExtraStart = ignoreMultiStarts;
159 | }
160 |
161 | void App::setAutoDeleteTerminals(bool autoDeleteTerminals, bool changeCurrent)
162 | {
163 | d->autoDelete = autoDeleteTerminals;
164 | if(changeCurrent) {
165 | foreach(auto terminal, d->activeTerminals)
166 | terminal->setAutoDelete(autoDeleteTerminals);
167 | }
168 | }
169 |
170 | void App::setAutoKillTerminals(bool autoKillTerminals, bool killCurrent)
171 | {
172 | d->autoKill = autoKillTerminals;
173 | if(killCurrent) {
174 | foreach(auto terminal, d->activeTerminals) {
175 | terminal->setAutoDelete(true);
176 | terminal->disconnectTerminal();
177 | }
178 | }
179 | }
180 |
181 | void App::setupParser(QCommandLineParser &parser, bool useShortOptions)
182 | {
183 | d->setupDefaultParser(parser, useShortOptions);
184 | if(d->parserFunc)
185 | d->parserFunc(parser);
186 | }
187 |
188 | int App::startupApp(const QCommandLineParser &parser)
189 | {
190 | if(d->startupFunc)
191 | return d->startupFunc(parser);
192 | else
193 | return EXIT_SUCCESS;
194 | }
195 |
196 | bool App::requestAppShutdown(Terminal *terminal, int &exitCode)
197 | {
198 | if(d->shutdownFunc)
199 | return d->shutdownFunc(terminal, exitCode);
200 | else
201 | return true;
202 | }
203 |
204 |
205 |
206 | NotAllowedInRunningStateException::NotAllowedInRunningStateException() :
207 | QException()
208 | {}
209 |
210 | const char *NotAllowedInRunningStateException::what() const noexcept
211 | {
212 | return "You are not allowed to perform this operation while the application is running!";
213 | }
214 |
215 | void NotAllowedInRunningStateException::raise() const
216 | {
217 | throw *this;
218 | }
219 |
220 | QException *NotAllowedInRunningStateException::clone() const
221 | {
222 | return new NotAllowedInRunningStateException();
223 | }
224 |
--------------------------------------------------------------------------------
/tests/auto/backgroundprocess/MasterTest/processhelper.cpp:
--------------------------------------------------------------------------------
1 | #include "processhelper.h"
2 | #include
3 | #ifdef Q_OS_UNIX
4 | #include
5 | #include
6 | #endif
7 |
8 | const char ProcessHelper::Stamp = '%';
9 |
10 | QString ProcessHelper::binPath()
11 | {
12 | #if defined(Q_OS_WIN)
13 | return QStringLiteral(OUTDIR) + QStringLiteral("../../../../examples/backgroundprocess/DemoApp/") + QStringLiteral(RMODE) + QStringLiteral("/DemoApp");
14 | #else
15 | return QStringLiteral(OUTDIR) + QStringLiteral("../../../../examples/backgroundprocess/DemoApp/DemoApp");
16 | #endif
17 | }
18 |
19 | ProcessHelper::ProcessHelper(QObject *parent) :
20 | QObject(parent),
21 | process(new QProcess(this)),
22 | exitCode(EXIT_SUCCESS)
23 | {
24 | process->setProgram(binPath());
25 |
26 | connect(process, &QProcess::errorOccurred,
27 | this, &ProcessHelper::errorOccurred);
28 | connect(process, QOverload::of(&QProcess::finished),
29 | this, &ProcessHelper::finished);
30 | }
31 |
32 | void ProcessHelper::setExitCode(int code)
33 | {
34 | exitCode = code;
35 | }
36 |
37 | void ProcessHelper::start(const QByteArrayList &commands, bool logpath, int timeout)
38 | {
39 | QStringList s;
40 | foreach(auto c, commands)
41 | s.append(QString::fromUtf8(c));
42 | if(logpath) {
43 | s.append({QStringLiteral("--logpath"), logPath()});
44 | s.append({QStringLiteral("--loglevel"), QStringLiteral("4")});
45 | }
46 | s.append({QStringLiteral("--terminallog"), QStringLiteral("4")});
47 |
48 | process->setArguments(s);
49 | process->start(QIODevice::ReadWrite);
50 | QVERIFY2(process->waitForStarted(5000), qUtf8Printable(process->errorString()));
51 | QThread::msleep(timeout);
52 | }
53 |
54 | void ProcessHelper::send(const QByteArray &message)
55 | {
56 | process->write(message + '\n');
57 | QCoreApplication::processEvents();
58 | }
59 |
60 | void ProcessHelper::termMaster()
61 | {
62 | auto line = process->readAll();
63 | auto ok = false;
64 | auto pid = line.toInt(&ok);
65 | QVERIFY2(ok, line.constData());
66 |
67 | #ifdef Q_OS_UNIX
68 | kill(pid, SIGTERM);
69 | #endif
70 | }
71 |
72 | void ProcessHelper::waitForFinished(bool terminate)
73 | {
74 | if(!process->waitForFinished(5000)) {
75 | if(terminate)
76 | process->terminate();
77 | QVERIFY2(false, "Process did not stop by itself");
78 | }
79 | }
80 |
81 | bool ProcessHelper::verifyLog(const QByteArrayList &log, bool isError, const char *file, int line)
82 | {
83 | QByteArray data;
84 | if(isError)
85 | data = process->readAllStandardError();
86 | else
87 | data = process->readAllStandardOutput();
88 | auto dataList = data.split('\n');
89 |
90 | if(!testLog(log, dataList, isError)) {
91 | QByteArray resStr = "Log does not contain device output for ";
92 | if(isError)
93 | resStr += "stderr:\n";
94 | else
95 | resStr += "stdout:\n";
96 | resStr += "Expected Log:";
97 | foreach(auto line, log)
98 | resStr += "\n\t" + line;
99 | resStr += "\nActual Log:";
100 | foreach(auto line, dataList)
101 | resStr += "\n\t" + line;
102 | QTest::qFail(resStr.constData(), file, line);
103 | return false;
104 | } else
105 | return true;
106 | }
107 |
108 | void ProcessHelper::waitForFinished(const QList &helpers)
109 | {
110 | foreach (auto h, helpers)
111 | h->waitForFinished();
112 | }
113 |
114 | void ProcessHelper::clearLog()
115 | {
116 | auto logFile = logPath();
117 | if(QFile::exists(logFile))
118 | QVERIFY(QFile::remove(logFile));
119 | }
120 |
121 | bool ProcessHelper::verifyMasterLog(const QByteArrayList &log, const char *file, int line)
122 | {
123 | auto logFile = logPath();
124 | QFile masterFile(logFile);
125 | if (!QTest::qVerify(masterFile.exists(), "masterLog.exists", "File does not exist", file, line))
126 | return false;
127 | if (!QTest::qVerify(masterFile.open(QIODevice::ReadOnly | QIODevice::Text), "masterLog.open", qUtf8Printable(masterFile.errorString()), file, line))
128 | return false;
129 |
130 | auto dataList = masterFile.readAll().split('\n');
131 | auto res = testLog(log, dataList, true);
132 | if(!res) {
133 | QByteArray resStr = "Master Log is not as expected:\nExpected Log:";
134 | foreach(auto line, log)
135 | resStr += "\n\t" + line;
136 | resStr += "\nActual Log:";
137 | foreach(auto line, dataList)
138 | resStr += "\n\t" + line;
139 | QTest::qFail(resStr.constData(), file, line);
140 | }
141 |
142 | masterFile.close();
143 | if (!QTest::qVerify(masterFile.remove(), "masterLog.remove", qUtf8Printable(masterFile.errorString()), file, line))
144 | return false;
145 |
146 | return res;
147 | }
148 |
149 | void ProcessHelper::errorOccurred(QProcess::ProcessError error)
150 | {
151 | Q_UNUSED(error)
152 | QFAIL(qUtf8Printable(process->errorString()));
153 | }
154 |
155 | void ProcessHelper::finished(int exitCode, QProcess::ExitStatus exitStatus)
156 | {
157 | QCOMPARE(exitStatus, QProcess::NormalExit);
158 | QCOMPARE(exitCode, this->exitCode);
159 | }
160 |
161 | QString ProcessHelper::logPath()
162 | {
163 | return QDir::temp().absoluteFilePath(QStringLiteral("MasterTest.log"));
164 | }
165 |
166 | bool ProcessHelper::testLog(const QByteArrayList &log, const QByteArrayList &device, bool fullEqual)
167 | {
168 | auto index = 0;
169 |
170 | foreach(auto line, device) {
171 | auto logStr = line.trimmed();
172 | if(logStr.isEmpty())
173 | continue;
174 | //filter out unrelated logs
175 | if(logStr.contains("QCtrlSignals") ||
176 | logStr.contains("XDG_RUNTIME_DIR"))
177 | continue;
178 |
179 | bool fullOk;
180 | do {
181 | fullOk = true;
182 | if(index >= log.size())
183 | return false;
184 | auto testStr = log[index++];
185 |
186 | auto testSplit = testStr.split(Stamp);
187 | if(testSplit.size() == 2) {
188 | if(logStr.startsWith(testSplit[0]) &&
189 | logStr.endsWith(testSplit[1]))
190 | break;
191 | }
192 |
193 | if(logStr == testStr)
194 | break;
195 |
196 | fullOk = false;
197 | } while(!fullEqual);
198 |
199 | if(!fullOk)
200 | return false;
201 | }
202 |
203 | return !fullEqual || index == log.size();
204 | }
205 |
--------------------------------------------------------------------------------
/src/backgroundprocess/app.h:
--------------------------------------------------------------------------------
1 | #ifndef QTBACKGROUNDPROCESS_APP_H
2 | #define QTBACKGROUNDPROCESS_APP_H
3 |
4 | #include "QtBackgroundProcess/qtbackgroundprocess_global.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | //! The Namespace containing all classes of the QtBackgroundProcess module
13 | namespace QtBackgroundProcess {
14 |
15 | class Terminal;
16 |
17 | //! Will be thrown, if you perform an operation, that is not allowed in running state
18 | class Q_BACKGROUNDPROCESS_EXPORT NotAllowedInRunningStateException : public QException
19 | {
20 | public:
21 | NotAllowedInRunningStateException();
22 |
23 | //! @inherit{std::exception::what}
24 | const char *what() const noexcept override;
25 |
26 | //! @inherit{QException::raise}
27 | void raise() const override;
28 | //! @inherit{QException::clone}
29 | QException *clone() const override;
30 | };
31 |
32 | class AppPrivate;
33 | //! The background process application. The main class of QtBackgroundProcess
34 | class Q_BACKGROUNDPROCESS_EXPORT App : public QCoreApplication
35 | {
36 | Q_OBJECT
37 | friend class AppPrivate;
38 |
39 | //! The current id of the singleton instance of the master process
40 | Q_PROPERTY(QString instanceID READ instanceID WRITE setInstanceID RESET createDefaultInstanceID)
41 | //! Specify whether the app should be a systemwide or userwide single instance
42 | Q_PROPERTY(bool globalInstance READ globalInstance WRITE setGlobalInstance)
43 | //! Specifies, whether the master should forward debug output to all terminals
44 | Q_PROPERTY(bool forwardMasterLog READ forwardMasterLog WRITE setForwardMasterLog)
45 | //! If true, the master process will always be started, not only with "start"
46 | Q_PROPERTY(bool autoStartMaster READ autoStartMaster WRITE setAutoStartMaster)
47 | //! If true, "start" commands will be ignored, if the master is already running
48 | Q_PROPERTY(bool ignoreMultiStarts READ ignoreMultiStarts WRITE setIgnoreMultiStarts)
49 | //! If true, the master process will automatically delete terminals that have been disconnected
50 | Q_PROPERTY(bool autoDeleteTerminals READ autoDeleteTerminals WRITE setAutoDeleteTerminals)
51 | //! If true, the master process will automatically close terminals after it received the parameters
52 | Q_PROPERTY(bool autoKillTerminals READ autoKillTerminals WRITE setAutoKillTerminals)
53 | //! Holds a list of all currently connected terminals
54 | Q_PROPERTY(QList connectedTerminals READ connectedTerminals NOTIFY connectedTerminalsChanged)
55 |
56 | public:
57 | //! Creates a new app with it's arguments
58 | App(int &argc, char **argv, int flags = ApplicationFlags);
59 | //! Destructor
60 | ~App();
61 |
62 | //! @readAcFn{App::instanceID}
63 | QString instanceID() const;
64 | //! @readAcFn{App::globalInstance}
65 | bool globalInstance() const;
66 | //! @readAcFn{App::forwardMasterLog}
67 | bool forwardMasterLog() const;
68 | //! @readAcFn{App::autoStartMaster}
69 | bool autoStartMaster() const;
70 | //! @readAcFn{App::ignoreMultiStarts}
71 | bool ignoreMultiStarts() const;
72 | //! @readAcFn{App::autoDeleteTerminals}
73 | bool autoDeleteTerminals() const;
74 | //! @readAcFn{App::autoKillTerminals}
75 | bool autoKillTerminals() const;
76 |
77 | //! Sets the function to be called for the creation of the parser (Instead of overriding)
78 | void setParserSetupFunction(const std::function &function);
79 | //! Sets the function to be called to startup the application (Instead of overriding)
80 | void setStartupFunction(const std::function &function);
81 | //! Sets the function to be called to handle shutdown requests (Instead of overriding)
82 | void setShutdownRequestFunction(const std::function &function);
83 | //! Sets the function to be called to handle shutdown requests (Instead of overriding)
84 | void setShutdownRequestFunction(const std::function &function);
85 |
86 | //! Executes the application event loop
87 | int exec();
88 |
89 | //! @readAcFn{App::connectedTerminals}
90 | QList connectedTerminals() const;
91 |
92 | public Q_SLOTS:
93 | //! @resetAcFn{App::instanceID}
94 | void createDefaultInstanceID(bool overwrite = true);
95 | //! @writeAcFn{App::instanceID}
96 | void setInstanceID(QString instanceID, bool useAsSeed = true);
97 | //! @writeAcFn{App::globalInstance}
98 | void setGlobalInstance(bool globalInstance);
99 | //! @writeAcFn{App::forwardMasterLog}
100 | void setForwardMasterLog(bool forwardMasterLog);
101 | //! @writeAcFn{App::autoStartMaster}
102 | void setAutoStartMaster(bool autoStartMaster);
103 | //! @writeAcFn{App::ignoreMultiStarts}
104 | void setIgnoreMultiStarts(bool ignoreMultiStarts);
105 | //! @writeAcFn{App::autoDeleteTerminals}
106 | void setAutoDeleteTerminals(bool autoDeleteTerminals, bool changeCurrent = false);
107 | //! @writeAcFn{App::autoKillTerminals}
108 | void setAutoKillTerminals(bool autoKillTerminals, bool killCurrent = false);
109 |
110 | Q_SIGNALS:
111 | //! Will be emitted when a new terminal has connected to the master
112 | void newTerminalConnected(QtBackgroundProcess::Terminal *terminal, QPrivateSignal);
113 | //! Will be emitted when a new terminal sent arguments to the master
114 | void commandReceived(QSharedPointer parser, bool isStarter, QPrivateSignal);
115 |
116 | //! @notifyAcFn{App::connectedTerminals}
117 | void connectedTerminalsChanged(QList connectedTerminals, QPrivateSignal);
118 |
119 | protected:
120 | //! Sets up the parser to parse commands and arguments
121 | virtual void setupParser(QCommandLineParser &parser, bool useShortOptions = true);
122 | //! Is called as initialization function of the master process
123 | virtual int startupApp(const QCommandLineParser &parser);
124 | //! Gets called when a terminal requests a shutdown of the master
125 | virtual bool requestAppShutdown(Terminal *terminal, int &exitCode);
126 |
127 | private:
128 | AppPrivate* d;
129 | };
130 |
131 | }
132 |
133 | #undef qApp
134 | #define qApp static_cast(QCoreApplication::instance())
135 |
136 | #endif // QTBACKGROUNDPROCESS_APP_H
137 |
--------------------------------------------------------------------------------
/examples/backgroundprocess/DemoApp/README.md:
--------------------------------------------------------------------------------
1 | # How to test:
2 | Do the following commands and compare with the output to validate:
3 |
4 | ## Simple Test
5 |
6 | ### Commands
7 | 1. `start Test1`
8 | 2. `Test2`
9 | 3. `start Test 3`
10 | 4. `stop Test 4`
11 |
12 | ### Outputs
13 | #### Terminal 1
14 | ```
15 | ```
16 |
17 | #### Terminal 2
18 | ```
19 | ```
20 |
21 | #### Terminal 3
22 | ```
23 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is
24 | ```
25 |
26 | #### Terminal 4
27 | ```
28 | ```
29 |
30 | #### Log File
31 | ```
32 | [2016-12-10T15:05:43 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test1") and options: ()
33 | [2016-12-10T15:05:43 Debug] skipping starter args: ("start", "Test1") and options: ()
34 | [2016-12-10T15:05:49 Debug] received new command: ("Test2") and options: ()
35 | [2016-12-10T15:05:56 Debug] received new command: ("start", "Test", "3") and options: ()
36 | [2016-12-10T15:06:01 Debug] received new command: ("stop", "Test", "4") and options: ()
37 | [2016-12-10T15:06:01 Debug] stop requested with ("stop", "Test", "4") and options: ()
38 | ```
39 |
40 | ## Commands Test
41 |
42 | ### Commands
43 | 1. `Test1`
44 | 2. `-a -f 1 -i Test2`
45 | 3. `start -i Test 3`
46 | 4. `start Test 4`
47 | 5. `-a -i Test5`
48 | 6. `-i Test6`
49 | 7. `-f 0 Test 7`
50 | 8. `Test 8`
51 | 9. `stop -f 1 Test9`
52 |
53 | ### Outputs
54 | #### Terminal 1
55 | ```
56 | [Critical] QtBackgroundProcess: Master process is not running! Please launch it by using: ".../TestApp.exe start"
57 | ```
58 |
59 | #### Terminal 2
60 | ##### After Command
61 | ```
62 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i")
63 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i")
64 | ```
65 |
66 | ##### Completely
67 | ```
68 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i")
69 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i")
70 | [2016-12-10T15:07:56 Debug] received new command: () and options: ()
71 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: ()
72 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
73 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
74 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
75 | ```
76 |
77 | #### Terminal 3
78 | ##### After Command
79 | ```
80 | [Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!
81 | [2016-12-10T15:07:56 Debug] received new command: () and options: ()
82 | ```
83 |
84 | ##### Completely
85 | ```
86 | [Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!
87 | [2016-12-10T15:07:56 Debug] received new command: () and options: ()
88 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: ()
89 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
90 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
91 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
92 | ```
93 |
94 | #### Terminal 4
95 | ##### After Command
96 | ```
97 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is
98 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: ()
99 | ```
100 |
101 | ##### Completely
102 | ```
103 | [Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is
104 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: ()
105 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
106 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
107 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
108 | ```
109 |
110 | #### Terminal 5
111 | ##### After Command
112 | ```
113 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
114 | ```
115 |
116 | ##### Completely
117 | ```
118 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
119 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
120 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
121 | ```
122 |
123 | #### Terminal 6
124 | ##### After Command
125 | ```
126 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
127 | ```
128 |
129 | ##### Completely
130 | ```
131 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
132 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
133 | ```
134 |
135 | #### Terminal 7
136 | ##### After Command
137 | ```
138 | ```
139 |
140 | ##### Completely
141 | ```
142 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
143 | ```
144 |
145 | #### Terminal 8
146 | ##### After Command
147 | ```
148 | ```
149 |
150 | ##### Completely
151 | ```
152 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
153 | ```
154 |
155 | #### Terminal 9
156 | ```
157 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
158 | ```
159 |
160 | #### Log File
161 | ```
162 | [2016-12-10T15:07:26 Debug] App Master Started with arguments: ("__qbckgrndprcss$start#master~", "Test2") and options: ("a", "f", "i")
163 | [2016-12-10T15:07:26 Debug] skipping starter args: ("Test2") and options: ("a", "f", "i")
164 | [2016-12-10T15:07:56 Debug] received new command: () and options: ()
165 | [2016-12-10T15:08:04 Debug] received new command: ("start", "Test", "4") and options: ()
166 | [2016-12-10T15:08:10 Debug] received new command: ("Test5") and options: ("a", "i")
167 | [2016-12-10T15:08:17 Debug] received new command: ("Test6") and options: ("i")
168 | [2016-12-10T15:08:24 Debug] received new command: ("Test", "7") and options: ("f")
169 | [2016-12-10T15:08:31 Debug] received new command: ("Test", "8") and options: ()
170 | [2016-12-10T15:08:38 Debug] received new command: ("stop", "Test") and options: ("f")
171 | [2016-12-10T15:08:38 Debug] stop requested with ("stop", "Test") and options: ("f")
172 | ```
173 |
--------------------------------------------------------------------------------
/src/backgroundprocess/translations/qtbackgroundprocess_template.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QtBackgroundProcess::AppPrivate
6 |
7 |
8 | A control command to control the background application. Possible options are:
9 | - start: starts the application
10 | - stop: stops the application
11 | - restart: stops the application and then starts it again with the given arguments
12 | - purge_master: purges local servers and lockfiles, in case the master process crashed. Pass "--accept" as second parameter, if you want to skip the prompt.
13 |
14 |
15 |
16 |
17 | It set, the terminal will only pass it's arguments to the master, and automatically finish after.
18 |
19 |
20 |
21 |
22 | Set the desired log <level>. Possible values are:
23 | - 0: log nothing
24 | - 1: critical errors only
25 | - 2: like 1 plus warnings
26 |
27 |
28 |
29 |
30 |
31 | - 3: like 2 plus information messages (default)
32 | - 4: verbose - log everything
33 |
34 |
35 |
36 |
37 |
38 |
39 | level
40 |
41 |
42 |
43 |
44 | - 3: like 2 plus information messages
45 | - 4: verbose - log everything (default)
46 |
47 |
48 |
49 |
50 | Overwrites the default log <path>. The default path is platform and application specific. For this instance, it defaults to "%1". NOTE: The application can override the value internally. Pass an empty string (--logpath "") to disable logging to a file.
51 |
52 |
53 |
54 |
55 | path
56 |
57 |
58 |
59 |
60 | Sets the log <level> for terminal only messages. This does not include messages forwarded from the master. Log levels are the same as for the <loglevel> option.
61 |
62 |
63 |
64 |
65 | Will prevent the master process from "closing" the console and other stuff that is done to daemonize the process. Can be useful for debugging purpose.
66 |
67 |
68 |
69 |
70 | Skips the prompt and accepts where possible.
71 |
72 |
73 |
74 |
75 | Failed to create local server with error:
76 |
77 |
78 |
79 |
80 | Unable to start master process. Failed with lock error:
81 |
82 |
83 |
84 |
85 | Failed to start master process! No master lock was detected.
86 |
87 |
88 |
89 |
90 | Start commands ignored because master is already running! The terminal will connect with an empty argument list!
91 |
92 |
93 |
94 |
95 | Master is already running. Start arguments will be passed to it as is
96 |
97 |
98 |
99 |
100 |
101 | Failed to stop the running master process.
102 | Do you want to restart it anyway? (y/N)
103 |
104 |
105 |
106 |
107 | y
108 |
109 |
110 |
111 |
112 | Y
113 |
114 |
115 |
116 |
117 | Master process successfully stopped
118 |
119 |
120 |
121 |
122 | Master process is not running! Please launch it by using:
123 |
124 |
125 |
126 |
127 | Are you shure you want to purge the master lock and server?
128 | Only do this if the master process is not running anymore, but the lock/server are not available (for example after a crash)
129 | Purging while the master process is still running will crash it.
130 | Press (Y) to purge, or (n) to cancel:
131 |
132 |
133 |
134 |
135 | n
136 |
137 |
138 |
139 |
140 | N
141 |
142 |
143 |
144 |
145 | Master lockfile successfully removed. It was locked by:
146 |
147 |
148 |
149 |
150 | Failed to remove master lockfile. Lock data is:
151 |
152 |
153 |
154 |
155 |
156 | - PID:
157 |
158 |
159 |
160 |
161 |
162 | - Hostname:
163 |
164 |
165 |
166 |
167 |
168 | - Appname:
169 |
170 |
171 |
172 |
173 | No lock file detected
174 |
175 |
176 |
177 |
178 | Master server successfully removed
179 |
180 |
181 |
182 |
183 | Failed to remove master server
184 |
185 |
186 |
187 |
188 | Terminal with invalid commands discarded. Error:
189 |
190 |
191 |
192 |
193 | QtBackgroundProcess::MasterConnecter
194 |
195 |
196 | Connection to Master process failed with error:
197 |
198 |
199 |
200 |
201 | stdin was unexpectedly closed! The terminal will not be able to foreward input to the master anymore!
202 |
203 |
204 |
205 |
206 | QtBackgroundProcess::TerminalPrivate
207 |
208 |
209 | Terminal closed due to connection error while loading terminal status:
210 |
211 |
212 |
213 |
214 | Invalid Terminal status received. Data is corrupted. Terminal will be disconnected
215 |
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QtBackgroundProcess
2 | A Library to create background applications with simple, automated foreground control.
3 |
4 | > This project was superseded by https://github.com/Skycoder42/QtService
5 |
6 | [](https://travis-ci.org/Skycoder42/QtBackgroundProcess)
7 | [](https://ci.appveyor.com/project/Skycoder42/qtbackgroundprocess/branch/master)
8 | [](https://aur.archlinux.org/packages/qt5-backgroundprocess/)
9 |
10 | In short, with this library you can easly create cross platform applications, that are ment to run in the background, without any UI. This however, is fairly easy and would not require a library. This one however adds a few things, that make it much more comfortable to use those.
11 |
12 | Have a look at the features.
13 |
14 | ## Features
15 | - Cross platform, supports all Qt-Platforms, that allow multi-process applications and console applications
16 | - Runs your application in the background, without a console window or similar (so called "master process")
17 | - Runs as a singleton (for the current user / root)
18 | - The background process will always start independently
19 | - A "execution" of an application (for example via the console) will start a "terminal"
20 | - Terminals will connect to the running master. If none is running, you can start it with the terminal
21 | - Terminals send their command line arguments to the running master
22 | - A connected terminal can send input to the master (via stdin) and receive output (stdout)
23 | - Terminals can disconnect by beeing closed, leaving the master running (even the one that started the master)
24 | - The master continues running, even if all terminals have been disconnected
25 | - Terminals can ask the master to stop
26 | - Terminals are a part of the library and work as is. You don't have to do anything with them, only "implement" you application as master process
27 | - The master can send output to all terminals at once, or directly communicate with a terminal
28 | - The master automates logging, by writing it to a log file, and sending it to the terminals (both optional)
29 | - Provides a basic CLI, with usefull commands for the master
30 | - Can be easily extended by your application
31 | - Integrates the [QCtrlSignals](https://github.com/Skycoder42/QCtrlSignals) to react on typical signals like `SIGINT`
32 |
33 | ## Use Case
34 | The QtBackgroundProcess is a library for console applications. It is not intended to be used with GUI-Apps or similar, and probably won't work with them. If you are looking for simply a "singleton instance", i.e. a library that will make shure only one instance of your application runs at a time, have a look at [QSingleInstance](https://github.com/Skycoder42/QSingleInstance).
35 |
36 | However, if you want to create an application to run silently in the background, and only interact with it to "control" it, you're at the right place.
37 |
38 | ## Download/Installation
39 | There are multiple ways to install the Qt module, sorted by preference:
40 |
41 | 1. Package Managers: The library is available via:
42 | - **Arch-Linux:** AUR-Repository: [`qt5-backgroundprocess`](https://aur.archlinux.org/packages/qt5-backgroundprocess/)
43 | - **Ubuntu:** Launchpad-PPA:
44 | - Artful: [ppa:skycoder42/qt-modules](https://launchpad.net/~skycoder42/+archive/ubuntu/qt-modules), package `libqt5backgroundprocess[1/-dev]`
45 | - Xenial: [ppa:skycoder42/qt-modules-opt](https://launchpad.net/~skycoder42/+archive/ubuntu/qt-modules-opt), package `qtbackgroundprocess`
46 | - **MacOs:**
47 | - Tap: [`brew tap Skycoder42/qt-modules`](https://github.com/Skycoder42/homebrew-qt-modules)
48 | - Package: `qtbackgroundprocess`
49 | - **IMPORTANT:** Due to limitations of homebrew, you must run `source /usr/local/opt/qtbackgroundprocess/bashrc.sh` before you can use the module. Some goes for the `qtjsonserializer` dependency
50 | 2. Simply add my repository to your Qt MaintenanceTool (Image-based How-To here: [Add custom repository](https://github.com/Skycoder42/QtModules/blob/master/README.md#add-my-repositories-to-qt-maintenancetool)):
51 | 1. Open the MaintenanceTool, located in your Qt install directory (e.g. `~/Qt/MaintenanceTool`)
52 | 2. Select `Add or remove components` and click on the `Settings` button
53 | 3. Go to `Repositories`, scroll to the bottom, select `User defined repositories` and press `Add`
54 | 4. In the right column (selected by default), type:
55 | - On Linux: https://install.skycoder42.de/qtmodules/linux_x64
56 | - On Windows: https://install.skycoder42.de/qtmodules/windows_x86
57 | - On Mac: https://install.skycoder42.de/qtmodules/mac_x64
58 | 5. Press `Ok`, make shure `Add or remove components` is still selected, and continue the install (`Next >`)
59 | 6. A new entry appears under all supported Qt Versions (e.g. `Qt > Qt 5.8 > Skycoder42 Qt modules`)
60 | 7. You can install either all of my modules, or select the one you need: `Qt Background Process`
61 | 8. Continue the setup and thats it! you can now use the module for all of your installed Kits for that Qt Version
62 | 3. Download the compiled modules from the release page. **Note:** You will have to add the correct ones yourself and may need to adjust some paths to fit your installation!
63 | 4. Build it yourself! **Note:** This requires perl, [qpmx](https://github.com/Skycoder42/qpmx) and [qpm](https://github.com/Cutehacks/qpm) to be installed. If you don't have/need cmake, you can ignore the related warnings. To automatically build and install to your Qt installation, run:
64 | - `qmake`
65 | - `make qmake_all`
66 | - `make`
67 | - `make install`
68 |
69 | ## Usage
70 | The background process is provided as a Qt module. Thus, all you have to do is add the module, and then, in your project, add `QT += backgroundprocess` to your `.pro` file!
71 |
72 | All you need to do in your code is to use `QtBackgroundProcess::App` instead of `QCoreApplication` and put your code in the startup function or extend it.
73 |
74 | ### Example
75 | The following example is a very simple background application, that logs all command arguments that have been passed by the terminals. In addition to that, it will send all debug output to all terminals.
76 |
77 | ```cpp
78 | #include
79 | #include
80 |
81 | int main(int argc, char *argv[])
82 | {
83 | QtBackgroundProcess::App a(argc, argv);
84 | QCoreApplication::setApplicationVersion("4.2.0");
85 |
86 | qApp->setForwardMasterLog(true);//forwards all debug log to terminals IF this becomes the master
87 | a.setStartupFunction([](const QCommandLineParser &){
88 | QObject::connect(qApp, &QtBackgroundProcess::App::commandReceived, qApp, [](QSharedPointer parser, bool isStarter){
89 | qDebug() << "Received command with arguments:"
90 | << parser->positionalArguments()
91 | << "and options:"
92 | << parser->optionNames()
93 | << (isStarter ? "(starter)" : "");
94 | });
95 |
96 | qDebug() << "Master process succsesfully stated!";
97 | return EXIT_SUCCESS;
98 | });
99 |
100 | return a.exec();
101 | }
102 | ```
103 | The first few commands (i.e. Version number) are done for every instance, including the terminals. The part inside of `setStartupFunction` will be executed on the master only.
104 |
105 | To find out what you can do with this application, simply call `/myApp --help` - this will show the command line arguments that can be used. Use `start` to start the master process and `stop` to stop it. Please remember the the master process will continue to run, even after you have closed all terminals. Use the `stop` command to stop it. For a demo app, see `examples/backgroundprocess/DemoApp`.
106 |
107 | ### Debugging the master process
108 | Since all of the application logic is happening in the master process, debugging the terminals does not really help. However, with "normal" commands you can only start terminals, not the master, and thus not debug it. If you need to debug the master, it can be helpful to start the master process directly, instead of using a terminal. To do so, all you need to do is launch it with a special first argument:
109 |
110 | If your normal command looks for example like this: `./myApp start some arguments`
111 | You can directly start the master process by using: `./myApp '__qbckgrndprcss$start#master~' some arguments`
112 |
113 | The master process is not meant to be directly run. Thus, it will for example, not show a console window, disconnect stdin/stdout, etc. This makes it hard to get the debug without a terminal. You can try the following options, depending on what suits you needs:
114 | - Start the master with a working console. This will only work as long as no terminals are attached, no logfile is used and you don't need stdin. For most situations, this is the recommended way of debugging. Use `-L "" --keep-console` as additional parameters for the master to enable this.
115 | - Watch the log file and reload it on changes, i.e. use `tail -f logfile`. To get the location, run `--help` and see the `-L` parameter. You can use terminals normally
116 | - Enable log forwarding to pass qDebug messages to all connected terminals. See QtBackgroundProcess::App::forwardMasterLog. Simply start a parameterless terminal after launching the master to see the output.
117 |
118 | ## Documentation
119 | The documentation is available on [github pages](https://skycoder42.github.io/QtBackgroundProcess/). It was created using [doxygen](http://www.doxygen.org/). The HTML-documentation and Qt-Help files are shipped
120 | together with the module for both the custom repository and the package on the release page. Please note that doxygen docs do not perfectly integrate with QtCreator/QtAssistant.
121 |
122 | ## Translations
123 | The project is prepared for translation. But since I speak only english and german, those are the only languages I can provide translations for. However, you can easily create the translations yourself. The file `src/backgroundprocess/translations/qtbackgroundprocess_template.ts` is a ready-made TS file. Just rename it (e.g. to `qtbackgroundprocess_jp.ts`) and open it with the QtLinguist to create the translations.
124 |
125 | ## Technical Stuff
126 | Please not, that this library does **not** create a real daemon/service. It merely creates a background process, with advanced terminal connections. This approach allows more flexibility and an easier cross-platform implementation. In addition to that, interaction with the process becomes possible everywhere, without any additional code. It tries however to "daemonize" itself as much as possible.
127 |
--------------------------------------------------------------------------------
/src/backgroundprocess/translations/qtbackgroundprocess_de.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | QtBackgroundProcess::AppPrivate
6 |
7 |
8 | A control command to control the background application. Possible options are:
9 | - start: starts the application
10 | - stop: stops the application
11 | - restart: stops the application and then starts it again with the given arguments
12 | - purge_master: purges local servers and lockfiles, in case the master process crashed. Pass "--accept" as second parameter, if you want to skip the prompt.
13 | Ein Kontroll-Kommando um die Hintergrundanwendung zu starten. Mögliche Kommandos sind:
14 | - start: Startet die Anwendung
15 | - stop: Stoppt die Anwendung
16 | - restart: Stoppt die Anwendung und startet sie danach erneut mit den gegebenen Argumenten
17 | - purge_master: Entfernt lokale server und lockfiles, falls der Master-Prozess abgestürzt ist. Übergeben sie "--accept" als zweiten Parameter, falls sie die Abfrage überpringen möchten.
18 |
19 |
20 |
21 | It set, the terminal will only pass it's arguments to the master, and automatically finish after.
22 | Wenn diese Option gesetzt ist, dann werden nur die Argumente an den Master weitergegeben und das Terminal danach geschlossen.
23 |
24 |
25 |
26 | Set the desired log <level>. Possible values are:
27 | - 0: log nothing
28 | - 1: critical errors only
29 | - 2: like 1 plus warnings
30 |
31 | Setzt das gewünschte Log-<Level>. Mögliche Werte sind:
32 | - 0: Nichts loggen
33 | - 1: Nur Kritische Fehler loggen
34 | - 2: Wie 1 plus Warnungen loggen
35 |
36 |
37 |
38 |
39 | - 3: like 2 plus information messages (default)
40 | - 4: verbose - log everything
41 | - 3: Wie 2 plus Informationen loggen (Standardwert)
42 | - 4: Alles loggen
43 |
44 |
45 |
46 |
47 |
48 | level
49 | Level
50 |
51 |
52 |
53 | - 3: like 2 plus information messages
54 | - 4: verbose - log everything (default)
55 | - 3: Wie 2 plus Informationen loggen
56 | - 4: Alles loggen (Standardwert)
57 |
58 |
59 |
60 | Overwrites the default log <path>. The default path is platform and application specific. For this instance, it defaults to "%1". NOTE: The application can override the value internally. Pass an empty string (--logpath "") to disable logging to a file.
61 | Überschreibt den standard Log-<Pfad>. Dieser ist Plattform-abhängig und Anwendungs-spezifisch. Für diese Instanz ist es "%1". HINWEIS: The Anwendung kann diesen Wert intern überschreiben. Übergeben sie einen leeren String (--logpath "") um das Logging in eine Datei komplett zu deaktivieren.
62 |
63 |
64 |
65 | path
66 | Pfad
67 |
68 |
69 |
70 | Sets the log <level> for terminal only messages. This does not include messages forwarded from the master. Log levels are the same as for the <loglevel> option.
71 | Setzt das gewünschte Log-<Level> für Terminal Ausgaben. Die schließt nicht die Ausgaben des Master-Prozesses ein. Die Level sind identisch zu denen der <loglevel> Option.
72 |
73 |
74 |
75 | Will prevent the master process from "closing" the console and other stuff that is done to daemonize the process. Can be useful for debugging purpose.
76 | Sorgt dafür, dass der Master-Prozess seine Konsole nicht "schließt", und auch andere "Daemonisierungen" nicht ausführt. Kann hilfreich zum debuggen sein.
77 |
78 |
79 |
80 | Skips the prompt and accepts where possible.
81 | Überspringt Bestätigungen und akzeptiert sie, wo möglich.
82 |
83 |
84 |
85 | Failed to create local server with error:
86 | Konnte lokalen Server nicht erstellen, mit Fehler:
87 |
88 |
89 |
90 | Unable to start master process. Failed with lock error:
91 | Konnte Master-Prozess nicht starten. Sperrung fehlgeschlagen mit Fehler:
92 |
93 |
94 |
95 | Failed to start master process! No master lock was detected.
96 | Konnte Master-Prozess nicht starten. Keine Sperrdatei des Masters erkannt.
97 |
98 |
99 |
100 | Start commands ignored because master is already running! The terminal will connect with an empty argument list!
101 | Start-Kommando wird irgnoriert da der Master-Prozess bereits läuft. Das Terminal wird sich mit leerer Argument-Liste verbinden!
102 |
103 |
104 |
105 | Master is already running. Start arguments will be passed to it as is
106 | Master-Prozess läuft bereits. Start-Argumente werden an diesen weitergegeben
107 |
108 |
109 |
110 |
111 | Failed to stop the running master process.
112 | Do you want to restart it anyway? (y/N)
113 |
114 | Master-Prozess konnte nicht gestoppt werden.
115 | Trotzdem neustarten? (j/N)
116 |
117 |
118 |
119 | y
120 | j
121 |
122 |
123 |
124 | Y
125 | J
126 |
127 |
128 |
129 | Master process successfully stopped
130 | Master-Prozess erfolgreich gestoppt
131 |
132 |
133 |
134 | Master process is not running! Please launch it by using:
135 | Master-Prozess läuft nicht! Bitte starten Sie diesen mit:
136 |
137 |
138 |
139 | Are you shure you want to purge the master lock and server?
140 | Only do this if the master process is not running anymore, but the lock/server are not available (for example after a crash)
141 | Purging while the master process is still running will crash it.
142 | Press (Y) to purge, or (n) to cancel:
143 | Sind Sie sicher, dass sie den Master-Server und Sperrdatei löschen möchten?
144 | Sie sollten dies nur tun, wenn kein Master-Prozess mehr läuft, und sie aber trotzdem keine neuen starten können (z.B. nach einen Crash)
145 | Eine Säuberung mit laufendem Master wird diesen zum Absturz bringen.
146 | Geben sie (J) ein zum säubern, oder (n) zum abbrechen:
147 |
148 |
149 |
150 | n
151 | n
152 |
153 |
154 |
155 | N
156 | N
157 |
158 |
159 |
160 | Master lockfile successfully removed. It was locked by:
161 | Master-Sperrdatei erfolgreich gelöscht. Gesperrt hat:
162 |
163 |
164 |
165 | Failed to remove master lockfile. Lock data is:
166 | Konnte Master-Sperrdatei nicht entfernen. Sie wird gesperrt von:
167 |
168 |
169 |
170 |
171 | - PID:
172 |
173 | - PID:
174 |
175 |
176 |
177 |
178 | - Hostname:
179 |
180 | - Hostname:
181 |
182 |
183 |
184 |
185 | - Appname:
186 |
187 | - Anwendungsname:
188 |
189 |
190 |
191 | No lock file detected
192 | Keine Sperrdatei gefunden
193 |
194 |
195 |
196 | Master server successfully removed
197 | Master-Server erfolgreich entfernt
198 |
199 |
200 |
201 | Failed to remove master server
202 | Master-Server konnte nicht entfernt werden
203 |
204 |
205 |
206 | Terminal with invalid commands discarded. Error:
207 | Terminal mit ungültigen Parametern wird ignoriert. Fehler:
208 |
209 |
210 |
211 | QtBackgroundProcess::MasterConnecter
212 |
213 |
214 | Connection to Master process failed with error:
215 | Verbindung mit Master-Process fehlgeschlagen. Fehler:
216 |
217 |
218 |
219 | stdin was unexpectedly closed! The terminal will not be able to foreward input to the master anymore!
220 | stdin wurde unerwartet geschlossen! Das Terminal ist nicht mehr in der Lage, Eingaben an den Master weiterzuleiten!
221 |
222 |
223 |
224 | QtBackgroundProcess::TerminalPrivate
225 |
226 |
227 | Terminal closed due to connection error while loading terminal status:
228 | Terminal aufgrund von Fehler während der Verbindung geschlossen. Fehler:
229 |
230 |
231 |
232 | Invalid Terminal status received. Data is corrupted. Terminal will be disconnected
233 | Ungültigen Terminal-Status erhalten. Daten sind corrumpiert. Das Terminal wird nun geschlossen
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/doc/app.dox:
--------------------------------------------------------------------------------
1 | /*!
2 | @class QtBackgroundProcess::App
3 |
4 | Use this class instead of QCoreApplication to create a background process. You will either have
5 | to override the startup/shutdown functions or set them with function objects.
6 |
7 | @sa App::setupParser, App::startupApp, App::requestAppShutdown
8 | */
9 |
10 | /*!
11 | @property QtBackgroundProcess::App::instanceID
12 |
13 | @default{empty, until exec() is called}
14 |
15 | The instance ID is an id that is generated for an application to identify it on runtime. It
16 | is the same for multiple instances of an application run by the same user/session, but unique
17 | for every "application". It is used internally for the singleton instance and to connecte the
18 | terminals to the correct master. Unless you need to run multiple master instances of the same
19 | application, there is typically no need to modify this variable.
20 |
21 | The id will be generated automatically as soon as you call App::exec. If you need the id before
22 | that, you can generate it using App::createDefaultInstanceID.
23 |
24 | @note The instance id is generated by using various information about the application. This
25 | includes properties that can be set in the main, like QCoreApplication::setOrganizationName. In
26 | order to include those, the generation is lazy. If you need to generate it on your own, make
27 | shure to do so after those initializations.
28 |
29 | @accessors{
30 | @readAc{instanceID()}
31 | @writeAc{setInstanceID()}
32 | @resetAc{createDefaultInstanceID()}
33 | }
34 |
35 | @sa App::exec, App::globalInstance
36 | */
37 |
38 | /*!
39 | @property QtBackgroundProcess::App::globalInstance
40 |
41 | @default{`false`}
42 |
43 | By default, the instance is a singleton per user, which means if multiple users are logged in,
44 | there can be multiple instances of the app, but just one per user.
45 |
46 | When set as a global instance, there can only be one instance per machine, for any user. Any user
47 | can start that instance, and once running, all user have access to it. There will always be only
48 | one instance in global mode for the whole system.
49 |
50 | @warning In global mode, ANY user has access to that global instance, and can send commands to
51 | it. You should be very careful when exposing an instance to the whole system, as it can lead to
52 | potential security risks, especially when exposing the instance to the system as root/admin
53 |
54 | @accessors{
55 | @readAc{globalInstance()}
56 | @writeAc{setGlobalInstance()}
57 | }
58 |
59 | @sa App::exec, App::instanceID
60 | */
61 |
62 | /*!
63 | @property QtBackgroundProcess::App::forwardMasterLog
64 |
65 | @default{`false`}
66 |
67 | If enabled, the master process will forward everything it writes to the logfile to all connected
68 | terminals as well. This includes all qDebug, qWarning, etc. kinds of messages. If you want this
69 | to work for all master messages, even before the startup function was called, set this true in
70 | terminal scope, i.e. the main function. This property has no effect on terminals.
71 |
72 | @accessors{
73 | @readAc{forwardMasterLog()}
74 | @writeAc{setForwardMasterLog()}
75 | }
76 |
77 | @sa GlobalTerminal, Terminal, App::startupApp
78 | */
79 |
80 | /*!
81 | @property QtBackgroundProcess::App::autoStartMaster
82 |
83 | @default{`false`}
84 |
85 | By default, you need to explicitly pass `start` as parameter to start the master process. If
86 | you want this to happen automatically, set this property to `true` in terminal scope! Do it
87 | before calling exec() in your main.
88 |
89 | @note If the master is already running, this property has no effect
90 |
91 | @accessors{
92 | @readAc{autoStartMaster()}
93 | @writeAc{setAutoStartMaster()}
94 | }
95 |
96 | @sa App::ignoreMultiStarts, App::exec
97 | */
98 |
99 | /*!
100 | @property QtBackgroundProcess::App::ignoreMultiStarts
101 |
102 | @default{`false`}
103 |
104 | By default, start commands will be passed to the master as is, if it is already running.
105 | If you don't want this to happen, because you treat starts differently, set this property to
106 | true. This will cause terminals that try to call start on a running master to discard **all**
107 | arguments and connect with an empty arguments list.
108 |
109 | @note This property only prevents explicit start calls. It does not interfere with the
110 | App::autoStartMaster property.
111 |
112 | @accessors{
113 | @readAc{ignoreMultiStarts()}
114 | @writeAc{setIgnoreMultiStarts()}
115 | }
116 |
117 | @sa App::autoStartMaster, App::startupApp
118 | */
119 |
120 | /*!
121 | @property QtBackgroundProcess::App::autoDeleteTerminals
122 |
123 | @default{`true`}
124 |
125 | If a terminal gets disconnected, the master can automatically delete it. Each terminal has
126 | it's own property to enable/disable auto deletion. By setting this property, you can set the
127 | default for newly created terminals. Disable it, if you handle terminals by yourself.
128 |
129 | @note To change the Terminal::autoDelete for all already connected terminals, pass `true` as
130 | second parameter to the set accessor
131 |
132 | @accessors{
133 | @readAc{autoDeleteTerminals()}
134 | @writeAc{setAutoDeleteTerminals()}
135 | }
136 |
137 | @sa App::autoKillTerminals, Terminal::autoDelete
138 | */
139 |
140 | /*!
141 | @property QtBackgroundProcess::App::autoKillTerminals
142 |
143 | @default{`false`}
144 |
145 | If you don't want terminals to be able to attach to the master, and only want to use them to
146 | receive new commands, you can enable this property. After receiving the command, the master
147 | automatically disconnects the terminal and deletes it.
148 |
149 | @note Setting this to true will prevent terminals from beeing added to the terminal list, and
150 | will not emit newTerminalConnected() anymore. However, commandReceived() with the command of
151 | the terminal is still beeing used.
152 |
153 | @note To kill all already connected terminals, pass `true` as second parameter to the set
154 | accessor
155 |
156 | @accessors{
157 | @readAc{autoKillTerminals()}
158 | @writeAc{setAutoKillTerminals()}
159 | }
160 |
161 | @sa App::autoDeleteTerminals, App::commandReceived
162 | */
163 |
164 | /*!
165 | @property QtBackgroundProcess::App::connectedTerminals
166 |
167 | @default{`[]`}
168 |
169 | Every time a terminal was connected or disconnect, this property changes. This does **not**
170 | include terminals that connect with the `--detached` option, as well as automatically killed
171 | terminals.
172 |
173 | @accessors{
174 | @readAc{connectedTerminals()}
175 | @notifyAc{connectedTerminalsChanged()}
176 | @notifyAc{newTerminalConnected() (implicit)}
177 | }
178 |
179 | @sa App::autoKillTerminals, App::commandReceived
180 | */
181 |
182 | /*!
183 | @fn QtBackgroundProcess::App::setParserSetupFunction
184 |
185 | @param function The function to be called by App::setupParser
186 |
187 | The handlers parameter are:
188 | - the command line parser to be set up (QCommandLineParser &)
189 |
190 | @note The parsers passed to this function are already set up with the options and arguments
191 | required for the app itself. You can simply add your own ones.
192 |
193 | @sa App::setupParser
194 | */
195 |
196 | /*!
197 | @fn QtBackgroundProcess::App::setStartupFunction
198 |
199 | @param function The function to be called by App::startupApp
200 |
201 | See App::startupApp for the handlers arguments.
202 |
203 | @sa App::startupApp
204 | */
205 |
206 | /*!
207 | @fn QtBackgroundProcess::App::setShutdownRequestFunction(const std::function &)
208 |
209 | @param function The function to be called by App::requestAppShutdown
210 |
211 | The handlers parameter are:
212 | - a command line parser holding the stop arguments (QCommandLineParser)
213 | - a reference to the exit code. Set this code if another value than `EXIT_SUCCESS` should be
214 | returned by the app (int &)
215 | - returns: `true` if the shutdown request should be accepted, `false` if not
216 |
217 | @sa App::requestAppShutdown
218 | */
219 |
220 | /*!
221 | @fn QtBackgroundProcess::App::setShutdownRequestFunction(const std::function &)
222 |
223 | @param function The function to be called by App::requestAppShutdown
224 |
225 | See App::requestAppShutdown for the handlers arguments.
226 |
227 | @sa App::requestAppShutdown
228 | */
229 |
230 | /*!
231 | @fn QtBackgroundProcess::App::exec
232 |
233 | @inherit{QCoreApplication::exec}
234 |
235 | @returns The applications exit code
236 |
237 | A reimplementation of exec, to perform the neccesary steps to start the execution.
238 |
239 | @attention It is very important you call **this** function, and **not** QCoreApplication::exec.
240 | If you don't, the background process won't properly start
241 |
242 | @sa App::startupApp
243 | */
244 |
245 | /*!
246 | @fn QtBackgroundProcess::App::createDefaultInstanceID
247 |
248 | @param overwrite If set to `false` no id will be generated, if one already exists
249 | @throws NotAllowedInRunningStateException Will be thrown if you try to change the instanceID
250 | after the application has already been started
251 |
252 | @sa App::exec
253 | */
254 |
255 | /*!
256 | @fn QtBackgroundProcess::App::setInstanceID
257 |
258 | @param instanceID The instance id or the seed for the id
259 | @param useAsSeed If set to yes, the instanceID will be used as a seed, and not set directly
260 | @throws NotAllowedInRunningStateException Will be thrown if you try to change the instanceID
261 | after the application has already been started
262 |
263 | If useAsSeed is false, whatever you pass as instanceID is used as the new id. If set to true,
264 | the id will be generated as usual, but with instanceID as seed to modify it. This has the
265 | advantage of maintaining the uniqueness of the default instanceID, without beeing limit to a
266 | single one.
267 | */
268 |
269 | /*!
270 | @fn QtBackgroundProcess::App::commandReceived
271 |
272 | @param parser A command line parser, holding the arguments that have been sent to the master
273 | @param isStarter If this arguments are the same that have been passed to App::startupApp, this
274 | parameter will be `true`
275 |
276 | @sa App::newTerminalConnected, Terminal::parser, Terminal::starter
277 | */
278 |
279 | /*!
280 | @fn QtBackgroundProcess::App::setupParser
281 |
282 | @param parser The command line parser to be set up
283 | @param useShortOptions Specify if short options should be enabled
284 |
285 | You can override this function to add additional options and arguments to the apps command line
286 | parser. The default implementation performs a basic setup and calls the handler set by
287 | App::setParserSetupFunction, if available.
288 | setup.
289 |
290 | @attention In order to do the setup required by the app, you will have to call this
291 | implementation in your override! The useShortOptions parameter will always be true. If you don't
292 | want short options, pass a different value to the base implementation
293 |
294 | Sample:
295 | @code{.cpp}
296 | void MyApp::setupParser(...)
297 | {
298 | QtBackgroundProcess::App::setupParser(parser, useShortOptions);// or (parser, false)
299 | parser.addOption(...);
300 | }
301 | @endcode
302 |
303 | @sa App::setParserSetupFunction
304 | */
305 |
306 | /*!
307 | @fn QtBackgroundProcess::App::startupApp
308 |
309 | @param parser a command line parser holding the startup arguments
310 | @returns The start exit code. Pass `EXIT_SUCCESS` to continue execution. Anything else will
311 | induce an error and stop the master with the given code
312 |
313 | You can override this function to add the code that should be run to startup the master
314 | process. The default implementation calls the handler set by App::setStartupFunction, if
315 | available.
316 |
317 | @sa App::setStartupFunction
318 | */
319 |
320 | /*!
321 | @fn QtBackgroundProcess::App::requestAppShutdown
322 |
323 | @param terminal The terminal that requested the shutdown
324 | @param exitCode A reference to the exit code. Set this code if another value than
325 | `EXIT_SUCCESS` should be returned by the app. Ignored if not returning `true`
326 | @returns `true` if the shutdown request should be accepted, `false` if not
327 |
328 | You can override this function to decide whether your application should accept the stop
329 | command and shut down (return `true`) or ignore it (return `false`). The default implementation
330 | calls the handler set by App::setStartupFunction, if available, or simply returns `true`, if
331 | no handler has been set.
332 |
333 | @sa App::setStartupFunction
334 | */
335 |
--------------------------------------------------------------------------------
/tests/auto/backgroundprocess/MasterTest/tst_master.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "processhelper.h"
6 |
7 | class MasterTest : public QObject
8 | {
9 | Q_OBJECT
10 |
11 | private Q_SLOTS:
12 | void initTestCase();
13 | void cleanupTestCase();
14 |
15 | void simpleTest();
16 | void commandsTest();
17 |
18 | void echoTest();
19 | void statusTest();
20 |
21 | void detachingTest();
22 | #ifdef Q_OS_UNIX
23 | void masterTermTest();
24 | #endif
25 | };
26 |
27 | void MasterTest::initTestCase()
28 | {
29 | #ifdef Q_OS_LINUX
30 | Q_ASSERT(qgetenv("LD_PRELOAD").contains("Qt5BackgroundProcess"));
31 | #endif
32 | ProcessHelper::clearLog();
33 | }
34 |
35 | void MasterTest::cleanupTestCase()
36 | {
37 | QProcess::execute(ProcessHelper::binPath(), {QStringLiteral("stop")});
38 | }
39 |
40 | void MasterTest::simpleTest()
41 | {
42 | auto p1 = new ProcessHelper(this);
43 | p1->start({"start", "Test1"}, true);
44 |
45 | auto p2 = new ProcessHelper(this);
46 | p2->start({"Test2"});
47 |
48 | auto p3 = new ProcessHelper(this);
49 | p3->start({"start", "Test", "3"});
50 |
51 | auto p4 = new ProcessHelper(this);
52 | p4->start({"stop", "Test", "4"});
53 |
54 | ProcessHelper::waitForFinished({p1, p2, p3, p4});
55 |
56 | QVERIFYERRLOG(p1);
57 | QVERIFYERRLOG(p2);
58 | #ifdef Q_OS_WIN
59 | QVERIFYERRLOG(p3, "[Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is");
60 | #else
61 | QVERIFYERRLOG(p3, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is");
62 | #endif
63 | QVERIFYERRLOG(p4);
64 | QVERIFYMASTERLOG(
65 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test1\") and options: (\"logpath\", \"loglevel\", \"terminallog\")",
66 | "[% Debug] skipping starter args: (\"start\", \"Test1\") and options: (\"logpath\", \"loglevel\", \"terminallog\")",
67 | "[% Debug] received new command: (\"Test2\") and options: (\"terminallog\")",
68 | "[% Debug] received new command: (\"start\", \"Test\", \"3\") and options: (\"terminallog\")",
69 | "[% Debug] received new command: (\"stop\", \"Test\", \"4\") and options: (\"terminallog\")",
70 | "[% Debug] stop requested with (\"stop\", \"Test\", \"4\") and options: (\"terminallog\")",
71 | "[% Debug] I am quitting!"
72 | );
73 |
74 | QCoreApplication::processEvents();
75 | p1->deleteLater();
76 | p2->deleteLater();
77 | p3->deleteLater();
78 | p4->deleteLater();
79 | }
80 |
81 | void MasterTest::commandsTest()
82 | {
83 | auto p1 = new ProcessHelper(this);
84 | p1->setExitCode(EXIT_FAILURE);
85 | p1->start({"Test1"}, true);
86 |
87 | auto p2 = new ProcessHelper(this);
88 | p2->start({"-a", "-f", "1", "-i", "Test2"}, true);
89 |
90 | auto p3 = new ProcessHelper(this);
91 | p3->start({"start", "-i", "Test", "3"});
92 |
93 | auto p4 = new ProcessHelper(this);
94 | p4->start({"start", "Test", "4"});
95 |
96 | auto p5 = new ProcessHelper(this);
97 | p5->start({"-a", "-i", "Test5"});
98 |
99 | auto p6 = new ProcessHelper(this);
100 | p6->start({"restart", "-f", "1", "-i", "Test6"}, true, 2000);
101 |
102 | auto p7 = new ProcessHelper(this);
103 | p7->start({"-f", "0", "Test", "7"});
104 |
105 | auto p8 = new ProcessHelper(this);
106 | p8->start({"Test", "8"});
107 |
108 | auto p9 = new ProcessHelper(this);
109 | p9->start({"stop", "-f", "1", "Test9"});
110 |
111 | ProcessHelper::waitForFinished({p1, p2, p3, p4, p5, p6, p7, p8, p9});
112 |
113 | #ifdef Q_OS_WIN
114 | QVERIFYERRLOG(p1, "[Critical] QtBackgroundProcess: Master process is not running! Please launch it by using: % start");
115 | #else
116 | QVERIFYERRLOG(p1, "[\x1B[31mCritical\x1B[0m] QtBackgroundProcess: Master process is not running! Please launch it by using: % start");
117 | #endif
118 |
119 | QVERIFYOUTLOG(p2,
120 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
121 | "[% Debug] skipping starter args: (\"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
122 | "[% Debug] received new command: () and options: ()",
123 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")",
124 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")",
125 | "[% Debug] received new command: (\"stop\") and options: ()",
126 | "[% Debug] stop requested with (\"stop\") and options: ()",
127 | "[% Debug] I am quitting!"
128 | );
129 | QVERIFYERRLOG(p2);
130 |
131 | QVERIFYOUTLOG(p3,
132 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")",
133 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")",
134 | "[% Debug] received new command: (\"stop\") and options: ()",
135 | "[% Debug] stop requested with (\"stop\") and options: ()",
136 | "[% Debug] I am quitting!"
137 | );
138 | #ifdef Q_OS_WIN
139 | QVERIFYERRLOG(p3, "[Warning] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!");
140 | #else
141 | QVERIFYERRLOG(p3, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Start commands ignored because master is already running! The terminal will connect with an empty argument list!");
142 | #endif
143 |
144 | QVERIFYOUTLOG(p4,
145 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")",
146 | "[% Debug] received new command: (\"stop\") and options: ()",
147 | "[% Debug] stop requested with (\"stop\") and options: ()",
148 | "[% Debug] I am quitting!"
149 | );
150 | #ifdef Q_OS_WIN
151 | QVERIFYERRLOG(p4, "[Warning] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is");
152 | #else
153 | QVERIFYERRLOG(p4, "[\x1B[33mWarning\x1B[0m] QtBackgroundProcess: Master is already running. Start arguments will be passed to it as is");
154 | #endif
155 |
156 | QVERIFYOUTLOG(p5,
157 | "[% Debug] received new command: (\"stop\") and options: ()",
158 | "[% Debug] stop requested with (\"stop\") and options: ()",
159 | "[% Debug] I am quitting!"
160 | );
161 | QVERIFYERRLOG(p5);
162 |
163 | QVERIFYOUTLOG(p6,
164 | "[% Debug] I am quitting!",
165 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
166 | "[% Debug] skipping starter args: (\"restart\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
167 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
168 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
169 | "[% Debug] I am quitting!"
170 | );
171 | #ifdef Q_OS_WIN
172 | QVERIFYERRLOG(p6, "[Debug] QtBackgroundProcess: Master process successfully stopped");
173 | #else
174 | QVERIFYERRLOG(p6, "[\x1B[32mDebug\x1B[0m] QtBackgroundProcess: Master process successfully stopped");
175 | #endif
176 |
177 | QVERIFYOUTLOG(p7,
178 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
179 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
180 | "[% Debug] I am quitting!"
181 | );
182 | QVERIFYERRLOG(p7);
183 |
184 | QVERIFYOUTLOG(p8,
185 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
186 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
187 | "[% Debug] I am quitting!"
188 | );
189 | QVERIFYERRLOG(p8);
190 |
191 | QVERIFYOUTLOG(p9, "[% Debug] I am quitting!");
192 | QVERIFYERRLOG(p9);
193 |
194 | QVERIFYMASTERLOG(
195 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
196 | "[% Debug] skipping starter args: (\"Test2\") and options: (\"a\", \"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
197 | "[% Debug] received new command: () and options: ()",
198 | "[% Debug] received new command: (\"start\", \"Test\", \"4\") and options: (\"terminallog\")",
199 | "[% Debug] received new command: (\"Test5\") and options: (\"a\", \"i\", \"terminallog\")",
200 | "[% Debug] received new command: (\"stop\") and options: ()",
201 | "[% Debug] stop requested with (\"stop\") and options: ()",
202 | "[% Debug] I am quitting!",
203 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
204 | "[% Debug] skipping starter args: (\"restart\", \"Test6\") and options: (\"f\", \"i\", \"logpath\", \"loglevel\", \"terminallog\")",
205 | "[% Debug] received new command: (\"Test\", \"7\") and options: (\"f\", \"terminallog\")",
206 | "[% Debug] received new command: (\"Test\", \"8\") and options: (\"terminallog\")",
207 | "[% Debug] received new command: (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
208 | "[% Debug] stop requested with (\"stop\", \"Test9\") and options: (\"f\", \"terminallog\")",
209 | "[% Debug] I am quitting!"
210 | );
211 |
212 | QCoreApplication::processEvents();
213 | p1->deleteLater();
214 | p2->deleteLater();
215 | p3->deleteLater();
216 | p4->deleteLater();
217 | p5->deleteLater();
218 | p6->deleteLater();
219 | p7->deleteLater();
220 | p8->deleteLater();
221 | p9->deleteLater();
222 | }
223 |
224 | void MasterTest::echoTest()
225 | {
226 | auto p1 = new ProcessHelper(this);
227 | p1->start({"start", "-m", "echo"}, true);
228 | p1->send("test1");
229 |
230 | auto p2 = new ProcessHelper(this);
231 | p2->start({});
232 |
233 | auto p3 = new ProcessHelper(this);
234 | p3->start({"-f", "1"});
235 | p2->send("test2");
236 | p3->send("test3");
237 |
238 | auto p4 = new ProcessHelper(this);
239 | p4->start({"stop"});
240 |
241 | ProcessHelper::waitForFinished({p1, p2, p3, p4});
242 |
243 | QVERIFYOUTLOG(p1,
244 | "test1",
245 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")",
246 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
247 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
248 | "[% Debug] I am quitting!"
249 | );
250 | QVERIFYERRLOG(p1);
251 | QVERIFYOUTLOG(p2,
252 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")",
253 | "test2",
254 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
255 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
256 | "[% Debug] I am quitting!"
257 | );
258 | QVERIFYERRLOG(p2);
259 | QVERIFYOUTLOG(p3,
260 | "test3",
261 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
262 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
263 | "[% Debug] I am quitting!"
264 | );
265 | QVERIFYERRLOG(p3);
266 | QVERIFYOUTLOG(p4, "[% Debug] I am quitting!");
267 | QVERIFYERRLOG(p4);
268 |
269 | QVERIFYMASTERLOG(
270 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
271 | "[% Debug] Master started in echo mode!",
272 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
273 | "[% Debug] received new command: () and options: (\"terminallog\")",
274 | "[% Debug] received new command: () and options: (\"f\", \"terminallog\")",
275 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
276 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
277 | "[% Debug] I am quitting!"
278 | );
279 |
280 | QCoreApplication::processEvents();
281 | p1->deleteLater();
282 | p2->deleteLater();
283 | p3->deleteLater();
284 | p4->deleteLater();
285 | }
286 |
287 | void MasterTest::statusTest()
288 | {
289 | auto p1 = new ProcessHelper(this);
290 | p1->start({"start", "-m", "status"}, true);
291 |
292 | auto p2 = new ProcessHelper(this);
293 | p2->start({});
294 |
295 | auto p3 = new ProcessHelper(this);
296 | p3->start({"hello", "world"});
297 |
298 | auto p4 = new ProcessHelper(this);
299 | p4->start({"stop", "me"});
300 |
301 | ProcessHelper::waitForFinished({p1, p2, p3, p4});
302 |
303 | QVERIFYOUTLOG(p1,
304 | "[]",
305 | "[hello, world]",
306 | "[stop, me]"
307 | );
308 | QVERIFYERRLOG(p1);
309 | QVERIFYOUTLOG(p2,
310 | "[hello, world]",
311 | "[stop, me]"
312 | );
313 | QVERIFYERRLOG(p2);
314 | QVERIFYOUTLOG(p3, "[stop, me]");
315 | QVERIFYERRLOG(p3);
316 | QVERIFYOUTLOG(p4);
317 | QVERIFYERRLOG(p4);
318 |
319 | QVERIFYMASTERLOG(
320 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
321 | "[% Debug] Master started in status mode!",
322 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
323 | "[% Debug] received new command: () and options: (\"terminallog\")",
324 | "[% Debug] received new command: (\"hello\", \"world\") and options: (\"terminallog\")",
325 | "[% Debug] received new command: (\"stop\", \"me\") and options: (\"terminallog\")",
326 | "[% Debug] stop requested with (\"stop\", \"me\") and options: (\"terminallog\")",
327 | "[% Debug] I am quitting!"
328 | );
329 |
330 | QCoreApplication::processEvents();
331 | p1->deleteLater();
332 | p2->deleteLater();
333 | p3->deleteLater();
334 | p4->deleteLater();
335 | }
336 |
337 | void MasterTest::detachingTest()
338 | {
339 | auto p1 = new ProcessHelper(this);
340 | p1->start({"start", "-f", "1", "--detached"}, true);
341 | p1->waitForFinished();
342 |
343 | auto p2 = new ProcessHelper(this);
344 | p2->start({});
345 | QEXPECT_FAIL("", "Undetached process does not finish", Continue);
346 | p2->waitForFinished(false);
347 |
348 | auto p3 = new ProcessHelper(this);
349 | p3->start({"--detached"});
350 | p3->waitForFinished();
351 |
352 | auto p4 = new ProcessHelper(this);
353 | p4->start({"stop"});
354 |
355 | ProcessHelper::waitForFinished({p2, p4});
356 |
357 | QVERIFYOUTLOG(p1);
358 | QVERIFYERRLOG(p1);
359 | QVERIFYOUTLOG(p2,
360 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")",
361 | "[% Debug] skipping starter args: (\"start\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")",
362 | "[% Debug] received new command: () and options: (\"terminallog\")",
363 | "[% Debug] received new command: () and options: (\"detached\", \"terminallog\")",
364 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
365 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
366 | "[% Debug] I am quitting!"
367 | );
368 | QVERIFYERRLOG(p2);
369 | QVERIFYOUTLOG(p3);
370 | QVERIFYERRLOG(p3);
371 | QVERIFYOUTLOG(p4, "[% Debug] I am quitting!");
372 | QVERIFYERRLOG(p4);
373 |
374 | QVERIFYMASTERLOG(
375 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")",
376 | "[% Debug] skipping starter args: (\"start\") and options: (\"f\", \"detached\", \"logpath\", \"loglevel\", \"terminallog\")",
377 | "[% Debug] received new command: () and options: (\"terminallog\")",
378 | "[% Debug] received new command: () and options: (\"detached\", \"terminallog\")",
379 | "[% Debug] received new command: (\"stop\") and options: (\"terminallog\")",
380 | "[% Debug] stop requested with (\"stop\") and options: (\"terminallog\")",
381 | "[% Debug] I am quitting!"
382 | );
383 |
384 | QCoreApplication::processEvents();
385 | p1->deleteLater();
386 | p2->deleteLater();
387 | p3->deleteLater();
388 | p4->deleteLater();
389 | }
390 |
391 | #ifdef Q_OS_UNIX
392 | void MasterTest::masterTermTest()
393 | {
394 | auto p1 = new ProcessHelper(this);
395 | p1->start({"start", "-m", "pid"}, true);
396 | p1->waitForFinished();
397 |
398 | auto p2 = new ProcessHelper(this);
399 | p2->start({});
400 |
401 | p1->termMaster();
402 | p2->waitForFinished();
403 |
404 | QVERIFYMASTERLOG(
405 | "[% Debug] App Master started with arguments: (\"__qbckgrndprcss$start#master~\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
406 | "[% Debug] Master started in pid mode!",
407 | "[% Debug] skipping starter args: (\"start\") and options: (\"m\", \"logpath\", \"loglevel\", \"terminallog\")",
408 | "[% Debug] pid written",
409 | "[% Debug] received new command: () and options: (\"terminallog\")",
410 | "[% Debug] I am quitting!"
411 | );
412 |
413 | QCoreApplication::processEvents();
414 | p1->deleteLater();
415 | p2->deleteLater();
416 | }
417 | #endif
418 |
419 | QTEST_MAIN(MasterTest)
420 |
421 | #include "tst_master.moc"
422 |
--------------------------------------------------------------------------------
/src/backgroundprocess/app_p.cpp:
--------------------------------------------------------------------------------
1 | #include "app_p.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #ifdef Q_OS_WIN
12 | #include
13 | #else
14 | #include
15 | #include
16 | #endif
17 |
18 | using namespace QtBackgroundProcess;
19 |
20 | //logging category
21 | Q_LOGGING_CATEGORY(QtBackgroundProcess::loggingCategory, "QtBackgroundProcess")
22 |
23 | bool AppPrivate::p_valid = false;
24 |
25 | const QString AppPrivate::masterArgument(QStringLiteral("__qbckgrndprcss$start#master~"));
26 | const QString AppPrivate::purgeArgument(QStringLiteral("purge_master"));
27 | const QString AppPrivate::startArgument(QStringLiteral("start"));
28 | const QString AppPrivate::restartArgument(QStringLiteral("restart"));
29 | #ifdef Q_OS_WIN
30 | const QString AppPrivate::terminalMessageFormat(QStringLiteral("%{if-debug}[Debug] %{endif}"
31 | "%{if-info}[Info] %{endif}"
32 | "%{if-warning}[Warning] %{endif}"
33 | "%{if-critical}[Critical] %{endif}"
34 | "%{if-fatal}[Fatal] %{endif}"
35 | "%{if-category}%{category}: %{endif}"
36 | "%{message}"));
37 | #else
38 | const QString AppPrivate::terminalMessageFormat(QStringLiteral("%{if-debug}[\033[32mDebug\033[0m] %{endif}"
39 | "%{if-info}[\033[36mInfo\033[0m] %{endif}"
40 | "%{if-warning}[\033[33mWarning\033[0m] %{endif}"
41 | "%{if-critical}[\033[31mCritical\033[0m] %{endif}"
42 | "%{if-fatal}[\033[35mFatal\033[0m] %{endif}"
43 | "%{if-category}%{category}: %{endif}"
44 | "%{message}"));
45 | #endif
46 | const QString AppPrivate::masterMessageFormat(QStringLiteral("[%{time} "
47 | "%{if-debug}Debug] %{endif}"
48 | "%{if-info}Info] %{endif}"
49 | "%{if-warning}Warning] %{endif}"
50 | "%{if-critical}Critical] %{endif}"
51 | "%{if-fatal}Fatal] %{endif}"
52 | "%{if-category}%{category}: %{endif}"
53 | "%{message}"));
54 |
55 | AppPrivate *AppPrivate::p_ptr()
56 | {
57 | return qApp->d;
58 | }
59 |
60 | void AppPrivate::qbackProcMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
61 | {
62 | auto fMsg = qFormatLogMessage(type, context, msg);
63 | QByteArray message = fMsg.toUtf8() + "\n";
64 | auto any = false;
65 |
66 | if(p_valid) {
67 | auto self = p_ptr();
68 |
69 | if(self->debugTerm) {
70 | self->debugTerm->write(message);
71 | self->debugTerm->flush();
72 | any = true;
73 | }
74 |
75 | if(self->logFile && self->logFile->isWritable()) {
76 | self->logFile->write(message);
77 | self->logFile->flush();
78 | any = true;
79 | }
80 | }
81 |
82 | if(!any)
83 | std::cerr << fMsg.toStdString() << std::endl;
84 |
85 | if(type == QtMsgType::QtFatalMsg)
86 | std::abort();
87 | }
88 |
89 | AppPrivate::AppPrivate(App *q_ptr) :
90 | QObject(q_ptr),
91 | running(false),
92 | masterLogging(false),
93 | autoStart(false),
94 | ignoreExtraStart(false),
95 | autoDelete(true),
96 | autoKill(false),
97 | instanceId(),
98 | globalInstance(false),
99 | masterLock(nullptr),
100 | masterServer(nullptr),
101 | parserFunc(),
102 | startupFunc(),
103 | shutdownFunc(),
104 | master(nullptr),
105 | debugTerm(nullptr),
106 | logFile(nullptr),
107 | q(q_ptr)
108 | {}
109 |
110 | QString AppPrivate::generateSingleId(const QString &seed)
111 | {
112 | auto fullId = QCoreApplication::applicationName().toLower();
113 | fullId.remove(QRegularExpression(QStringLiteral("[^a-zA-Z0-9_]")));
114 | fullId.truncate(8);
115 | fullId.prepend(QStringLiteral("qtbackproc-"));
116 | QByteArray hashBase = (QCoreApplication::organizationName() +
117 | QCoreApplication::organizationDomain() +
118 | seed).toUtf8();
119 | fullId += QLatin1Char('-') +
120 | QString::number(qChecksum(hashBase.data(), hashBase.size()), 16);
121 |
122 | if(!globalInstance) {
123 | fullId += QLatin1Char('-');
124 | #ifdef Q_OS_WIN
125 | DWORD sessID;
126 | if(::ProcessIdToSessionId(::GetCurrentProcessId(), &sessID))
127 | fullId += QString::number(sessID, 16);
128 | #else
129 | fullId += QString::number(::getuid(), 16);
130 | #endif
131 | }
132 |
133 | return fullId;
134 | }
135 |
136 | void AppPrivate::setInstanceId(const QString &id)
137 | {
138 | if(running)
139 | throw NotAllowedInRunningStateException();
140 |
141 | auto lockDir = QDir::temp();
142 | if(!globalInstance) {
143 | auto dirName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
144 | QDir dir(dirName);
145 | if(!dirName.isEmpty() && dir.exists())
146 | lockDir = dir;
147 | }
148 |
149 | instanceId = id;
150 | auto lockPath = lockDir.absoluteFilePath(id + QStringLiteral(".lock"));
151 | masterLock.reset(new QLockFile(lockPath));
152 | masterLock->setStaleLockTime(0);
153 | }
154 |
155 | QString AppPrivate::socketName() const
156 | {
157 | #ifdef Q_OS_UNIX
158 | QString socket = instanceId + QStringLiteral(".socket");
159 | if(!globalInstance) {
160 | auto dirName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
161 | QDir dir(dirName);
162 | if(!dirName.isEmpty() && dir.exists())
163 | socket = dir.absoluteFilePath(socket);
164 | }
165 | return socket;
166 | #else
167 | return instanceId;
168 | #endif
169 | }
170 |
171 | void AppPrivate::setupDefaultParser(QCommandLineParser &parser, bool useShortOptions)
172 | {
173 | parser.addHelpOption();
174 | if(!QCoreApplication::applicationVersion().isEmpty())
175 | parser.addVersionOption();
176 |
177 | QStringList DParams(QStringLiteral("detached"));
178 | QStringList lParams({QStringLiteral("log"), QStringLiteral("loglevel")});
179 | QStringList LParams(QStringLiteral("logpath"));
180 | if(useShortOptions) {
181 | DParams.prepend(QStringLiteral("D"));
182 | lParams.prepend(QStringLiteral("l"));
183 | LParams.prepend(QStringLiteral("L"));
184 | }
185 |
186 | parser.addPositionalArgument(QStringLiteral(""),
187 | tr("A control command to control the background application. "
188 | "Possible options are:\n"
189 | " - start: starts the application\n"
190 | " - stop: stops the application\n"
191 | " - restart: stops the application and then starts it again with the given arguments\n"
192 | " - purge_master: purges local servers and lockfiles, in case the master process crashed. "
193 | "Pass \"--accept\" as second parameter, if you want to skip the prompt."),
194 | QStringLiteral("[start|stop|purge_master]"));
195 |
196 | parser.addOption({
197 | DParams,
198 | tr("It set, the terminal will only pass it's arguments to the master, and automatically finish after.")
199 | });
200 | parser.addOption({
201 | lParams,
202 | tr("Set the desired log . Possible values are:\n"
203 | " - 0: log nothing\n"
204 | " - 1: critical errors only\n"
205 | " - 2: like 1 plus warnings\n") +
206 | #ifdef QT_NO_DEBUG
207 | tr(" - 3: like 2 plus information messages (default)\n"
208 | " - 4: verbose - log everything"),
209 | tr("level"),
210 | QStringLiteral("3")
211 | #else
212 | tr(" - 3: like 2 plus information messages\n"
213 | " - 4: verbose - log everything (default)"),
214 | tr("level"),
215 | QStringLiteral("4")
216 | #endif
217 | });
218 |
219 | QString defaultPath;
220 | #ifdef Q_OS_UNIX
221 | if(QFileInfo(QStringLiteral("/var/log")).isWritable())
222 | defaultPath = QStringLiteral("/var/log/%1.log").arg(QCoreApplication::applicationName());
223 | else
224 | #endif
225 | {
226 | auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
227 | QDir(basePath).mkpath(QStringLiteral("."));
228 | defaultPath = QStringLiteral("%1/%2.log")
229 | .arg(basePath)
230 | .arg(QCoreApplication::applicationName());
231 | }
232 |
233 | parser.addOption({
234 | LParams,
235 | tr("Overwrites the default log . The default path is platform and application specific. "
236 | "For this instance, it defaults to \"%1\". NOTE: The application can override the value internally. "
237 | "Pass an empty string (--logpath \"\") to disable logging to a file.")
238 | .arg(defaultPath),
239 | tr("path"),
240 | defaultPath
241 | });
242 | parser.addOption({
243 | QStringLiteral("terminallog"),
244 | tr("Sets the log for terminal only messages. This does not include messages forwarded from the master. "
245 | "Log levels are the same as for the option."),
246 | tr("level"),
247 | #ifdef QT_NO_DEBUG
248 | QStringLiteral("3")
249 | #else
250 | QStringLiteral("4")
251 | #endif
252 | });
253 | parser.addOption({
254 | {QStringLiteral("no-daemon"), QStringLiteral("keep-console")},
255 | tr("Will prevent the master process from \"closing\" the console and other stuff that is done to daemonize the process. "
256 | "Can be useful for debugging purpose.")
257 | });
258 |
259 | parser.addOption({
260 | QStringLiteral("accept"),
261 | tr("Skips the prompt and accepts where possible.")
262 | });
263 | }
264 |
265 | void AppPrivate::updateLoggingMode(int level)
266 | {
267 | QString logStr;
268 | QT_WARNING_PUSH
269 | QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough=")
270 | switch (level) {
271 | case 0:
272 | logStr.prepend(QStringLiteral("\n*.critical=false"));
273 | case 1:
274 | logStr.prepend(QStringLiteral("\n*.warning=false"));
275 | case 2:
276 | logStr.prepend(QStringLiteral("\n*.info=false"));
277 | case 3:
278 | logStr.prepend(QStringLiteral("*.debug=false"));
279 | case 4:
280 | break;
281 | default:
282 | return;
283 | }
284 | QT_WARNING_POP
285 | QLoggingCategory::setFilterRules(logStr);
286 | }
287 |
288 | void AppPrivate::updateLoggingPath(const QString &path)
289 | {
290 | if(logFile) {
291 | logFile->close();
292 | logFile->deleteLater();
293 | logFile.clear();
294 | }
295 | if(!path.isEmpty()) {
296 | logFile = new QFile(path, this);
297 | if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
298 | auto error = logFile->errorString();
299 | logFile->deleteLater();
300 | logFile.clear();
301 | qWarning() << "Failed to open logfile with error:" << error;
302 | }
303 | }
304 | }
305 |
306 | int AppPrivate::initControlFlow(const QCommandLineParser &parser)
307 | {
308 | //start/make master
309 | auto args = parser.positionalArguments();
310 | if(args.size() > 0) {
311 | if(args[0] == masterArgument)
312 | return makeMaster(parser);
313 | else if(args[0] == purgeArgument)
314 | return purgeMaster(parser);
315 | else if(args[0] == startArgument)
316 | return startMaster();
317 | else if(args[0] == restartArgument)
318 | return restartMaster(parser);
319 | }
320 |
321 | //neither start nor make master --> "normal" client or autostart
322 | if(autoStart)
323 | return startMaster(true);
324 | else
325 | return commandMaster();
326 | }
327 |
328 | int AppPrivate::makeMaster(const QCommandLineParser &parser)
329 | {
330 | //create local server --> do first to make shure clients only see a "valid" master lock if the master started successfully
331 | masterServer = new QLocalServer(this);
332 | masterServer->setSocketOptions(globalInstance ? QLocalServer::WorldAccessOption : QLocalServer::UserAccessOption);
333 | connect(masterServer, &QLocalServer::newConnection,
334 | this, &AppPrivate::newTerminalConnected,
335 | Qt::QueuedConnection);
336 | if(!masterServer->listen(socketName())) {
337 | qCritical() << tr("Failed to create local server with error:")
338 | << masterServer->errorString();
339 | return EXIT_FAILURE;
340 | }
341 |
342 | //get the lock
343 | if(!masterLock->tryLock(5000)) {//wait at most 5 sec
344 | masterServer->close();
345 | qCritical() << tr("Unable to start master process. Failed with lock error:")
346 | << masterLock->error();
347 | return EXIT_FAILURE;
348 | } else {
349 | //setup master logging stuff
350 | qSetMessagePattern(AppPrivate::masterMessageFormat);
351 | if(masterLogging)
352 | debugTerm = new GlobalTerminal(this, true);
353 | updateLoggingMode(parser.value(QStringLiteral("loglevel")).toInt());
354 | updateLoggingPath(parser.value(QStringLiteral("logpath")));
355 |
356 | //detache from any console, if wished
357 | if(!parser.isSet(QStringLiteral("no-daemon"))) {
358 | #ifdef Q_OS_WIN //detach the console window
359 | if(!FreeConsole()) {
360 | auto console = GetConsoleWindow();
361 | if(console)
362 | ShowWindow(GetConsoleWindow(), SW_HIDE);
363 | }
364 |
365 | auto sigHandler = QCtrlSignalHandler::instance();
366 | sigHandler->setAutoQuitActive(true);
367 |
368 | //set current directory
369 | QDir::setCurrent(QDir::rootPath());
370 | #else
371 | daemon(false, false);
372 |
373 | auto sigHandler = QCtrlSignalHandler::instance();
374 | sigHandler->setAutoQuitActive(true);
375 | sigHandler->registerForSignal(SIGINT);
376 | sigHandler->registerForSignal(SIGHUP);
377 | sigHandler->registerForSignal(SIGWINCH);
378 | #endif
379 | } else
380 | QDir::setCurrent(QDir::rootPath());
381 |
382 | auto res = q->startupApp(parser);
383 | if(res != EXIT_SUCCESS) {
384 | //cleanup
385 | masterServer->close();
386 | masterLock->unlock();
387 | }
388 |
389 | return res;
390 | }
391 | }
392 |
393 | int AppPrivate::startMaster(bool isAutoStart, bool isRestart)
394 | {
395 | auto arguments = QCoreApplication::arguments();
396 | arguments.removeFirst();//remove app name
397 | //check if master already lives
398 | if(masterLock->tryLock()) {//no master alive
399 | auto ok = false;
400 |
401 | auto args = arguments;
402 | if(isRestart)
403 | args.removeOne(restartArgument);
404 | else if(!isAutoStart)
405 | args.removeOne(startArgument);
406 | args.prepend(masterArgument);
407 | if(QProcess::startDetached(QCoreApplication::applicationFilePath(), args)) {//start MASTER with additional start params
408 | //wait for the master to start
409 | masterLock->unlock();
410 | for(auto i = 0; i < 50; i++) {//wait at most 5 sec
411 | qint64 pid;
412 | QString hostname;
413 | QString appname;
414 | if(masterLock->getLockInfo(&pid, &hostname, &appname)) {
415 | ok = true;
416 | break;
417 | } else
418 | QThread::msleep(100);
419 | }
420 | }
421 |
422 | if(ok) {//master started --> start to connect (after safety delay)
423 | QThread::msleep(250);
424 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection,
425 | Q_ARG(QStringList, arguments),//send original arguments
426 | Q_ARG(bool, true));
427 | return EXIT_SUCCESS;
428 | } else {
429 | qCritical() << tr("Failed to start master process! No master lock was detected.");
430 | return EXIT_FAILURE;
431 | }
432 | } else {//master is running --> ok
433 | if(!isAutoStart && ignoreExtraStart) {// ignore only on normal starts, not on auto start
434 | qWarning() << tr("Start commands ignored because master is already running! "
435 | "The terminal will connect with an empty argument list!");
436 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection,
437 | Q_ARG(QStringList, QStringList()),
438 | Q_ARG(bool, false));
439 | return EXIT_SUCCESS;
440 | } else {
441 | if(!isAutoStart)
442 | qWarning() << tr("Master is already running. Start arguments will be passed to it as is");
443 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection,
444 | Q_ARG(QStringList, arguments),//send original arguments
445 | Q_ARG(bool, false));
446 | return EXIT_SUCCESS;
447 | }
448 | }
449 | }
450 |
451 | int AppPrivate::restartMaster(const QCommandLineParser &parser)
452 | {
453 | //step 1 -> stop master, by running another terminal
454 | auto res = QProcess::execute(QCoreApplication::applicationFilePath(), {QStringLiteral("stop")});
455 | if(res != EXIT_SUCCESS && !parser.isSet(QStringLiteral("accept"))) {
456 | std::cout << tr("\nFailed to stop the running master process.\n"
457 | "Do you want to restart it anyway? (y/N)").toStdString();
458 | std::cout.flush();
459 | char res = (char)std::cin.get();
460 | if(res != tr("y") && res != tr("Y"))
461 | return EXIT_FAILURE;
462 | } else
463 | qDebug() << tr("Master process successfully stopped");
464 |
465 | //step 2 -> start master
466 | return startMaster(false, true);
467 | }
468 |
469 | int AppPrivate::commandMaster()
470 | {
471 | auto arguments = QCoreApplication::arguments();
472 | arguments.removeFirst();//remove app name
473 | if(masterLock->tryLock()) {
474 | masterLock->unlock();
475 | qCritical() << tr("Master process is not running! Please launch it by using:")
476 | << QCoreApplication::applicationFilePath() + QStringLiteral(" start");
477 | return EXIT_FAILURE;
478 | } else {
479 | QMetaObject::invokeMethod(this, "beginMasterConnect", Qt::QueuedConnection,
480 | Q_ARG(QStringList, arguments),
481 | Q_ARG(bool, false));
482 | return EXIT_SUCCESS;
483 | }
484 | }
485 |
486 | int AppPrivate::purgeMaster(const QCommandLineParser &parser)
487 | {
488 | if(!parser.isSet(QStringLiteral("accept"))) {
489 | std::cout << tr("Are you shure you want to purge the master lock and server?\n"
490 | "Only do this if the master process is not running anymore, but the lock/server "
491 | "are not available (for example after a crash)\n"
492 | "Purging while the master process is still running will crash it.\n"
493 | "Press (Y) to purge, or (n) to cancel:").toStdString();
494 | std::cout.flush();
495 | char res = (char)std::cin.get();
496 | if(res == tr("n") || res == tr("N"))
497 | return EXIT_FAILURE;
498 | }
499 |
500 | auto res = 0;
501 |
502 | qint64 pid;
503 | QString hostname;
504 | QString appname;
505 | if(masterLock->getLockInfo(&pid, &hostname, &appname)) {
506 | if(masterLock->removeStaleLockFile())
507 | std::cout << tr("Master lockfile successfully removed. It was locked by:").toStdString();
508 | else {
509 | std::cout << tr("Failed to remove master lockfile. Lock data is:").toStdString();
510 | res |= 0x02;
511 | }
512 | std::cout << tr("\n - PID: ").toStdString()
513 | << pid
514 | << tr("\n - Hostname: ").toStdString()
515 | << hostname.toStdString()
516 | << tr("\n - Appname: ").toStdString()
517 | << appname.toStdString()
518 | << std::endl;
519 | } else
520 | std::cout << tr("No lock file detected").toStdString() << std::endl;
521 |
522 | if(QLocalServer::removeServer(socketName()))
523 | std::cout << tr("Master server successfully removed").toStdString() << std::endl;
524 | else {
525 | std::cout << tr("Failed to remove master server").toStdString() << std::endl;
526 | res |= 0x04;
527 | }
528 |
529 | return res == 0 ? -1 : res;
530 | }
531 |
532 | void AppPrivate::newTerminalConnected()
533 | {
534 | while(masterServer->hasPendingConnections()) {
535 | auto termp = new TerminalPrivate(masterServer->nextPendingConnection(), this);
536 | connect(termp, &TerminalPrivate::statusLoadComplete,
537 | this, &AppPrivate::terminalLoaded);
538 | }
539 | }
540 |
541 | void AppPrivate::terminalLoaded(TerminalPrivate *terminal, bool success)
542 | {
543 | if(success) {
544 | //create terminal parser and validate it
545 | terminal->parser.reset(new QCommandLineParser());
546 | qApp->setupParser(*terminal->parser.data());
547 | if(!terminal->loadParser()) {
548 | qWarning() << tr("Terminal with invalid commands discarded. Error:")
549 | << terminal->parser->errorText();
550 | terminal->deleteLater();
551 | return;
552 | }
553 |
554 | //handle own arguments (logging)
555 | if(terminal->parser->isSet(QStringLiteral("loglevel")))
556 | updateLoggingMode(terminal->parser->value(QStringLiteral("loglevel")).toInt());
557 | if(terminal->parser->isSet(QStringLiteral("logpath")))
558 | updateLoggingPath(terminal->parser->value(QStringLiteral("logpath")));
559 |
560 | //create real terminal
561 | auto rTerm = new Terminal(terminal, this);
562 | rTerm->setAutoDelete(autoDelete);
563 | //emit the command
564 | emit q->commandReceived(rTerm->parser(), rTerm->isStarter(), App::QPrivateSignal());
565 |
566 | //test if stop command
567 | auto args = rTerm->parser()->positionalArguments();
568 | if(!args.isEmpty() && args.first() == QStringLiteral("stop"))
569 | stopMaster(rTerm);
570 |
571 | if(autoKill || rTerm->parser()->isSet(QStringLiteral("detached"))) {
572 | rTerm->setAutoDelete(true);
573 | rTerm->disconnectTerminal();
574 | } else {
575 | //add terminal to terminal list
576 | connect(rTerm, &Terminal::destroyed, this, [=](){
577 | activeTerminals.removeOne(rTerm);
578 | emit q->connectedTerminalsChanged(activeTerminals, App::QPrivateSignal());
579 | });
580 | activeTerminals.append(rTerm);
581 | emit q->connectedTerminalsChanged(activeTerminals, App::QPrivateSignal());
582 | //new terminal signal
583 | emit q->newTerminalConnected(rTerm, App::QPrivateSignal());
584 | }
585 | } else
586 | terminal->deleteLater();
587 | }
588 |
589 | void AppPrivate::stopMaster(Terminal *term)
590 | {
591 | int eCode = EXIT_SUCCESS;
592 | if(q->requestAppShutdown(term, eCode)) {
593 | foreach(auto termin, activeTerminals)
594 | termin->flush();
595 | QMetaObject::invokeMethod(this, "doExit", Qt::QueuedConnection,
596 | Q_ARG(int, eCode));
597 | }
598 | }
599 |
600 | void AppPrivate::doExit(int code)
601 | {
602 | QCoreApplication::exit(code);
603 | }
604 |
605 | void AppPrivate::beginMasterConnect(const QStringList &arguments, bool isStarter)
606 | {
607 | master = new MasterConnecter(socketName(), arguments, isStarter, this);
608 | }
609 |
--------------------------------------------------------------------------------