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 f0c60d68..df4bbdaf 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -92,6 +92,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 \ @@ -179,6 +180,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/api.json b/tests/auto/api.json index 602e53e0..b1c0dfc2 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -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": { @@ -2058,6 +2080,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": { 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());