diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 120992b1..c8b86c67 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -45,6 +45,7 @@ #include "integrations/browseritemresult.h" #include +#include namespace nymeaserver { @@ -400,6 +401,37 @@ DeviceHandler::DeviceHandler(QObject *parent) : connect(NymeaCore::instance(), &NymeaCore::thingAdded, this, &DeviceHandler::deviceAddedNotification); connect(NymeaCore::instance(), &NymeaCore::thingChanged, this, &DeviceHandler::deviceChangedNotification); connect(NymeaCore::instance(), &NymeaCore::thingSettingChanged, this, &DeviceHandler::deviceSettingChangedNotification); + + connect(NymeaCore::instance(), &NymeaCore::initialized, this, [=](){ + // Generating cache hashes. + // NOTE: We need to sort the lists to get a stable result + QHash thingClassesMap; + foreach (const ThingClass &tc, NymeaCore::instance()->thingManager()->supportedThings()) { + thingClassesMap.insert(tc.id(), tc); + } + QList thingClassIds = thingClassesMap.keys(); + std::sort(thingClassIds.begin(), thingClassIds.end()); + DeviceClasses thingClasses; + foreach (const ThingClassId &id, thingClassIds) { + thingClasses.append(thingClassesMap.value(id)); + } + QByteArray hash = QCryptographicHash::hash(QJsonDocument::fromVariant(pack(thingClasses)).toJson(), QCryptographicHash::Md5).toHex(); + m_cacheHashes.insert("GetSupportedDevices", hash); + + QHash vendorsMap; + foreach (const Vendor &v, NymeaCore::instance()->thingManager()->supportedVendors()) { + vendorsMap.insert(v.id(), v); + } + QList vendorIds = vendorsMap.keys(); + std::sort(vendorIds.begin(), vendorIds.end()); + Vendors vendors; + foreach (const VendorId &id, vendorIds) { + vendors.append(vendorsMap.value(id)); + } + hash = QCryptographicHash::hash(QJsonDocument::fromVariant(pack(vendors)).toJson(), QCryptographicHash::Md5).toHex(); + m_cacheHashes.insert("GetSupportedVendors", hash); + }); + } /*! Returns the name of the \l{DeviceHandler}. In this case \b Devices.*/ @@ -408,6 +440,11 @@ QString DeviceHandler::name() const return "Devices"; } +QHash DeviceHandler::cacheHashes() const +{ + return m_cacheHashes; +} + JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap ¶ms, const JsonContext &context) const { Q_UNUSED(params) diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index 95873c0e..e97fce5d 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -139,6 +139,8 @@ public: explicit DeviceHandler(QObject *parent = nullptr); QString name() const override; + QHash cacheHashes() const override; + QVariantMap translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) override; Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap ¶ms, const JsonContext &context) const; @@ -196,6 +198,8 @@ private slots: private: QVariantMap statusToReply(Device::ThingError status) const; + + QHash m_cacheHashes; }; } diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index 51ac9b1f..60b566a7 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -45,6 +45,7 @@ #include "integrations/browseritemresult.h" #include +#include namespace nymeaserver { @@ -445,6 +446,36 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa connect(NymeaCore::instance(), &NymeaCore::thingAdded, this, &IntegrationsHandler::thingAddedNotification); connect(NymeaCore::instance(), &NymeaCore::thingChanged, this, &IntegrationsHandler::thingChangedNotification); connect(NymeaCore::instance(), &NymeaCore::thingSettingChanged, this, &IntegrationsHandler::thingSettingChangedNotification); + + connect(NymeaCore::instance(), &NymeaCore::initialized, this, [=](){ + // Generating cache hashes. + // NOTE: We need to sort the lists to get a stable result + QHash thingClassesMap; + foreach (const ThingClass &tc, m_thingManager->supportedThings()) { + thingClassesMap.insert(tc.id(), tc); + } + QList thingClassIds = thingClassesMap.keys(); + std::sort(thingClassIds.begin(), thingClassIds.end()); + ThingClasses thingClasses; + foreach (const ThingClassId &id, thingClassIds) { + thingClasses.append(thingClassesMap.value(id)); + } + QByteArray hash = QCryptographicHash::hash(QJsonDocument::fromVariant(pack(thingClasses)).toJson(), QCryptographicHash::Md5).toHex(); + m_cacheHashes.insert("GetThingClasses", hash); + + QHash vendorsMap; + foreach (const Vendor &v, m_thingManager->supportedVendors()) { + vendorsMap.insert(v.id(), v); + } + QList vendorIds = vendorsMap.keys(); + std::sort(vendorIds.begin(), vendorIds.end()); + Vendors vendors; + foreach (const VendorId &id, vendorIds) { + vendors.append(vendorsMap.value(id)); + } + hash = QCryptographicHash::hash(QJsonDocument::fromVariant(pack(vendors)).toJson(), QCryptographicHash::Md5).toHex(); + m_cacheHashes.insert("GetVendors", hash); + }); } QString IntegrationsHandler::name() const @@ -452,6 +483,11 @@ QString IntegrationsHandler::name() const return "Integrations"; } +QHash IntegrationsHandler::cacheHashes() const +{ + return m_cacheHashes; +} + JsonReply* IntegrationsHandler::GetVendors(const QVariantMap ¶ms, const JsonContext &context) const { Q_UNUSED(params) diff --git a/libnymea-core/jsonrpc/integrationshandler.h b/libnymea-core/jsonrpc/integrationshandler.h index cad64521..f61233f0 100644 --- a/libnymea-core/jsonrpc/integrationshandler.h +++ b/libnymea-core/jsonrpc/integrationshandler.h @@ -43,6 +43,7 @@ public: explicit IntegrationsHandler(ThingManager *thingManager, QObject *parent = nullptr); QString name() const override; + QHash cacheHashes() const override; Q_INVOKABLE JsonReply *GetVendors(const QVariantMap ¶ms, const JsonContext &context) const; Q_INVOKABLE JsonReply *GetThingClasses(const QVariantMap ¶ms, const JsonContext &context) const; @@ -105,6 +106,8 @@ private slots: private: ThingManager *m_thingManager = nullptr; QVariantMap statusToReply(Thing::ThingError status) const; + + QHash m_cacheHashes; }; } diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 3bf4852c..33671c20 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -101,6 +101,11 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration experiece.insert("version", enumValueName(String)); registerObject("Experience", experiece); + QVariantMap cacheHash; + cacheHash.insert("method", enumValueName(String)); + cacheHash.insert("hash", enumValueName(String)); + registerObject("CacheHash", cacheHash); + // Methods QString description; QVariantMap returns; QVariantMap params; description = "Initiates a connection. Use this method to perform an initial handshake of the " @@ -111,7 +116,10 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration "about this core instance such as version information, uuid and its name. The locale value" "indicates the locale used for this connection. Note: This method can be called multiple " "times. The locale used in the last call for this connection will be used. Other values, " - "like initialSetupRequired might change if the setup has been performed in the meantime."; + "like initialSetupRequired might change if the setup has been performed in the meantime.\n " + "The field cacheHashes may contain a map of methods and MD5 hashes. As long as the hash for " + "a method does not change, a client may use a previously cached copy of the call instead of " + "fetching the content again."; params.insert("o:locale", enumValueName(String)); returns.insert("server", enumValueName(String)); returns.insert("name", enumValueName(String)); @@ -124,6 +132,7 @@ JsonRPCServerImplementation::JsonRPCServerImplementation(const QSslConfiguration returns.insert("authenticationRequired", enumValueName(Bool)); returns.insert("pushButtonAuthAvailable", enumValueName(Bool)); returns.insert("o:experiences", QVariantList() << objectRef("Experience")); + returns.insert("o:cacheHashes", QVariantList() << objectRef("CacheHash")); registerMethod("Hello", description, params, returns); params.clear(); returns.clear(); @@ -555,6 +564,19 @@ QVariantMap JsonRPCServerImplementation::createWelcomeMessage(TransportInterface } handshake.insert("experiences", experiences); } + QVariantList cacheHashes; + foreach (const QString &handlerName, m_handlers.keys()) { + QHash hashes = m_handlers.value(handlerName)->cacheHashes(); + foreach (const QString &hashName, hashes.keys()) { + QVariantMap cacheHash; + cacheHash.insert("method", handlerName + "." + hashName); + cacheHash.insert("hash", hashes.value(hashName)); + cacheHashes.append(cacheHash); + } + } + if (!cacheHashes.isEmpty()) { + handshake.insert("cacheHashes", cacheHashes); + } return handshake; } diff --git a/libnymea/jsonrpc/jsonhandler.cpp b/libnymea/jsonrpc/jsonhandler.cpp index ab292a9b..1dc446ee 100644 --- a/libnymea/jsonrpc/jsonhandler.cpp +++ b/libnymea/jsonrpc/jsonhandler.cpp @@ -41,6 +41,11 @@ JsonHandler::JsonHandler(QObject *parent) : QObject(parent) registerEnum(); } +QHash JsonHandler::cacheHashes() const +{ + return QHash(); +} + QVariantMap JsonHandler::translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale) { Q_UNUSED(notification) diff --git a/libnymea/jsonrpc/jsonhandler.h b/libnymea/jsonrpc/jsonhandler.h index 980b4471..2111926f 100644 --- a/libnymea/jsonrpc/jsonhandler.h +++ b/libnymea/jsonrpc/jsonhandler.h @@ -64,6 +64,7 @@ public: virtual ~JsonHandler() = default; virtual QString name() const = 0; + virtual QHash cacheHashes() const; virtual QVariantMap translateNotification(const QString ¬ification, const QVariantMap ¶ms, const QLocale &locale); diff --git a/nymea.pro b/nymea.pro index 82cacc52..8c5464ab 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=1 +JSON_PROTOCOL_VERSION_MINOR=2 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=6 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/tests/auto/api.json b/tests/auto/api.json index bb399bdd..1a25139c 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -5.1 +5.2 { "enums": { "BasicType": [ @@ -1240,7 +1240,7 @@ } }, "JSONRPC.Hello": { - "description": "Initiates a connection. Use this method to perform an initial handshake of the connection. Optionally, a parameter \"locale\" is can be passed to set up the used locale for this connection. Strings such as ThingClass displayNames etc will be localized to this locale. If this parameter is omitted, the default system locale (depending on the configuration) is used. The reply of this method contains information about this core instance such as version information, uuid and its name. The locale valueindicates the locale used for this connection. Note: This method can be called multiple times. The locale used in the last call for this connection will be used. Other values, like initialSetupRequired might change if the setup has been performed in the meantime.", + "description": "Initiates a connection. Use this method to perform an initial handshake of the connection. Optionally, a parameter \"locale\" is can be passed to set up the used locale for this connection. Strings such as ThingClass displayNames etc will be localized to this locale. If this parameter is omitted, the default system locale (depending on the configuration) is used. The reply of this method contains information about this core instance such as version information, uuid and its name. The locale valueindicates the locale used for this connection. Note: This method can be called multiple times. The locale used in the last call for this connection will be used. Other values, like initialSetupRequired might change if the setup has been performed in the meantime.\n The field cacheHashes may contain a map of methods and MD5 hashes. As long as the hash for a method does not change, a client may use a previously cached copy of the call instead of fetching the content again.", "params": { "o:locale": "String" }, @@ -1250,6 +1250,9 @@ "language": "String", "locale": "String", "name": "String", + "o:cacheHashes": [ + "$ref:CacheHash" + ], "o:experiences": [ "$ref:Experience" ], @@ -2380,6 +2383,10 @@ "o:mediaIcon": "$ref:MediaBrowserIcon", "thumbnail": "String" }, + "CacheHash": { + "hash": "String", + "method": "String" + }, "CalendarItem": { "duration": "Uint", "o:datetime": "Uint",