refactor: Move daemon process stop command to new Qt IPC

This commit is contained in:
Nick Bolton
2025-02-10 11:22:04 +00:00
parent 7c672b06d8
commit 5980fb741b
8 changed files with 150 additions and 61 deletions

View File

@ -55,6 +55,8 @@ int main(int argc, char **argv)
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
// Thread must be heap-allocated for deferred deletion on thread exit.
// Avoid setting Qt ownership to prevent premature deletion (thread may run longer than Qt loop).
auto *pDaemonThread = new QThread(); // NOSONAR auto *pDaemonThread = new QThread(); // NOSONAR
pDaemon->moveToThread(pDaemonThread); pDaemon->moveToThread(pDaemonThread);
@ -78,9 +80,14 @@ int main(int argc, char **argv)
Qt::DirectConnection Qt::DirectConnection
); );
QObject::connect( QObject::connect(
ipcServer, &ipc::DaemonIpcServer::restartRequested, pDaemon, &DaemonApp::restartCoreProcess, // ipcServer, &ipc::DaemonIpcServer::startProcessRequested, pDaemon, &DaemonApp::applyWatchdogCommand, //
Qt::DirectConnection Qt::DirectConnection
); );
QObject::connect(
ipcServer, &ipc::DaemonIpcServer::stopProcessRequested, pDaemon, &DaemonApp::clearWatchdogCommand, //
Qt::DirectConnection
);
pDaemonThread->start(); pDaemonThread->start();
return QCoreApplication::exec(); return QCoreApplication::exec();
} }

View File

@ -159,14 +159,25 @@ void DaemonApp::setCommand(const QString &command)
} }
} }
void DaemonApp::restartCoreProcess() void DaemonApp::applyWatchdogCommand() const
{ {
LOG_DEBUG("service restart requested"); LOG_DEBUG("applying watchdog command");
#if SYSAPI_WIN32 #if SYSAPI_WIN32
m_watchdog->setCommand(m_command, m_elevate); m_watchdog->setCommand(m_command, m_elevate);
#else #else
LOG_ERR("restart not implemented on this platform"); LOG_ERR("applying watchdog command not implemented on this platform");
#endif
}
void DaemonApp::clearWatchdogCommand()
{
LOG_DEBUG("clearing watchdog command");
#if SYSAPI_WIN32
m_watchdog->setCommand("", false);
#else
LOG_ERR("clearing watchdog command not implemented on this platform");
#endif #endif
} }

View File

@ -54,6 +54,8 @@ public:
void saveLogLevel(const QString &logLevel) const; void saveLogLevel(const QString &logLevel) const;
void setElevate(bool elevate); void setElevate(bool elevate);
void setCommand(const QString &command); void setCommand(const QString &command);
void applyWatchdogCommand() const;
void clearWatchdogCommand();
static DaemonApp &instance() static DaemonApp &instance()
{ {

View File

@ -14,13 +14,18 @@
namespace deskflow::core::ipc { namespace deskflow::core::ipc {
DaemonIpcServer::DaemonIpcServer(QObject *parent) : QObject(parent), m_server{new QLocalServer(this)} const auto kAckMessage = "ok\n";
const auto kErrorMessage = "error\n";
DaemonIpcServer::DaemonIpcServer(QObject *parent)
: QObject(parent), //
m_server{new QLocalServer(this)} // NOSONAR
{ {
// Daemon runs as system, but GUI runs as regular user, so we need to allow world access. // Daemon runs as system, but GUI runs as regular user, so we need to allow world access.
m_server->setSocketOptions(QLocalServer::WorldAccessOption); m_server->setSocketOptions(QLocalServer::WorldAccessOption);
connect(m_server, &QLocalServer::newConnection, this, &DaemonIpcServer::handleNewConnection); connect(m_server, &QLocalServer::newConnection, this, &DaemonIpcServer::handleNewConnection);
m_server->removeServer(kDaemonIpcName); QLocalServer::removeServer(kDaemonIpcName);
if (m_server->listen(kDaemonIpcName)) { if (m_server->listen(kDaemonIpcName)) {
LOG_DEBUG("ipc server listening on: %s", kDaemonIpcName); LOG_DEBUG("ipc server listening on: %s", kDaemonIpcName);
} else { } else {
@ -68,7 +73,7 @@ void DaemonIpcServer::handleReadyRead()
// each message is delimited by a newline to keep the protocol super simple. // each message is delimited by a newline to keep the protocol super simple.
while (data.contains('\n')) { while (data.contains('\n')) {
int index = data.indexOf('\n'); const auto index = data.indexOf('\n');
QByteArray messageData = data.left(index); QByteArray messageData = data.left(index);
data.remove(0, index + 1); data.remove(0, index + 1);
QString message = QString::fromUtf8(messageData); QString message = QString::fromUtf8(messageData);
@ -94,53 +99,101 @@ void DaemonIpcServer::handleErrorOccurred()
void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &message) void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &message)
{ {
const auto kAckMessage = "ok\n";
const auto kErrorMessage = "error\n";
LOG_DEBUG("ipc server got message: %s", message.toUtf8().constData()); LOG_DEBUG("ipc server got message: %s", message.toUtf8().constData());
if (message == "hello") { const auto parts = message.split('=');
if (parts.size() < 1) {
LOG_ERR("ipc server got invalid message: %s", message.toUtf8().constData());
clientSocket->write(kErrorMessage);
return;
}
const auto &command = parts[0];
if (command == "hello") { // NOSONAR
clientSocket->write("hello\n"); clientSocket->write("hello\n");
} else if (message == "noop") { } else if (command == "noop") {
clientSocket->write(kAckMessage); clientSocket->write(kAckMessage);
} else if (message.startsWith("logLevel=")) { } else if (command == "logLevel") {
const auto logLevel = message.split('=')[1]; processLogLevel(clientSocket, parts);
if (logLevel.isEmpty()) { } else if (command == "elevate") {
LOG_ERR("ipc server got empty log level"); processElevate(clientSocket, parts);
clientSocket->write(kErrorMessage); } else if (command == "command") {
} else { processCommand(clientSocket, parts);
LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData()); } else if (command == "start") {
Q_EMIT logLevelChanged(logLevel); LOG_DEBUG("ipc server got start message");
clientSocket->write(kAckMessage); Q_EMIT startProcessRequested();
}
} else if (message.startsWith("elevate=")) {
const auto elevateMode = message.split('=')[1];
if (elevateMode != "yes" && elevateMode != "no") {
LOG_ERR("ipc server got invalid elevate value: %s", elevateMode.toUtf8().constData());
clientSocket->write(kErrorMessage);
return;
} else {
LOG_DEBUG("ipc server got new elevate value: %s", elevateMode.toUtf8().constData());
Q_EMIT elevateModeChanged(elevateMode == "yes");
clientSocket->write(kAckMessage);
}
} else if (message.startsWith("command=")) {
const auto command = message.split('=')[1];
if (command.isEmpty()) {
LOG_ERR("ipc server got empty command");
clientSocket->write(kErrorMessage);
} else {
LOG_DEBUG("ipc server got new command: %s", command.toUtf8().constData());
Q_EMIT commandChanged(command);
clientSocket->write(kAckMessage);
}
} else if (message == "restart") {
LOG_DEBUG("ipc server got restart message");
Q_EMIT restartRequested();
clientSocket->write(kAckMessage); clientSocket->write(kAckMessage);
} else if (command == "stop") {
LOG_DEBUG("ipc server got stop message");
Q_EMIT stopProcessRequested();
clientSocket->write(kAckMessage);
} else if (command == "logPath") {
LOG_DEBUG("ipc server got log path request");
// TODO: send log path
} else { } else {
LOG_WARN("ipc server got unknown message: %s", message.toUtf8().constData()); LOG_WARN("ipc server got unknown message: %s", message.toUtf8().constData());
} }
clientSocket->flush(); clientSocket->flush();
} }
void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts)
{
if (messageParts.size() < 2) {
LOG_ERR("ipc server got invalid log level message");
clientSocket->write(kErrorMessage);
return;
}
const auto &logLevel = messageParts[1];
if (logLevel.isEmpty()) {
LOG_ERR("ipc server got empty log level");
clientSocket->write(kErrorMessage);
return;
}
LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData());
Q_EMIT logLevelChanged(logLevel);
clientSocket->write(kAckMessage);
}
void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts)
{
if (messageParts.size() < 2) {
LOG_ERR("ipc server got invalid elevate message");
clientSocket->write(kErrorMessage);
return;
}
const auto &elevate = messageParts[1];
if (elevate != "yes" && elevate != "no") {
LOG_ERR("ipc server got invalid elevate value: %s", elevate.toUtf8().constData());
clientSocket->write(kErrorMessage);
return;
}
LOG_DEBUG("ipc server got new elevate value: %s", elevate.toUtf8().constData());
Q_EMIT elevateModeChanged(elevate == "yes");
clientSocket->write(kAckMessage);
}
void DaemonIpcServer::processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts)
{
if (messageParts.size() < 2) {
LOG_ERR("ipc server got invalid command message");
clientSocket->write(kErrorMessage);
return;
}
const auto &command = messageParts[1];
if (command.isEmpty()) {
LOG_ERR("ipc server got empty command");
clientSocket->write(kErrorMessage);
return;
}
LOG_DEBUG("ipc server got new command: %s", command.toUtf8().constData());
Q_EMIT commandChanged(command);
clientSocket->write(kAckMessage);
}
} // namespace deskflow::core::ipc } // namespace deskflow::core::ipc

View File

@ -19,17 +19,21 @@ class DaemonIpcServer : public QObject
Q_OBJECT Q_OBJECT
public: public:
DaemonIpcServer(QObject *parent); explicit DaemonIpcServer(QObject *parent);
~DaemonIpcServer(); ~DaemonIpcServer() override;
signals: signals:
void logLevelChanged(const QString &logLevel); void logLevelChanged(const QString &logLevel);
void elevateModeChanged(bool elevate); void elevateModeChanged(bool elevate);
void commandChanged(const QString &command); void commandChanged(const QString &command);
void restartRequested(); void startProcessRequested();
void stopProcessRequested();
private: private:
void processMessage(QLocalSocket *clientSocket, const QString &message); void processMessage(QLocalSocket *clientSocket, const QString &message);
void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts);
void processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts);
void processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts);
private slots: private slots:
void handleNewConnection(); void handleNewConnection();

View File

@ -291,8 +291,8 @@ void CoreProcess::startProcessFromDaemon(const QString &app, const QStringList &
qInfo("running command: %s", qPrintable(commandQuoted)); qInfo("running command: %s", qPrintable(commandQuoted));
if (!m_daemonIpcClient->sendCommand(commandQuoted, m_appConfig.elevateMode())) { if (!m_daemonIpcClient->sendStartProcess(commandQuoted, m_appConfig.elevateMode())) {
qCritical("aborting process start, ipc connect failed"); qCritical("cannot start process, ipc command failed");
return; return;
} }
@ -325,12 +325,11 @@ void CoreProcess::stopProcessFromDaemon()
qFatal("core process must be in stopping state"); qFatal("core process must be in stopping state");
} }
if (!m_pDeps->ipcClient().isConnected()) { if (!m_daemonIpcClient->sendStopProcess()) {
qDebug("cannot stop process, ipc not connected"); qCritical("cannot stop process, ipc command failed");
return; return;
} }
m_pDeps->ipcClient().sendCommand("", m_appConfig.elevateMode());
setProcessState(ProcessState::Stopped); setProcessState(ProcessState::Stopped);
} }

View File

@ -17,7 +17,9 @@ namespace deskflow::gui::ipc {
const auto kTimeout = 1000; const auto kTimeout = 1000;
DaemonIpcClient::DaemonIpcClient(QObject *parent) : QObject(parent), m_socket{new QLocalSocket(this)} DaemonIpcClient::DaemonIpcClient(QObject *parent)
: QObject(parent), //
m_socket{new QLocalSocket(this)} // NOSONAR
{ {
} }
@ -81,15 +83,25 @@ bool DaemonIpcClient::sendLogLevel(const QString &logLevel)
return true; return true;
} }
bool DaemonIpcClient::sendCommand(const QString &command, ElevateMode elevateMode) bool DaemonIpcClient::sendStartProcess(const QString &command, ElevateMode elevateMode)
{ {
if (!keepAlive()) if (!keepAlive())
return false; return false;
sendMessage("elevate=" + (elevateMode == ElevateMode::kAlways ? QStringLiteral("yes") : QStringLiteral("no"))); if (!sendMessage("elevate=" + (elevateMode == ElevateMode::kAlways ? QStringLiteral("yes") : QStringLiteral("no")))) {
sendMessage("command=" + command); return false;
sendMessage("restart"); }
return true;
if (!sendMessage("command=" + command)) {
return false;
}
return sendMessage("start");
}
bool DaemonIpcClient::sendStopProcess()
{
return sendMessage("stop");
} }
bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected) bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected)

View File

@ -22,7 +22,8 @@ public:
explicit DaemonIpcClient(QObject *parent = nullptr); explicit DaemonIpcClient(QObject *parent = nullptr);
bool connectToServer(); bool connectToServer();
bool sendLogLevel(const QString &logLevel); bool sendLogLevel(const QString &logLevel);
bool sendCommand(const QString &command, ElevateMode elevateMode); bool sendStartProcess(const QString &command, ElevateMode elevateMode);
bool sendStopProcess();
bool isConnected() const bool isConnected() const
{ {