1 #include "instance.h"
2 
3 #include <QEventLoop>
4 #include <QTimer>
5 #include <QJsonDocument>
6 #include <QJsonObject>
7 #include <QJsonArray>
8 #include <QApplication>
9 
10 namespace IPC {
11   static const auto STATUS_LINE_RESPONSE_OK = "HTTP/1.1 257 R U OK\r\n\r\n";
12   static const auto STATUS_LINE_REQUEST = "POST / HTTP/1.1\r\n\r\n";
13 
Instance(int prt,int timeo,QObject * parent)14   Instance::Instance(int prt, int timeo, QObject *parent) : QObject(parent), timeout_ms(timeo), port(prt) {
15     connect(&server, &QTcpServer::newConnection, this, &Instance::on_server_connection);
16   }
17 
anotherPid() const18   int Instance::anotherPid() const {
19     return send(QVariantMap());
20   }
21 
send(const QVariantMap & data) const22   int Instance::send(const QVariantMap &data) const {
23     QTimer timer;
24     timer.setSingleShot(true);
25     timer.setInterval(timeout_ms);
26 
27     QTcpSocket socket;
28 
29     int pid = -1;
30 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
31     auto conn_error = connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::errorOccurred), [&](QAbstractSocket::SocketError code) {
32 #else
33     auto conn_error = connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), [&](QAbstractSocket::SocketError code) {
34 #endif
35       Q_UNUSED(code);
36       pid = -1;
37     });
38 
39     socket.connectToHost(QHostAddress::LocalHost, port);
40     if (socket.waitForConnected(timeout_ms)) {
41       if (socket.state() == QTcpSocket::ConnectedState) {
42         socket.write(STATUS_LINE_REQUEST);
43         socket.write("\r\n");
44         socket.write(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
45         socket.waitForBytesWritten(timeout_ms);
46         if (socket.waitForReadyRead(timeout_ms)) {
47           auto recvd_data = QString::fromUtf8(socket.readAll());
48           if (recvd_data.startsWith(STATUS_LINE_RESPONSE_OK)) {
49             auto body = QJsonDocument::fromJson(recvd_data.split("\r\n").last().toUtf8());
50             if (body.isObject()) {
51               pid = body.object()["pid"].toInt();
52             }
53           }
54         }
55       }
56     }
57     socket.close();
58     disconnect(conn_error);
59 
60     return pid;
61   }
62 
63   bool Instance::start() {
64     if (!server.listen(QHostAddress::LocalHost, port)) {
65       qWarning() << "error starting tcp server instance" << server.errorString();
66       return false;
67     }
68     qDebug() << "first instance started, listen tcp port" << port << "pid" << qApp->applicationPid();
69     return true;
70   }
71 
72   bool Instance::load_files_send(const QStringList &list) {
73     QVariantMap map;
74     map["load_files"] = list;
75     return send(map) > 0;
76   }
77 
78   QByteArray Instance::process_received(const QByteArray &request) {
79     QByteArray response(STATUS_LINE_RESPONSE_OK);
80     response.append("\r\n");
81 
82     auto body = QString::fromStdString(request.toStdString()).split("\r\n").last();
83     auto json_body = QJsonDocument::fromJson(body.toUtf8());
84     if (json_body.isObject()) {
85       if (json_body.object()["load_files"].isArray()) {
86         QStringList lst;
87         for (auto i : json_body.object()["load_files"].toArray()) {
88           lst.append(i.toString());
89         }
90         emit load_files_received(lst);
91       }
92     }
93 
94     QVariantMap json_body_response;
95     json_body_response["pid"] = qApp->applicationPid();
96     response.append(QJsonDocument::fromVariant(json_body_response).toJson(QJsonDocument::Compact));
97 
98     return response;
99   }
100 
101   QUrl Instance::url() const {
102     auto i = QString("http://%1:%2").arg(QHostAddress(QHostAddress::LocalHost).toString()).arg(port);
103     return QUrl(i);
104   }
105 
106   void Instance::on_server_connection() {
107     QTcpSocket *socket = server.nextPendingConnection();
108     QTimer timer;
109     QEventLoop loop;
110 
111     timer.setSingleShot(true);
112     timer.setInterval(timeout_ms);
113     timer.start();
114     auto conn_read = connect(socket, &QTcpSocket::readyRead, [&]() {
115       socket->write(process_received(socket->readAll()));
116       socket->waitForBytesWritten(timeout_ms);
117       socket->flush();
118       socket->disconnectFromHost();
119       loop.quit();
120     });
121     auto conn_timer = connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
122 
123     loop.exec();
124 
125     timer.stop();
126     disconnect(conn_read);
127     disconnect(conn_timer);
128 
129     socket->close();
130     socket->deleteLater();
131   }
132 }
133