From 61a7a68ee0082394ff262c3b24ad16cd7816dd78 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 30 Mar 2014 01:14:39 +0100 Subject: [PATCH] added JSONRPC.Version and JSONRPC.SetNotificationStatus --- server/jsonrpc/jsonrpcserver.cpp | 113 ++++++++++++++++++++++++------- server/jsonrpc/jsonrpcserver.h | 22 ++++-- server/jsonrpc/jsontypes.cpp | 1 - server/jsonrpc/jsontypes.h | 1 - server/tcpserver.cpp | 33 +++++---- server/tcpserver.h | 15 ++-- tests/auto/mocktcpserver.cpp | 6 +- tests/auto/mocktcpserver.h | 8 +-- tests/auto/testjsonrpc.cpp | 78 +++++++++++++++------ tests/scripts/geteventtypes.sh | 2 +- tests/scripts/getversion.sh | 7 ++ 11 files changed, 205 insertions(+), 81 deletions(-) create mode 100755 tests/scripts/getversion.sh diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp index e9f0404f..bf2a2996 100644 --- a/server/jsonrpc/jsonrpcserver.cpp +++ b/server/jsonrpc/jsonrpcserver.cpp @@ -24,23 +24,86 @@ #include JsonRPCServer::JsonRPCServer(QObject *parent): - QObject(parent), + JsonHandler(parent), #ifdef TESTING_ENABLED m_tcpServer(new MockTcpServer(this)) #else m_tcpServer(new TcpServer(this)) #endif { - connect(m_tcpServer, SIGNAL(jsonDataAvailable(int,QByteArray)), this, SLOT(processData(int,QByteArray))); + // First, define our own JSONRPC methods + QVariantMap returns; + QVariantMap params; + + params.clear(); returns.clear(); + setDescription("Introspect", "Introspect this API."); + setParams("Introspect", params); + returns.insert("methods", "object"); + returns.insert("types", "object"); + setReturns("Introspect", returns); + + params.clear(); returns.clear(); + setDescription("Version", "Version of this Guh/JSONRPC interface."); + setParams("Version", params); + returns.insert("version", "string"); + setReturns("Version", returns); + + params.clear(); returns.clear(); + setDescription("SetNotificationStatus", "Enable/Disable notifications for this connections."); + setParams("SetNotificationStatus", params); + returns.insert("status", "string"); +// returns.insert("enabled", "bool"); + setReturns("SetNotificationStatus", returns); + + // Now set up the logic + connect(m_tcpServer, SIGNAL(clientConnected(const QUuid &)), this, SLOT(clientConnected(const QUuid &))); + connect(m_tcpServer, SIGNAL(clientDisconnected(const QUuid &)), this, SLOT(clientDisconnected(const QUuid &))); + connect(m_tcpServer, SIGNAL(dataAvailable(const QUuid &, QByteArray)), this, SLOT(processData(const QUuid &, QByteArray))); m_tcpServer->startServer(); - + registerHandler(this); registerHandler(new DeviceHandler(this)); registerHandler(new ActionHandler(this)); registerHandler(new RulesHandler(this)); } -void JsonRPCServer::processData(int clientId, const QByteArray &jsonData) +QString JsonRPCServer::name() const +{ + return QStringLiteral("JSONRPC"); +} + +QVariantMap JsonRPCServer::Introspect(const QVariantMap ¶ms) const +{ + QVariantMap data; + data.insert("types", JsonTypes::allTypes()); + QVariantMap methods; + foreach (JsonHandler *handler, m_handlers) { +// qDebug() << "got handler" << handler->name() << handler->introspect(); + methods.unite(handler->introspect()); + } + data.insert("methods", methods); + return data; +} + +QVariantMap JsonRPCServer::Version(const QVariantMap ¶ms) const +{ + QVariantMap data; + data.insert("version", "0.0.0"); + return data; +} + +QVariantMap JsonRPCServer::SetNotificationStatus(const QVariantMap ¶ms) +{ + QUuid clientId = this->property("clientId").toUuid(); +// qDebug() << "got client socket" << clientId; + m_clients[clientId] = params.value("enabled").toBool(); + QVariantMap returns; + returns.insert("status", "success"); + returns.insert("enabled", m_clients[clientId]); + return returns; +} + +void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonData) { QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); @@ -48,7 +111,7 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData) if(error.error != QJsonParseError::NoError) { qDebug() << "failed to parse data" << jsonData << ":" << error.errorString(); } - qDebug() << "-------------------------\n" << jsonDoc.toJson(); +// qDebug() << "-------------------------\n" << jsonDoc.toJson(); QVariantMap message = jsonDoc.toVariant().toMap(); @@ -72,21 +135,6 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData) qDebug() << "got:" << targetNamespace << method << params; emit commandReceived(targetNamespace, method, params); - if (targetNamespace == "JSONRPC") { - if (method == "Introspect") { - QVariantMap data; - data.insert("types", JsonTypes::allTypes()); - QVariantMap methods; - foreach (JsonHandler *handler, m_handlers) { -// qDebug() << "got handler" << handler->name() << handler->introspect(); - methods.unite(handler->introspect()); - } - data.insert("methods", methods); - sendResponse(clientId, commandId, data); - return; - } - } - JsonHandler *handler = m_handlers.value(targetNamespace); if (!handler) { sendErrorResponse(clientId, commandId, "No such namespace"); @@ -100,9 +148,13 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData) sendErrorResponse(clientId, commandId, "Invalid params"); return; } + + // Hack: attach clientId to handler to be able to handle the JSONRPC methods. Do not use this outside of jsonrpcserver + handler->setProperty("clientId", clientId); + QVariantMap returns; QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(QVariantMap, returns), Q_ARG(QVariantMap, params)); - Q_ASSERT(handler->validateReturns(method, returns)); + Q_ASSERT((targetNamespace == "JSONRPC" && method == "Introspect") || handler->validateReturns(method, returns)); sendResponse(clientId, commandId, returns); } @@ -111,7 +163,18 @@ void JsonRPCServer::registerHandler(JsonHandler *handler) m_handlers.insert(handler->name(), handler); } -void JsonRPCServer::sendResponse(int clientId, int commandId, const QVariantMap ¶ms) +void JsonRPCServer::clientConnected(const QUuid &clientId) +{ + // Notifications disabled by default + m_clients.insert(clientId, false); +} + +void JsonRPCServer::clientDisconnected(const QUuid &clientId) +{ + m_clients.remove(clientId); +} + +void JsonRPCServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms) { QVariantMap rsp; rsp.insert("id", commandId); @@ -119,10 +182,10 @@ void JsonRPCServer::sendResponse(int clientId, int commandId, const QVariantMap rsp.insert("params", params); QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp); - m_tcpServer->sendResponse(clientId, jsonDoc.toJson()); + m_tcpServer->sendData(clientId, jsonDoc.toJson()); } -void JsonRPCServer::sendErrorResponse(int clientId, int commandId, const QString &error) +void JsonRPCServer::sendErrorResponse(const QUuid &clientId, int commandId, const QString &error) { QVariantMap rsp; rsp.insert("id", commandId); @@ -130,7 +193,7 @@ void JsonRPCServer::sendErrorResponse(int clientId, int commandId, const QString rsp.insert("error", error); QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp); - m_tcpServer->sendResponse(clientId, jsonDoc.toJson()); + m_tcpServer->sendData(clientId, jsonDoc.toJson()); } diff --git a/server/jsonrpc/jsonrpcserver.h b/server/jsonrpc/jsonrpcserver.h index d9eb3963..5890bfce 100644 --- a/server/jsonrpc/jsonrpcserver.h +++ b/server/jsonrpc/jsonrpcserver.h @@ -4,6 +4,7 @@ #include "deviceclass.h" #include "action.h" #include "event.h" +#include "jsonhandler.h" #include #include @@ -15,25 +16,33 @@ class MockTcpServer; class TcpServer; #endif class Device; -class JsonHandler; -class JsonRPCServer: public QObject +class JsonRPCServer: public JsonHandler { Q_OBJECT public: JsonRPCServer(QObject *parent = 0); + // JsonHandler API implementation + QString name() const; + Q_INVOKABLE QVariantMap Introspect(const QVariantMap ¶ms) const; + Q_INVOKABLE QVariantMap Version(const QVariantMap ¶ms) const; + Q_INVOKABLE QVariantMap SetNotificationStatus(const QVariantMap ¶ms); + signals: void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap ¶ms); private slots: - void processData(int clientId, const QByteArray &jsonData); + void clientConnected(const QUuid &clientId); + void clientDisconnected(const QUuid &clientId); + + void processData(const QUuid &clientId, const QByteArray &jsonData); private: void registerHandler(JsonHandler *handler); - void sendResponse(int clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); - void sendErrorResponse(int clientId, int commandId, const QString &error); + void sendResponse(const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap()); + void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error); private: #ifdef TESTING_ENABLED @@ -42,6 +51,9 @@ private: TcpServer *m_tcpServer; #endif QHash m_handlers; + + // clientId, notificationsEnabled + QHash m_clients; }; #endif diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 641dcd4d..172c39c9 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -95,7 +95,6 @@ void JsonTypes::init() s_rule.insert("actions", QVariantList() << actionRef()); s_rule.insert("states", QVariantList() << stateRef()); - s_initialized = true; } diff --git a/server/jsonrpc/jsontypes.h b/server/jsonrpc/jsontypes.h index 8f1e5d6e..82649aac 100644 --- a/server/jsonrpc/jsontypes.h +++ b/server/jsonrpc/jsontypes.h @@ -44,7 +44,6 @@ public: DECLARE_TYPE(basicTypes, "BasicType") DECLARE_TYPE(ruleTypes, "RuleType") - DECLARE_OBJECT(paramType, "ParamType") DECLARE_OBJECT(param, "Param") DECLARE_OBJECT(stateType, "StateType") diff --git a/server/tcpserver.cpp b/server/tcpserver.cpp index 2c6b9960..769459df 100644 --- a/server/tcpserver.cpp +++ b/server/tcpserver.cpp @@ -17,7 +17,14 @@ TcpServer::TcpServer(QObject *parent) : } -void TcpServer::sendResponse(int clientId, const QByteArray &data) +void TcpServer::sendData(const QList &clients, const QByteArray &data) +{ + foreach (const QUuid &client, clients) { + sendData(client, data); + } +} + +void TcpServer::sendData(const QUuid &clientId, const QByteArray &data) { QTcpSocket *client = m_clientList.value(clientId); if (client) { @@ -32,14 +39,15 @@ void TcpServer::newClientConnected() QTcpSocket *newConnection = server->nextPendingConnection(); qDebug() << "new client connected:" << newConnection->peerAddress().toString(); + QUuid clientId = QUuid::createUuid(); + // append the new client to the client list - m_clientList.insert(m_clientList.count(), newConnection); + m_clientList.insert(clientId, newConnection); connect(newConnection, SIGNAL(readyRead()),this,SLOT(readPackage())); - connect(newConnection,SIGNAL(disconnected()),this,SLOT(clientDisconnected())); + connect(newConnection,SIGNAL(disconnected()),this,SLOT(slotClientDisconnected())); - // TODO: properly handle this with jsonrpcserver - newConnection->write("{\n \"id\":0,\n \"status\": \"connected\",\n \"server\":\"Hive JSONRPC Interface\",\n \"version\":\"0.0.0\"\n}\n"); + emit clientConnected(clientId); } @@ -53,16 +61,18 @@ void TcpServer::readPackage() qDebug() << "line in:" << dataLine; message.append(dataLine); if(dataLine.endsWith('\n')){ - emit jsonDataAvailable(m_clientList.key(client), message); + emit dataAvailable(m_clientList.key(client), message); message.clear(); } } } -void TcpServer::clientDisconnected() +void TcpServer::slotClientDisconnected() { QTcpSocket *client = qobject_cast(sender()); qDebug() << "client disconnected:" << client->peerAddress().toString(); + QUuid clientId = m_clientList.key(client); + m_clientList.take(clientId)->deleteLater(); } bool TcpServer::startServer() @@ -73,7 +83,7 @@ bool TcpServer::startServer() if(server->listen(address, 1234)) { qDebug() << "server listening on" << address.toString(); connect(server, SIGNAL(newConnection()), SLOT(newClientConnected())); - m_serverList.insert(m_serverList.count(), server); + m_serverList.insert(QUuid::createUuid(), server); } else { qDebug() << "ERROR: can not listening to" << address.toString(); delete server; @@ -99,11 +109,4 @@ bool TcpServer::stopServer() return true; } -void TcpServer::sendToAll(QByteArray data) -{ - foreach(QTcpSocket *client,m_clientList){ - client->write(data); - } -} - diff --git a/server/tcpserver.h b/server/tcpserver.h index 04029845..d8a8f276 100644 --- a/server/tcpserver.h +++ b/server/tcpserver.h @@ -5,6 +5,7 @@ #include #include #include +#include class TcpServer : public QObject { @@ -12,24 +13,26 @@ class TcpServer : public QObject public: explicit TcpServer(QObject *parent = 0); - void sendResponse(int clientId, const QByteArray &data); + void sendData(const QUuid &clientId, const QByteArray &data); + void sendData(const QList &clients, const QByteArray &data); private: - QHash m_serverList; - QHash m_clientList; + QHash m_serverList; + QHash m_clientList; signals: - void jsonDataAvailable(int clientId, const QByteArray &data); + void clientConnected(const QUuid &clientId); + void clientDisconnected(const QUuid &clientId); + void dataAvailable(const QUuid &clientId, const QByteArray &data); private slots: void newClientConnected(); void readPackage(); - void clientDisconnected(); + void slotClientDisconnected(); public slots: bool startServer(); bool stopServer(); - void sendToAll(QByteArray data); }; #endif // TCPSERVER_H diff --git a/tests/auto/mocktcpserver.cpp b/tests/auto/mocktcpserver.cpp index 6f7c5b4a..6c8d1802 100644 --- a/tests/auto/mocktcpserver.cpp +++ b/tests/auto/mocktcpserver.cpp @@ -13,7 +13,7 @@ MockTcpServer::~MockTcpServer() s_allServers.removeAll(this); } -void MockTcpServer::sendResponse(int clientId, const QByteArray &data) +void MockTcpServer::sendData(const QUuid &clientId, const QByteArray &data) { emit outgoingData(clientId, data); } @@ -23,9 +23,9 @@ QList MockTcpServer::servers() return s_allServers; } -void MockTcpServer::injectData(int clientId, const QByteArray &data) +void MockTcpServer::injectData(const QUuid &clientId, const QByteArray &data) { - emit jsonDataAvailable(clientId, data); + emit dataAvailable(clientId, data); } bool MockTcpServer::startServer() diff --git a/tests/auto/mocktcpserver.h b/tests/auto/mocktcpserver.h index a53f7e0e..0807cd5e 100644 --- a/tests/auto/mocktcpserver.h +++ b/tests/auto/mocktcpserver.h @@ -12,17 +12,17 @@ public: explicit MockTcpServer(QObject *parent = 0); ~MockTcpServer(); - void sendResponse(int clientId, const QByteArray &data); + void sendData(const QUuid &clientId, const QByteArray &data); /************** Used for testing **************************/ static QList servers(); - void injectData(int clientId, const QByteArray &data); + void injectData(const QUuid &clientId, const QByteArray &data); signals: - void outgoingData(int clientId, const QByteArray &data); + void outgoingData(const QUuid &clientId, const QByteArray &data); /************** Used for testing **************************/ signals: - void jsonDataAvailable(int clientId, const QByteArray &data); + void dataAvailable(const QUuid &clientId, const QByteArray &data); public slots: bool startServer(); diff --git a/tests/auto/testjsonrpc.cpp b/tests/auto/testjsonrpc.cpp index ed00f103..6d67615b 100644 --- a/tests/auto/testjsonrpc.cpp +++ b/tests/auto/testjsonrpc.cpp @@ -13,12 +13,22 @@ class TestJSONRPC: public QObject Q_OBJECT private slots: void initTestcase(); + void introspect(); + void getSupportedDevices(); + void enableDisableNotifications_data(); + void enableDisableNotifications(); + + void version(); + +private: + QVariant injectAndWait(const QByteArray data); + private: MockTcpServer *m_mockTcpServer; - int m_clientId; + QUuid m_clientId; }; void TestJSONRPC::initTestcase() @@ -36,12 +46,29 @@ void TestJSONRPC::initTestcase() // If Hive should create more than one TcpServer at some point, this needs to be updated. QCOMPARE(MockTcpServer::servers().count(), 1); m_mockTcpServer = MockTcpServer::servers().first(); - m_clientId = 0; + m_clientId = QUuid::createUuid(); +} + +QVariant TestJSONRPC::injectAndWait(const QByteArray data) +{ + QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + + m_mockTcpServer->injectData(m_clientId, data); + + if (spy.count() == 0) { + spy.wait(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.takeFirst().last().toByteArray(), &error); + + return jsonDoc.toVariant(); } void TestJSONRPC::introspect() { - QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(int,QByteArray))); + QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); QVERIFY(spy.isValid()); m_mockTcpServer->injectData(m_clientId, "{\"id\":42, \"method\":\"JSONRPC.Introspect\"}"); @@ -54,7 +81,7 @@ void TestJSONRPC::introspect() QVERIFY(spy.count() == 1); // Make sure the introspect response goes to the correct clientId - QCOMPARE(spy.first().first().toInt(), m_clientId); + QCOMPARE(spy.first().first().toString(), m_clientId.toString()); // Make sure the response it a valid JSON string QJsonParseError error; @@ -67,22 +94,7 @@ void TestJSONRPC::introspect() void TestJSONRPC::getSupportedDevices() { - QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(int,QByteArray))); - - m_mockTcpServer->injectData(m_clientId, "{\"id\":1, \"method\":\"Devices.GetSupportedDevices\"}"); - - if (spy.count() == 0) { - spy.wait(); - } - - // Make sure the response it a valid JSON string - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().last().toByteArray(), &error); - QCOMPARE(error.error, QJsonParseError::NoError); - - QVariant supportedDevices = jsonDoc.toVariant(); - - qDebug() << spy.first().last(); + QVariant supportedDevices = injectAndWait("{\"id\":1, \"method\":\"Devices.GetSupportedDevices\"}"); // Make sure there is exactly 1 supported device class with the name Mock Wifi Device QCOMPARE(supportedDevices.toMap().value("params").toMap().value("deviceClasses").toList().count(), 1); @@ -90,5 +102,31 @@ void TestJSONRPC::getSupportedDevices() QCOMPARE(deviceName, QString("Mock WiFi Device")); } +void TestJSONRPC::enableDisableNotifications_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << "true"; + QTest::newRow("disabled") << "false"; +} + +void TestJSONRPC::enableDisableNotifications() +{ + QFETCH(QString, enabled); + + QVariant response = injectAndWait(QString("{\"id\":1, \"method\":\"JSONRPC.SetNotificationStatus\", \"params\":{\"enabled\": " + enabled + " }}").toLatin1()); + + QCOMPARE(response.toMap().value("params").toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("enabled").toString(), enabled); + +} + +void TestJSONRPC::version() +{ + QVariant response = injectAndWait("{\"id\":1, \"method\":\"JSONRPC.Version\"}"); + + QCOMPARE(response.toMap().value("params").toMap().value("version").toString(), QString("0.0.0")); +} + QTEST_MAIN(TestJSONRPC) #include "testjsonrpc.moc" diff --git a/tests/scripts/geteventtypes.sh b/tests/scripts/geteventtypes.sh index 76f35a93..a8394464 100755 --- a/tests/scripts/geteventtypes.sh +++ b/tests/scripts/geteventtypes.sh @@ -3,5 +3,5 @@ if [ -z $2 ]; then echo "usage: $0 host deviceClassId" else - (echo '{"id":1, "method":"Devices.GetTriggerTypes", "params":{"deviceClassId":"'$2'"}}'; sleep 1) | nc $1 1234 + (echo '{"id":1, "method":"Devices.GetEventTypes", "params":{"deviceClassId":"'$2'"}}'; sleep 1) | nc $1 1234 fi diff --git a/tests/scripts/getversion.sh b/tests/scripts/getversion.sh new file mode 100755 index 00000000..93f11499 --- /dev/null +++ b/tests/scripts/getversion.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ -z $1 ]; then + echo "usage: $0 host" +else + (echo '{"id":1, "method": "JSONRPC.Version"}'; sleep 1) | nc $1 1234 +fi