From 60de0a3eed55b5adf97745eb1eebeba5df2ddca0 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 6 Apr 2021 22:56:26 +0200 Subject: [PATCH 1/2] Add support for storing application data on the core --- libnymea-core/jsonrpc/appdatahandler.cpp | 82 +++++++++++++++++++ libnymea-core/jsonrpc/appdatahandler.h | 22 +++++ .../jsonrpc/jsonrpcserverimplementation.cpp | 2 + libnymea-core/libnymea-core.pro | 2 + libnymea/integrations/plugin.pri | 2 + tests/auto/jsonrpc/testjsonrpc.cpp | 2 +- 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 libnymea-core/jsonrpc/appdatahandler.cpp create mode 100644 libnymea-core/jsonrpc/appdatahandler.h diff --git a/libnymea-core/jsonrpc/appdatahandler.cpp b/libnymea-core/jsonrpc/appdatahandler.cpp new file mode 100644 index 00000000..bd274a82 --- /dev/null +++ b/libnymea-core/jsonrpc/appdatahandler.cpp @@ -0,0 +1,82 @@ +#include "appdatahandler.h" +#include "jsonrpc/jsonrpcserver.h" + +#include "nymeasettings.h" + +#include +#include + +AppDataHandler::AppDataHandler(QObject *parent) : JsonHandler(parent) +{ + // Methods + QString description; QVariantMap params; QVariantMap returns; + description = "Store an app data entry to the server. App data can be used by the client application " + "to store configuration values. The app data storage is a key-value pair storage. Each " + "entry value is identified by an appId, a key and optionally a group. The value data is " + "a bytearray and can contain arbitrary data, such as a JSON map or image data, however, " + "be aware of the maximum packet size for the used transport.\n" + "This might be useful to a client application to sync settings across multiple instances of " + "the same application.\n" + "The group parameter might be used to create groups for this application.\n" + "IMPORTANT: Currently no verification of the appId is done. The appid is merely a mechanism " + "to prevent different different client apps from colliding by using the same key for data " + "entries. This implies that the app data storage may not be suited for sensitive data given " + "that anyone with a valid server token can read it.\n "; + params.insert("appId", enumValueName(String)); + params.insert("o:group", enumValueName(String)); + params.insert("key", enumValueName(String)); + params.insert("value", enumValueName(String)); + registerMethod("Store", description, params, returns); + + description.clear(); params.clear(); returns.clear(); + description = "Retrieve an app data storage value that has previously been set with Store(). If no value " + "had been set for this appId/key combination before, an empty value will be returned."; + params.insert("appId", enumValueName(String)); + params.insert("o:group", enumValueName(String)); + params.insert("key", enumValueName(String)); + returns.insert("value", enumValueName(String)); + registerMethod("Load", description, params, returns); + + // Notifications + description.clear(); params.clear(); + description = "Emitted whenever the app data is changed on the server."; + params.insert("appId", enumValueName(String)); + params.insert("o:group", enumValueName(String)); + params.insert("key", enumValueName(String)); + params.insert("value", enumValueName(String)); + registerNotification("Changed", description, params); +} + +QString AppDataHandler::name() const +{ + return "AppData"; +} + +JsonReply* AppDataHandler::Store(const QVariantMap ¶ms) +{ + QString appId = params.value("appId").toString(); + QString group = params.value("group").toString(); + QString key = params.value("key").toString(); + QVariant value = params.value("value"); + + // Note: we're using a different file for each group as QSettings tends to get slow with loads of keys. + // Might be replaced with a DB at some point if needed. However, current estimate is this won't be + // used for excessive amounts of data as it is mostly meant as a config file syncing mechanism. + QSettings settings(NymeaSettings::storagePath() + "/appdata/" + appId + '/' + group + ".conf", QSettings::IniFormat); + settings.setValue(key, value); + return createReply(QVariantMap()); +} + +JsonReply* AppDataHandler::Load(const QVariantMap ¶ms) +{ + QString appId = params.value("appId").toString(); + QString group = params.value("group").toString(); + QString key = params.value("key").toString(); + + QSettings settings(NymeaSettings::storagePath() + "/appdata/" + appId + '/' + group + ".conf", QSettings::IniFormat); + + QVariantMap returns; + + returns.insert("value", settings.value(key).toString()); + return createReply(returns); +} diff --git a/libnymea-core/jsonrpc/appdatahandler.h b/libnymea-core/jsonrpc/appdatahandler.h new file mode 100644 index 00000000..5635f953 --- /dev/null +++ b/libnymea-core/jsonrpc/appdatahandler.h @@ -0,0 +1,22 @@ +#ifndef APPDATAHANDLER_H +#define APPDATAHANDLER_H + +#include +#include "jsonrpc/jsonhandler.h" + +class AppDataHandler : public JsonHandler +{ + Q_OBJECT +public: + explicit AppDataHandler(QObject *parent = nullptr); + + QString name() const override; + + Q_INVOKABLE JsonReply *Store(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *Load(const QVariantMap ¶ms); + +signals: + +}; + +#endif // APPDATAHANDLER_H diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 25213cdc..36a75646 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -71,6 +71,7 @@ #include "configurationhandler.h" #include "networkmanagerhandler.h" #include "tagshandler.h" +#include "appdatahandler.h" #include "systemhandler.h" #include "usershandler.h" #include "zigbeehandler.h" @@ -594,6 +595,7 @@ void JsonRPCServerImplementation::setup() registerHandler(new ConfigurationHandler(this)); registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this)); registerHandler(new TagsHandler(this)); + registerHandler(new AppDataHandler(this)); registerHandler(new SystemHandler(NymeaCore::instance()->platform(), this)); registerHandler(new UsersHandler(NymeaCore::instance()->userManager(), this)); registerHandler(new ZigbeeHandler(NymeaCore::instance()->zigbeeManager(), this)); diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index ece76d66..845e81f3 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -86,6 +86,7 @@ HEADERS += nymeacore.h \ jsonrpc/configurationhandler.h \ jsonrpc/networkmanagerhandler.h \ jsonrpc/tagshandler.h \ + jsonrpc/appdatahandler.h \ jsonrpc/systemhandler.h \ jsonrpc/scriptshandler.h \ jsonrpc/usershandler.h \ @@ -173,6 +174,7 @@ SOURCES += nymeacore.cpp \ jsonrpc/configurationhandler.cpp \ jsonrpc/networkmanagerhandler.cpp \ jsonrpc/tagshandler.cpp \ + jsonrpc/appdatahandler.cpp \ jsonrpc/systemhandler.cpp \ jsonrpc/scriptshandler.cpp \ jsonrpc/usershandler.cpp \ diff --git a/libnymea/integrations/plugin.pri b/libnymea/integrations/plugin.pri index dd6a90f5..03c0294d 100644 --- a/libnymea/integrations/plugin.pri +++ b/libnymea/integrations/plugin.pri @@ -83,6 +83,8 @@ translations.files = $$[QT_SOURCE_TREE]/translations/*.qm # Redefine target to make output file suite the plugin filename schema TARGET = $$qtLibraryTarget(nymea_integrationplugin"$$TARGET") +target.depends += $${JSONFILE} + # Install plugin target.path = $$[QT_INSTALL_LIBS]/nymea/plugins/ INSTALLS += target translations diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp index ab46d928..813b7215 100644 --- a/tests/auto/jsonrpc/testjsonrpc.cpp +++ b/tests/auto/jsonrpc/testjsonrpc.cpp @@ -674,7 +674,7 @@ void TestJSONRPC::enableDisableNotifications_legacy() QStringList expectedNamespaces; if (enabled == "true") { - expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "Integrations" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users" << "Zigbee"; + expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "Integrations" << "System" << "Rules" << "States" << "Logging" << "Tags" << "AppData" << "JSONRPC" << "Configuration" << "Events" << "Scripts" << "Users" << "Zigbee"; } std::sort(expectedNamespaces.begin(), expectedNamespaces.end()); From b64ac431bf59401d854e8d3c40fc8337a7722d19 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 7 Apr 2021 12:27:51 +0200 Subject: [PATCH 2/2] Update json api --- nymea.pro | 2 +- tests/auto/api.json | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/nymea.pro b/nymea.pro index fcf3eea8..f51820af 100644 --- a/nymea.pro +++ b/nymea.pro @@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=5 -JSON_PROTOCOL_VERSION_MINOR=4 +JSON_PROTOCOL_VERSION_MINOR=5 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/tests/auto/api.json b/tests/auto/api.json index 1c5f22d6..3c3b3bed 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -5.4 +5.5 { "enums": { "BasicType": [ @@ -441,6 +441,28 @@ "o:actionType": "$ref:ActionType" } }, + "AppData.Load": { + "description": "Retrieve an app data storage value that has previously been set with Store(). If no value had been set for this appId/key combination before, an empty value will be returned.", + "params": { + "appId": "String", + "key": "String", + "o:group": "String" + }, + "returns": { + "value": "String" + } + }, + "AppData.Store": { + "description": "Store an app data entry to the server. App data can be used by the client application to store configuration values. The app data storage is a key-value pair storage. Each entry value is identified by an appId, a key and optionally a group. The value data is a bytearray and can contain arbitrary data, such as a JSON map or image data, however, be aware of the maximum packet size for the used transport.\nThis might be useful to a client application to sync settings across multiple instances of the same application.\nThe group parameter might be used to create groups for this application.\nIMPORTANT: Currently no verification of the appId is done. The appid is merely a mechanism to prevent different different client apps from colliding by using the same key for data entries. This implies that the app data storage may not be suited for sensitive data given that anyone with a valid server token can read it.\n ", + "params": { + "appId": "String", + "key": "String", + "o:group": "String", + "value": "String" + }, + "returns": { + } + }, "Configuration.DeleteMqttPolicy": { "description": "Delete a MQTT policy from the broker.", "params": { @@ -2050,6 +2072,15 @@ } }, "notifications": { + "AppData.Changed": { + "description": "Emitted whenever the app data is changed on the server.", + "params": { + "appId": "String", + "key": "String", + "o:group": "String", + "value": "String" + } + }, "Configuration.BasicConfigurationChanged": { "description": "Emitted whenever the basic configuration of this server changes.", "params": {