Add caching information to client API

This allows JsonHandlers to set a hash for certain API calls.
Clients can use this information to load data for such a call
from a cache instead of calling the method over the network
just to get the same result as last time.
pull/336/head
Michael Zanetti 2020-09-14 23:31:42 +02:00
parent 2ca4b2f32f
commit f9cd2a99df
9 changed files with 119 additions and 4 deletions

View File

@ -45,6 +45,7 @@
#include "integrations/browseritemresult.h"
#include <QDebug>
#include <QJsonDocument>
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<ThingClassId, ThingClass> thingClassesMap;
foreach (const ThingClass &tc, NymeaCore::instance()->thingManager()->supportedThings()) {
thingClassesMap.insert(tc.id(), tc);
}
QList<ThingClassId> 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<VendorId, Vendor> vendorsMap;
foreach (const Vendor &v, NymeaCore::instance()->thingManager()->supportedVendors()) {
vendorsMap.insert(v.id(), v);
}
QList<VendorId> 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<QString, QString> DeviceHandler::cacheHashes() const
{
return m_cacheHashes;
}
JsonReply* DeviceHandler::GetSupportedVendors(const QVariantMap &params, const JsonContext &context) const
{
Q_UNUSED(params)

View File

@ -139,6 +139,8 @@ public:
explicit DeviceHandler(QObject *parent = nullptr);
QString name() const override;
QHash<QString, QString> cacheHashes() const override;
QVariantMap translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale) override;
Q_INVOKABLE JsonReply *GetSupportedVendors(const QVariantMap &params, const JsonContext &context) const;
@ -196,6 +198,8 @@ private slots:
private:
QVariantMap statusToReply(Device::ThingError status) const;
QHash<QString, QString> m_cacheHashes;
};
}

View File

@ -45,6 +45,7 @@
#include "integrations/browseritemresult.h"
#include <QDebug>
#include <QJsonDocument>
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<ThingClassId, ThingClass> thingClassesMap;
foreach (const ThingClass &tc, m_thingManager->supportedThings()) {
thingClassesMap.insert(tc.id(), tc);
}
QList<ThingClassId> 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<VendorId, Vendor> vendorsMap;
foreach (const Vendor &v, m_thingManager->supportedVendors()) {
vendorsMap.insert(v.id(), v);
}
QList<VendorId> 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<QString, QString> IntegrationsHandler::cacheHashes() const
{
return m_cacheHashes;
}
JsonReply* IntegrationsHandler::GetVendors(const QVariantMap &params, const JsonContext &context) const
{
Q_UNUSED(params)

View File

@ -43,6 +43,7 @@ public:
explicit IntegrationsHandler(ThingManager *thingManager, QObject *parent = nullptr);
QString name() const override;
QHash<QString, QString> cacheHashes() const override;
Q_INVOKABLE JsonReply *GetVendors(const QVariantMap &params, const JsonContext &context) const;
Q_INVOKABLE JsonReply *GetThingClasses(const QVariantMap &params, const JsonContext &context) const;
@ -105,6 +106,8 @@ private slots:
private:
ThingManager *m_thingManager = nullptr;
QVariantMap statusToReply(Thing::ThingError status) const;
QHash<QString, QString> m_cacheHashes;
};
}

View File

@ -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<QString, QString> 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;
}

View File

@ -41,6 +41,11 @@ JsonHandler::JsonHandler(QObject *parent) : QObject(parent)
registerEnum<BasicType>();
}
QHash<QString, QString> JsonHandler::cacheHashes() const
{
return QHash<QString, QString>();
}
QVariantMap JsonHandler::translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale)
{
Q_UNUSED(notification)

View File

@ -64,6 +64,7 @@ public:
virtual ~JsonHandler() = default;
virtual QString name() const = 0;
virtual QHash<QString, QString> cacheHashes() const;
virtual QVariantMap translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale);

View File

@ -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

View File

@ -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",