js8call/JS8_Main/MessageServer.cpp
2026-01-18 19:19:15 -06:00

218 lines
5.2 KiB
C++

/**
* @file MessageServer.cpp
* @brief Implementation of the external client message server
*/
#include "MessageServer.h"
#include <QLoggingCategory>
#include <stdexcept>
Q_DECLARE_LOGGING_CATEGORY(messageserver_js8)
MessageServer::MessageServer(QObject *parent) : QTcpServer(parent) {}
MessageServer::~MessageServer() { stop(); }
bool MessageServer::start() {
if (isListening()) {
qCDebug(messageserver_js8)
<< "MessageServer already listening:" << m_host << m_port;
return false;
}
auto address = QHostAddress();
if (m_host.isEmpty() || !address.setAddress(m_host)) {
qCDebug(messageserver_js8)
<< "MessageServer address invalid:" << m_host << m_port;
return false;
}
if (m_port <= 0) {
qCDebug(messageserver_js8)
<< "MessageServer port invalid:" << m_host << m_port;
return false;
}
bool listening = listen(address, m_port);
qCDebug(messageserver_js8)
<< "MessageServer listening:" << listening << m_host << m_port;
return listening;
}
void MessageServer::stop() {
// disconnect all clients
foreach (auto client, m_clients) {
client->close();
}
// then close the server
close();
}
void MessageServer::setServer(QString host, quint16 port) {
bool listening = isListening();
if (listening && (m_host != host || m_port != port)) {
stop();
}
m_host = host;
m_port = port;
if (listening) {
start();
}
}
void MessageServer::setPause(bool paused) {
m_paused = paused;
if (paused) {
pauseAccepting();
} else {
resumeAccepting();
}
}
void MessageServer::setMaxConnections(int n) {
// set the maximum number of connections allowed
m_maxConnections = n;
// then, prune old ones greater than the max (fifo)
pruneConnections();
}
int MessageServer::activeConnections() {
int i = 0;
foreach (auto client, m_clients) {
if (client->isConnected())
i++;
}
return i;
}
void MessageServer::pruneConnections() {
// keep only the n most recent connections (fifo)
if (m_maxConnections && m_maxConnections < activeConnections()) {
for (int i = m_maxConnections; i < activeConnections(); i++) {
auto client = m_clients.first();
client->close();
m_clients.removeFirst();
}
}
}
void MessageServer::send(const Message &message) {
foreach (auto client, m_clients) {
if (!client->awaitingResponse(message.id())) {
continue;
}
client->send(message);
}
}
void MessageServer::incomingConnection(qintptr handle) {
qCDebug(messageserver_js8) << "MessageServer incomingConnection" << handle;
auto client = new Client(this, this);
client->setSocket(handle);
#if JS8_MESSAGESERVER_IS_SINGLE_CLIENT
while (!m_clients.isEmpty()) {
auto client = m_clients.first();
client->close();
m_clients.removeFirst();
}
#endif
if (m_maxConnections && m_maxConnections <= activeConnections()) {
qCDebug(messageserver_js8)
<< "MessageServer connections full, dropping incoming connection";
client->send(Message("API.ERROR", "Connections Full"));
client->close();
return;
}
m_clients.append(client);
}
Client::Client(MessageServer *server, QObject *parent)
: QObject(parent), m_server{server} {
setConnected(true);
}
void Client::setSocket(qintptr handle) {
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::disconnected, this, &Client::onDisconnected);
connect(m_socket, &QTcpSocket::readyRead, this, &Client::readyRead);
m_socket->setSocketDescriptor(handle);
}
void Client::setConnected(bool connected) { m_connected = connected; }
void Client::close() {
if (!m_socket) {
return;
}
m_socket->close();
m_socket = nullptr;
}
void Client::send(const Message &message) {
if (!isConnected()) {
return;
}
if (!m_socket) {
return;
}
if (!m_socket->isOpen()) {
qCDebug(messageserver_js8) << "client socket isn't open";
return;
}
qCDebug(messageserver_js8) << "client writing" << message.toJson();
m_socket->write(message.toJson());
m_socket->write("\n");
m_socket->flush();
// remove if needed
if (m_requests.contains(message.id())) {
m_requests.remove(message.id());
}
}
void Client::onDisconnected() {
qCDebug(messageserver_js8) << "MessageServer client disconnected";
setConnected(false);
}
void Client::readyRead() {
qCDebug(messageserver_js8) << "MessageServer client readyRead";
while (m_socket->canReadLine()) {
auto const msg = m_socket->readLine().trimmed();
qCDebug(messageserver_js8)
<< "-> Client" << m_socket->socketDescriptor() << msg;
if (msg.isEmpty())
return;
try {
auto m = Message::fromJson(msg);
m_requests[m.ensureId()] = m;
emit m_server->message(m);
} catch (std::exception const &e) {
send({"API.ERROR", e.what()});
return;
}
}
}
Q_LOGGING_CATEGORY(messageserver_js8, "messageserver.js8", QtWarningMsg)