From c6a0cecb0f30e499927b8bf6e1bd1f574895ff18 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 15 Jul 2019 17:00:36 +0200 Subject: [PATCH 01/22] Fix parenting mechanism for discovered devices --- .../devices/devicemanagerimplementation.cpp | 5 +- .../devices/devicemanagerimplementation.h | 2 +- plugins/mock/devicepluginmock.cpp | 21 +++++ plugins/mock/devicepluginmock.json | 4 +- plugins/mock/mock.pro | 2 + tests/auto/devices/testdevices.cpp | 76 +++++++++++++++++++ 6 files changed, 105 insertions(+), 5 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index e9c5e9c0..44f06a09 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -361,7 +361,7 @@ Device::DeviceError DeviceManagerImplementation::addConfiguredDevice(const Devic } } - return addConfiguredDeviceInternal(deviceClassId, name, finalParams, deviceId); + return addConfiguredDeviceInternal(deviceClassId, name, finalParams, deviceId, descriptor.parentDeviceId()); } @@ -619,7 +619,7 @@ Device::DeviceError DeviceManagerImplementation::confirmPairing(const PairingTra /*! This method will only be used from the DeviceManagerImplementation in order to add a \l{Device} with the given \a deviceClassId, \a name, \a params and \ id. * Returns \l{DeviceError} to inform about the result. */ -Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id) +Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id, const DeviceId &parentDeviceId) { DeviceClass deviceClass = findDeviceClass(deviceClassId); if (deviceClass.id().isNull()) { @@ -648,6 +648,7 @@ Device::DeviceError DeviceManagerImplementation::addConfiguredDeviceInternal(con } Device *device = new Device(plugin, deviceClass, id, this); + device->setParentId(parentDeviceId); if (name.isEmpty()) { device->setName(deviceClass.name()); } else { diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 608e5a70..5b4aa43d 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -125,7 +125,7 @@ private slots: void slotDeviceSettingChanged(const ParamTypeId ¶mTypeId, const QVariant &value); private: - Device::DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id = DeviceId::createDeviceId()); + Device::DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id = DeviceId::createDeviceId(), const DeviceId &parentDeviceId = DeviceId()); Device::DeviceSetupStatus setupDevice(Device *device); void postSetupDevice(Device *device); void storeDeviceStates(Device *device); diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 8ae7a89d..2c4f94e4 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -75,7 +75,28 @@ Device::DeviceError DevicePluginMock::discoverDevices(const DeviceClassId &devic m_discoveredDeviceCount = params.paramValue(mockDisplayPinDiscoveryResultCountParamTypeId).toInt(); QTimer::singleShot(1000, this, SLOT(emitDisplayPinDevicesDiscovered())); return Device::DeviceErrorAsync; + } else if (deviceClassId == mockParentDeviceClassId) { + qCDebug(dcMockDevice()) << "Starting discovery for mock device parent"; + QTimer::singleShot(1000, this, [this](){ + DeviceDescriptor descriptor(mockParentDeviceClassId, "Mock Parent (Discovered)"); + emit devicesDiscovered(mockParentDeviceClassId, {descriptor}); + + }); + return Device::DeviceErrorAsync; + } else if (deviceClassId == mockChildDeviceClassId) { + QTimer::singleShot(1000, this, [this](){ + QList descriptors; + if (!myDevices().filterByDeviceClassId(mockParentDeviceClassId).isEmpty()) { + Device *parent = myDevices().filterByDeviceClassId(mockParentDeviceClassId).first(); + DeviceDescriptor descriptor(mockChildDeviceClassId, "Mock Child (Discovered)", QString(), parent->id()); + descriptors.append(descriptor); + } + emit devicesDiscovered(mockChildDeviceClassId, descriptors); + }); + return Device::DeviceErrorAsync; } + + qCWarning(dcMockDevice()) << "Cannot discover for deviceClassId" << deviceClassId; return Device::DeviceErrorDeviceClassNotFound; } diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index f00f0a34..0ea2282b 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -501,7 +501,7 @@ "name": "mockParent", "displayName": "Mock Device (Parent)", "interfaces": ["system"], - "createMethods": ["user"], + "createMethods": ["user", "discovery"], "paramTypes": [ ], "stateTypes": [ { @@ -520,7 +520,7 @@ "id": "40893c9f-bc47-40c1-8bf7-b390c7c1b4fc", "name": "mockChild", "displayName": "Mock Device (Child)", - "createMethods": ["auto"], + "createMethods": ["auto", "discovery"], "paramTypes": [], "stateTypes": [ { diff --git a/plugins/mock/mock.pro b/plugins/mock/mock.pro index a9eecc28..9613a6cc 100644 --- a/plugins/mock/mock.pro +++ b/plugins/mock/mock.pro @@ -4,6 +4,8 @@ QT+= network TARGET = $$qtLibraryTarget(nymea_devicepluginmock) +OTHER_FILES += devicepluginmock.json + SOURCES += \ devicepluginmock.cpp \ httpdaemon.cpp diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index 1a316d17..fe7e708e 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -29,6 +29,9 @@ class TestDevices : public NymeaTestBase { Q_OBJECT +protected slots: + void initTestCase(); + private slots: void getPlugins(); @@ -99,8 +102,20 @@ private slots: void removeDevice(); void removeAutoDevice(); + + void discoverDeviceParenting(); }; +void TestDevices::initTestCase() +{ + NymeaTestBase::initTestCase(); + QLoggingCategory::setFilterRules("*.debug=false\n" + "Tests.debug=true\n" + "MockDevice.debug=true\n" + ); + +} + void TestDevices::getPlugins() { QVariant response = injectAndWait("Devices.GetPlugins"); @@ -1466,6 +1481,67 @@ void TestDevices::removeAutoDevice() QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId).count() == 0, "Mock device has not disappeared even though it should have."); } +void TestDevices::discoverDeviceParenting() +{ + // Try to discover a mock child device. We don't have a mockParent yet, so it should fail + QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::devicesDiscovered); + Device::DeviceError status = NymeaCore::instance()->deviceManager()->discoverDevices(mockChildDeviceClassId, ParamList()); + QCOMPARE(status, Device::DeviceErrorAsync); + spy.wait(); + QCOMPARE(spy.first().at(0).value().toString(), mockChildDeviceClassId.toString()); + QList descriptors = spy.first().at(1).value >(); + QVERIFY(descriptors.count() == 0); + + + // Now create a mock parent by discovering... + spy.clear(); + status = NymeaCore::instance()->deviceManager()->discoverDevices(mockParentDeviceClassId, ParamList()); + QCOMPARE(status, Device::DeviceErrorAsync); + spy.wait(); + QVERIFY(spy.count() == 1); + QCOMPARE(spy.first().at(0).value().toString(), mockParentDeviceClassId.toString()); + descriptors = spy.first().at(1).value >(); + QVERIFY(descriptors.count() == 1); + DeviceDescriptorId descriptorId = descriptors.first().id(); + + QSignalSpy addSpy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceAdded); + status = NymeaCore::instance()->deviceManager()->addConfiguredDevice(mockParentDeviceClassId, "Mock Parent (Discovered)", descriptorId); + QCOMPARE(status, Device::DeviceErrorNoError); + QCOMPARE(addSpy.count(), 2); // Mock device parent will also auto-create a child instantly + + Device *parentDevice = addSpy.at(1).first().value(); + qCDebug(dcTests()) << "Added device:" << parentDevice->name(); + QVERIFY(parentDevice->deviceClassId() == mockParentDeviceClassId); + + + // Ok we have our parent device, let's discover for childs again + spy.clear(); + status = NymeaCore::instance()->deviceManager()->discoverDevices(mockChildDeviceClassId, ParamList()); + QCOMPARE(status, Device::DeviceErrorAsync); + spy.wait(); + QCOMPARE(spy.first().at(0).value().toString(), mockChildDeviceClassId.toString()); + descriptors = spy.first().at(1).value >(); + QVERIFY(descriptors.count() == 1); + descriptorId = descriptors.first().id(); + + // Found one! Adding it... + addSpy.clear(); + status = NymeaCore::instance()->deviceManager()->addConfiguredDevice(mockChildDeviceClassId, "Mock Child (Discovered)", descriptorId); + QCOMPARE(status, Device::DeviceErrorNoError); + QCOMPARE(addSpy.count(), 1); + + Device *childDevice = addSpy.at(0).first().value(); + qCDebug(dcTests()) << "Added device:" << childDevice->name(); + QVERIFY(childDevice->deviceClassId() == mockChildDeviceClassId); + + // Now delete the parent and make sure the child will be deleted too + QSignalSpy removeSpy(NymeaCore::instance(), &NymeaCore::deviceRemoved); + QPair > ret = NymeaCore::instance()->removeConfiguredDevice(parentDevice->id(), QHash()); + QCOMPARE(ret.first, Device::DeviceErrorNoError); + QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock + +} + #include "testdevices.moc" QTEST_MAIN(TestDevices) From 933d92ab06851c75196be5390bf8d556875e3427 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 17 Jun 2019 15:18:56 +0200 Subject: [PATCH 02/22] Intermediate commit (device browser) --- .../devices/devicemanagerimplementation.cpp | 8 +++++++ .../devices/devicemanagerimplementation.h | 2 ++ libnymea-core/jsonrpc/devicehandler.cpp | 15 +++++++++++++ libnymea-core/jsonrpc/devicehandler.h | 2 ++ libnymea/devices/devicemanager.h | 3 +++ libnymea/libnymea.pro | 2 ++ libnymea/types/browseritem.cpp | 6 ++++++ libnymea/types/browseritem.h | 21 +++++++++++++++++++ libnymea/typeutils.h | 2 ++ 9 files changed, 61 insertions(+) create mode 100644 libnymea/types/browseritem.cpp create mode 100644 libnymea/types/browseritem.h diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 44f06a09..63901ab8 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -710,6 +710,14 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De return Device::DeviceErrorNoError; } +BrowserItems DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node) +{ + Q_UNUSED(deviceId) + Q_UNUSED(browser) + Q_UNUSED(node) + return BrowserItems(); +} + QString DeviceManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale) { return m_translator->translate(pluginId, string, locale); diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 5b4aa43d..66e2c7f6 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -97,6 +97,8 @@ public: Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) override; + BrowserItems browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node = BrowserItemId()) override; + QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; signals: diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 1de015e7..85f57abe 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -665,6 +665,21 @@ JsonReply *DeviceHandler::GetStateValues(const QVariantMap ¶ms) const return createReply(returns); } +JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const +{ + QVariantMap returns; + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + QString browser = params.value("browser").toString(); + + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(deviceId); + if (!device) { + returns.insert("deviceError", JsonTypes::deviceErrorToString(Device::DeviceErrorDeviceNotFound)); + return createReply(returns); + } + NymeaCore::instance()->deviceManager()->browseDevice(device->id(), browser); + return createReply(returns); +} + void DeviceHandler::pluginConfigChanged(const PluginId &id, const ParamList &config) { QVariantMap params; diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index 7d8af31d..d0994ef3 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -57,6 +57,8 @@ public: Q_INVOKABLE JsonReply *GetStateValue(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap ¶ms) const; + signals: void PluginConfigurationChanged(const QVariantMap ¶ms); void StateChanged(const QVariantMap ¶ms); diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index 14c3d793..aee6693a 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -29,6 +29,7 @@ #include "deviceplugin.h" #include "types/interface.h" #include "types/vendor.h" +#include "types/browseritem.h" class DeviceManager : public QObject { @@ -69,6 +70,8 @@ public: virtual Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) = 0; + virtual BrowserItems browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node = BrowserItemId()) = 0; + virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0; signals: diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 262a8309..3702e1d5 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -15,6 +15,7 @@ HEADERS += \ libnymea.h \ platform/package.h \ platform/repository.h \ + types/browseritem.h \ typeutils.h \ loggingcategories.h \ nymeasettings.h \ @@ -110,6 +111,7 @@ SOURCES += \ coap/corelinkparser.cpp \ coap/corelink.cpp \ coap/coapobserveresource.cpp \ + types/browseritem.cpp \ types/deviceclass.cpp \ types/action.cpp \ types/actiontype.cpp \ diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp new file mode 100644 index 00000000..c95cdf36 --- /dev/null +++ b/libnymea/types/browseritem.cpp @@ -0,0 +1,6 @@ +#include "browseritem.h" + +BrowserItem::BrowserItem() +{ + +} diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h new file mode 100644 index 00000000..ad5e301c --- /dev/null +++ b/libnymea/types/browseritem.h @@ -0,0 +1,21 @@ +#ifndef BROWSERITEM_H +#define BROWSERITEM_H + +#include "libnymea.h" +#include "typeutils.h" + +#include + +class LIBNYMEA_EXPORT BrowserItem +{ +public: + BrowserItem(); +}; + + +class LIBNYMEA_EXPORT BrowserItems: public QList +{ + +}; + +#endif // BROWSERITEM_H diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index d7c91dfc..8aced417 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -60,6 +60,8 @@ DECLARE_TYPE_ID(Rule) DECLARE_TYPE_ID(PairingTransaction) +DECLARE_TYPE_ID(BrowserItem) + class LIBNYMEA_EXPORT Types { Q_GADGET From 57f68bcdc0ed0fae22715830cefa02c68847a1bf Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 4 Jul 2019 13:32:47 +0200 Subject: [PATCH 03/22] More work on adding a Device browser --- .../devices/devicemanagerimplementation.cpp | 37 +++++++++-- .../devices/devicemanagerimplementation.h | 3 +- libnymea-core/jsonrpc/devicehandler.cpp | 61 +++++++++++++++-- libnymea-core/jsonrpc/devicehandler.h | 4 ++ libnymea-core/jsonrpc/jsontypes.cpp | 40 +++++++++++ libnymea-core/jsonrpc/jsontypes.h | 5 +- libnymea/devices/device.cpp | 12 ++++ libnymea/devices/device.h | 16 ++++- libnymea/devices/devicemanager.cpp | 11 ++++ libnymea/devices/devicemanager.h | 7 +- libnymea/devices/deviceplugin.cpp | 16 +++++ libnymea/devices/deviceplugin.h | 4 ++ libnymea/devices/pluginmetadata.cpp | 3 +- libnymea/types/browseritem.cpp | 66 ++++++++++++++++++- libnymea/types/browseritem.h | 31 ++++++++- libnymea/types/deviceclass.cpp | 10 +++ libnymea/types/deviceclass.h | 4 ++ libnymea/typeutils.h | 7 +- plugins/mock/devicepluginmock.cpp | 59 +++++++++++++++++ plugins/mock/devicepluginmock.h | 5 ++ plugins/mock/devicepluginmock.json | 1 + 21 files changed, 382 insertions(+), 20 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 63901ab8..1f9a26bc 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -710,12 +710,40 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De return Device::DeviceErrorNoError; } -BrowserItems DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node) +Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &nodeId) { Q_UNUSED(deviceId) - Q_UNUSED(browser) - Q_UNUSED(node) - return BrowserItems(); + Q_UNUSED(nodeId) + + Device::BrowseResult result = createBrowseResult(); + + Device *device = m_configuredDevices.value(deviceId); + if (!device) { + qCWarning(dcDeviceManager()) << "Cannot browse device. No such device:" << deviceId.toString(); + result.status = Device::DeviceErrorDeviceNotFound; + return result; + } + + if (!device->deviceClass().browsable()) { + qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable."; + result.status = Device::DeviceErrorUnsupportedFeature; + return result; + } + + result = device->plugin()->browseDevice(device, result, nodeId); + return result; +} + +Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) +{ + Device *device = m_configuredDevices.value(deviceId); + if (!device) { + return Device::DeviceErrorDeviceNotFound; + } + if (!device->deviceClass().browsable()) { + return Device::DeviceErrorUnsupportedFeature; + } + return device->plugin()->executeBrowserItem(device, nodeId); } QString DeviceManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale) @@ -980,6 +1008,7 @@ void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const Pl connect(pluginIface, &DevicePlugin::pairingFinished, this, &DeviceManagerImplementation::slotPairingFinished); connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManagerImplementation::onAutoDevicesAppeared); connect(pluginIface, &DevicePlugin::autoDeviceDisappeared, this, &DeviceManagerImplementation::onAutoDeviceDisappeared); + connect(pluginIface, &DevicePlugin::browseRequestFinished, this, &DeviceManagerImplementation::browseRequestFinished); } diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 66e2c7f6..86df086e 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -97,7 +97,8 @@ public: Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) override; - BrowserItems browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node = BrowserItemId()) override; + Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &nodeId = QString()) override; + Device::DeviceError executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) override; QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 85f57abe..5f2deaf4 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -283,6 +283,23 @@ DeviceHandler::DeviceHandler(QObject *parent) : returns.insert("o:values", states); setReturns("GetStateValues", returns); + params.clear(); returns.clear(); + setDescription("BrowseDevice", "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("o:nodeId", JsonTypes::basicTypeToString(JsonTypes::String)); + setParams("BrowseDevice", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + returns.insert("items", QVariantList() << JsonTypes::browserItemRef()); + setReturns("BrowseDevice", returns); + + params.clear(); returns.clear(); + setDescription("ExecuteBrowserItem", "Execute the item identified by nodeId on the given device."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("o:nodeId", JsonTypes::basicTypeToString(JsonTypes::String)); + setParams("ExecuteBrowserItem", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + setReturns("ExecuteBrowserItem", returns); + // Notifications params.clear(); returns.clear(); setDescription("StateChanged", "Emitted whenever a State of a device changes."); @@ -329,6 +346,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : connect(NymeaCore::instance(), &NymeaCore::deviceSetupFinished, this, &DeviceHandler::deviceSetupFinished); connect(NymeaCore::instance(), &NymeaCore::deviceReconfigurationFinished, this, &DeviceHandler::deviceReconfigurationFinished); connect(NymeaCore::instance(), &NymeaCore::pairingFinished, this, &DeviceHandler::pairingFinished); + connect(NymeaCore::instance()->deviceManager(), &DeviceManager::browseRequestFinished, this, &DeviceHandler::browseRequestFinished); } /*! Returns the name of the \l{DeviceHandler}. In this case \b Devices.*/ @@ -669,14 +687,31 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const { QVariantMap returns; DeviceId deviceId = DeviceId(params.value("deviceId").toString()); - QString browser = params.value("browser").toString(); + QString nodeId = params.value("nodeId").toString(); - Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(deviceId); - if (!device) { - returns.insert("deviceError", JsonTypes::deviceErrorToString(Device::DeviceErrorDeviceNotFound)); - return createReply(returns); + Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, nodeId); + + if (result.status == Device::DeviceErrorAsync ) { + JsonReply *reply = createAsyncReply("BrowseDevice"); + m_asyncBrowseRequests.insert(result.id, reply); + connect(reply, &JsonReply::finished, this, [this, result](){ + m_asyncBrowseRequests.remove(result.id); + }); + return reply; } - NymeaCore::instance()->deviceManager()->browseDevice(device->id(), browser); + + returns.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); + returns.insert("items", JsonTypes::packBrowserItems(result.items)); + return createReply(returns); +} + +JsonReply *DeviceHandler::ExecuteBrowserItem(const QVariantMap ¶ms) +{ + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + QString nodeId = params.value("nodeId").toString(); + Device::DeviceError status = NymeaCore::instance()->deviceManager()->executeBrowserItem(deviceId, nodeId); + QVariantMap returns; + returns.insert("deviceError", JsonTypes::deviceErrorToString(status)); return createReply(returns); } @@ -805,4 +840,18 @@ void DeviceHandler::pairingFinished(const PairingTransactionId &pairingTransacti m_asynDeviceAdditions.insert(deviceId, reply); } +void DeviceHandler::browseRequestFinished(const Device::BrowseResult &result) +{ + if (!m_asyncBrowseRequests.contains(result.id)) { + qCWarning(dcJsonRpc()) << "No pending JsonRpc reply. Did it time out?"; + return; + } + JsonReply *reply = m_asyncBrowseRequests.take(result.id); + QVariantMap params; + params.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); + params.insert("items", JsonTypes::packBrowserItems(result.items)); + reply->setData(params); + reply->finished(); +} + } diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index d0994ef3..217de3ec 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -58,6 +58,7 @@ public: Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms); signals: void PluginConfigurationChanged(const QVariantMap ¶ms); @@ -88,12 +89,15 @@ private slots: void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId); + void browseRequestFinished(const Device::BrowseResult &result); + private: // A cache for async replies mutable QHash m_discoverRequests; mutable QHash m_asynDeviceAdditions; mutable QHash m_asynDeviceEditAdditions; mutable QHash m_asyncPairingRequests; + mutable QHash m_asyncBrowseRequests; }; } diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index 089175de..e2cd7d37 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -127,6 +127,7 @@ QVariantMap JsonTypes::s_tag; QVariantMap JsonTypes::s_mqttPolicy; QVariantMap JsonTypes::s_package; QVariantMap JsonTypes::s_repository; +QVariantMap JsonTypes::s_browserItem; void JsonTypes::init() { @@ -273,6 +274,7 @@ void JsonTypes::init() s_deviceClass.insert("name", basicTypeToString(String)); s_deviceClass.insert("displayName", basicTypeToString(String)); s_deviceClass.insert("interfaces", QVariantList() << basicTypeToString(String)); + s_deviceClass.insert("browsable", basicTypeToString(Bool)); s_deviceClass.insert("setupMethod", setupMethodRef()); s_deviceClass.insert("createMethods", QVariantList() << createMethodRef()); s_deviceClass.insert("stateTypes", QVariantList() << stateTypeRef()); @@ -403,6 +405,7 @@ void JsonTypes::init() s_tag.insert("tagId", basicTypeToString(QVariant::String)); s_tag.insert("o:value", basicTypeToString(QVariant::String)); + // Package s_package.insert("id", basicTypeToString(QVariant::String)); s_package.insert("displayName", basicTypeToString(QVariant::String)); s_package.insert("summary", basicTypeToString(QVariant::String)); @@ -413,10 +416,19 @@ void JsonTypes::init() s_package.insert("rollbackAvailable", basicTypeToString(QVariant::Bool)); s_package.insert("canRemove", basicTypeToString(QVariant::Bool)); + // Repository s_repository.insert("id", basicTypeToString(QVariant::String)); s_repository.insert("displayName", basicTypeToString(QVariant::String)); s_repository.insert("enabled", basicTypeToString(QVariant::Bool)); + // BrowserItem + s_browserItem.insert("id", basicTypeToString(QVariant::String)); + s_browserItem.insert("displayName", basicTypeToString(QVariant::String)); + s_browserItem.insert("description", basicTypeToString(QVariant::String)); + s_browserItem.insert("thumbnail", basicTypeToString(QVariant::String)); + s_browserItem.insert("executable", basicTypeToString(QVariant::Bool)); + s_browserItem.insert("browsable", basicTypeToString(QVariant::Bool)); + s_initialized = true; } @@ -706,6 +718,18 @@ QVariantMap JsonTypes::packParam(const Param ¶m) return variantMap; } +QVariantMap JsonTypes::packBrowserItem(const BrowserItem &item) +{ + QVariantMap ret; + ret.insert("id", item.id()); + ret.insert("displayName", item.displayName()); + ret.insert("description", item.description()); + ret.insert("thumbnail", item.thumbnail()); + ret.insert("executable", item.executable()); + ret.insert("browsable", item.browsable()); + return ret; +} + QVariantList JsonTypes::packParams(const ParamList ¶mList) { QVariantList ret; @@ -790,6 +814,7 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo variant.insert("vendorId", deviceClass.vendorId().toString()); variant.insert("pluginId", deviceClass.pluginId().toString()); variant.insert("interfaces", deviceClass.interfaces()); + variant.insert("browsable", deviceClass.browsable()); QVariantList stateTypes; foreach (const StateType &stateType, deviceClass.stateTypes()) @@ -1186,6 +1211,15 @@ QVariantList JsonTypes::packDeviceDescriptors(const QList devi return deviceDescriptorList; } +QVariantList JsonTypes::packBrowserItems(const BrowserItems &items) +{ + QVariantList ret; + foreach (const BrowserItem &item, items) { + ret.append(packBrowserItem(item)); + } + return ret; +} + /*! Returns a variant map with the current basic configuration of the server. */ QVariantMap JsonTypes::packBasicConfiguration() { @@ -2080,6 +2114,12 @@ QPair JsonTypes::validateVariant(const QVariant &templateVariant, qCWarning(dcJsonRpc) << "Repository not matching"; return result; } + } else if (refName == browserItemRef()) { + QPair result = validateMap(browserItemDescription(), variant.toMap()); + if (!result.first) { + qCWarning(dcJsonRpc) << "BrowserItem not matching"; + return result; + } } else if (refName == basicTypeRef()) { QPair result = validateBasicType(variant); if (!result.first) { diff --git a/libnymea-core/jsonrpc/jsontypes.h b/libnymea-core/jsonrpc/jsontypes.h index cacb1636..33ac9e2d 100644 --- a/libnymea-core/jsonrpc/jsontypes.h +++ b/libnymea-core/jsonrpc/jsontypes.h @@ -180,6 +180,7 @@ public: DECLARE_OBJECT(mqttPolicy, "MqttPolicy") DECLARE_OBJECT(package, "Package") DECLARE_OBJECT(repository, "Repository") + DECLARE_OBJECT(browserItem, "BrowserItem") // pack types static QVariantMap packEventType(const EventType &eventType, const PluginId &pluginId, const QLocale &locale); @@ -194,7 +195,7 @@ public: static QVariantMap packStateDescriptor(const StateDescriptor &stateDescriptor); static QVariantMap packStateEvaluator(const StateEvaluator &stateEvaluator); static QVariantMap packParam(const Param ¶m); - static QVariantList packParams(const ParamList ¶mList); + static QVariantMap packBrowserItem(const BrowserItem &item); static QVariantMap packParamType(const ParamType ¶mType, const PluginId &pluginId, const QLocale &locale); static QVariantMap packParamDescriptor(const ParamDescriptor ¶mDescriptor); static QVariantMap packVendor(const Vendor &vendor, const QLocale &locale); @@ -214,6 +215,8 @@ public: static QVariantMap packWiredNetworkDevice(WiredNetworkDevice *networkDevice); static QVariantMap packWirelessNetworkDevice(WirelessNetworkDevice *networkDevice); + static QVariantList packParams(const ParamList ¶mList); + static QVariantList packBrowserItems(const BrowserItems &items); static QVariantList packRules(const QList rules); static QVariantList packCreateMethods(DeviceClass::CreateMethods createMethods); static QVariantList packSupportedVendors(const QLocale &locale); diff --git a/libnymea/devices/device.cpp b/libnymea/devices/device.cpp index 1de3160e..a0b24ada 100644 --- a/libnymea/devices/device.cpp +++ b/libnymea/devices/device.cpp @@ -154,6 +154,18 @@ PluginId Device::pluginId() const return m_plugin->pluginId(); } +/*! Returns the \l{DeviceClass} of this device. */ +DeviceClass Device::deviceClass() const +{ + return m_deviceClass; +} + +/*! Returns the the \l{DevicePlugin} this Device is managed by. */ +DevicePlugin *Device::plugin() const +{ + return m_plugin; +} + /*! Returns the name of this Device. This is visible to the user. */ QString Device::name() const { diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index 9f91d3a8..d304fdbe 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -30,6 +30,7 @@ #include "types/deviceclass.h" #include "types/state.h" #include "types/param.h" +#include "types/browseritem.h" #include #include @@ -69,7 +70,8 @@ public: DeviceErrorDeviceInRule, DeviceErrorDeviceIsChild, DeviceErrorPairingTransactionIdNotFound, - DeviceErrorParameterNotWritable + DeviceErrorParameterNotWritable, + DeviceErrorUnsupportedFeature, }; Q_ENUM(DeviceError) @@ -80,12 +82,22 @@ public: }; Q_ENUM(DeviceSetupStatus) + class BrowseResult { + public: + QUuid id; + Device::DeviceError status = Device::DeviceErrorNoError; + BrowserItems items; + private: + BrowseResult(): id(QUuid::createUuid()) {} + friend class DeviceManager; + }; + DeviceId id() const; DeviceClassId deviceClassId() const; PluginId pluginId() const; DeviceClass deviceClass() const; - DevicePlugin* plugin(); + DevicePlugin* plugin() const; QString name() const; void setName(const QString &name); diff --git a/libnymea/devices/devicemanager.cpp b/libnymea/devices/devicemanager.cpp index a2bb77e4..e9f0f5c0 100644 --- a/libnymea/devices/devicemanager.cpp +++ b/libnymea/devices/devicemanager.cpp @@ -39,3 +39,14 @@ DeviceManager::DeviceManager(QObject *parent) : QObject(parent) { } + +Device::BrowseResult DeviceManager::createBrowseResult() +{ + Device::BrowseResult result = Device::BrowseResult(); + return result; +} + +QUuid DeviceManager::browseResultId(const Device::BrowseResult &result) +{ + return result.id; +} diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index aee6693a..ef77f1cd 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -70,7 +70,8 @@ public: virtual Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) = 0; - virtual BrowserItems browseDevice(const DeviceId &deviceId, const QString &browser, const BrowserItemId &node = BrowserItemId()) = 0; + virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &nodeId = QString()) = 0; + virtual Device::DeviceError executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) = 0; virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0; @@ -88,7 +89,11 @@ signals: void deviceReconfigurationFinished(Device *device, Device::DeviceError status); void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId = DeviceId()); void actionExecutionFinished(const ActionId &actionId, Device::DeviceError status); + void browseRequestFinished(const Device::BrowseResult &result); +protected: + Device::BrowseResult createBrowseResult(); + QUuid browseResultId(const Device::BrowseResult &result); }; #endif // DEVICEMANAGER_H diff --git a/libnymea/devices/deviceplugin.cpp b/libnymea/devices/deviceplugin.cpp index 46fc71e9..5d826aa7 100644 --- a/libnymea/devices/deviceplugin.cpp +++ b/libnymea/devices/deviceplugin.cpp @@ -248,6 +248,22 @@ Device::DeviceError DevicePlugin::executeAction(Device *device, const Action &ac return Device::DeviceErrorNoError; } +Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId) +{ + Q_UNUSED(device) + Q_UNUSED(nodeId) + + result.status = Device::DeviceErrorUnsupportedFeature; + return result; +} + +Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const QString &nodeId) +{ + Q_UNUSED(device) + Q_UNUSED(nodeId) + return Device::DeviceErrorUnsupportedFeature; +} + /*! Returns the configuration description of this DevicePlugin as a list of \l{ParamType}{ParamTypes}. */ ParamTypes DevicePlugin::configurationDescription() const { diff --git a/libnymea/devices/deviceplugin.h b/libnymea/devices/deviceplugin.h index 037a5018..29e9e0f6 100644 --- a/libnymea/devices/deviceplugin.h +++ b/libnymea/devices/deviceplugin.h @@ -78,6 +78,9 @@ public: virtual Device::DeviceError executeAction(Device *device, const Action &action); + virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()); + virtual Device::DeviceError executeBrowserItem(Device *device, const QString &nodeId); + // Configuration ParamTypes configurationDescription() const; Device::DeviceError setConfiguration(const ParamList &configuration); @@ -96,6 +99,7 @@ signals: void configValueChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); void autoDeviceDisappeared(const DeviceId &deviceId); + void browseRequestFinished(const Device::BrowseResult &result); protected: Devices myDevices() const; diff --git a/libnymea/devices/pluginmetadata.cpp b/libnymea/devices/pluginmetadata.cpp index eb7ccceb..5d11cfd2 100644 --- a/libnymea/devices/pluginmetadata.cpp +++ b/libnymea/devices/pluginmetadata.cpp @@ -162,7 +162,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) QJsonObject deviceClassObject = deviceClassJson.toObject(); /*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */ QStringList deviceClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" - << "interfaces" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes" + << "interfaces" << "browsable" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes" << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes"; QStringList mandatoryDeviceClassProperties = QStringList() << "id" << "name" << "displayName"; @@ -192,6 +192,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) DeviceClass deviceClass(pluginId(), vendorId, deviceClassId); deviceClass.setName(deviceClassName); deviceClass.setDisplayName(deviceClassObject.value("displayName").toString()); + deviceClass.setBrowsable(deviceClassObject.value("browsable").toBool()); // Read create methods DeviceClass::CreateMethods createMethods; diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index c95cdf36..7aecb491 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -1,6 +1,70 @@ #include "browseritem.h" -BrowserItem::BrowserItem() + +BrowserItem::BrowserItem(const QString &id, const QString &displayName, bool browsable): + m_id(id), + m_displayName(displayName), + m_browsable(browsable) +{ + +} + +QString BrowserItem::id() const +{ + return m_id; +} + +QString BrowserItem::displayName() const +{ + return m_displayName; +} + +QString BrowserItem::description() const +{ + return m_description; +} + +void BrowserItem::setDescription(const QString &description) +{ + m_description = description; +} + +bool BrowserItem::executable() const +{ + return m_executable; +} + +void BrowserItem::setExecutable(bool executable) +{ + m_executable = executable; +} + +bool BrowserItem::browsable() const +{ + return m_browsable; +} + +void BrowserItem::setBrowsable(bool browsable) +{ + m_browsable = browsable; +} + +QString BrowserItem::thumbnail() const +{ + return m_thumbnail; +} + +void BrowserItem::setThumbnail(const QString &thumbnail) +{ + m_thumbnail = thumbnail; +} + +BrowserItems::BrowserItems() +{ + +} + +BrowserItems::BrowserItems(const QList &other): QList(other) { } diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index ad5e301c..43dbafdc 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -9,12 +9,41 @@ class LIBNYMEA_EXPORT BrowserItem { public: - BrowserItem(); + BrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false); + + QString id() const; + void setId(const QString &id); + + QString displayName() const; + void setDisplayName(const QString &displayName); + + QString description() const; + void setDescription(const QString &description); + + bool executable() const; + void setExecutable(bool executable); + + bool browsable() const; + void setBrowsable(bool browsable); + + QString thumbnail() const; + void setThumbnail(const QString &thumbnail); + +private: + QString m_id; + QString m_displayName; + QString m_description; + bool m_executable = false; + bool m_browsable = false; + QString m_thumbnail; }; class LIBNYMEA_EXPORT BrowserItems: public QList { +public: + BrowserItems(); + BrowserItems(const QList &other); }; diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index c7641d9d..08dbb71f 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -308,6 +308,16 @@ void DeviceClass::setInterfaces(const QStringList &interfaces) m_interfaces = interfaces; } +bool DeviceClass::browsable() const +{ + return m_browsable; +} + +void DeviceClass::setBrowsable(bool browsable) +{ + m_browsable = browsable; +} + /*! Compare this \a deviceClass to another. This is effectively the same as calling a.id() == b.id(). Returns true if the ids match.*/ bool DeviceClass::operator==(const DeviceClass &deviceClass) const { diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index cd5bf07d..111888ea 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -105,6 +105,9 @@ public: QStringList interfaces() const; void setInterfaces(const QStringList &interfaces); + bool browsable() const; + void setBrowsable(bool browsable); + bool operator==(const DeviceClass &device) const; private: @@ -113,6 +116,7 @@ private: PluginId m_pluginId; QString m_name; QString m_displayName; + bool m_browsable = false; StateTypes m_stateTypes; EventTypes m_eventTypes; ActionTypes m_actionTypes; diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index 8aced417..2066d7a2 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -57,11 +57,10 @@ DECLARE_TYPE_ID(ActionType) DECLARE_TYPE_ID(Action) DECLARE_TYPE_ID(Plugin) DECLARE_TYPE_ID(Rule) +DECLARE_TYPE_ID(Browser) DECLARE_TYPE_ID(PairingTransaction) -DECLARE_TYPE_ID(BrowserItem) - class LIBNYMEA_EXPORT Types { Q_GADGET @@ -153,6 +152,10 @@ public: }; Q_ENUM(StateOperator) + enum BrowserType { + BrowserTypeGeneric, + }; + Q_ENUM(BrowserType) }; Q_DECLARE_METATYPE(Types::InputType) diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 2c4f94e4..4bd5e398 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -239,6 +239,30 @@ Device::DeviceError DevicePluginMock::displayPin(const PairingTransactionId &pai return Device::DeviceErrorNoError; } +Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId) +{ + qCDebug(dcMockDevice()) << "Browse device called" << device; + if (device->deviceClassId() == mockDeviceClassId) { + if (device->paramValue(mockDeviceAsyncParamTypeId).toBool()) { + result.status = Device::DeviceErrorAsync; + QTimer::singleShot(1000, device, [this, device, result, nodeId]() mutable { + if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) { + result.status = Device::DeviceErrorHardwareFailure; + } else { + result = generateBrowseItems(nodeId, result); + } + emit browseRequestFinished(result); + }); + } + else if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) { + result.status = Device::DeviceErrorHardwareFailure; + } else { + result = generateBrowseItems(nodeId, result); + } + } + return result; +} + Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action &action) { if (!myDevices().contains(device)) @@ -566,3 +590,38 @@ void DevicePluginMock::onPluginConfigChanged() { } + +Device::BrowseResult DevicePluginMock::generateBrowseItems(const QString &nodeId, Device::BrowseResult result) +{ + result.status = Device::DeviceErrorNoError; + + if (nodeId.isEmpty()) { + result.items.append(BrowserItem("0", "Item 0", true)); + result.items.append(BrowserItem("1", "Item 1")); + result.items.append(BrowserItem("2", "Item 2", true)); + result.items.append(BrowserItem("3", "Item 3")); + result.items.append(BrowserItem("4", "Item 4")); + } + else if (nodeId == "0") { + result.items.append(BrowserItem("5", "Item 5")); + result.items.append(BrowserItem("6", "Item 6")); + result.items.append(BrowserItem("7", "Item 7")); + result.items.append(BrowserItem("8", "Item 8")); + result.items.append(BrowserItem("9", "Item 9")); + } + else if (nodeId == "2") { + result.items.append(BrowserItem("10", "Item 10", true)); + result.items.append(BrowserItem("11", "Item 11")); + result.items.append(BrowserItem("12", "Item 12")); + result.items.append(BrowserItem("13", "Item 13")); + result.items.append(BrowserItem("14", "Item 14")); + } + else if (nodeId == "10") { + result.items.append(BrowserItem("15", "Item 15")); + result.items.append(BrowserItem("16", "Item 16")); + } else { + result.status = Device::DeviceErrorInvalidParameter; + } + + return result; +} diff --git a/plugins/mock/devicepluginmock.h b/plugins/mock/devicepluginmock.h index 49beefa8..2081fa24 100644 --- a/plugins/mock/devicepluginmock.h +++ b/plugins/mock/devicepluginmock.h @@ -52,6 +52,8 @@ public: Device::DeviceSetupStatus confirmPairing(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const ParamList ¶ms, const QString &secret) override; Device::DeviceError displayPin(const PairingTransactionId &pairingTransactionId, const DeviceDescriptor &deviceDescriptor) override; + Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()) override; + public slots: Device::DeviceError executeAction(Device *device, const Action &action) override; @@ -72,6 +74,9 @@ private slots: void onChildDeviceDiscovered(const DeviceId &parentId); void onPluginConfigChanged(); +private: + Device::BrowseResult generateBrowseItems(const QString &nodeId, Device::BrowseResult result); + private: QHash m_daemons; QList m_asyncSetupDevices; diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 0ea2282b..168f14ce 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -32,6 +32,7 @@ "displayName": "Mock Device", "interfaces": ["system", "light", "battery"], "createMethods": ["user", "discovery"], + "browsable": true, "discoveryParamTypes": [ { "id": "d222adb4-2f9c-4c3f-8655-76400d0fb6ce", From a385188d37c0174b40f96e561efa23935d490bd0 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Jul 2019 13:11:21 +0200 Subject: [PATCH 04/22] More work on the device browser --- libnymea-core/jsonrpc/jsontypes.cpp | 27 ++++++++++++++ libnymea-core/jsonrpc/jsontypes.h | 11 +++--- libnymea-core/logging/logging.h | 12 +++---- libnymea-core/networkmanager/networkdevice.h | 8 ++--- libnymea-core/networkmanager/networkmanager.h | 8 ++--- libnymea-core/nymeaconfiguration.h | 2 +- libnymea-core/ruleengine/ruleengine.h | 4 +-- libnymea-core/servers/tcpserver.cpp | 3 +- libnymea-core/time/repeatingoption.h | 2 +- libnymea-core/usermanager/usermanager.h | 2 +- libnymea/coap/coapoption.h | 2 +- libnymea/libnymea.pro | 6 ++-- libnymea/types/browseritem.cpp | 25 +++++++++++++ libnymea/types/browseritem.h | 36 +++++++++++++++++++ libnymea/types/deviceclass.h | 6 ++-- 15 files changed, 122 insertions(+), 32 deletions(-) diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index e2cd7d37..4e8bc131 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -58,6 +58,8 @@ #include "loggingcategories.h" #include "logging/logvaluetool.h" +#include "types/mediabrowseritem.h" + #include #include #include @@ -90,6 +92,8 @@ QVariantList JsonTypes::s_networkDeviceState; QVariantList JsonTypes::s_userError; QVariantList JsonTypes::s_tagError; QVariantList JsonTypes::s_cloudConnectionState; +QVariantList JsonTypes::s_browserIcon; +QVariantList JsonTypes::s_mediaBrowserIcon; QVariantMap JsonTypes::s_paramType; QVariantMap JsonTypes::s_param; @@ -154,6 +158,8 @@ void JsonTypes::init() s_userError = enumToStrings(UserManager::staticMetaObject, "UserError"); s_tagError = enumToStrings(TagsStorage::staticMetaObject, "TagError"); s_cloudConnectionState = enumToStrings(CloudManager::staticMetaObject, "CloudConnectionState"); + s_browserIcon = enumToStrings(BrowserItem::staticMetaObject, "BrowserIcon"); + s_mediaBrowserIcon = enumToStrings(MediaBrowserItem::staticMetaObject, "MediaBrowserIcon"); // ParamType s_paramType.insert("id", basicTypeToString(Uuid)); @@ -425,9 +431,11 @@ void JsonTypes::init() s_browserItem.insert("id", basicTypeToString(QVariant::String)); s_browserItem.insert("displayName", basicTypeToString(QVariant::String)); s_browserItem.insert("description", basicTypeToString(QVariant::String)); + s_browserItem.insert("icon", browserIconRef()); s_browserItem.insert("thumbnail", basicTypeToString(QVariant::String)); s_browserItem.insert("executable", basicTypeToString(QVariant::Bool)); s_browserItem.insert("browsable", basicTypeToString(QVariant::Bool)); + s_browserItem.insert("o:mediaIcon", mediaBrowserIconRef()); s_initialized = true; } @@ -477,6 +485,8 @@ QVariantMap JsonTypes::allTypes() allTypes.insert("UserError", userError()); allTypes.insert("TagError", tagError()); allTypes.insert("CloudConnectionState", cloudConnectionState()); + allTypes.insert("BrowserIcon", browserIconRef()); + allTypes.insert("MediaBrowserIcon", mediaBrowserIconRef()); allTypes.insert("StateType", stateTypeDescription()); allTypes.insert("StateDescriptor", stateDescriptorDescription()); @@ -513,6 +523,7 @@ QVariantMap JsonTypes::allTypes() allTypes.insert("MqttPolicy", mqttPolicyDescription()); allTypes.insert("Package", packageDescription()); allTypes.insert("Repository", repositoryDescription()); + allTypes.insert("BrowserItem", browserItemDescription()); return allTypes; } @@ -724,6 +735,10 @@ QVariantMap JsonTypes::packBrowserItem(const BrowserItem &item) ret.insert("id", item.id()); ret.insert("displayName", item.displayName()); ret.insert("description", item.description()); + ret.insert("icon", browserIconToString(item.icon())); + if (item.extendedPropertiesFlags().testFlag(BrowserItem::ExtendedPropertiesMedia)) { + ret.insert("mediaIcon", mediaBrowserIconToString(static_cast(item.extendedProperty("mediaIcon").toInt()))); + } ret.insert("thumbnail", item.thumbnail()); ret.insert("executable", item.executable()); ret.insert("browsable", item.browsable()); @@ -2252,6 +2267,18 @@ QPair JsonTypes::validateVariant(const QVariant &templateVariant, qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(cloudConnectionStateRef()); return result; } + } else if (refName == browserIconRef()) { + QPair result = validateEnum(s_browserIcon, variant); + if (!result.first) { + qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(browserIconRef()); + return result; + } + } else if (refName == mediaBrowserIconRef()) { + QPair result = validateEnum(s_mediaBrowserIcon, variant); + if (!result.first) { + qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(mediaBrowserIconRef()); + return result; + } } else { Q_ASSERT_X(false, "JsonTypes", QString("Unhandled ref: %1").arg(refName).toLatin1().data()); return report(false, QString("Unhandled ref %1. Server implementation incomplete.").arg(refName)); diff --git a/libnymea-core/jsonrpc/jsontypes.h b/libnymea-core/jsonrpc/jsontypes.h index 33ac9e2d..7f7e7f20 100644 --- a/libnymea-core/jsonrpc/jsontypes.h +++ b/libnymea-core/jsonrpc/jsontypes.h @@ -37,6 +37,7 @@ #include "types/paramtype.h" #include "types/paramdescriptor.h" #include "types/ruleactionparam.h" +#include "types/mediabrowseritem.h" #include "logging/logging.h" #include "logging/logentry.h" @@ -90,10 +91,8 @@ namespace nymeaserver { return s_##typeName; \ } \ static QString typeName##ToString(className::enumName value) { \ - const QMetaObject &metaObject = className::staticMetaObject; \ - int enumIndex = metaObject.indexOfEnumerator(enumString); \ - QMetaEnum metaEnum = metaObject.enumerator(enumIndex); \ - return metaEnum.valueToKey(metaEnum.value(value)); \ + QMetaEnum metaEnum = QMetaEnum::fromType(); \ + return metaEnum.valueToKey(value); \ } \ private: \ static QVariantList s_##typeName; \ @@ -102,7 +101,6 @@ namespace nymeaserver { class JsonTypes { Q_GADGET - Q_ENUMS(BasicType) public: enum BasicType { @@ -118,6 +116,7 @@ public: Time, Object }; + Q_ENUM(BasicType) static QVariantMap allTypes(); @@ -143,6 +142,8 @@ public: DECLARE_TYPE(userError, "UserError", UserManager, UserError) DECLARE_TYPE(tagError, "TagError", TagsStorage, TagError) DECLARE_TYPE(cloudConnectionState, "CloudConnectionState", CloudManager, CloudConnectionState) + DECLARE_TYPE(browserIcon, "BrowserIcon", BrowserItem, BrowserIcon) + DECLARE_TYPE(mediaBrowserIcon, "MediaBrowserIcon", MediaBrowserItem, MediaBrowserIcon) DECLARE_OBJECT(paramType, "ParamType") DECLARE_OBJECT(param, "Param") diff --git a/libnymea-core/logging/logging.h b/libnymea-core/logging/logging.h index 672c171d..e07d3622 100644 --- a/libnymea-core/logging/logging.h +++ b/libnymea-core/logging/logging.h @@ -28,11 +28,6 @@ namespace nymeaserver { class Logging { Q_GADGET - Q_ENUMS(LoggingError) - Q_ENUMS(LoggingSource) - Q_FLAGS(LoggingSources) - Q_ENUMS(LoggingLevel) - Q_ENUMS(LoggingEventType) public: enum LoggingError { @@ -40,6 +35,7 @@ public: LoggingErrorLogEntryNotFound, LoggingErrorInvalidFilterParameter }; + Q_ENUM(LoggingError) enum LoggingSource { LoggingSourceSystem, @@ -48,12 +44,15 @@ public: LoggingSourceStates, LoggingSourceRules }; + Q_ENUM(LoggingSource) + Q_FLAGS(LoggingSources) Q_DECLARE_FLAGS(LoggingSources, LoggingSource) enum LoggingLevel { LoggingLevelInfo, LoggingLevelAlert }; + Q_ENUM(LoggingLevel) enum LoggingEventType { LoggingEventTypeTrigger, @@ -62,8 +61,9 @@ public: LoggingEventTypeActionsExecuted, LoggingEventTypeExitActionsExecuted }; + Q_ENUM(LoggingEventType) - Logging(QObject *parent = 0); + Logging(QObject *parent = nullptr); }; } diff --git a/libnymea-core/networkmanager/networkdevice.h b/libnymea-core/networkmanager/networkdevice.h index 286e1c0e..384b095f 100644 --- a/libnymea-core/networkmanager/networkdevice.h +++ b/libnymea-core/networkmanager/networkdevice.h @@ -35,9 +35,6 @@ namespace nymeaserver { class NetworkDevice : public QObject { Q_OBJECT - Q_ENUMS(NetworkDeviceType) - Q_ENUMS(NetworkDeviceState) - Q_ENUMS(NetworkDeviceStateReason) public: enum NetworkDeviceState { @@ -55,6 +52,7 @@ public: NetworkDeviceStateDeactivating = 110, NetworkDeviceStateFailed = 120 }; + Q_ENUM(NetworkDeviceState) enum NetworkDeviceStateReason { NetworkDeviceStateReasonNone = 0, @@ -121,6 +119,7 @@ public: NetworkDeviceStateReasonParentChanged = 61, NetworkDeviceStateReasonParentManagedChanged = 62 }; + Q_ENUM(NetworkDeviceStateReason) enum NetworkDeviceType { NetworkDeviceTypeUnknown = 0, @@ -143,8 +142,9 @@ public: NetworkDeviceTypeVXLan = 19, NetworkDeviceTypeVEth = 20, }; + Q_ENUM(NetworkDeviceType) - explicit NetworkDevice(const QDBusObjectPath &objectPath, QObject *parent = 0); + explicit NetworkDevice(const QDBusObjectPath &objectPath, QObject *parent = nullptr); QDBusObjectPath objectPath() const; diff --git a/libnymea-core/networkmanager/networkmanager.h b/libnymea-core/networkmanager/networkmanager.h index 2d4de4df..91f38f5d 100644 --- a/libnymea-core/networkmanager/networkmanager.h +++ b/libnymea-core/networkmanager/networkmanager.h @@ -40,9 +40,6 @@ namespace nymeaserver { class NetworkManager : public QObject { Q_OBJECT - Q_ENUMS(NetworkManagerState) - Q_ENUMS(NetworkManagerConnectivityState) - Q_ENUMS(NetworkManagerError) public: enum NetworkManagerState { @@ -55,6 +52,7 @@ public: NetworkManagerStateConnectedSite = 60, NetworkManagerStateConnectedGlobal = 70 }; + Q_ENUM(NetworkManagerState) enum NetworkManagerConnectivityState { NetworkManagerConnectivityStateUnknown = 0, @@ -63,6 +61,7 @@ public: NetworkManagerConnectivityStateLimited = 3, NetworkManagerConnectivityStateFull = 4 }; + Q_ENUM(NetworkManagerConnectivityState) enum NetworkManagerError { NetworkManagerErrorNoError, @@ -76,8 +75,9 @@ public: NetworkManagerErrorNetworkingDisabled, NetworkManagerErrorNetworkManagerNotAvailable }; + Q_ENUM(NetworkManagerError) - explicit NetworkManager(QObject *parent = 0); + explicit NetworkManager(QObject *parent = nullptr); bool available(); bool wifiAvailable(); diff --git a/libnymea-core/nymeaconfiguration.h b/libnymea-core/nymeaconfiguration.h index 37e7195f..e6ada65b 100644 --- a/libnymea-core/nymeaconfiguration.h +++ b/libnymea-core/nymeaconfiguration.h @@ -69,7 +69,6 @@ typedef QList MqttPolicies; class NymeaConfiguration : public QObject { Q_OBJECT - Q_ENUMS(ConfigurationError) public: enum ConfigurationError { @@ -82,6 +81,7 @@ public: ConfigurationErrorBluetoothHardwareNotAvailable, ConfigurationErrorInvalidCertificate }; + Q_ENUM(ConfigurationError) explicit NymeaConfiguration(QObject *parent = nullptr); diff --git a/libnymea-core/ruleengine/ruleengine.h b/libnymea-core/ruleengine/ruleengine.h index 7917044c..00a67dd2 100644 --- a/libnymea-core/ruleengine/ruleengine.h +++ b/libnymea-core/ruleengine/ruleengine.h @@ -36,8 +36,6 @@ namespace nymeaserver { class RuleEngine : public QObject { Q_OBJECT - Q_ENUMS(RuleError) - Q_ENUMS(RemovePolicy) public: enum RuleError { RuleErrorNoError, @@ -62,11 +60,13 @@ public: RuleErrorNoExitActions, RuleErrorInterfaceNotFound }; + Q_ENUM(RuleError) enum RemovePolicy { RemovePolicyCascade, RemovePolicyUpdate }; + Q_ENUM(RemovePolicy) explicit RuleEngine(QObject *parent = nullptr); ~RuleEngine(); diff --git a/libnymea-core/servers/tcpserver.cpp b/libnymea-core/servers/tcpserver.cpp index 150d54f9..ca895a06 100644 --- a/libnymea-core/servers/tcpserver.cpp +++ b/libnymea-core/servers/tcpserver.cpp @@ -117,9 +117,10 @@ void TcpServer::sendData(const QUuid &clientId, const QByteArray &data) QTcpSocket *client = nullptr; client = m_clientList.value(clientId); if (client) { + qCDebug(dcTcpServer()) << "Sending to client" << clientId.toString() << data; client->write(data + '\n'); } else { - qWarning(dcTcpServer()) << "Client" << clientId << "unknown to this transport"; + qCWarning(dcTcpServer()) << "Client" << clientId << "unknown to this transport"; } } diff --git a/libnymea-core/time/repeatingoption.h b/libnymea-core/time/repeatingoption.h index 74cfdfc2..28055546 100644 --- a/libnymea-core/time/repeatingoption.h +++ b/libnymea-core/time/repeatingoption.h @@ -31,7 +31,6 @@ namespace nymeaserver { class RepeatingOption { Q_GADGET - Q_ENUMS(RepeatingMode) public: enum RepeatingMode { @@ -42,6 +41,7 @@ public: RepeatingModeMonthly, RepeatingModeYearly }; + Q_ENUM(RepeatingMode) RepeatingOption(); RepeatingOption(const RepeatingMode &mode, const QList &weekDays = QList(), const QList &monthDays = QList()); diff --git a/libnymea-core/usermanager/usermanager.h b/libnymea-core/usermanager/usermanager.h index cb2e59a5..73bc2dab 100644 --- a/libnymea-core/usermanager/usermanager.h +++ b/libnymea-core/usermanager/usermanager.h @@ -33,7 +33,6 @@ class PushButtonDBusService; class UserManager : public QObject { Q_OBJECT - Q_ENUMS(UserError) public: enum UserError { UserErrorNoError, @@ -44,6 +43,7 @@ public: UserErrorTokenNotFound, UserErrorPermissionDenied }; + Q_ENUM(UserError) explicit UserManager(const QString &dbName, QObject *parent = nullptr); diff --git a/libnymea/coap/coapoption.h b/libnymea/coap/coapoption.h index 1629b8fe..afb4b537 100644 --- a/libnymea/coap/coapoption.h +++ b/libnymea/coap/coapoption.h @@ -30,7 +30,6 @@ class LIBNYMEA_EXPORT CoapOption { Q_GADGET - Q_ENUMS(Option) public: // Options format: https://tools.ietf.org/html/rfc7252#section-3.1 @@ -54,6 +53,7 @@ public: ProxyScheme = 39, Size1 = 60 }; + Q_ENUM(Option) CoapOption(); diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 3702e1d5..4e06ec2a 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -15,7 +15,8 @@ HEADERS += \ libnymea.h \ platform/package.h \ platform/repository.h \ - types/browseritem.h \ + types/browseritem.h \ + types/mediabrowseritem.h \ typeutils.h \ loggingcategories.h \ nymeasettings.h \ @@ -111,7 +112,8 @@ SOURCES += \ coap/corelinkparser.cpp \ coap/corelink.cpp \ coap/coapobserveresource.cpp \ - types/browseritem.cpp \ + types/browseritem.cpp \ + types/mediabrowseritem.cpp \ types/deviceclass.cpp \ types/action.cpp \ types/actiontype.cpp \ diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index 7aecb491..5a3f1c7b 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -19,6 +19,11 @@ QString BrowserItem::displayName() const return m_displayName; } +void BrowserItem::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + QString BrowserItem::description() const { return m_description; @@ -49,6 +54,16 @@ void BrowserItem::setBrowsable(bool browsable) m_browsable = browsable; } +BrowserItem::BrowserIcon BrowserItem::icon() const +{ + return m_icon; +} + +void BrowserItem::setIcon(BrowserIcon icon) +{ + m_icon = icon; +} + QString BrowserItem::thumbnail() const { return m_thumbnail; @@ -59,6 +74,16 @@ void BrowserItem::setThumbnail(const QString &thumbnail) m_thumbnail = thumbnail; } +BrowserItem::ExtendedPropertiesFlags BrowserItem::extendedPropertiesFlags() const +{ + return m_extendedPropertiesFlags; +} + +QVariant BrowserItem::extendedProperty(const QString &propertyName) const +{ + return m_extendedProperties[propertyName]; +} + BrowserItems::BrowserItems() { diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index 43dbafdc..c0d516a1 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -8,7 +8,30 @@ class LIBNYMEA_EXPORT BrowserItem { + Q_GADGET public: + enum BrowserIcon { + BrowserIconNone, + BrowserIconFolder, + BrowserIconFile, + BrowserIconMusic, + BrowserIconVideo, + BrowserIconPictures, + BrowserIconApplication, + BrowserIconDocument, + BrowserIconPackage, + BrowserIconFavorites, + }; + Q_ENUM(BrowserIcon) + + enum ExtendedProperties { + ExtendedPropertiesNone = 0x00, + ExtendedPropertiesMedia = 0x01 + }; + Q_ENUM(ExtendedProperties) + Q_DECLARE_FLAGS(ExtendedPropertiesFlags, ExtendedProperties) + + BrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false); QString id() const; @@ -26,18 +49,31 @@ public: bool browsable() const; void setBrowsable(bool browsable); + BrowserIcon icon() const; + void setIcon(BrowserIcon icon); + QString thumbnail() const; void setThumbnail(const QString &thumbnail); + ExtendedPropertiesFlags extendedPropertiesFlags() const; + QVariant extendedProperty(const QString &propertyName) const; + private: QString m_id; QString m_displayName; QString m_description; bool m_executable = false; bool m_browsable = false; + BrowserIcon m_icon = BrowserIconNone; QString m_thumbnail; + +protected: + ExtendedPropertiesFlags m_extendedPropertiesFlags = ExtendedPropertiesNone; + QHash m_extendedProperties; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(BrowserItem::ExtendedPropertiesFlags) + class LIBNYMEA_EXPORT BrowserItems: public QList { diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index 111888ea..cd89f81a 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -38,10 +38,6 @@ class LIBNYMEA_EXPORT DeviceClass { Q_GADGET - Q_ENUMS(CreateMethod) - Q_ENUMS(SetupMethod) - Q_ENUMS(BasicTag) - Q_ENUMS(CreateMethods) public: enum CreateMethod { @@ -49,6 +45,7 @@ public: CreateMethodAuto = 0x02, CreateMethodDiscovery = 0x04 }; + Q_ENUM(CreateMethod) Q_DECLARE_FLAGS(CreateMethods, CreateMethod) enum SetupMethod { @@ -57,6 +54,7 @@ public: SetupMethodEnterPin, SetupMethodPushButton }; + Q_ENUM(SetupMethod) DeviceClass(const PluginId &pluginId = PluginId(), const VendorId &vendorId = VendorId(), const DeviceClassId &id = DeviceClassId()); From 10a0a0ea6c571b4455be8632a6aaf5493c8d9474 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Jul 2019 14:07:06 +0200 Subject: [PATCH 05/22] Add missing files, copyright headers --- libnymea/types/browseritem.cpp | 22 ++++++++++ libnymea/types/browseritem.h | 22 ++++++++++ libnymea/types/mediabrowseritem.cpp | 53 +++++++++++++++++++++++ libnymea/types/mediabrowseritem.h | 67 +++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 libnymea/types/mediabrowseritem.cpp create mode 100644 libnymea/types/mediabrowseritem.h diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index 5a3f1c7b..58c745c4 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -1,3 +1,25 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "browseritem.h" diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index c0d516a1..047eeea3 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -1,3 +1,25 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef BROWSERITEM_H #define BROWSERITEM_H diff --git a/libnymea/types/mediabrowseritem.cpp b/libnymea/types/mediabrowseritem.cpp new file mode 100644 index 00000000..f0ea0264 --- /dev/null +++ b/libnymea/types/mediabrowseritem.cpp @@ -0,0 +1,53 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "mediabrowseritem.h" + +MediaBrowserItem::MediaBrowserItem(const QString &id, const QString &displayName, bool browsable): + BrowserItem(id, displayName, browsable) +{ + // Init defaults + m_extendedProperties["mediaIcon"] = static_cast(MediaBrowserIconNone); + m_extendedProperties["playCount"] = 0; + + m_extendedPropertiesFlags.setFlag(BrowserItem::ExtendedPropertiesMedia); +} + +MediaBrowserItem::MediaBrowserIcon MediaBrowserItem::mediaIcon() const +{ + return static_cast(m_extendedProperties.value("mediaIcon").toInt()); +} + +void MediaBrowserItem::setMediaIcon(MediaBrowserIcon mediaIcon) +{ + m_extendedProperties["mediaIcon"] = static_cast(mediaIcon); +} + +int MediaBrowserItem::playCount() const +{ + return m_extendedProperties.value("playCount").toInt(); +} + +void MediaBrowserItem::setPlayCount(int playCount) +{ + m_extendedProperties["playCount"] = playCount; +} diff --git a/libnymea/types/mediabrowseritem.h b/libnymea/types/mediabrowseritem.h new file mode 100644 index 00000000..69ba0f5b --- /dev/null +++ b/libnymea/types/mediabrowseritem.h @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MEDIABROWSERITEM_H +#define MEDIABROWSERITEM_H + +#include + +#include "browseritem.h" + +class MediaBrowserItem: public BrowserItem +{ + Q_GADGET +public: + enum MediaBrowserIcon { + MediaBrowserIconNone = 1, + MediaBrowserIconPlaylist = 2, + MediaBrowserIconRecentlyPlayed = 3, + MediaBrowserIconLibrary = 4, + MediaBrowserIconMusicLibrary = 5, + MediaBrowserIconVideoLibrary = 6, + MediaBrowserIconPictureLibrary = 7, + + MediaBrowserIconDisk = 100, + MediaBrowserIconUSB = 101, + MediaBrowserIconNetwork = 102, + MediaBrowserIconAux = 103, + + MediaBrowserIconSpotify = 200, + MediaBrowserIconAmazon = 201, + MediaBrowserIconTuneIn = 202, + MediaBrowserIconSiriusXM = 203, + MediaBrowserIconVTuner = 204, + MediaBrowserIconTidal = 205, + MediaBrowserIconAirable = 206, + }; + Q_ENUM(MediaBrowserIcon) + + MediaBrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false); + + MediaBrowserIcon mediaIcon() const; + void setMediaIcon(MediaBrowserIcon mediaIcon); + + int playCount() const; + void setPlayCount(int playCount); +}; + +#endif // MEDIABROWSERITEM_H From 9bfb395faee04b950f5355d9e76e74539ebd46dd Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Jul 2019 14:23:54 +0200 Subject: [PATCH 06/22] fix include --- libnymea/types/browseritem.h | 2 ++ libnymea/types/mediabrowseritem.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index 047eeea3..cce473a8 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -27,6 +27,8 @@ #include "typeutils.h" #include +#include +#include class LIBNYMEA_EXPORT BrowserItem { diff --git a/libnymea/types/mediabrowseritem.h b/libnymea/types/mediabrowseritem.h index 69ba0f5b..0d496d8e 100644 --- a/libnymea/types/mediabrowseritem.h +++ b/libnymea/types/mediabrowseritem.h @@ -23,8 +23,6 @@ #ifndef MEDIABROWSERITEM_H #define MEDIABROWSERITEM_H -#include - #include "browseritem.h" class MediaBrowserItem: public BrowserItem From 4a12b5bfc4f135174985c15ea04aa2fffa142add Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Jul 2019 15:40:00 +0200 Subject: [PATCH 07/22] compiler with older qt versions --- libnymea/types/mediabrowseritem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnymea/types/mediabrowseritem.cpp b/libnymea/types/mediabrowseritem.cpp index f0ea0264..cbc6142d 100644 --- a/libnymea/types/mediabrowseritem.cpp +++ b/libnymea/types/mediabrowseritem.cpp @@ -29,7 +29,7 @@ MediaBrowserItem::MediaBrowserItem(const QString &id, const QString &displayName m_extendedProperties["mediaIcon"] = static_cast(MediaBrowserIconNone); m_extendedProperties["playCount"] = 0; - m_extendedPropertiesFlags.setFlag(BrowserItem::ExtendedPropertiesMedia); + m_extendedPropertiesFlags = BrowserItem::ExtendedPropertiesMedia; } MediaBrowserItem::MediaBrowserIcon MediaBrowserItem::mediaIcon() const From 5006a9eb70855f2b751439fd1741dadd46b715b5 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Jul 2019 15:52:25 +0200 Subject: [PATCH 08/22] bump version, update api --- nymea.pri | 2 +- tests/auto/api.json | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/nymea.pri b/nymea.pri index 1a665797..f4700c85 100644 --- a/nymea.pri +++ b/nymea.pri @@ -3,7 +3,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=2 -JSON_PROTOCOL_VERSION_MINOR=2 +JSON_PROTOCOL_VERSION_MINOR=3 REST_API_VERSION=1 LIBNYMEA_API_VERSION_MAJOR=2 LIBNYMEA_API_VERSION_MINOR=1 diff --git a/tests/auto/api.json b/tests/auto/api.json index 448c61be..259b6320 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -2.2 +2.3 { "methods": { "Actions.ExecuteAction": { @@ -249,6 +249,19 @@ "o:deviceId": "Uuid" } }, + "Devices.BrowseDevice": { + "description": "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node.", + "params": { + "deviceId": "Uuid", + "o:nodeId": "String" + }, + "returns": { + "deviceError": "$ref:DeviceError", + "items": [ + "$ref:BrowserItem" + ] + } + }, "Devices.ConfirmPairing": { "description": "Confirm an ongoing pairing. In case of SetupMethodEnterPin also provide the pin in the params.", "params": { @@ -270,6 +283,16 @@ "deviceError": "$ref:DeviceError" } }, + "Devices.ExecuteBrowserItem": { + "description": "Execute the item identified by nodeId on the given device.", + "params": { + "deviceId": "Uuid", + "o:nodeId": "String" + }, + "returns": { + "deviceError": "$ref:DeviceError" + } + }, "Devices.GetActionTypes": { "description": "Get action types for a specified deviceClassId.", "params": { @@ -1351,6 +1374,17 @@ "Time", "Object" ], + "BrowserIcon": "$ref:BrowserIcon", + "BrowserItem": { + "browsable": "Bool", + "description": "String", + "displayName": "String", + "executable": "Bool", + "icon": "$ref:BrowserIcon", + "id": "String", + "o:mediaIcon": "$ref:MediaBrowserIcon", + "thumbnail": "String" + }, "CalendarItem": { "duration": "Uint", "o:datetime": "Uint", @@ -1401,6 +1435,7 @@ "actionTypes": [ "$ref:ActionType" ], + "browsable": "Bool", "createMethods": [ "$ref:CreateMethod" ], @@ -1462,7 +1497,8 @@ "DeviceErrorDeviceInRule", "DeviceErrorDeviceIsChild", "DeviceErrorPairingTransactionIdNotFound", - "DeviceErrorParameterNotWritable" + "DeviceErrorParameterNotWritable", + "DeviceErrorUnsupportedFeature" ], "Event": { "deviceId": "Uuid", @@ -1535,6 +1571,7 @@ "LoggingSourceStates", "LoggingSourceRules" ], + "MediaBrowserIcon": "$ref:MediaBrowserIcon", "MqttPolicy": { "allowedPublishTopicFilters": "StringList", "allowedSubscribeTopicFilters": "StringList", From ff337bf8b604d3e639a70a9e1ed7164ad0076c6d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 9 Jul 2019 01:37:33 +0200 Subject: [PATCH 09/22] Make browser item execution work async too --- doc/jsonrpc-api.qdoc | 5 +- .../devices/devicemanagerimplementation.cpp | 13 ++-- .../devices/devicemanagerimplementation.h | 7 +- libnymea-core/jsonrpc/actionhandler.cpp | 37 +++++++++++ libnymea-core/jsonrpc/actionhandler.h | 3 + libnymea-core/jsonrpc/devicehandler.cpp | 24 +------ libnymea-core/jsonrpc/devicehandler.h | 1 - libnymea-core/jsonrpc/jsontypes.cpp | 5 ++ libnymea-core/logging/logengine.cpp | 10 +++ libnymea-core/logging/logengine.h | 2 + libnymea-core/logging/logentry.h | 2 +- libnymea-core/logging/logging.h | 3 +- libnymea-core/nymeacore.cpp | 21 ++++++ libnymea-core/nymeacore.h | 4 ++ libnymea/devices/devicemanager.h | 8 ++- libnymea/devices/deviceplugin.cpp | 4 +- libnymea/devices/deviceplugin.h | 4 +- libnymea/libnymea.pro | 2 + libnymea/types/browseritem.cpp | 5 +- libnymea/types/browseritem.h | 4 +- libnymea/types/browseritemaction.cpp | 66 +++++++++++++++++++ libnymea/types/browseritemaction.h | 50 ++++++++++++++ plugins/mock/devicepluginmock.cpp | 59 ++++++++++++++--- plugins/mock/devicepluginmock.h | 3 +- 24 files changed, 287 insertions(+), 55 deletions(-) create mode 100644 libnymea/types/browseritemaction.cpp create mode 100644 libnymea/types/browseritemaction.h diff --git a/doc/jsonrpc-api.qdoc b/doc/jsonrpc-api.qdoc index b6370686..b0488c1a 100644 --- a/doc/jsonrpc-api.qdoc +++ b/doc/jsonrpc-api.qdoc @@ -3523,8 +3523,9 @@ See also: \l{Tag} "LoggingSourceEvents", "LoggingSourceActions", "LoggingSourceStates", - "LoggingSourceRules" - ], + "LoggingSourceRules", + "LoggingSourceBrowserActions" + ], "NetworkDeviceState": [ "NetworkDeviceStateUnknown", "NetworkDeviceStateUnmanaged", diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 1f9a26bc..b45ae186 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -710,10 +710,10 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De return Device::DeviceErrorNoError; } -Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &nodeId) +Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId) { Q_UNUSED(deviceId) - Q_UNUSED(nodeId) + Q_UNUSED(itemId) Device::BrowseResult result = createBrowseResult(); @@ -730,20 +730,20 @@ Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &d return result; } - result = device->plugin()->browseDevice(device, result, nodeId); + result = device->plugin()->browseDevice(device, result, itemId); return result; } -Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) +Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const BrowserItemAction &browserItemAction) { - Device *device = m_configuredDevices.value(deviceId); + Device *device = m_configuredDevices.value(browserItemAction.deviceId()); if (!device) { return Device::DeviceErrorDeviceNotFound; } if (!device->deviceClass().browsable()) { return Device::DeviceErrorUnsupportedFeature; } - return device->plugin()->executeBrowserItem(device, nodeId); + return device->plugin()->executeBrowserItem(device, browserItemAction); } QString DeviceManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale) @@ -1009,6 +1009,7 @@ void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const Pl connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManagerImplementation::onAutoDevicesAppeared); connect(pluginIface, &DevicePlugin::autoDeviceDisappeared, this, &DeviceManagerImplementation::onAutoDeviceDisappeared); connect(pluginIface, &DevicePlugin::browseRequestFinished, this, &DeviceManagerImplementation::browseRequestFinished); + connect(pluginIface, &DevicePlugin::browserItemExecutionFinished, this, &DeviceManagerImplementation::browserItemExecutionFinished); } diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 86df086e..99437dfe 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -97,8 +97,10 @@ public: Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) override; - Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &nodeId = QString()) override; - Device::DeviceError executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) override; + Device::DeviceError executeAction(const Action &action) override; + + Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) override; + Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction) override; QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; @@ -106,7 +108,6 @@ signals: void loaded(); public slots: - Device::DeviceError executeAction(const Action &action); void timeTick(); private slots: diff --git a/libnymea-core/jsonrpc/actionhandler.cpp b/libnymea-core/jsonrpc/actionhandler.cpp index 2105e994..bb096dae 100644 --- a/libnymea-core/jsonrpc/actionhandler.cpp +++ b/libnymea-core/jsonrpc/actionhandler.cpp @@ -62,7 +62,16 @@ ActionHandler::ActionHandler(QObject *parent) : returns.insert("o:actionType", JsonTypes::actionTypeDescription()); setReturns("GetActionType", returns); + params.clear(); returns.clear(); + setDescription("ExecuteBrowserItem", "Execute the item identified by itemId on the given device."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String)); + setParams("ExecuteBrowserItem", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + setReturns("ExecuteBrowserItem", returns); + connect(NymeaCore::instance(), &NymeaCore::actionExecuted, this, &ActionHandler::actionExecuted); + connect(NymeaCore::instance(), &NymeaCore::browserItemExecuted, this, &ActionHandler::browserItemExecuted); } /*! Returns the name of the \l{ActionHandler}. In this case \b Actions.*/ @@ -119,4 +128,32 @@ void ActionHandler::actionExecuted(const ActionId &id, Device::DeviceError statu reply->finished(); } +JsonReply *ActionHandler::ExecuteBrowserItem(const QVariantMap ¶ms) +{ + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + QString itemId = params.value("itemId").toString(); + BrowserItemAction action(deviceId, itemId); + Device::DeviceError status = NymeaCore::instance()->executeBrowserItem(action); + if (status == Device::DeviceErrorAsync) { + JsonReply *reply = createAsyncReply("ExecuteBrowserItem"); + ActionId id = action.id(); + connect(reply, &JsonReply::finished, [this, id](){ m_asyncActionExecutions.remove(id); }); + m_asyncActionExecutions.insert(id, reply); + return reply; + } + return createReply(statusToReply(status)); +} + +void ActionHandler::browserItemExecuted(const ActionId &id, Device::DeviceError status) +{ + if (!m_asyncActionExecutions.contains(id)) { + return; // Not the action we are waiting for. + } + + JsonReply *reply = m_asyncActionExecutions.take(id); + reply->setData(statusToReply(status)); + reply->finished(); +} + + } diff --git a/libnymea-core/jsonrpc/actionhandler.h b/libnymea-core/jsonrpc/actionhandler.h index d641c917..346ed920 100644 --- a/libnymea-core/jsonrpc/actionhandler.h +++ b/libnymea-core/jsonrpc/actionhandler.h @@ -38,8 +38,11 @@ public: Q_INVOKABLE JsonReply *ExecuteAction(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *GetActionType(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms); + private slots: void actionExecuted(const ActionId &id, Device::DeviceError status); + void browserItemExecuted(const ActionId &id, Device::DeviceError status); private: QHash m_asyncActionExecutions; diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 5f2deaf4..b4479818 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -286,20 +286,12 @@ DeviceHandler::DeviceHandler(QObject *parent) : params.clear(); returns.clear(); setDescription("BrowseDevice", "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node."); params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); - params.insert("o:nodeId", JsonTypes::basicTypeToString(JsonTypes::String)); + params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String)); setParams("BrowseDevice", params); returns.insert("deviceError", JsonTypes::deviceErrorRef()); returns.insert("items", QVariantList() << JsonTypes::browserItemRef()); setReturns("BrowseDevice", returns); - params.clear(); returns.clear(); - setDescription("ExecuteBrowserItem", "Execute the item identified by nodeId on the given device."); - params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); - params.insert("o:nodeId", JsonTypes::basicTypeToString(JsonTypes::String)); - setParams("ExecuteBrowserItem", params); - returns.insert("deviceError", JsonTypes::deviceErrorRef()); - setReturns("ExecuteBrowserItem", returns); - // Notifications params.clear(); returns.clear(); setDescription("StateChanged", "Emitted whenever a State of a device changes."); @@ -687,9 +679,9 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const { QVariantMap returns; DeviceId deviceId = DeviceId(params.value("deviceId").toString()); - QString nodeId = params.value("nodeId").toString(); + QString itemId = params.value("itemId").toString(); - Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, nodeId); + Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, itemId); if (result.status == Device::DeviceErrorAsync ) { JsonReply *reply = createAsyncReply("BrowseDevice"); @@ -705,16 +697,6 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const return createReply(returns); } -JsonReply *DeviceHandler::ExecuteBrowserItem(const QVariantMap ¶ms) -{ - DeviceId deviceId = DeviceId(params.value("deviceId").toString()); - QString nodeId = params.value("nodeId").toString(); - Device::DeviceError status = NymeaCore::instance()->deviceManager()->executeBrowserItem(deviceId, nodeId); - QVariantMap returns; - returns.insert("deviceError", JsonTypes::deviceErrorToString(status)); - return createReply(returns); -} - void DeviceHandler::pluginConfigChanged(const PluginId &id, const ParamList &config) { QVariantMap params; diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index 217de3ec..7ae2352a 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -58,7 +58,6 @@ public: Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap ¶ms) const; - Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms); signals: void PluginConfigurationChanged(const QVariantMap ¶ms); diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index 4e8bc131..d7666ed9 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -335,6 +335,7 @@ void JsonTypes::init() s_logEntry.insert("source", loggingSourceRef()); s_logEntry.insert("o:typeId", basicTypeToString(Uuid)); s_logEntry.insert("o:deviceId", basicTypeToString(Uuid)); + s_logEntry.insert("o:itemId", basicTypeToString(String)); s_logEntry.insert("o:value", basicTypeToString(String)); s_logEntry.insert("o:active", basicTypeToString(Bool)); s_logEntry.insert("o:eventType", loggingEventTypeRef()); @@ -993,6 +994,7 @@ QVariantMap JsonTypes::packLogEntry(const LogEntry &logEntry) case Logging::LoggingSourceActions: case Logging::LoggingSourceEvents: case Logging::LoggingSourceStates: + case Logging::LoggingSourceBrowserActions: logEntryMap.insert("errorCode", s_deviceError.at(logEntry.errorCode())); break; case Logging::LoggingSourceSystem: @@ -1016,6 +1018,9 @@ QVariantMap JsonTypes::packLogEntry(const LogEntry &logEntry) case Logging::LoggingSourceRules: logEntryMap.insert("typeId", logEntry.typeId().toString()); break; + case Logging::LoggingSourceBrowserActions: + logEntryMap.insert("itemId", logEntry.value()); + break; } return logEntryMap; diff --git a/libnymea-core/logging/logengine.cpp b/libnymea-core/logging/logengine.cpp index f17a9352..c85186a8 100644 --- a/libnymea-core/logging/logengine.cpp +++ b/libnymea-core/logging/logengine.cpp @@ -108,6 +108,8 @@ This \l{LogEntry} was created from an \l{State} which hase changed. \value LoggingSourceRules This \l{LogEntry} represents the enable/disable event from an \l{Rule}. + \value LoggingSourceBrowserActions + This \l{LogEntry} was created from a \l{BrowserItemAction}. */ #include "nymeasettings.h" @@ -312,6 +314,14 @@ void LogEngine::logAction(const Action &action, Logging::LoggingLevel level, int appendLogEntry(entry); } +void LogEngine::logBrowserAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level, int errorCode) +{ + LogEntry entry(level, Logging::LoggingSourceBrowserActions, errorCode); + entry.setDeviceId(browserItemAction.deviceId()); + entry.setValue(browserItemAction.itemId()); + appendLogEntry(entry); +} + void LogEngine::logRuleTriggered(const Rule &rule) { LogEntry entry(Logging::LoggingSourceRules); diff --git a/libnymea-core/logging/logengine.h b/libnymea-core/logging/logengine.h index 7115ecca..d115b404 100644 --- a/libnymea-core/logging/logengine.h +++ b/libnymea-core/logging/logengine.h @@ -26,6 +26,7 @@ #include "logfilter.h" #include "types/event.h" #include "types/action.h" +#include "types/browseritemaction.h" #include "ruleengine/rule.h" #include @@ -49,6 +50,7 @@ public: void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo); void logEvent(const Event &event); void logAction(const Action &action, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); + void logBrowserAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); void logRuleTriggered(const Rule &rule); void logRuleActiveChanged(const Rule &rule); void logRuleEnabledChanged(const Rule &rule, const bool &enabled); diff --git a/libnymea-core/logging/logentry.h b/libnymea-core/logging/logentry.h index f49c60ad..5cd8fb84 100644 --- a/libnymea-core/logging/logentry.h +++ b/libnymea-core/logging/logentry.h @@ -56,7 +56,7 @@ public: DeviceId deviceId() const; void setDeviceId(const DeviceId &deviceId); - // Valid for LoggingSourceStates + // Valid for LoggingSourceStates, LoggingSourceBrowserActions QVariant value() const; void setValue(const QVariant &value); diff --git a/libnymea-core/logging/logging.h b/libnymea-core/logging/logging.h index e07d3622..f4e13517 100644 --- a/libnymea-core/logging/logging.h +++ b/libnymea-core/logging/logging.h @@ -42,7 +42,8 @@ public: LoggingSourceEvents, LoggingSourceActions, LoggingSourceStates, - LoggingSourceRules + LoggingSourceRules, + LoggingSourceBrowserActions, }; Q_ENUM(LoggingSource) Q_FLAGS(LoggingSources) diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 102276f6..cb8fbfb4 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -201,6 +201,7 @@ void NymeaCore::init() { connect(m_deviceManager, &DeviceManagerImplementation::deviceRemoved, this, &NymeaCore::deviceRemoved); connect(m_deviceManager, &DeviceManagerImplementation::deviceDisappeared, this, &NymeaCore::onDeviceDisappeared); connect(m_deviceManager, &DeviceManagerImplementation::actionExecutionFinished, this, &NymeaCore::actionExecutionFinished); + connect(m_deviceManager, &DeviceManagerImplementation::browserItemExecutionFinished, this, &NymeaCore::browserItemExecutionFinished); connect(m_deviceManager, &DeviceManagerImplementation::devicesDiscovered, this, &NymeaCore::devicesDiscovered); connect(m_deviceManager, &DeviceManagerImplementation::deviceSetupFinished, this, &NymeaCore::deviceSetupFinished); connect(m_deviceManager, &DeviceManagerImplementation::deviceReconfigurationFinished, this, &NymeaCore::deviceReconfigurationFinished); @@ -447,6 +448,19 @@ Device::DeviceError NymeaCore::executeAction(const Action &action) return ret; } +Device::DeviceError NymeaCore::executeBrowserItem(const BrowserItemAction &browserItemAction) +{ + Device::DeviceError ret = m_deviceManager->executeBrowserItem(browserItemAction); + if (ret == Device::DeviceErrorNoError) { + m_logger->logBrowserAction(browserItemAction); + } else if (ret == Device::DeviceErrorAsync) { + m_pendingBrowserItemActions.insert(browserItemAction.id(), browserItemAction); + } else { + m_logger->logBrowserAction(browserItemAction, Logging::LoggingLevelAlert, ret); + } + return ret; +} + /*! Execute the given \a ruleActions. */ void NymeaCore::executeRuleActions(const QList ruleActions) { @@ -797,6 +811,13 @@ void NymeaCore::actionExecutionFinished(const ActionId &id, Device::DeviceError m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); } +void NymeaCore::browserItemExecutionFinished(const ActionId &id, Device::DeviceError status) +{ + emit browserItemExecuted(id, status); + Action action = m_pendingActions.take(id); + m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); +} + void NymeaCore::onDeviceDisappeared(const DeviceId &deviceId) { Device *device = m_deviceManager->findConfiguredDevice(deviceId); diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index f05db29f..e3d1e888 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -72,6 +72,7 @@ public: Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId, const RuleEngine::RemovePolicy &removePolicy); Device::DeviceError executeAction(const Action &action); + Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction); void executeRuleActions(const QList ruleActions); @@ -106,6 +107,7 @@ signals: void deviceChanged(Device *device); void deviceSettingChanged(const DeviceId deviceId, const ParamTypeId &settingParamTypeId, const QVariant &value); void actionExecuted(const ActionId &id, Device::DeviceError status); + void browserItemExecuted(const ActionId &id, Device::DeviceError status); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); void deviceSetupFinished(Device *device, Device::DeviceError status); @@ -139,12 +141,14 @@ private: System *m_system; QHash m_pendingActions; + QHash m_pendingBrowserItemActions; QList m_executingRules; private slots: void gotEvent(const Event &event); void onDateTimeChanged(const QDateTime &dateTime); void actionExecutionFinished(const ActionId &id, Device::DeviceError status); + void browserItemExecutionFinished(const ActionId &id, Device::DeviceError status); void onDeviceDisappeared(const DeviceId &deviceId); void deviceManagerLoaded(); diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index ef77f1cd..d1fe3290 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -30,6 +30,7 @@ #include "types/interface.h" #include "types/vendor.h" #include "types/browseritem.h" +#include "types/browseritemaction.h" class DeviceManager : public QObject { @@ -70,8 +71,10 @@ public: virtual Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId) = 0; - virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &nodeId = QString()) = 0; - virtual Device::DeviceError executeBrowserItem(const DeviceId &deviceId, const QString &nodeId) = 0; + virtual Device::DeviceError executeAction(const Action &action) = 0; + + virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) = 0; + virtual Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction) = 0; virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0; @@ -90,6 +93,7 @@ signals: void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId = DeviceId()); void actionExecutionFinished(const ActionId &actionId, Device::DeviceError status); void browseRequestFinished(const Device::BrowseResult &result); + void browserItemExecutionFinished(const ActionId &actionId, Device::DeviceError status); protected: Device::BrowseResult createBrowseResult(); diff --git a/libnymea/devices/deviceplugin.cpp b/libnymea/devices/deviceplugin.cpp index 5d826aa7..05e30e13 100644 --- a/libnymea/devices/deviceplugin.cpp +++ b/libnymea/devices/deviceplugin.cpp @@ -257,10 +257,10 @@ Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseRe return result; } -Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const QString &nodeId) +Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) { Q_UNUSED(device) - Q_UNUSED(nodeId) + Q_UNUSED(browserItemAction) return Device::DeviceErrorUnsupportedFeature; } diff --git a/libnymea/devices/deviceplugin.h b/libnymea/devices/deviceplugin.h index 29e9e0f6..3d28293d 100644 --- a/libnymea/devices/deviceplugin.h +++ b/libnymea/devices/deviceplugin.h @@ -37,6 +37,7 @@ #include "types/vendor.h" #include "types/param.h" #include "types/interface.h" +#include "types/browseritemaction.h" #include "hardwaremanager.h" @@ -79,7 +80,7 @@ public: virtual Device::DeviceError executeAction(Device *device, const Action &action); virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()); - virtual Device::DeviceError executeBrowserItem(Device *device, const QString &nodeId); + virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction); // Configuration ParamTypes configurationDescription() const; @@ -100,6 +101,7 @@ signals: void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); void autoDeviceDisappeared(const DeviceId &deviceId); void browseRequestFinished(const Device::BrowseResult &result); + void browserItemExecutionFinished(const ActionId &actionid, Device::DeviceError status); protected: Devices myDevices() const; diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 4e06ec2a..21f46c81 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -16,6 +16,7 @@ HEADERS += \ platform/package.h \ platform/repository.h \ types/browseritem.h \ + types/browseritemaction.h \ types/mediabrowseritem.h \ typeutils.h \ loggingcategories.h \ @@ -113,6 +114,7 @@ SOURCES += \ coap/corelink.cpp \ coap/coapobserveresource.cpp \ types/browseritem.cpp \ + types/browseritemaction.cpp \ types/mediabrowseritem.cpp \ types/deviceclass.cpp \ types/action.cpp \ diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index 58c745c4..77af9ae7 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -23,10 +23,11 @@ #include "browseritem.h" -BrowserItem::BrowserItem(const QString &id, const QString &displayName, bool browsable): +BrowserItem::BrowserItem(const QString &id, const QString &displayName, bool browsable, bool executable): m_id(id), m_displayName(displayName), - m_browsable(browsable) + m_browsable(browsable), + m_executable(executable) { } diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index cce473a8..7bbd14cb 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -56,7 +56,7 @@ public: Q_DECLARE_FLAGS(ExtendedPropertiesFlags, ExtendedProperties) - BrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false); + BrowserItem(const QString &id = QString(), const QString &displayName = QString(), bool browsable = false, bool executable = false); QString id() const; void setId(const QString &id); @@ -86,8 +86,8 @@ private: QString m_id; QString m_displayName; QString m_description; - bool m_executable = false; bool m_browsable = false; + bool m_executable = false; BrowserIcon m_icon = BrowserIconNone; QString m_thumbnail; diff --git a/libnymea/types/browseritemaction.cpp b/libnymea/types/browseritemaction.cpp new file mode 100644 index 00000000..e9236482 --- /dev/null +++ b/libnymea/types/browseritemaction.cpp @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "browseritemaction.h" + +BrowserItemAction::BrowserItemAction(const DeviceId &deviceId, const QString &itemId): + m_id(ActionId::createActionId()), + m_deviceId(deviceId), + m_itemId(itemId) +{ + +} + +BrowserItemAction::BrowserItemAction(const BrowserItemAction &other): + m_id(other.id()), + m_deviceId(other.deviceId()), + m_itemId(other.itemId()) +{ + +} + +ActionId BrowserItemAction::id() const +{ + return m_id; +} + +bool BrowserItemAction::isValid() const +{ + return !m_id.isNull() && !m_deviceId.isNull() && !m_itemId.isNull(); +} + +DeviceId BrowserItemAction::deviceId() const +{ + return m_deviceId; +} + +QString BrowserItemAction::itemId() const +{ + return m_itemId; +} + +void BrowserItemAction::operator=(const BrowserItemAction &other) +{ + m_id = other.id(); + m_deviceId = other.deviceId(); + m_itemId = other.itemId(); +} diff --git a/libnymea/types/browseritemaction.h b/libnymea/types/browseritemaction.h new file mode 100644 index 00000000..e5ee311d --- /dev/null +++ b/libnymea/types/browseritemaction.h @@ -0,0 +1,50 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef BROWSERITEMACTION_H +#define BROWSERITEMACTION_H + +#include "typeutils.h" + +class BrowserItemAction +{ +public: + BrowserItemAction(); + + explicit BrowserItemAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString()); + BrowserItemAction(const BrowserItemAction &other); + + ActionId id() const; + + bool isValid() const; + + DeviceId deviceId() const; + QString itemId() const; + + void operator=(const BrowserItemAction &other); +private: + ActionId m_id; + DeviceId m_deviceId; + QString m_itemId; +}; + +#endif // BROWSERITEMACTION_H diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 4bd5e398..531031dd 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -393,6 +393,24 @@ Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action return Device::DeviceErrorDeviceClassNotFound; } +Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) +{ + bool broken = device->paramValue(mockDeviceBrokenParamTypeId).toBool(); + bool async = device->paramValue(mockDeviceAsyncParamTypeId).toBool(); + + if (!async){ + if (broken) { + return Device::DeviceErrorHardwareFailure; + } + return Device::DeviceErrorNoError; + } + + QTimer::singleShot(2000, device, [this, broken, browserItemAction](){ + emit this->browserItemExecutionFinished(browserItemAction.id(), broken ? Device::DeviceErrorHardwareFailure : Device::DeviceErrorNoError); + }); + return Device::DeviceErrorAsync; +} + void DevicePluginMock::setState(const StateTypeId &stateTypeId, const QVariant &value) { HttpDaemon *daemon = qobject_cast(sender()); @@ -591,32 +609,53 @@ void DevicePluginMock::onPluginConfigChanged() } -Device::BrowseResult DevicePluginMock::generateBrowseItems(const QString &nodeId, Device::BrowseResult result) +Device::BrowseResult DevicePluginMock::generateBrowseItems(const QString &itemId, Device::BrowseResult result) { result.status = Device::DeviceErrorNoError; - if (nodeId.isEmpty()) { - result.items.append(BrowserItem("0", "Item 0", true)); - result.items.append(BrowserItem("1", "Item 1")); - result.items.append(BrowserItem("2", "Item 2", true)); - result.items.append(BrowserItem("3", "Item 3")); - result.items.append(BrowserItem("4", "Item 4")); + if (itemId.isEmpty()) { + BrowserItem item = BrowserItem("0", "Item 0", true); + item.setDescription("I'm a folder"); + item.setIcon(BrowserItem::BrowserIconFolder); + result.items.append(item); + + item = BrowserItem("1", "Item 1", false, true); + item.setDescription("I'm executable"); + item.setIcon(BrowserItem::BrowserIconApplication); + result.items.append(item); + + item = BrowserItem("2", "Item 2", false, true); + item.setDescription("I'm a file"); + item.setIcon(BrowserItem::BrowserIconFile); + result.items.append(item); + + item = BrowserItem("3", "Item 3", false, true); + item.setDescription("I have a nice thumbnail"); + item.setIcon(BrowserItem::BrowserIconFile); + item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png"); + result.items.append(item); + + item = BrowserItem("4", "Item 4", false, false); + item.setDescription("I'm disabled"); + item.setIcon(BrowserItem::BrowserIconFile); + result.items.append(item); + } - else if (nodeId == "0") { + else if (itemId == "0") { result.items.append(BrowserItem("5", "Item 5")); result.items.append(BrowserItem("6", "Item 6")); result.items.append(BrowserItem("7", "Item 7")); result.items.append(BrowserItem("8", "Item 8")); result.items.append(BrowserItem("9", "Item 9")); } - else if (nodeId == "2") { + else if (itemId == "2") { result.items.append(BrowserItem("10", "Item 10", true)); result.items.append(BrowserItem("11", "Item 11")); result.items.append(BrowserItem("12", "Item 12")); result.items.append(BrowserItem("13", "Item 13")); result.items.append(BrowserItem("14", "Item 14")); } - else if (nodeId == "10") { + else if (itemId == "10") { result.items.append(BrowserItem("15", "Item 15")); result.items.append(BrowserItem("16", "Item 16")); } else { diff --git a/plugins/mock/devicepluginmock.h b/plugins/mock/devicepluginmock.h index 2081fa24..521a3e17 100644 --- a/plugins/mock/devicepluginmock.h +++ b/plugins/mock/devicepluginmock.h @@ -56,6 +56,7 @@ public: public slots: Device::DeviceError executeAction(Device *device, const Action &action) override; + Device::DeviceError executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) override; private slots: void setState(const StateTypeId &stateTypeId, const QVariant &value); @@ -75,7 +76,7 @@ private slots: void onPluginConfigChanged(); private: - Device::BrowseResult generateBrowseItems(const QString &nodeId, Device::BrowseResult result); + Device::BrowseResult generateBrowseItems(const QString &itemId, Device::BrowseResult result); private: QHash m_daemons; From f0e8e5a609bf70b5b316440e22a1e04077d3aada Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 9 Jul 2019 02:08:26 +0200 Subject: [PATCH 10/22] bump version --- debian/changelog | 5 ++++ libnymea/types/ruleaction.cpp | 48 ++++++++++++++++++++++++++++++----- libnymea/types/ruleaction.h | 12 ++++++--- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/debian/changelog b/debian/changelog index 48728e51..396b37a6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,8 @@ +nymea (0.15.0) UNRELEASED; urgency=medium + + + -- Michael Zanetti Tue, 09 Jul 2019 02:08:05 +0200 + nymea (0.14.0) xenial; urgency=medium [ Michael Zanetti ] * Bump minimum required TLS version to 1.2 diff --git a/libnymea/types/ruleaction.cpp b/libnymea/types/ruleaction.cpp index 08882ccd..556ba2c1 100644 --- a/libnymea/types/ruleaction.cpp +++ b/libnymea/types/ruleaction.cpp @@ -46,17 +46,21 @@ #include "ruleaction.h" -/*! Constructs a RuleAction with the given by \a actionTypeId, \a deviceId and \a params. */ +/*! Constructs a RuleAction with the given by \a actionTypeId, \a deviceId and \a params. + * Use this to create a RuleAction for regular actions, that is, identifying the Action by deviceId and actionTypeId. + */ RuleAction::RuleAction(const ActionTypeId &actionTypeId, const DeviceId &deviceId, const RuleActionParamList ¶ms): m_id(ActionId::createActionId()), - m_actionTypeId(actionTypeId), m_deviceId(deviceId), + m_actionTypeId(actionTypeId), m_ruleActionParams(params) { } -/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction. */ +/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction. + * This will create an interface based RuleAction. Meaning, the Action is idenfified by an interface and and interfaceAction. + */ RuleAction::RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList ¶ms) : m_interface(interface), m_interfaceAction(interfaceAction), @@ -65,11 +69,22 @@ RuleAction::RuleAction(const QString &interface, const QString &interfaceAction, } +/*! Constructs a RuleAction with the given by \a interface and \a interfaceAction. + * Use this to create a RuleAction for executing browser items. + */ +RuleAction::RuleAction(const DeviceId &deviceId, const QString &browserItemId): + m_deviceId(deviceId), + m_browserItemId(browserItemId) +{ + +} + /*! Constructs a copy of the given \a other RuleAction. */ RuleAction::RuleAction(const RuleAction &other) : m_id(other.id()), - m_actionTypeId(other.actionTypeId()), m_deviceId(other.deviceId()), + m_actionTypeId(other.actionTypeId()), + m_browserItemId(other.browserItemId()), m_interface(other.interface()), m_interfaceAction(other.interfaceAction()), m_ruleActionParams(other.ruleActionParams()) @@ -86,13 +101,25 @@ ActionId RuleAction::id() const /*! Return true, if the actionTypeId and the deviceId of this RuleAction are valid (set).*/ bool RuleAction::isValid() const { - return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) || (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()); + return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) + || (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()) + || (!m_deviceId.isNull() && !m_browserItemId.isEmpty()); } /*! Returns whether this RuleAction is targetting a specific device or rather an interface. */ RuleAction::Type RuleAction::type() const { - return (!m_actionTypeId.isNull() && !m_deviceId.isNull()) ? TypeDevice : TypeInterface; + if (!m_deviceId.isNull() && !m_actionTypeId.isNull()) { + return TypeDevice; + } + if (!m_deviceId.isNull() && m_browserItemId.isEmpty()) { + return TypeBrowser; + } + if (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()) { + return TypeInterface; + } + // uhmm... invalid... + return TypeDevice; } /*! Return true, if this RuleAction contains a \l{RuleActionParam} which is based on an EventTypeId.*/ @@ -129,6 +156,13 @@ Action RuleAction::toAction() const return action; } +/*! Converts this \l{RuleAction} to a \l{BrowserItemAction}. + * \sa BrowserItemAction, */ +BrowserItemAction RuleAction::toBrowserItemAction() const +{ + return BrowserItemAction(m_deviceId, m_browserItemId); +} + /*! Returns the actionTypeId of this RuleAction. */ ActionTypeId RuleAction::actionTypeId() const { @@ -204,7 +238,7 @@ void RuleAction::operator=(const RuleAction &other) /*! Print a RuleAction including RuleActionParams to QDebug. */ QDebug operator<<(QDebug dbg, const RuleAction &ruleAction) { - dbg.nospace() << "RuleAction(ActionTypeId:" << ruleAction.actionTypeId().toString() << ", DeviceId:" << ruleAction.deviceId().toString() << ", Interface:" << ruleAction.interface() << ", InterfaceAction:" << ruleAction.interfaceAction() << ")" << endl; + dbg.nospace() << "RuleAction(ActionTypeId:" << ruleAction.actionTypeId().toString() << ", DeviceId:" << ruleAction.deviceId().toString() << ", Interface:" << ruleAction.interface() << ", InterfaceAction:" << ruleAction.interfaceAction() << ", BrowserItemId:" << ruleAction.browserItemId() << ")" << endl; for (int i = 0; i < ruleAction.ruleActionParams().count(); i++) { dbg.nospace() << " " << i << ": " << ruleAction.ruleActionParams().at(i) << endl; } diff --git a/libnymea/types/ruleaction.h b/libnymea/types/ruleaction.h index 98dee3b9..3dc50ab3 100644 --- a/libnymea/types/ruleaction.h +++ b/libnymea/types/ruleaction.h @@ -27,16 +27,19 @@ #include "libnymea.h" #include "action.h" #include "ruleactionparam.h" +#include "browseritemaction.h" class LIBNYMEA_EXPORT RuleAction { public: enum Type { TypeDevice, - TypeInterface + TypeInterface, + TypeBrowser }; explicit RuleAction(const ActionTypeId &actionTypeId = ActionTypeId(), const DeviceId &deviceId = DeviceId(), const RuleActionParamList ¶ms = RuleActionParamList()); explicit RuleAction(const QString &interface, const QString &interfaceAction, const RuleActionParamList ¶ms = RuleActionParamList()); + explicit RuleAction(const DeviceId &deviceId, const QString &browserItemId); RuleAction(const RuleAction &other); ActionId id() const; @@ -48,9 +51,11 @@ public: bool isStateBased() const; Action toAction() const; + BrowserItemAction toBrowserItemAction() const; - ActionTypeId actionTypeId() const; DeviceId deviceId() const; + ActionTypeId actionTypeId() const; + QString browserItemId() const; QString interface() const; QString interfaceAction() const; @@ -64,8 +69,9 @@ public: private: ActionId m_id; - ActionTypeId m_actionTypeId; DeviceId m_deviceId; + ActionTypeId m_actionTypeId; + QString m_browserItemId; QString m_interface; QString m_interfaceAction; RuleActionParamList m_ruleActionParams; From 968f1fe2fac56c3dfebbe842aa4e23c3a53a3103 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 9 Jul 2019 21:56:53 +0200 Subject: [PATCH 11/22] intermediate commit --- libnymea/types/browseritem.cpp | 10 ++++++++++ libnymea/types/browseritem.h | 4 ++++ libnymea/types/deviceclass.cpp | 31 +++++++++++++++++++++++++++--- libnymea/types/deviceclass.h | 11 ++++++++--- libnymea/types/ruleaction.cpp | 6 ++++++ plugins/mock/devicepluginmock.json | 7 +++++++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index 77af9ae7..da914a22 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -97,6 +97,16 @@ void BrowserItem::setThumbnail(const QString &thumbnail) m_thumbnail = thumbnail; } +QList BrowserItem::actionTypeIds() const +{ + return m_actionTypeIds; +} + +void BrowserItem::setActionTypeIs(const QList &actionTypeIds) +{ + m_actionTypeIds = actionTypeIds; +} + BrowserItem::ExtendedPropertiesFlags BrowserItem::extendedPropertiesFlags() const { return m_extendedPropertiesFlags; diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index 7bbd14cb..260a64ad 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -79,6 +79,9 @@ public: QString thumbnail() const; void setThumbnail(const QString &thumbnail); + QList actionTypeIds() const; + void setActionTypeIs(const QList &actionTypeIds); + ExtendedPropertiesFlags extendedPropertiesFlags() const; QVariant extendedProperty(const QString &propertyName) const; @@ -94,6 +97,7 @@ private: protected: ExtendedPropertiesFlags m_extendedPropertiesFlags = ExtendedPropertiesNone; QHash m_extendedProperties; + QList m_actionTypeIds; }; Q_DECLARE_OPERATORS_FOR_FLAGS(BrowserItem::ExtendedPropertiesFlags) diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index 08dbb71f..6ddb7ab0 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -147,7 +147,7 @@ StateType DeviceClass::getStateType(const StateTypeId &stateTypeId) /*! Set the \a stateTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their states matching to this template. */ -void DeviceClass::setStateTypes(const QList &stateTypes) +void DeviceClass::setStateTypes(const StateTypes &stateTypes) { m_stateTypes = stateTypes; } @@ -172,7 +172,7 @@ EventTypes DeviceClass::eventTypes() const /*! Set the \a eventTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their events matching to this template. */ -void DeviceClass::setEventTypes(const QList &eventTypes) +void DeviceClass::setEventTypes(const EventTypes &eventTypes) { m_eventTypes = eventTypes; } @@ -197,7 +197,7 @@ ActionTypes DeviceClass::actionTypes() const /*! Set the \a actionTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their actions matching to this template. */ -void DeviceClass::setActionTypes(const QList &actionTypes) +void DeviceClass::setActionTypes(const ActionTypes &actionTypes) { m_actionTypes = actionTypes; } @@ -213,6 +213,31 @@ bool DeviceClass::hasActionType(const ActionTypeId &actionTypeId) return false; } +/*! Returns the browserActionTypes of this DeviceClass. \{Device}{Devices} created + from this \l{DeviceClass} may set those actions to their browser items. */ +ActionTypes DeviceClass::browserActionTypes() const +{ + return m_browserActionTypes; +} + +/*! Set the \a browserActionTypes of this DeviceClass. \{Device}{Devices} created + from this \l{DeviceClass} may set those actions to their browser items. */ +void DeviceClass::setBrowserActionTypes(const ActionTypes &browserActionTypes) +{ + m_browserActionTypes = browserActionTypes; +} + +/*! Returns true if this DeviceClass has a \l{ActionType} with the given \a actionTypeId. */ +bool DeviceClass::hasBrowserActionType(const ActionTypeId &actionTypeId) +{ + foreach (const ActionType &actionType, m_actionTypes) { + if (actionType.id() == actionTypeId) { + return true; + } + } + return false; +} + /*! Returns the params description of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their params matching to this template. */ ParamTypes DeviceClass::paramTypes() const diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index cd89f81a..d40b5324 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -71,17 +71,21 @@ public: StateTypes stateTypes() const; StateType getStateType(const StateTypeId &stateTypeId); - void setStateTypes(const QList &stateTypes); + void setStateTypes(const StateTypes &stateTypes); bool hasStateType(const StateTypeId &stateTypeId); EventTypes eventTypes() const; - void setEventTypes(const QList &eventTypes); + void setEventTypes(const EventTypes &eventTypes); bool hasEventType(const EventTypeId &eventTypeId); ActionTypes actionTypes() const; - void setActionTypes(const QList &actionTypes); + void setActionTypes(const ActionTypes &actionTypes); bool hasActionType(const ActionTypeId &actionTypeId); + ActionTypes browserActionTypes() const; + void setBrowserActionTypes(const ActionTypes &browserActionTypes); + bool hasBrowserActionType(const ActionTypeId &actionTypeId); + ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); @@ -118,6 +122,7 @@ private: StateTypes m_stateTypes; EventTypes m_eventTypes; ActionTypes m_actionTypes; + ActionTypes m_browserActionTypes; ParamTypes m_paramTypes; ParamTypes m_settingsTypes; ParamTypes m_discoveryParamTypes; diff --git a/libnymea/types/ruleaction.cpp b/libnymea/types/ruleaction.cpp index 556ba2c1..1e7580e2 100644 --- a/libnymea/types/ruleaction.cpp +++ b/libnymea/types/ruleaction.cpp @@ -169,6 +169,12 @@ ActionTypeId RuleAction::actionTypeId() const return m_actionTypeId; } +/*! Returns the browserItemId of this RuleAction. */ +QString RuleAction::browserItemId() const +{ + return m_browserItemId; +} + /*! Returns the deviceId of this RuleAction. */ DeviceId RuleAction::deviceId() const { diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 168f14ce..205b654e 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -194,6 +194,13 @@ "name": "asyncFailing", "displayName": "Mock Action 5 (async, broken)" } + ], + "brwserItemActionTypes": [ + { + "id": "00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3", + "name": "addToFolder", + "displayName": "Add to Favorites" + } ] }, { From aee554a1eef0bd42967597d0a05baf3cecdd0cc4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 10 Jul 2019 10:55:34 +0200 Subject: [PATCH 12/22] BrowserItemActions, yay! --- .../devices/devicemanagerimplementation.cpp | 19 ++- .../devices/devicemanagerimplementation.h | 3 +- libnymea-core/jsonrpc/actionhandler.cpp | 44 +++++- libnymea-core/jsonrpc/actionhandler.h | 2 + libnymea-core/jsonrpc/jsontypes.cpp | 14 ++ libnymea-core/logging/logengine.cpp | 11 +- libnymea-core/logging/logengine.h | 4 +- libnymea-core/nymeacore.cpp | 32 +++- libnymea-core/nymeacore.h | 6 +- libnymea/devices/device.h | 1 + libnymea/devices/devicemanager.h | 5 +- libnymea/devices/deviceplugin.cpp | 33 ++++- libnymea/devices/deviceplugin.h | 5 +- libnymea/devices/pluginmetadata.cpp | 67 +++++++-- libnymea/libnymea.pro | 6 +- libnymea/types/actiontype.cpp | 6 + libnymea/types/actiontype.h | 2 + libnymea/types/browseraction.cpp | 66 +++++++++ libnymea/types/browseraction.h | 48 ++++++ libnymea/types/browseritem.cpp | 10 ++ libnymea/types/browseritem.h | 4 + libnymea/types/browseritemaction.cpp | 23 ++- libnymea/types/browseritemaction.h | 12 +- libnymea/types/deviceclass.cpp | 16 +- libnymea/types/deviceclass.h | 14 +- plugins/mock/devicepluginmock.cpp | 137 +++++++++++------- plugins/mock/devicepluginmock.h | 25 +++- plugins/mock/devicepluginmock.json | 11 +- plugins/mock/extern-plugininfo.h | 2 + plugins/mock/plugininfo.h | 8 + .../plugininfocompiler.cpp | 19 ++- .../plugininfocompiler.h | 1 + 32 files changed, 542 insertions(+), 114 deletions(-) create mode 100644 libnymea/types/browseraction.cpp create mode 100644 libnymea/types/browseraction.h diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index b45ae186..f5f8aa9a 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -734,7 +734,19 @@ Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &d return result; } -Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const BrowserItemAction &browserItemAction) +Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const BrowserAction &browserAction) +{ + Device *device = m_configuredDevices.value(browserAction.deviceId()); + if (!device) { + return Device::DeviceErrorDeviceNotFound; + } + if (!device->deviceClass().browsable()) { + return Device::DeviceErrorUnsupportedFeature; + } + return device->plugin()->executeBrowserItem(device, browserAction); +} + +Device::DeviceError DeviceManagerImplementation::executeBrowserItemAction(const BrowserItemAction &browserItemAction) { Device *device = m_configuredDevices.value(browserItemAction.deviceId()); if (!device) { @@ -743,7 +755,9 @@ Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const Browse if (!device->deviceClass().browsable()) { return Device::DeviceErrorUnsupportedFeature; } - return device->plugin()->executeBrowserItem(device, browserItemAction); + // TODO: check browserItemAction.params with deviceClass + + return device->plugin()->executeBrowserItemAction(device, browserItemAction); } QString DeviceManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale) @@ -879,6 +893,7 @@ void DeviceManagerImplementation::loadPlugins() loader.setFileName(fi.absoluteFilePath()); loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); + qCDebug(dcDeviceManager()) << "Loading plugin from:" << fi.absoluteFilePath(); if (!loader.load()) { qCWarning(dcDeviceManager) << "Could not load plugin data of" << entry << "\n" << loader.errorString(); continue; diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 99437dfe..24766d7e 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -100,7 +100,8 @@ public: Device::DeviceError executeAction(const Action &action) override; Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) override; - Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction) override; + Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) override; + Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) override; QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) override; diff --git a/libnymea-core/jsonrpc/actionhandler.cpp b/libnymea-core/jsonrpc/actionhandler.cpp index bb096dae..8ff4c73c 100644 --- a/libnymea-core/jsonrpc/actionhandler.cpp +++ b/libnymea-core/jsonrpc/actionhandler.cpp @@ -65,11 +65,21 @@ ActionHandler::ActionHandler(QObject *parent) : params.clear(); returns.clear(); setDescription("ExecuteBrowserItem", "Execute the item identified by itemId on the given device."); params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); - params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String)); + params.insert("itemId", JsonTypes::basicTypeToString(JsonTypes::String)); setParams("ExecuteBrowserItem", params); returns.insert("deviceError", JsonTypes::deviceErrorRef()); setReturns("ExecuteBrowserItem", returns); + params.clear(); returns.clear(); + setDescription("ExecuteBrowserItemAction", "Execute the action for the browser item identified by actionTypeId and the itemId on the given device."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("itemId", JsonTypes::basicTypeToString(JsonTypes::String)); + params.insert("actionTypeId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("o:params", QVariantList() << JsonTypes::paramRef()); + setParams("ExecuteBrowserItemAction", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + setReturns("ExecuteBrowserItemAction", returns); + connect(NymeaCore::instance(), &NymeaCore::actionExecuted, this, &ActionHandler::actionExecuted); connect(NymeaCore::instance(), &NymeaCore::browserItemExecuted, this, &ActionHandler::browserItemExecuted); } @@ -132,7 +142,7 @@ JsonReply *ActionHandler::ExecuteBrowserItem(const QVariantMap ¶ms) { DeviceId deviceId = DeviceId(params.value("deviceId").toString()); QString itemId = params.value("itemId").toString(); - BrowserItemAction action(deviceId, itemId); + BrowserAction action(deviceId, itemId); Device::DeviceError status = NymeaCore::instance()->executeBrowserItem(action); if (status == Device::DeviceErrorAsync) { JsonReply *reply = createAsyncReply("ExecuteBrowserItem"); @@ -144,6 +154,25 @@ JsonReply *ActionHandler::ExecuteBrowserItem(const QVariantMap ¶ms) return createReply(statusToReply(status)); } +JsonReply *ActionHandler::ExecuteBrowserItemAction(const QVariantMap ¶ms) +{ + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + QString itemId = params.value("itemId").toString(); + ActionTypeId actionTypeId = ActionTypeId(params.value("actionTypeId").toString()); + ParamList paramList = JsonTypes::unpackParams(params.value("params").toList()); + BrowserItemAction browserItemAction(deviceId, itemId, actionTypeId, paramList); + Device::DeviceError status = NymeaCore::instance()->executeBrowserItemAction(browserItemAction); + if (status == Device::DeviceErrorAsync) { + JsonReply *reply = createAsyncReply("ExecuteBrowserItemAction"); + ActionId id = browserItemAction.id(); + connect(reply, &JsonReply::finished, [this, id](){ m_asyncActionExecutions.remove(id); }); + m_asyncActionExecutions.insert(id, reply); + return reply; + } + return createReply(statusToReply(status)); + +} + void ActionHandler::browserItemExecuted(const ActionId &id, Device::DeviceError status) { if (!m_asyncActionExecutions.contains(id)) { @@ -155,5 +184,16 @@ void ActionHandler::browserItemExecuted(const ActionId &id, Device::DeviceError reply->finished(); } +void ActionHandler::browserItemActionExecuted(const ActionId &id, Device::DeviceError status) +{ + if (!m_asyncActionExecutions.contains(id)) { + return; // Not the action we are waiting for. + } + + JsonReply *reply = m_asyncActionExecutions.take(id); + reply->setData(statusToReply(status)); + reply->finished(); +} + } diff --git a/libnymea-core/jsonrpc/actionhandler.h b/libnymea-core/jsonrpc/actionhandler.h index 346ed920..06cdf23f 100644 --- a/libnymea-core/jsonrpc/actionhandler.h +++ b/libnymea-core/jsonrpc/actionhandler.h @@ -39,10 +39,12 @@ public: Q_INVOKABLE JsonReply *GetActionType(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *ExecuteBrowserItem(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *ExecuteBrowserItemAction(const QVariantMap ¶ms); private slots: void actionExecuted(const ActionId &id, Device::DeviceError status); void browserItemExecuted(const ActionId &id, Device::DeviceError status); + void browserItemActionExecuted(const ActionId &id, Device::DeviceError status); private: QHash m_asyncActionExecutions; diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index d7666ed9..b682567f 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -286,6 +286,7 @@ void JsonTypes::init() s_deviceClass.insert("stateTypes", QVariantList() << stateTypeRef()); s_deviceClass.insert("eventTypes", QVariantList() << eventTypeRef()); s_deviceClass.insert("actionTypes", QVariantList() << actionTypeRef()); + s_deviceClass.insert("browserItemActionTypes", QVariantList() << actionTypeRef()); s_deviceClass.insert("paramTypes", QVariantList() << paramTypeRef()); s_deviceClass.insert("settingsTypes", QVariantList() << paramTypeRef()); s_deviceClass.insert("discoveryParamTypes", QVariantList() << paramTypeRef()); @@ -436,6 +437,8 @@ void JsonTypes::init() s_browserItem.insert("thumbnail", basicTypeToString(QVariant::String)); s_browserItem.insert("executable", basicTypeToString(QVariant::Bool)); s_browserItem.insert("browsable", basicTypeToString(QVariant::Bool)); + s_browserItem.insert("disabled", basicTypeToString(QVariant::Bool)); + s_browserItem.insert("actionTypeIds", QVariantList() << basicTypeToString(QVariant::Uuid)); s_browserItem.insert("o:mediaIcon", mediaBrowserIconRef()); s_initialized = true; @@ -743,6 +746,12 @@ QVariantMap JsonTypes::packBrowserItem(const BrowserItem &item) ret.insert("thumbnail", item.thumbnail()); ret.insert("executable", item.executable()); ret.insert("browsable", item.browsable()); + ret.insert("disabled", item.disabled()); + QVariantList actionTypeIds; + foreach (const ActionTypeId &id, item.actionTypeIds()) { + actionTypeIds.append(id.toString()); + } + ret.insert("actionTypeIds", actionTypeIds); return ret; } @@ -844,6 +853,10 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo foreach (const ActionType &actionType, deviceClass.actionTypes()) actionTypes.append(packActionType(actionType, deviceClass.pluginId(), locale)); + QVariantList browserItemActionTypes; + foreach (const ActionType &actionType, deviceClass.browserItemActionTypes()) + browserItemActionTypes.append(packActionType(actionType, deviceClass.pluginId(), locale)); + QVariantList paramTypes; foreach (const ParamType ¶mType, deviceClass.paramTypes()) paramTypes.append(packParamType(paramType, deviceClass.pluginId(), locale)); @@ -862,6 +875,7 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo variant.insert("stateTypes", stateTypes); variant.insert("eventTypes", eventTypes); variant.insert("actionTypes", actionTypes); + variant.insert("browserItemActionTypes", browserItemActionTypes); variant.insert("createMethods", packCreateMethods(deviceClass.createMethods())); variant.insert("setupMethod", s_setupMethod.at(deviceClass.setupMethod())); return variant; diff --git a/libnymea-core/logging/logengine.cpp b/libnymea-core/logging/logengine.cpp index c85186a8..4faa9689 100644 --- a/libnymea-core/logging/logengine.cpp +++ b/libnymea-core/logging/logengine.cpp @@ -314,10 +314,19 @@ void LogEngine::logAction(const Action &action, Logging::LoggingLevel level, int appendLogEntry(entry); } -void LogEngine::logBrowserAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level, int errorCode) +void LogEngine::logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level, int errorCode) +{ + LogEntry entry(level, Logging::LoggingSourceBrowserActions, errorCode); + entry.setDeviceId(browserAction.deviceId()); + entry.setValue(browserAction.itemId()); + appendLogEntry(entry); +} + +void LogEngine::logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level, int errorCode) { LogEntry entry(level, Logging::LoggingSourceBrowserActions, errorCode); entry.setDeviceId(browserItemAction.deviceId()); + entry.setTypeId(browserItemAction.actionTypeId()); entry.setValue(browserItemAction.itemId()); appendLogEntry(entry); } diff --git a/libnymea-core/logging/logengine.h b/libnymea-core/logging/logengine.h index d115b404..461bd98b 100644 --- a/libnymea-core/logging/logengine.h +++ b/libnymea-core/logging/logengine.h @@ -27,6 +27,7 @@ #include "types/event.h" #include "types/action.h" #include "types/browseritemaction.h" +#include "types/browseraction.h" #include "ruleengine/rule.h" #include @@ -50,7 +51,8 @@ public: void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo); void logEvent(const Event &event); void logAction(const Action &action, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); - void logBrowserAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); + void logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); + void logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); void logRuleTriggered(const Rule &rule); void logRuleActiveChanged(const Rule &rule); void logRuleEnabledChanged(const Rule &rule, const bool &enabled); diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index cb8fbfb4..9cfa886f 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -448,15 +448,28 @@ Device::DeviceError NymeaCore::executeAction(const Action &action) return ret; } -Device::DeviceError NymeaCore::executeBrowserItem(const BrowserItemAction &browserItemAction) +Device::DeviceError NymeaCore::executeBrowserItem(const BrowserAction &browserAction) { - Device::DeviceError ret = m_deviceManager->executeBrowserItem(browserItemAction); + Device::DeviceError ret = m_deviceManager->executeBrowserItem(browserAction); if (ret == Device::DeviceErrorNoError) { - m_logger->logBrowserAction(browserItemAction); + m_logger->logBrowserAction(browserAction); + } else if (ret == Device::DeviceErrorAsync) { + m_pendingBrowserActions.insert(browserAction.id(), browserAction); + } else { + m_logger->logBrowserAction(browserAction, Logging::LoggingLevelAlert, ret); + } + return ret; +} + +Device::DeviceError NymeaCore::executeBrowserItemAction(const BrowserItemAction &browserItemAction) +{ + Device::DeviceError ret = m_deviceManager->executeBrowserItemAction(browserItemAction); + if (ret == Device::DeviceErrorNoError) { + m_logger->logBrowserItemAction(browserItemAction); } else if (ret == Device::DeviceErrorAsync) { m_pendingBrowserItemActions.insert(browserItemAction.id(), browserItemAction); } else { - m_logger->logBrowserAction(browserItemAction, Logging::LoggingLevelAlert, ret); + m_logger->logBrowserItemAction(browserItemAction, Logging::LoggingLevelAlert, ret); } return ret; } @@ -814,8 +827,15 @@ void NymeaCore::actionExecutionFinished(const ActionId &id, Device::DeviceError void NymeaCore::browserItemExecutionFinished(const ActionId &id, Device::DeviceError status) { emit browserItemExecuted(id, status); - Action action = m_pendingActions.take(id); - m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); + BrowserAction action = m_pendingBrowserActions.take(id); + m_logger->logBrowserAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); +} + +void NymeaCore::browserItemActionExecutionFinished(const ActionId &id, Device::DeviceError status) +{ + emit browserItemActionExecuted(id, status); + BrowserItemAction action = m_pendingBrowserItemActions.take(id); + m_logger->logBrowserItemAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); } void NymeaCore::onDeviceDisappeared(const DeviceId &deviceId) diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index e3d1e888..8b10dee2 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -72,7 +72,8 @@ public: Device::DeviceError removeConfiguredDevice(const DeviceId &deviceId, const RuleEngine::RemovePolicy &removePolicy); Device::DeviceError executeAction(const Action &action); - Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction); + Device::DeviceError executeBrowserItem(const BrowserAction &browserAction); + Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction); void executeRuleActions(const QList ruleActions); @@ -108,6 +109,7 @@ signals: void deviceSettingChanged(const DeviceId deviceId, const ParamTypeId &settingParamTypeId, const QVariant &value); void actionExecuted(const ActionId &id, Device::DeviceError status); void browserItemExecuted(const ActionId &id, Device::DeviceError status); + void browserItemActionExecuted(const ActionId &id, Device::DeviceError status); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); void deviceSetupFinished(Device *device, Device::DeviceError status); @@ -141,6 +143,7 @@ private: System *m_system; QHash m_pendingActions; + QHash m_pendingBrowserActions; QHash m_pendingBrowserItemActions; QList m_executingRules; @@ -149,6 +152,7 @@ private slots: void onDateTimeChanged(const QDateTime &dateTime); void actionExecutionFinished(const ActionId &id, Device::DeviceError status); void browserItemExecutionFinished(const ActionId &id, Device::DeviceError status); + void browserItemActionExecutionFinished(const ActionId &id, Device::DeviceError status); void onDeviceDisappeared(const DeviceId &deviceId); void deviceManagerLoaded(); diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index d304fdbe..c34ad219 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -71,6 +71,7 @@ public: DeviceErrorDeviceIsChild, DeviceErrorPairingTransactionIdNotFound, DeviceErrorParameterNotWritable, + DeviceErrorItemNotFound, DeviceErrorUnsupportedFeature, }; Q_ENUM(DeviceError) diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index d1fe3290..b1ac9308 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -30,6 +30,7 @@ #include "types/interface.h" #include "types/vendor.h" #include "types/browseritem.h" +#include "types/browseraction.h" #include "types/browseritemaction.h" class DeviceManager : public QObject @@ -74,7 +75,8 @@ public: virtual Device::DeviceError executeAction(const Action &action) = 0; virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) = 0; - virtual Device::DeviceError executeBrowserItem(const BrowserItemAction &browserItemAction) = 0; + virtual Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) = 0; + virtual Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) = 0; virtual QString translate(const PluginId &pluginId, const QString &string, const QLocale &locale) = 0; @@ -94,6 +96,7 @@ signals: void actionExecutionFinished(const ActionId &actionId, Device::DeviceError status); void browseRequestFinished(const Device::BrowseResult &result); void browserItemExecutionFinished(const ActionId &actionId, Device::DeviceError status); + void browserItemActionExecutionFinished(const ActionId &actionId, Device::DeviceError status); protected: Device::BrowseResult createBrowseResult(); diff --git a/libnymea/devices/deviceplugin.cpp b/libnymea/devices/deviceplugin.cpp index 05e30e13..7b45dd1a 100644 --- a/libnymea/devices/deviceplugin.cpp +++ b/libnymea/devices/deviceplugin.cpp @@ -248,16 +248,43 @@ Device::DeviceError DevicePlugin::executeAction(Device *device, const Action &ac return Device::DeviceErrorNoError; } -Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId) +/*! Implement this if your devices support browsing. When the system calls this method, + * fill the \a result object's items list with entries from the browser. If \a itemId is empty + * it means that the root node of the file system should be returned. Each item in the result set + * shall be uniquely identifiable using its \l{BrowserItem::id}{id} property. + * The system might call this method again, with an \a itemId returned in a previous query, provided + * that item's \l{BrowserItem::browsable} property is true. In this case all children of the given + * item shall be returned. + * When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's + * status to Device::DeviceErrorAsync if this operation requires async behavior and emit + * \l{browseRequestFinished} when done. + */ +Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId) { Q_UNUSED(device) - Q_UNUSED(nodeId) + Q_UNUSED(itemId) result.status = Device::DeviceErrorUnsupportedFeature; return result; } -Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) +/*! Implement this if your devices support browsing and execute the itemId defined in \a browserAction. + * Return Device::DeviceErrorAsync if this operation requires async behavior and emit + * \l{browserItemExecutionFinished} when done. + */ +Device::DeviceError DevicePlugin::executeBrowserItem(Device *device, const BrowserAction &browserAction) +{ + Q_UNUSED(device) + Q_UNUSED(browserAction) + return Device::DeviceErrorUnsupportedFeature; +} + +/*! Implement this if your devices support browsing and execute the item's action for the itemId defined + * in \a browserItemAction. + * Return Device::DeviceErrorAsync if this operation requires async behavior and emit + * \l{browserItemActionExecutionFinished} when done. + */ +Device::DeviceError DevicePlugin::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) { Q_UNUSED(device) Q_UNUSED(browserItemAction) diff --git a/libnymea/devices/deviceplugin.h b/libnymea/devices/deviceplugin.h index 3d28293d..818d249c 100644 --- a/libnymea/devices/deviceplugin.h +++ b/libnymea/devices/deviceplugin.h @@ -37,6 +37,7 @@ #include "types/vendor.h" #include "types/param.h" #include "types/interface.h" +#include "types/browseraction.h" #include "types/browseritemaction.h" #include "hardwaremanager.h" @@ -80,7 +81,8 @@ public: virtual Device::DeviceError executeAction(Device *device, const Action &action); virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()); - virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction); + virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction); + virtual Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction); // Configuration ParamTypes configurationDescription() const; @@ -102,6 +104,7 @@ signals: void autoDeviceDisappeared(const DeviceId &deviceId); void browseRequestFinished(const Device::BrowseResult &result); void browserItemExecutionFinished(const ActionId &actionid, Device::DeviceError status); + void browserItemActionExecutionFinished(const ActionId &actionid, Device::DeviceError status); protected: Devices myDevices() const; diff --git a/libnymea/devices/pluginmetadata.cpp b/libnymea/devices/pluginmetadata.cpp index 5d11cfd2..4b673636 100644 --- a/libnymea/devices/pluginmetadata.cpp +++ b/libnymea/devices/pluginmetadata.cpp @@ -163,7 +163,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) /*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */ QStringList deviceClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" << "interfaces" << "browsable" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes" - << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes"; + << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes"; QStringList mandatoryDeviceClassProperties = QStringList() << "id" << "name" << "displayName"; QPair verificationResult = verifyFields(deviceClassProperties, mandatoryDeviceClassProperties, deviceClassObject); @@ -261,9 +261,10 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Read pairing info deviceClass.setPairingInfo(deviceClassObject.value("pairingInfo").toString()); - QList actionTypes; - QList stateTypes; - QList eventTypes; + ActionTypes actionTypes; + StateTypes stateTypes; + EventTypes eventTypes; + ActionTypes browserItemActionTypes; // Read StateTypes int index = 0; @@ -458,6 +459,48 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } deviceClass.setEventTypes(eventTypes); + // BrowserItemActionTypes + index = 0; + foreach (const QJsonValue &browserItemActionTypesJson, deviceClassObject.value("browserItemActionTypes").toArray()) { + QJsonObject at = browserItemActionTypesJson.toObject(); + QPair verificationResult = verifyFields(ActionType::typeProperties(), ActionType::mandatoryTypeProperties(), at); + + // Check mandatory fields + if (!verificationResult.first.isEmpty()) { + qCWarning(dcPluginMetadata()) << "Device class" << deviceClass.name() << " has missing fields" << verificationResult.first.join(", ") << "in browser item action type:" << endl << at; + hasError = true; + continue; + } + + // Check if there are any unknown fields + if (!verificationResult.second.isEmpty()) { + qCWarning(dcPluginMetadata()) << pluginName() << "Device class" << deviceClass.name() << "has unknown fields:" << verificationResult.second.join(", ") << "in browser item action type:" << endl << at; + hasError = true; + } + + ActionTypeId actionTypeId = ActionTypeId(at.value("id").toString()); + QString actionTypeName = at.value("name").toString(); + if (!verifyDuplicateUuid(actionTypeId)) { + qCWarning(dcPluginMetadata()) << "Browser Action Type" << actionTypeName << "has duplicate UUID:" << actionTypeId.toString(); + hasError = true; + } + ActionType actionType(actionTypeId); + actionType.setName(actionTypeName); + actionType.setDisplayName(at.value("displayName").toString()); + actionType.setIndex(index++); + + QPair > paramVerification = parseParamTypes(at.value("paramTypes").toArray()); + if (!paramVerification.first) { + hasError = true; + break; + } else { + actionType.setParamTypes(paramVerification.second); + } + + browserItemActionTypes.append(actionType); + } + deviceClass.setBrowserItemActionTypes(browserItemActionTypes); + // Read interfaces QStringList interfaces; foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) { @@ -617,6 +660,7 @@ QPair PluginMetadata::verifyFields(const QStringList & QPair PluginMetadata::parseParamTypes(const QJsonArray &array) { + bool hasErrors = false; int index = 0; QList paramTypes; foreach (const QJsonValue ¶mTypesJson, array) { @@ -627,13 +671,14 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) // Check mandatory fields if (!verificationResult.first.isEmpty()) { qCWarning(dcPluginMetadata()) << pluginName() << "Error parsing ParamType: missing fields:" << verificationResult.first.join(", ") << endl << pt; - return QPair >(false, QList()); + hasErrors = true; + continue; } // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { qCWarning(dcPluginMetadata()) << pluginName() << "Error parsing ParamType: unknown fields:" << verificationResult.second.join(", ") << endl << pt; - return QPair >(false, QList()); + hasErrors = true; } // Check type @@ -642,14 +687,14 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid type %1 for param %2 in json file.") .arg(pt.value("type").toString()) .arg(pt.value("name").toString()).toLatin1().data(); - return QPair >(false, QList()); + hasErrors = true; } ParamTypeId paramTypeId = ParamTypeId(pt.value("id").toString()); QString paramName = pt.value("name").toString(); if (!verifyDuplicateUuid(paramTypeId)) { qCWarning(dcPluginMetadata()) << "Param" << paramName << "has duplicate UUID:" << paramTypeId.toString(); - return QPair >(false, QList()); + hasErrors = true; } ParamType paramType(paramTypeId, paramName, t, pt.value("defaultValue").toVariant()); paramType.setDisplayName(pt.value("displayName").toString()); @@ -666,7 +711,7 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) QPair inputTypeVerification = loadAndVerifyInputType(pt.value("inputType").toString()); if (!inputTypeVerification.first) { qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid inputType for paramType") << pt; - return QPair >(false, QList()); + hasErrors = true; } else { paramType.setInputType(inputTypeVerification.second); } @@ -677,7 +722,7 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) QPair unitVerification = loadAndVerifyUnit(pt.value("unit").toString()); if (!unitVerification.first) { qCWarning(dcPluginMetadata()) << pluginName() << QString("Invalid unit type for paramType") << pt; - return QPair >(false, QList()); + hasErrors = true; } else { paramType.setUnit(unitVerification.second); } @@ -693,7 +738,7 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) paramTypes.append(paramType); } - return QPair >(true, paramTypes); + return QPair >(!hasErrors, paramTypes); } QPair PluginMetadata::loadAndVerifyInputType(const QString &inputType) diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 21f46c81..bee2ccdc 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -16,7 +16,8 @@ HEADERS += \ platform/package.h \ platform/repository.h \ types/browseritem.h \ - types/browseritemaction.h \ + types/browseritemaction.h \ + types/browseraction.h \ types/mediabrowseritem.h \ typeutils.h \ loggingcategories.h \ @@ -114,7 +115,8 @@ SOURCES += \ coap/corelink.cpp \ coap/coapobserveresource.cpp \ types/browseritem.cpp \ - types/browseritemaction.cpp \ + types/browseritemaction.cpp \ + types/browseraction.cpp \ types/mediabrowseritem.cpp \ types/deviceclass.cpp \ types/action.cpp \ diff --git a/libnymea/types/actiontype.cpp b/libnymea/types/actiontype.cpp index 9f7dc1c3..959183bd 100644 --- a/libnymea/types/actiontype.cpp +++ b/libnymea/types/actiontype.cpp @@ -142,3 +142,9 @@ ActionType ActionTypes::findById(const ActionTypeId &id) } return ActionType(ActionTypeId()); } + +QDebug operator<<(QDebug dbg, const ActionType &actionType) +{ + dbg.nospace().noquote() << "ActionType: " << actionType.name() << actionType.displayName() << actionType.id(); + return dbg; +} diff --git a/libnymea/types/actiontype.h b/libnymea/types/actiontype.h index 09853a36..d809ab4e 100644 --- a/libnymea/types/actiontype.h +++ b/libnymea/types/actiontype.h @@ -60,6 +60,8 @@ private: ParamTypes m_paramTypes; }; +QDebug operator<<(QDebug dbg, const ActionType &actionType); + class ActionTypes: public QList { public: diff --git a/libnymea/types/browseraction.cpp b/libnymea/types/browseraction.cpp new file mode 100644 index 00000000..9d0a2841 --- /dev/null +++ b/libnymea/types/browseraction.cpp @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "browseraction.h" + +BrowserAction::BrowserAction(const DeviceId &deviceId, const QString &itemId): + m_id(ActionId::createActionId()), + m_deviceId(deviceId), + m_itemId(itemId) +{ + +} + +BrowserAction::BrowserAction(const BrowserAction &other): + m_id(other.id()), + m_deviceId(other.deviceId()), + m_itemId(other.itemId()) +{ + +} + +ActionId BrowserAction::id() const +{ + return m_id; +} + +bool BrowserAction::isValid() const +{ + return !m_id.isNull() && !m_deviceId.isNull() && !m_itemId.isNull(); +} + +DeviceId BrowserAction::deviceId() const +{ + return m_deviceId; +} + +QString BrowserAction::itemId() const +{ + return m_itemId; +} + +void BrowserAction::operator=(const BrowserAction &other) +{ + m_id = other.id(); + m_deviceId = other.deviceId(); + m_itemId = other.itemId(); +} diff --git a/libnymea/types/browseraction.h b/libnymea/types/browseraction.h new file mode 100644 index 00000000..781f4372 --- /dev/null +++ b/libnymea/types/browseraction.h @@ -0,0 +1,48 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef BROWSERACTION_H +#define BROWSERACTION_H + +#include "typeutils.h" + +class BrowserAction +{ +public: + explicit BrowserAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString()); + BrowserAction(const BrowserAction &other); + + ActionId id() const; + + bool isValid() const; + + DeviceId deviceId() const; + QString itemId() const; + + void operator=(const BrowserAction &other); +private: + ActionId m_id; + DeviceId m_deviceId; + QString m_itemId; +}; + +#endif // BROWSERACTION_H diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index da914a22..3aa5354f 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -77,6 +77,16 @@ void BrowserItem::setBrowsable(bool browsable) m_browsable = browsable; } +bool BrowserItem::disabled() const +{ + return m_disabled; +} + +void BrowserItem::setDisabled(bool disabled) +{ + m_disabled = disabled; +} + BrowserItem::BrowserIcon BrowserItem::icon() const { return m_icon; diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index 260a64ad..a4daba95 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -73,6 +73,9 @@ public: bool browsable() const; void setBrowsable(bool browsable); + bool disabled() const; + void setDisabled(bool disabled); + BrowserIcon icon() const; void setIcon(BrowserIcon icon); @@ -91,6 +94,7 @@ private: QString m_description; bool m_browsable = false; bool m_executable = false; + bool m_disabled = false; BrowserIcon m_icon = BrowserIconNone; QString m_thumbnail; diff --git a/libnymea/types/browseritemaction.cpp b/libnymea/types/browseritemaction.cpp index e9236482..94868283 100644 --- a/libnymea/types/browseritemaction.cpp +++ b/libnymea/types/browseritemaction.cpp @@ -22,10 +22,13 @@ #include "browseritemaction.h" -BrowserItemAction::BrowserItemAction(const DeviceId &deviceId, const QString &itemId): + +BrowserItemAction::BrowserItemAction(const DeviceId &deviceId, const QString &itemId, const ActionTypeId &actionTypeId, const ParamList ¶ms): m_id(ActionId::createActionId()), m_deviceId(deviceId), - m_itemId(itemId) + m_itemId(itemId), + m_actionTypeId(actionTypeId), + m_params(params) { } @@ -33,7 +36,9 @@ BrowserItemAction::BrowserItemAction(const DeviceId &deviceId, const QString &it BrowserItemAction::BrowserItemAction(const BrowserItemAction &other): m_id(other.id()), m_deviceId(other.deviceId()), - m_itemId(other.itemId()) + m_itemId(other.itemId()), + m_actionTypeId(other.actionTypeId()), + m_params(other.params()) { } @@ -58,9 +63,21 @@ QString BrowserItemAction::itemId() const return m_itemId; } +ActionTypeId BrowserItemAction::actionTypeId() const +{ + return m_actionTypeId; +} + +ParamList BrowserItemAction::params() const +{ + return m_params; +} + void BrowserItemAction::operator=(const BrowserItemAction &other) { m_id = other.id(); m_deviceId = other.deviceId(); m_itemId = other.itemId(); + m_actionTypeId = other.actionTypeId(); + m_params = other.params(); } diff --git a/libnymea/types/browseritemaction.h b/libnymea/types/browseritemaction.h index e5ee311d..aa58cfe3 100644 --- a/libnymea/types/browseritemaction.h +++ b/libnymea/types/browseritemaction.h @@ -24,13 +24,12 @@ #define BROWSERITEMACTION_H #include "typeutils.h" +#include "types/param.h" class BrowserItemAction { public: - BrowserItemAction(); - - explicit BrowserItemAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString()); + explicit BrowserItemAction(const DeviceId &deviceId = DeviceId(), const QString &itemId = QString(), const ActionTypeId &actionTypeId = ActionTypeId(), const ParamList ¶ms = ParamList()); BrowserItemAction(const BrowserItemAction &other); ActionId id() const; @@ -39,12 +38,19 @@ public: DeviceId deviceId() const; QString itemId() const; + ActionTypeId actionTypeId() const; + + ParamList params() const; + void setParams(const ParamList ¶ms); + Param param(const ParamTypeId ¶mTypeId) const; void operator=(const BrowserItemAction &other); private: ActionId m_id; DeviceId m_deviceId; QString m_itemId; + ActionTypeId m_actionTypeId; + ParamList m_params; }; #endif // BROWSERITEMACTION_H diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index 6ddb7ab0..7a62f26e 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -213,24 +213,24 @@ bool DeviceClass::hasActionType(const ActionTypeId &actionTypeId) return false; } -/*! Returns the browserActionTypes of this DeviceClass. \{Device}{Devices} created +/*! Returns the browserItemActionTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} may set those actions to their browser items. */ -ActionTypes DeviceClass::browserActionTypes() const +ActionTypes DeviceClass::browserItemActionTypes() const { - return m_browserActionTypes; + return m_browserItemActionTypes; } /*! Set the \a browserActionTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} may set those actions to their browser items. */ -void DeviceClass::setBrowserActionTypes(const ActionTypes &browserActionTypes) +void DeviceClass::setBrowserItemActionTypes(const ActionTypes &browserItemActionTypes) { - m_browserActionTypes = browserActionTypes; + m_browserItemActionTypes = browserItemActionTypes; } /*! Returns true if this DeviceClass has a \l{ActionType} with the given \a actionTypeId. */ -bool DeviceClass::hasBrowserActionType(const ActionTypeId &actionTypeId) +bool DeviceClass::hasBrowserItemActionType(const ActionTypeId &actionTypeId) { - foreach (const ActionType &actionType, m_actionTypes) { + foreach (const ActionType &actionType, m_browserItemActionTypes) { if (actionType.id() == actionTypeId) { return true; } @@ -333,11 +333,13 @@ void DeviceClass::setInterfaces(const QStringList &interfaces) m_interfaces = interfaces; } +/*! Returns whether \l{Device}{Devices} created from this \l{DeviceClass} are browsable */ bool DeviceClass::browsable() const { return m_browsable; } +/*! Sets whether \l{Device}{Devices} created from this \l{DeviceClass} are browsable */ void DeviceClass::setBrowsable(bool browsable) { m_browsable = browsable; diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index d40b5324..d01846d6 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -82,9 +82,12 @@ public: void setActionTypes(const ActionTypes &actionTypes); bool hasActionType(const ActionTypeId &actionTypeId); - ActionTypes browserActionTypes() const; - void setBrowserActionTypes(const ActionTypes &browserActionTypes); - bool hasBrowserActionType(const ActionTypeId &actionTypeId); + bool browsable() const; + void setBrowsable(bool browsable); + + ActionTypes browserItemActionTypes() const; + void setBrowserItemActionTypes(const ActionTypes &browserItemActionTypes); + bool hasBrowserItemActionType(const ActionTypeId &actionTypeId); ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); @@ -107,9 +110,6 @@ public: QStringList interfaces() const; void setInterfaces(const QStringList &interfaces); - bool browsable() const; - void setBrowsable(bool browsable); - bool operator==(const DeviceClass &device) const; private: @@ -122,7 +122,7 @@ private: StateTypes m_stateTypes; EventTypes m_eventTypes; ActionTypes m_actionTypes; - ActionTypes m_browserActionTypes; + ActionTypes m_browserItemActionTypes; ParamTypes m_paramTypes; ParamTypes m_settingsTypes; ParamTypes m_discoveryParamTypes; diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 531031dd..4b80cb91 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -51,7 +51,7 @@ DevicePluginMock::DevicePluginMock() { - + generateBrowseItems(); } DevicePluginMock::~DevicePluginMock() @@ -239,17 +239,21 @@ Device::DeviceError DevicePluginMock::displayPin(const PairingTransactionId &pai return Device::DeviceErrorNoError; } -Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId) +Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId) { qCDebug(dcMockDevice()) << "Browse device called" << device; if (device->deviceClassId() == mockDeviceClassId) { if (device->paramValue(mockDeviceAsyncParamTypeId).toBool()) { result.status = Device::DeviceErrorAsync; - QTimer::singleShot(1000, device, [this, device, result, nodeId]() mutable { + QTimer::singleShot(1000, device, [this, device, result, itemId]() mutable { if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) { result.status = Device::DeviceErrorHardwareFailure; } else { - result = generateBrowseItems(nodeId, result); + VirtualFsNode *node = m_virtualFs->findNode(itemId); + foreach (VirtualFsNode *child, node->childs) { + result.items.append(child->item); + } + result.status = Device::DeviceErrorNoError; } emit browseRequestFinished(result); }); @@ -257,7 +261,11 @@ Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::Brow else if (device->paramValue(mockDeviceBrokenParamTypeId).toBool()) { result.status = Device::DeviceErrorHardwareFailure; } else { - result = generateBrowseItems(nodeId, result); + VirtualFsNode *node = m_virtualFs->findNode(itemId); + foreach (VirtualFsNode *child, node->childs) { + result.items.append(child->item); + } + result.status = Device::DeviceErrorNoError; } } return result; @@ -393,8 +401,9 @@ Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action return Device::DeviceErrorDeviceClassNotFound; } -Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) +Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const BrowserAction &browserAction) { + qCDebug(dcMockDevice()) << "ExecuteBrowserItem called"; bool broken = device->paramValue(mockDeviceBrokenParamTypeId).toBool(); bool async = device->paramValue(mockDeviceAsyncParamTypeId).toBool(); @@ -405,12 +414,46 @@ Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const B return Device::DeviceErrorNoError; } - QTimer::singleShot(2000, device, [this, broken, browserItemAction](){ - emit this->browserItemExecutionFinished(browserItemAction.id(), broken ? Device::DeviceErrorHardwareFailure : Device::DeviceErrorNoError); + QTimer::singleShot(2000, device, [this, broken, browserAction](){ + emit this->browserItemExecutionFinished(browserAction.id(), broken ? Device::DeviceErrorHardwareFailure : Device::DeviceErrorNoError); }); return Device::DeviceErrorAsync; } +Device::DeviceError DevicePluginMock::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) +{ + qCDebug(dcMockDevice()) << "TODO" << device << browserItemAction.id(); + if (browserItemAction.actionTypeId() == mockAddToFavoritesBrowserItemActionTypeId) { + + VirtualFsNode *node = m_virtualFs->findNode(browserItemAction.itemId()); + if (!node) { + return Device::DeviceErrorInvalidParameter; + } + VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites"); + if (favoritesNode->findNode(browserItemAction.itemId())) { + return Device::DeviceErrorDeviceInUse; + } + BrowserItem newItem = node->item; + newItem.setActionTypeIs({mockRemoveFromFavoritesBrowserItemActionTypeId}); + VirtualFsNode *newNode = new VirtualFsNode(newItem); + favoritesNode->addChild(newNode); + return Device::DeviceErrorNoError; + } + + if (browserItemAction.actionTypeId() == mockRemoveFromFavoritesBrowserItemActionTypeId) { + VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites"); + VirtualFsNode *nodeToRemove = favoritesNode->findNode(browserItemAction.itemId()); + if (!nodeToRemove) { + return Device::DeviceErrorItemNotFound; + } + int idx = favoritesNode->childs.indexOf(nodeToRemove); + delete favoritesNode->childs.takeAt(idx); + return Device::DeviceErrorNoError; + } + + return Device::DeviceErrorActionTypeNotFound; +} + void DevicePluginMock::setState(const StateTypeId &stateTypeId, const QVariant &value) { HttpDaemon *daemon = qobject_cast(sender()); @@ -609,58 +652,42 @@ void DevicePluginMock::onPluginConfigChanged() } -Device::BrowseResult DevicePluginMock::generateBrowseItems(const QString &itemId, Device::BrowseResult result) +void DevicePluginMock::generateBrowseItems() { - result.status = Device::DeviceErrorNoError; + m_virtualFs = new VirtualFsNode(BrowserItem()); - if (itemId.isEmpty()) { - BrowserItem item = BrowserItem("0", "Item 0", true); - item.setDescription("I'm a folder"); - item.setIcon(BrowserItem::BrowserIconFolder); - result.items.append(item); + BrowserItem item = BrowserItem(QUuid::createUuid().toString(), "Item 0", true); + item.setDescription("I'm a folder"); + item.setIcon(BrowserItem::BrowserIconFolder); + m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem("1", "Item 1", false, true); - item.setDescription("I'm executable"); - item.setIcon(BrowserItem::BrowserIconApplication); - result.items.append(item); + item = BrowserItem(QUuid::createUuid().toString(), "Item 1", false, true); + item.setDescription("I'm executable"); + item.setIcon(BrowserItem::BrowserIconApplication); + item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem("2", "Item 2", false, true); - item.setDescription("I'm a file"); - item.setIcon(BrowserItem::BrowserIconFile); - result.items.append(item); + item = BrowserItem(QUuid::createUuid().toString(), "Item 2", false, true); + item.setDescription("I'm a file"); + item.setIcon(BrowserItem::BrowserIconFile); + item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem("3", "Item 3", false, true); - item.setDescription("I have a nice thumbnail"); - item.setIcon(BrowserItem::BrowserIconFile); - item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png"); - result.items.append(item); + item = BrowserItem(QUuid::createUuid().toString(), "Item 3", false, true); + item.setDescription("I have a nice thumbnail"); + item.setIcon(BrowserItem::BrowserIconFile); + item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png"); + item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem("4", "Item 4", false, false); - item.setDescription("I'm disabled"); - item.setIcon(BrowserItem::BrowserIconFile); - result.items.append(item); + item = BrowserItem(QUuid::createUuid().toString(), "Item 4", false, false); + item.setDescription("I'm disabled"); + item.setDisabled(true); + item.setIcon(BrowserItem::BrowserIconFile); + m_virtualFs->addChild(new VirtualFsNode(item)); - } - else if (itemId == "0") { - result.items.append(BrowserItem("5", "Item 5")); - result.items.append(BrowserItem("6", "Item 6")); - result.items.append(BrowserItem("7", "Item 7")); - result.items.append(BrowserItem("8", "Item 8")); - result.items.append(BrowserItem("9", "Item 9")); - } - else if (itemId == "2") { - result.items.append(BrowserItem("10", "Item 10", true)); - result.items.append(BrowserItem("11", "Item 11")); - result.items.append(BrowserItem("12", "Item 12")); - result.items.append(BrowserItem("13", "Item 13")); - result.items.append(BrowserItem("14", "Item 14")); - } - else if (itemId == "10") { - result.items.append(BrowserItem("15", "Item 15")); - result.items.append(BrowserItem("16", "Item 16")); - } else { - result.status = Device::DeviceErrorInvalidParameter; - } - - return result; + item = BrowserItem("favorites", "Favorites", true, false); + item.setDescription("Yay! I'm the best!"); + item.setIcon(BrowserItem::BrowserIconFavorites); + m_virtualFs->addChild(new VirtualFsNode(item)); } diff --git a/plugins/mock/devicepluginmock.h b/plugins/mock/devicepluginmock.h index 521a3e17..fef7c73c 100644 --- a/plugins/mock/devicepluginmock.h +++ b/plugins/mock/devicepluginmock.h @@ -52,11 +52,12 @@ public: Device::DeviceSetupStatus confirmPairing(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const ParamList ¶ms, const QString &secret) override; Device::DeviceError displayPin(const PairingTransactionId &pairingTransactionId, const DeviceDescriptor &deviceDescriptor) override; - Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()) override; + Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId = QString()) override; public slots: Device::DeviceError executeAction(Device *device, const Action &action) override; - Device::DeviceError executeBrowserItem(Device *device, const BrowserItemAction &browserItemAction) override; + Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction) override; + Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override; private slots: void setState(const StateTypeId &stateTypeId, const QVariant &value); @@ -76,9 +77,25 @@ private slots: void onPluginConfigChanged(); private: - Device::BrowseResult generateBrowseItems(const QString &itemId, Device::BrowseResult result); + void generateBrowseItems(); private: + class VirtualFsNode { + public: + VirtualFsNode(const BrowserItem &item):item(item) {} + BrowserItem item; + QList childs; + void addChild(VirtualFsNode* child) {childs.append(child); } + VirtualFsNode *findNode(const QString &id) { + if (item.id() == id) return this; + foreach (VirtualFsNode *child, childs) { + VirtualFsNode *node = child->findNode(id); + if (node) return node; + } + return nullptr; + } + }; + QHash m_daemons; QList m_asyncSetupDevices; QList > m_asyncActions; @@ -87,6 +104,8 @@ private: int m_discoveredDeviceCount; bool m_pushbuttonPressed; + + VirtualFsNode* m_virtualFs = nullptr; }; #endif // DEVICEPLUGINMOCK_H diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 205b654e..6455525d 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -195,11 +195,16 @@ "displayName": "Mock Action 5 (async, broken)" } ], - "brwserItemActionTypes": [ + "browserItemActionTypes": [ { "id": "00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3", - "name": "addToFolder", - "displayName": "Add to Favorites" + "name": "addToFavorites", + "displayName": "Add to favorites" + }, + { + "id": "da6faef8-2816-430e-93bb-57e8f9582d29", + "name": "removeFromFavorites", + "displayName": "Remove from favorites" } ] }, diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index cdce91d9..cb3d8438 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -50,6 +50,8 @@ extern ActionTypeId mockWithoutParamsActionTypeId; extern ActionTypeId mockAsyncActionTypeId; extern ActionTypeId mockFailingActionTypeId; extern ActionTypeId mockAsyncFailingActionTypeId; +extern ActionTypeId mockAddToFavoritesBrowserItemActionTypeId; +extern ActionTypeId mockRemoveFromFavoritesBrowserItemActionTypeId; extern DeviceClassId mockDeviceAutoDeviceClassId; extern ParamTypeId mockDeviceAutoDeviceHttpportParamTypeId; extern ParamTypeId mockDeviceAutoDeviceAsyncParamTypeId; diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index 4c8ad1d7..39e0e5c0 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -54,6 +54,8 @@ ActionTypeId mockWithoutParamsActionTypeId = ActionTypeId("{defd3ed6-1a0d-400b-8 ActionTypeId mockAsyncActionTypeId = ActionTypeId("{fbae06d3-7666-483e-a39e-ec50fe89054e}"); ActionTypeId mockFailingActionTypeId = ActionTypeId("{df3cf33d-26d5-4577-9132-9823bd33fad0}"); ActionTypeId mockAsyncFailingActionTypeId = ActionTypeId("{bfe89a1d-3497-4121-8318-e77c37537219}"); +ActionTypeId mockAddToFavoritesBrowserItemActionTypeId = ActionTypeId("{00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}"); +ActionTypeId mockRemoveFromFavoritesBrowserItemActionTypeId = ActionTypeId("{da6faef8-2816-430e-93bb-57e8f9582d29}"); DeviceClassId mockDeviceAutoDeviceClassId = DeviceClassId("{ab4257b3-7548-47ee-9bd4-7dc3004fd197}"); ParamTypeId mockDeviceAutoDeviceHttpportParamTypeId = ParamTypeId("{bfeb0613-dab6-408c-aa27-c362c921d0d1}"); ParamTypeId mockDeviceAutoDeviceAsyncParamTypeId = ParamTypeId("{a5c4315f-0624-4971-87c1-4bbfbfdbd16e}"); @@ -247,6 +249,9 @@ ActionTypeId mockInputTypeWritableTimestampUIntActionTypeId = ActionTypeId("{45d ParamTypeId mockInputTypeWritableTimestampUIntActionWritableTimestampUIntParamTypeId = ParamTypeId("{45d0069a-63ac-4265-8170-8152778608ee}"); const QString translations[] { + //: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of DeviceClass mock + QT_TRANSLATE_NOOP("mockDevice", "Add to favorites"), + //: The name of the ParamType (DeviceClass: mockInputType, EventType: bool, ID: {3bad3a09-5826-4ed7-a832-10e3e2ee2a7d}) QT_TRANSLATE_NOOP("mockDevice", "Bool"), @@ -412,6 +417,9 @@ const QString translations[] { //: The pairing info of deviceClass mockDisplayPin QT_TRANSLATE_NOOP("mockDevice", "Please enter the secret which normaly will be displayed on the device. For the mockdevice the pin is 243681."), + //: The name of the Browser Item ActionType ({da6faef8-2816-430e-93bb-57e8f9582d29}) of DeviceClass mock + QT_TRANSLATE_NOOP("mockDevice", "Remove from favorites"), + //: The name of the ParamType (DeviceClass: mockInputType, Type: device, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3}) QT_TRANSLATE_NOOP("mockDevice", "Search text"), diff --git a/tools/nymea-plugininfocompiler/plugininfocompiler.cpp b/tools/nymea-plugininfocompiler/plugininfocompiler.cpp index 7c418d56..eb58f4ec 100644 --- a/tools/nymea-plugininfocompiler/plugininfocompiler.cpp +++ b/tools/nymea-plugininfocompiler/plugininfocompiler.cpp @@ -228,6 +228,7 @@ void PluginInfoCompiler::writeDeviceClass(const DeviceClass &deviceClass) writeStateTypes(deviceClass.stateTypes(), deviceClass.name()); writeEventTypes(deviceClass.eventTypes(), deviceClass.name()); writeActionTypes(deviceClass.actionTypes(), deviceClass.name()); + writeBrowserItemActionTypes(deviceClass.browserItemActionTypes(), deviceClass.name()); } void PluginInfoCompiler::writeStateTypes(const StateTypes &stateTypes, const QString &deviceClassName) @@ -276,8 +277,24 @@ void PluginInfoCompiler::writeActionTypes(const ActionTypes &actionTypes, const writeExtern(QString("extern ActionTypeId %1;").arg(variableName)); writeParams(actionType.paramTypes(), deviceClassName, "Action", actionType.name()); - } + } +} +void PluginInfoCompiler::writeBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName) +{ + foreach (const ActionType &actionType, actionTypes) { + QString variableName = QString("%1%2BrowserItemActionTypeId").arg(deviceClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1)); + if (m_variableNames.contains(variableName)) { + qWarning().nospace() << "Error: Duplicate name " << variableName << " for Browser Item ActionType " << actionType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry."; + return; + } + m_variableNames.append(variableName); + write(QString("ActionTypeId %1 = ActionTypeId(\"%2\");").arg(variableName).arg(actionType.id().toString())); + m_translationStrings.insert(actionType.displayName(), QString("The name of the Browser Item ActionType (%1) of DeviceClass %2").arg(actionType.id().toString()).arg(deviceClassName)); + writeExtern(QString("extern ActionTypeId %1;").arg(variableName)); + + writeParams(actionType.paramTypes(), deviceClassName, "BrowserItemAction", actionType.name()); + } } void PluginInfoCompiler::write(const QString &line) diff --git a/tools/nymea-plugininfocompiler/plugininfocompiler.h b/tools/nymea-plugininfocompiler/plugininfocompiler.h index aea74c25..c816416c 100644 --- a/tools/nymea-plugininfocompiler/plugininfocompiler.h +++ b/tools/nymea-plugininfocompiler/plugininfocompiler.h @@ -45,6 +45,7 @@ private: void writeStateTypes(const StateTypes &stateTypes, const QString &deviceClassName); void writeEventTypes(const EventTypes &eventTypes, const QString &deviceClassName); void writeActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName); + void writeBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName); void write(const QString &line = QString()); void writeExtern(const QString &line = QString()); From 379845c0ba1a456c59d642cacbda681d6666fba4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 10 Jul 2019 18:09:18 +0200 Subject: [PATCH 13/22] update api --- tests/auto/api.json | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/auto/api.json b/tests/auto/api.json index 259b6320..7f378ef9 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -14,6 +14,30 @@ "deviceError": "$ref:DeviceError" } }, + "Actions.ExecuteBrowserItem": { + "description": "Execute the item identified by itemId on the given device.", + "params": { + "deviceId": "Uuid", + "itemId": "String" + }, + "returns": { + "deviceError": "$ref:DeviceError" + } + }, + "Actions.ExecuteBrowserItemAction": { + "description": "Execute the action for the browser item identified by actionTypeId and the itemId on the given device.", + "params": { + "actionTypeId": "Uuid", + "deviceId": "Uuid", + "itemId": "String", + "o:params": [ + "$ref:Param" + ] + }, + "returns": { + "deviceError": "$ref:DeviceError" + } + }, "Actions.GetActionType": { "description": "Get the ActionType for the given ActionTypeId", "params": { @@ -253,7 +277,7 @@ "description": "Browse a device. If a DeviceClass indicates a device is browsable, this method will return the BrowserItems. If no parameter besides the deviceId is used, the root node of this device will be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node.", "params": { "deviceId": "Uuid", - "o:nodeId": "String" + "o:itemId": "String" }, "returns": { "deviceError": "$ref:DeviceError", @@ -283,16 +307,6 @@ "deviceError": "$ref:DeviceError" } }, - "Devices.ExecuteBrowserItem": { - "description": "Execute the item identified by nodeId on the given device.", - "params": { - "deviceId": "Uuid", - "o:nodeId": "String" - }, - "returns": { - "deviceError": "$ref:DeviceError" - } - }, "Devices.GetActionTypes": { "description": "Get action types for a specified deviceClassId.", "params": { @@ -1376,8 +1390,12 @@ ], "BrowserIcon": "$ref:BrowserIcon", "BrowserItem": { + "actionTypeIds": [ + "Uuid" + ], "browsable": "Bool", "description": "String", + "disabled": "Bool", "displayName": "String", "executable": "Bool", "icon": "$ref:BrowserIcon", @@ -1436,6 +1454,9 @@ "$ref:ActionType" ], "browsable": "Bool", + "browserItemActionTypes": [ + "$ref:ActionType" + ], "createMethods": [ "$ref:CreateMethod" ], @@ -1498,6 +1519,7 @@ "DeviceErrorDeviceIsChild", "DeviceErrorPairingTransactionIdNotFound", "DeviceErrorParameterNotWritable", + "DeviceErrorItemNotFound", "DeviceErrorUnsupportedFeature" ], "Event": { @@ -1543,6 +1565,7 @@ "o:deviceId": "Uuid", "o:errorCode": "String", "o:eventType": "$ref:LoggingEventType", + "o:itemId": "String", "o:typeId": "Uuid", "o:value": "String", "source": "$ref:LoggingSource", @@ -1569,7 +1592,8 @@ "LoggingSourceEvents", "LoggingSourceActions", "LoggingSourceStates", - "LoggingSourceRules" + "LoggingSourceRules", + "LoggingSourceBrowserActions" ], "MediaBrowserIcon": "$ref:MediaBrowserIcon", "MqttPolicy": { From 692bf6cf967a332165cfe231783de4bcb6a1b14a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 11 Jul 2019 00:21:01 +0200 Subject: [PATCH 14/22] typo fix --- libnymea/types/browseritem.cpp | 2 +- libnymea/types/browseritem.h | 2 +- plugins/mock/devicepluginmock.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index 3aa5354f..d4e54e28 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -112,7 +112,7 @@ QList BrowserItem::actionTypeIds() const return m_actionTypeIds; } -void BrowserItem::setActionTypeIs(const QList &actionTypeIds) +void BrowserItem::setActionTypeIds(const QList &actionTypeIds) { m_actionTypeIds = actionTypeIds; } diff --git a/libnymea/types/browseritem.h b/libnymea/types/browseritem.h index a4daba95..466f725e 100644 --- a/libnymea/types/browseritem.h +++ b/libnymea/types/browseritem.h @@ -83,7 +83,7 @@ public: void setThumbnail(const QString &thumbnail); QList actionTypeIds() const; - void setActionTypeIs(const QList &actionTypeIds); + void setActionTypeIds(const QList &actionTypeIds); ExtendedPropertiesFlags extendedPropertiesFlags() const; QVariant extendedProperty(const QString &propertyName) const; diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 4b80cb91..bff30454 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -434,7 +434,7 @@ Device::DeviceError DevicePluginMock::executeBrowserItemAction(Device *device, c return Device::DeviceErrorDeviceInUse; } BrowserItem newItem = node->item; - newItem.setActionTypeIs({mockRemoveFromFavoritesBrowserItemActionTypeId}); + newItem.setActionTypeIds({mockRemoveFromFavoritesBrowserItemActionTypeId}); VirtualFsNode *newNode = new VirtualFsNode(newItem); favoritesNode->addChild(newNode); return Device::DeviceErrorNoError; @@ -664,20 +664,20 @@ void DevicePluginMock::generateBrowseItems() item = BrowserItem(QUuid::createUuid().toString(), "Item 1", false, true); item.setDescription("I'm executable"); item.setIcon(BrowserItem::BrowserIconApplication); - item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); item = BrowserItem(QUuid::createUuid().toString(), "Item 2", false, true); item.setDescription("I'm a file"); item.setIcon(BrowserItem::BrowserIconFile); - item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); item = BrowserItem(QUuid::createUuid().toString(), "Item 3", false, true); item.setDescription("I have a nice thumbnail"); item.setIcon(BrowserItem::BrowserIconFile); item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png"); - item.setActionTypeIs({mockAddToFavoritesBrowserItemActionTypeId}); + item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); item = BrowserItem(QUuid::createUuid().toString(), "Item 4", false, false); From 4e7557f9640acfef3c1fdd9d82745317e5595d22 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 15 Jul 2019 10:15:23 +0200 Subject: [PATCH 15/22] add locale option --- .../devices/devicemanagerimplementation.cpp | 4 ++-- .../devices/devicemanagerimplementation.h | 2 +- libnymea-core/jsonrpc/devicehandler.cpp | 2 +- libnymea/devices/devicemanager.h | 2 +- libnymea/devices/deviceplugin.cpp | 14 ++++++++------ libnymea/devices/deviceplugin.h | 2 +- plugins/mock/devicepluginmock.cpp | 3 ++- plugins/mock/devicepluginmock.h | 2 +- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index f5f8aa9a..863bc7d8 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -710,7 +710,7 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De return Device::DeviceErrorNoError; } -Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId) +Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) { Q_UNUSED(deviceId) Q_UNUSED(itemId) @@ -730,7 +730,7 @@ Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &d return result; } - result = device->plugin()->browseDevice(device, result, itemId); + result = device->plugin()->browseDevice(device, result, itemId, locale); return result; } diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 24766d7e..0362567e 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -99,7 +99,7 @@ public: Device::DeviceError executeAction(const Action &action) override; - Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) override; + Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) override; Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) override; Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) override; diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index b4479818..9a13983c 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -681,7 +681,7 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const DeviceId deviceId = DeviceId(params.value("deviceId").toString()); QString itemId = params.value("itemId").toString(); - Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, itemId); + Device::BrowseResult result = NymeaCore::instance()->deviceManager()->browseDevice(deviceId, itemId, params.value("locale").toLocale()); if (result.status == Device::DeviceErrorAsync ) { JsonReply *reply = createAsyncReply("BrowseDevice"); diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index b1ac9308..4b9d4464 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -74,7 +74,7 @@ public: virtual Device::DeviceError executeAction(const Action &action) = 0; - virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId = QString()) = 0; + virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) = 0; virtual Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) = 0; virtual Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) = 0; diff --git a/libnymea/devices/deviceplugin.cpp b/libnymea/devices/deviceplugin.cpp index 7b45dd1a..a4607b6f 100644 --- a/libnymea/devices/deviceplugin.cpp +++ b/libnymea/devices/deviceplugin.cpp @@ -248,21 +248,23 @@ Device::DeviceError DevicePlugin::executeAction(Device *device, const Action &ac return Device::DeviceErrorNoError; } -/*! Implement this if your devices support browsing. When the system calls this method, - * fill the \a result object's items list with entries from the browser. If \a itemId is empty - * it means that the root node of the file system should be returned. Each item in the result set - * shall be uniquely identifiable using its \l{BrowserItem::id}{id} property. +/*! Implement this if your devices support browsing (besides settings browsable to true in the metadata). + * When the system calls this method, fill the \a result object's items list with entries from the browser. + * If \a itemId is empty it means that the root node of the file system should be returned. Each item in + * the result set shall be uniquely identifiable using its \l{BrowserItem::id}{id} property. * The system might call this method again, with an \a itemId returned in a previous query, provided * that item's \l{BrowserItem::browsable} property is true. In this case all children of the given - * item shall be returned. + * item shall be returned. All browser \l {BrowserItem::displayName} properties shall be localized + * using the given \a locale. * When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's * status to Device::DeviceErrorAsync if this operation requires async behavior and emit * \l{browseRequestFinished} when done. */ -Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId) +Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) { Q_UNUSED(device) Q_UNUSED(itemId) + Q_UNUSED(locale) result.status = Device::DeviceErrorUnsupportedFeature; return result; diff --git a/libnymea/devices/deviceplugin.h b/libnymea/devices/deviceplugin.h index 818d249c..2b425313 100644 --- a/libnymea/devices/deviceplugin.h +++ b/libnymea/devices/deviceplugin.h @@ -80,7 +80,7 @@ public: virtual Device::DeviceError executeAction(Device *device, const Action &action); - virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId = QString()); + virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId, const QLocale &locale); virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction); virtual Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction); diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index bff30454..efe64e7a 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -239,8 +239,9 @@ Device::DeviceError DevicePluginMock::displayPin(const PairingTransactionId &pai return Device::DeviceErrorNoError; } -Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId) +Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) { + Q_UNUSED(locale) qCDebug(dcMockDevice()) << "Browse device called" << device; if (device->deviceClassId() == mockDeviceClassId) { if (device->paramValue(mockDeviceAsyncParamTypeId).toBool()) { diff --git a/plugins/mock/devicepluginmock.h b/plugins/mock/devicepluginmock.h index fef7c73c..76cbd647 100644 --- a/plugins/mock/devicepluginmock.h +++ b/plugins/mock/devicepluginmock.h @@ -52,7 +52,7 @@ public: Device::DeviceSetupStatus confirmPairing(const PairingTransactionId &pairingTransactionId, const DeviceClassId &deviceClassId, const ParamList ¶ms, const QString &secret) override; Device::DeviceError displayPin(const PairingTransactionId &pairingTransactionId, const DeviceDescriptor &deviceDescriptor) override; - Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId = QString()) override; + Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override; public slots: Device::DeviceError executeAction(Device *device, const Action &action) override; From 0fb42681af1623601a0382b717e90e4ba7abe2e1 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 17 Jul 2019 20:48:16 +0200 Subject: [PATCH 16/22] A little more work on browser item support for rules --- libnymea-core/jsonrpc/jsontypes.cpp | 11 +- libnymea-core/libnymea-core.pro | 8 +- libnymea-core/nymeacore.cpp | 32 +++++ .../ruleengine}/ruleaction.cpp | 2 +- .../ruleengine}/ruleaction.h | 4 +- .../ruleengine}/ruleactionparam.cpp | 0 .../ruleengine}/ruleactionparam.h | 2 +- libnymea-core/ruleengine/ruleengine.cpp | 132 +++++++++--------- libnymea-core/ruleengine/ruleengine.h | 2 + .../stateevaluator.cpp | 0 .../{devices => ruleengine}/stateevaluator.h | 0 libnymea/libnymea.pro | 4 - 12 files changed, 120 insertions(+), 77 deletions(-) rename {libnymea/types => libnymea-core/ruleengine}/ruleaction.cpp (99%) rename {libnymea/types => libnymea-core/ruleengine}/ruleaction.h (98%) rename {libnymea/types => libnymea-core/ruleengine}/ruleactionparam.cpp (100%) rename {libnymea/types => libnymea-core/ruleengine}/ruleactionparam.h (99%) rename libnymea-core/{devices => ruleengine}/stateevaluator.cpp (100%) rename libnymea-core/{devices => ruleengine}/stateevaluator.h (100%) diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index b682567f..88326016 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -184,6 +184,7 @@ void JsonTypes::init() s_ruleAction.insert("o:actionTypeId", basicTypeToString(Uuid)); s_ruleAction.insert("o:interface", basicTypeToString(String)); s_ruleAction.insert("o:interfaceAction", basicTypeToString(String)); + s_ruleAction.insert("o:browserItemId", basicTypeToString(String)); s_ruleAction.insert("o:ruleActionParams", QVariantList() << ruleActionParamRef()); // RuleActionParam @@ -617,8 +618,11 @@ QVariantMap JsonTypes::packRuleAction(const RuleAction &ruleAction) { QVariantMap variant; if (ruleAction.type() == RuleAction::TypeDevice) { - variant.insert("actionTypeId", ruleAction.actionTypeId().toString()); variant.insert("deviceId", ruleAction.deviceId().toString()); + variant.insert("actionTypeId", ruleAction.actionTypeId().toString()); + } else if (ruleAction.type() == RuleAction::TypeBrowser) { + variant.insert("deviceId", ruleAction.deviceId().toString()); + variant.insert("browserItemId", ruleAction.browserItemId()); } else { variant.insert("interface", ruleAction.interface()); variant.insert("interfaceAction", ruleAction.interfaceAction()); @@ -1500,10 +1504,13 @@ RuleAction JsonTypes::unpackRuleAction(const QVariantMap &ruleActionMap) DeviceId actionDeviceId(ruleActionMap.value("deviceId").toString()); QString interface = ruleActionMap.value("interface").toString(); QString interfaceAction = ruleActionMap.value("interfaceAction").toString(); + QString browserItemId = ruleActionMap.value("browserItemId").toString(); RuleActionParamList actionParamList = JsonTypes::unpackRuleActionParams(ruleActionMap.value("ruleActionParams").toList()); - if (!actionTypeId.isNull() && !actionDeviceId.isNull()) { + if (!actionDeviceId.isNull() && !actionTypeId.isNull()) { return RuleAction(actionTypeId, actionDeviceId, actionParamList); + } else if (!actionDeviceId.isNull() && !browserItemId.isNull()) { + return RuleAction(actionDeviceId, browserItemId); } return RuleAction(interface, interfaceAction, actionParamList); } diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index d539ac5b..c8964329 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -18,9 +18,11 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ devices/devicemanagerimplementation.h \ devices/translator.h \ - devices/stateevaluator.h \ ruleengine/ruleengine.h \ ruleengine/rule.h \ + ruleengine/stateevaluator.h \ + ruleengine/ruleaction.h \ + ruleengine/ruleactionparam.h \ transportinterface.h \ nymeaconfiguration.h \ servermanager.h \ @@ -102,9 +104,11 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ devices/devicemanagerimplementation.cpp \ devices/translator.cpp \ - devices/stateevaluator.cpp \ ruleengine/ruleengine.cpp \ ruleengine/rule.cpp \ + ruleengine/stateevaluator.cpp \ + ruleengine/ruleaction.cpp \ + ruleengine/ruleactionparam.cpp \ transportinterface.cpp \ nymeaconfiguration.cpp \ servermanager.cpp \ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 9cfa886f..39489898 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -478,9 +478,14 @@ Device::DeviceError NymeaCore::executeBrowserItemAction(const BrowserItemAction void NymeaCore::executeRuleActions(const QList ruleActions) { QList actions; + QList browserActions; foreach (const RuleAction &ruleAction, ruleActions) { if (ruleAction.type() == RuleAction::TypeDevice) { Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId()); + if (!device) { + qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "for rule action" << ruleAction; + continue; + } ActionTypeId actionTypeId = ruleAction.actionTypeId(); ParamList params; bool ok = true; @@ -510,6 +515,14 @@ void NymeaCore::executeRuleActions(const QList ruleActions) Action action(actionTypeId, device->id()); action.setParams(params); actions.append(action); + } else if (ruleAction.type() == RuleAction::TypeBrowser) { + Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId()); + if (!device) { + qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "for rule action" << ruleAction; + continue; + } + BrowserAction browserAction(ruleAction.deviceId(), ruleAction.browserItemId()); + browserActions.append(browserAction); } else { QList devices = m_deviceManager->findConfiguredDevices(ruleAction.interface()); foreach (Device* device, devices) { @@ -581,6 +594,25 @@ void NymeaCore::executeRuleActions(const QList ruleActions) // if (status != Device::DeviceErrorAsync) // m_logger->logAction(action, status == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); } + + foreach (const BrowserAction &browserAction, browserActions) { + Device::DeviceError status = executeBrowserItem(browserAction); + switch(status) { + case Device::DeviceErrorNoError: + break; + case Device::DeviceErrorSetupFailed: + qCWarning(dcRuleEngine) << "Error executing action. Device setup failed."; + break; + case Device::DeviceErrorAsync: + qCDebug(dcRuleEngine) << "Executing asynchronous action."; + break; + case Device::DeviceErrorInvalidParameter: + qCWarning(dcRuleEngine) << "Error executing action. Invalid action parameter."; + break; + default: + qCWarning(dcRuleEngine) << "Error executing action:" << status; + } + } } /*! Calls the metheod RuleEngine::removeRule(\a id). diff --git a/libnymea/types/ruleaction.cpp b/libnymea-core/ruleengine/ruleaction.cpp similarity index 99% rename from libnymea/types/ruleaction.cpp rename to libnymea-core/ruleengine/ruleaction.cpp index 1e7580e2..10b31d11 100644 --- a/libnymea/types/ruleaction.cpp +++ b/libnymea-core/ruleengine/ruleaction.cpp @@ -112,7 +112,7 @@ RuleAction::Type RuleAction::type() const if (!m_deviceId.isNull() && !m_actionTypeId.isNull()) { return TypeDevice; } - if (!m_deviceId.isNull() && m_browserItemId.isEmpty()) { + if (!m_deviceId.isNull() && !m_browserItemId.isEmpty()) { return TypeBrowser; } if (!m_interface.isEmpty() && !m_interfaceAction.isEmpty()) { diff --git a/libnymea/types/ruleaction.h b/libnymea-core/ruleengine/ruleaction.h similarity index 98% rename from libnymea/types/ruleaction.h rename to libnymea-core/ruleengine/ruleaction.h index 3dc50ab3..d71b318a 100644 --- a/libnymea/types/ruleaction.h +++ b/libnymea-core/ruleengine/ruleaction.h @@ -25,9 +25,9 @@ #define RULEACTION_H #include "libnymea.h" -#include "action.h" +#include "types/action.h" +#include "types/browseritemaction.h" #include "ruleactionparam.h" -#include "browseritemaction.h" class LIBNYMEA_EXPORT RuleAction { diff --git a/libnymea/types/ruleactionparam.cpp b/libnymea-core/ruleengine/ruleactionparam.cpp similarity index 100% rename from libnymea/types/ruleactionparam.cpp rename to libnymea-core/ruleengine/ruleactionparam.cpp diff --git a/libnymea/types/ruleactionparam.h b/libnymea-core/ruleengine/ruleactionparam.h similarity index 99% rename from libnymea/types/ruleactionparam.h rename to libnymea-core/ruleengine/ruleactionparam.h index 20257546..5608844c 100644 --- a/libnymea/types/ruleactionparam.h +++ b/libnymea-core/ruleengine/ruleactionparam.h @@ -28,7 +28,7 @@ #include #include -#include "param.h" +#include "types/param.h" #include "libnymea.h" #include "typeutils.h" diff --git a/libnymea-core/ruleengine/ruleengine.cpp b/libnymea-core/ruleengine/ruleengine.cpp index 76f4e0b2..35f0e93d 100644 --- a/libnymea-core/ruleengine/ruleengine.cpp +++ b/libnymea-core/ruleengine/ruleengine.cpp @@ -912,7 +912,7 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, const Rule &rule) { if (!ruleAction.isValid()) { - qWarning(dcRuleEngine()) << "Action is incomplete. It must have either actionTypeId and deviceId, or interface and interfaceAction"; + qWarning(dcRuleEngine()) << "Action is incomplete. It must have either deviceId and actionTypeId/browserItemId, or interface and interfaceAction:" << ruleAction; return RuleErrorActionTypeNotFound; } @@ -920,7 +920,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, if (ruleAction.type() == RuleAction::TypeDevice) { Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId()); if (!device) { - qCWarning(dcRuleEngine) << "Cannot create rule. No configured device for action with actionTypeId" << ruleAction.actionTypeId(); + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device with ID" << ruleAction.deviceId(); return RuleErrorDeviceNotFound; } @@ -942,30 +942,44 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, qCWarning(dcRuleEngine()) << "Cannot create rule. Interface" << iface.name() << "does not implement action" << ruleAction.interfaceAction(); return RuleError::RuleErrorActionTypeNotFound; } + } else if (ruleAction.type() == RuleAction::TypeBrowser) { + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(ruleAction.deviceId()); + if (!device) { + qCWarning(dcRuleEngine) << "Cannot create rule. No configured device with ID" << ruleAction.deviceId(); + return RuleErrorDeviceNotFound; + } + if (ruleAction.browserItemId().isEmpty()) { + qCWarning(dcRuleEngine()) << "Cannot create rule with empty browserItemId"; + return RuleErrorInvalidRuleActionParameter; + } + } else { return RuleErrorActionTypeNotFound; } - // Verify given params - foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - RuleError ruleActionParamError = checkRuleActionParam(ruleActionParam, actionType, rule); - if (ruleActionParamError != RuleErrorNoError) { - return ruleActionParamError; - } - } - - // Verify all required params are given - foreach (const ParamType ¶mType, actionType.paramTypes()) { - bool found = false; + // Not all rule actions might have an actiontype (e.g. browser item executions) + if (!actionType.id().isNull()) { + // Verify given params foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - if (ruleActionParam.paramTypeId() == paramType.id() - || ruleActionParam.paramName() == paramType.name()) { - found = true; - break; + RuleError ruleActionParamError = checkRuleActionParam(ruleActionParam, actionType, rule); + if (ruleActionParamError != RuleErrorNoError) { + return ruleActionParamError; } } - if (!found) { - return RuleErrorMissingParameter; + + // Verify all required params are given + foreach (const ParamType ¶mType, actionType.paramTypes()) { + bool found = false; + foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { + if (ruleActionParam.paramTypeId() == paramType.id() + || ruleActionParam.paramName() == paramType.name()) { + found = true; + break; + } + } + if (!found) { + return RuleErrorMissingParameter; + } } } @@ -1190,66 +1204,54 @@ void RuleEngine::saveRule(const Rule &rule) rule.stateEvaluator().dumpToSettings(settings, "stateEvaluator"); // Save ruleActions - int i = 0; settings.beginGroup("ruleActions"); - foreach (const RuleAction &action, rule.actions()) { - settings.beginGroup(QString::number(i)); - if (!action.deviceId().isNull() && !action.actionTypeId().isNull()) { - settings.setValue("deviceId", action.deviceId().toString()); - settings.setValue("actionTypeId", action.actionTypeId().toString()); - } else { - settings.setValue("interface", action.interface()); - settings.setValue("interfaceAction", action.interfaceAction()); - } - foreach (const RuleActionParam ¶m, action.ruleActionParams()) { - if (!param.paramTypeId().isNull()) { - settings.beginGroup("RuleActionParam-" + param.paramTypeId().toString()); - } else { - settings.beginGroup("RuleActionParam-" + param.paramName()); - } - settings.setValue("valueType", static_cast(param.value().type())); - settings.setValue("value", param.value()); - if (param.isEventBased()) { - settings.setValue("eventTypeId", param.eventTypeId().toString()); - settings.setValue("eventParamTypeId", param.eventParamTypeId()); - } else if (param.isStateBased()) { - settings.setValue("stateDeviceId", param.stateDeviceId().toString()); - settings.setValue("stateTypeId", param.stateTypeId()); - } - settings.endGroup(); - } - i++; - settings.endGroup(); - } + saveRuleActions(&settings, rule.actions()); settings.endGroup(); // Save ruleExitActions settings.beginGroup("ruleExitActions"); - i = 0; - foreach (const RuleAction &action, rule.exitActions()) { - settings.beginGroup(QString::number(i)); - if (!action.deviceId().isNull() && !action.actionTypeId().isNull()) { - settings.setValue("deviceId", action.deviceId().toString()); - settings.setValue("actionTypeId", action.actionTypeId().toString()); + saveRuleActions(&settings, rule.exitActions()); + settings.endGroup(); + qCDebug(dcRuleEngineDebug()) << "Saved rule to config:" << rule; +} + +void RuleEngine::saveRuleActions(NymeaSettings *settings, const QList &ruleActions) +{ + int i = 0; + foreach (const RuleAction &action, ruleActions) { + settings->beginGroup(QString::number(i)); + if (action.type() == RuleAction::TypeDevice) { + settings->setValue("deviceId", action.deviceId().toString()); + settings->setValue("actionTypeId", action.actionTypeId().toString()); + } else if (action.type() == RuleAction::TypeBrowser) { + settings->setValue("deviceId", action.deviceId().toString()); + settings->setValue("browserItemId", action.browserItemId()); + } else if (action.type() == RuleAction::TypeInterface){ + settings->setValue("interface", action.interface()); + settings->setValue("interfaceAction", action.interfaceAction()); } else { - settings.setValue("interface", action.interface()); - settings.setValue("interfaceAction", action.interfaceAction()); + Q_ASSERT_X(false, "RuleEngine::saveRule", "Unhandled rule action type."); } foreach (const RuleActionParam ¶m, action.ruleActionParams()) { if (!param.paramTypeId().isNull()) { - settings.beginGroup("RuleActionParam-" + param.paramTypeId().toString()); + settings->beginGroup("RuleActionParam-" + param.paramTypeId().toString()); } else { - settings.beginGroup("RuleActionParam-" + param.paramName()); + settings->beginGroup("RuleActionParam-" + param.paramName()); } - settings.setValue("valueType", static_cast(param.value().type())); - settings.setValue("value", param.value()); - settings.endGroup(); + settings->setValue("valueType", static_cast(param.value().type())); + settings->setValue("value", param.value()); + if (param.isEventBased()) { + settings->setValue("eventTypeId", param.eventTypeId().toString()); + settings->setValue("eventParamTypeId", param.eventParamTypeId()); + } else if (param.isStateBased()) { + settings->setValue("stateDeviceId", param.stateDeviceId().toString()); + settings->setValue("stateTypeId", param.stateTypeId()); + } + settings->endGroup(); } i++; - settings.endGroup(); + settings->endGroup(); } - settings.endGroup(); - qCDebug(dcRuleEngineDebug()) << "Saved rule to config:" << rule; } void RuleEngine::init() diff --git a/libnymea-core/ruleengine/ruleengine.h b/libnymea-core/ruleengine/ruleengine.h index 00a67dd2..e2039fae 100644 --- a/libnymea-core/ruleengine/ruleengine.h +++ b/libnymea-core/ruleengine/ruleengine.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace nymeaserver { @@ -112,6 +113,7 @@ private: void appendRule(const Rule &rule); void saveRule(const Rule &rule); + void saveRuleActions(NymeaSettings *settings, const QList &ruleActions); private: QList m_ruleIds; // Keeping a list of RuleIds to keep sorting order... diff --git a/libnymea-core/devices/stateevaluator.cpp b/libnymea-core/ruleengine/stateevaluator.cpp similarity index 100% rename from libnymea-core/devices/stateevaluator.cpp rename to libnymea-core/ruleengine/stateevaluator.cpp diff --git a/libnymea-core/devices/stateevaluator.h b/libnymea-core/ruleengine/stateevaluator.h similarity index 100% rename from libnymea-core/devices/stateevaluator.h rename to libnymea-core/ruleengine/stateevaluator.h diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index bee2ccdc..a0d0fc0f 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -63,8 +63,6 @@ HEADERS += \ types/paramtype.h \ types/param.h \ types/paramdescriptor.h \ - types/ruleaction.h \ - types/ruleactionparam.h \ types/statedescriptor.h \ types/interface.h \ hardwareresource.h \ @@ -130,8 +128,6 @@ SOURCES += \ types/paramtype.cpp \ types/param.cpp \ types/paramdescriptor.cpp \ - types/ruleaction.cpp \ - types/ruleactionparam.cpp \ types/statedescriptor.cpp \ types/interface.cpp \ hardwareresource.cpp \ From df4c87de9bea00874aee3c49d4a61aaf259df20c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 17 Jul 2019 20:50:40 +0200 Subject: [PATCH 17/22] Add Devices::filterByParentDeviceId(const DeviceId &deviceId) --- libnymea/devices/device.cpp | 11 +++++++++++ libnymea/devices/device.h | 1 + 2 files changed, 12 insertions(+) diff --git a/libnymea/devices/device.cpp b/libnymea/devices/device.cpp index a0b24ada..85350867 100644 --- a/libnymea/devices/device.cpp +++ b/libnymea/devices/device.cpp @@ -449,3 +449,14 @@ Devices Devices::filterByDeviceClassId(const DeviceClassId &deviceClassId) } return ret; } + +Devices Devices::filterByParentDeviceId(const DeviceId &deviceId) +{ + Devices ret; + foreach (Device *device, *this) { + if (device->parentId() == deviceId) { + ret << device; + } + } + return ret; +} diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index c34ad219..f9e5c5e0 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -169,6 +169,7 @@ public: Device* findByParams(const ParamList ¶ms) const; Devices filterByParam(const ParamTypeId ¶mTypeId, const QVariant &value = QVariant()); Devices filterByDeviceClassId(const DeviceClassId &deviceClassId); + Devices filterByParentDeviceId(const DeviceId &deviceId); }; Q_DECLARE_METATYPE(Device::DeviceError) From 1608e07d9935a147732608ad1ea031036a385241 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 18 Jul 2019 01:11:30 +0200 Subject: [PATCH 18/22] BrowserItems in rules working --- .../devices/devicemanagerimplementation.cpp | 33 +++- .../devices/devicemanagerimplementation.h | 1 + libnymea-core/jsonrpc/devicehandler.cpp | 61 ++++++- libnymea-core/jsonrpc/devicehandler.h | 4 + libnymea-core/jsonrpc/jsontypes.h | 2 +- libnymea-core/ruleengine/rule.h | 4 +- libnymea-core/ruleengine/ruleengine.cpp | 163 +++++++----------- libnymea-core/ruleengine/ruleengine.h | 3 +- libnymea/devices/device.h | 16 +- libnymea/devices/devicemanager.cpp | 7 +- libnymea/devices/devicemanager.h | 6 +- libnymea/devices/deviceplugin.cpp | 19 +- libnymea/devices/deviceplugin.h | 4 +- plugins/mock/devicepluginmock.cpp | 26 ++- plugins/mock/devicepluginmock.h | 1 + tests/auto/api.json | 12 ++ 16 files changed, 237 insertions(+), 125 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 863bc7d8..85b2dff9 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -712,9 +712,6 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) { - Q_UNUSED(deviceId) - Q_UNUSED(itemId) - Device::BrowseResult result = createBrowseResult(); Device *device = m_configuredDevices.value(deviceId); @@ -734,6 +731,35 @@ Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &d return result; } +Device::BrowserItemResult DeviceManagerImplementation::browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) +{ + Device::BrowserItemResult result = createBrowserItemResult(); + + Device *device = m_configuredDevices.value(deviceId); + if (!device) { + qCWarning(dcDeviceManager()) << "Cannot browse device. No such device:" << deviceId.toString(); + result.status = Device::DeviceErrorDeviceNotFound; + return result; + } + + if (!device->deviceClass().browsable()) { + qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable."; + result.status = Device::DeviceErrorUnsupportedFeature; + return result; + } + + result = device->plugin()->browserItem(device, result, itemId, locale); + if (result.status == Device::DeviceErrorAsync) { + // Error or Async + return result; + } + if (result.status != Device::DeviceErrorNoError) { + qCWarning(dcDeviceManager()) << "Browse device failed:" << result.status; + return result; + } + return result; +} + Device::DeviceError DeviceManagerImplementation::executeBrowserItem(const BrowserAction &browserAction) { Device *device = m_configuredDevices.value(browserAction.deviceId()); @@ -1024,6 +1050,7 @@ void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const Pl connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManagerImplementation::onAutoDevicesAppeared); connect(pluginIface, &DevicePlugin::autoDeviceDisappeared, this, &DeviceManagerImplementation::onAutoDeviceDisappeared); connect(pluginIface, &DevicePlugin::browseRequestFinished, this, &DeviceManagerImplementation::browseRequestFinished); + connect(pluginIface, &DevicePlugin::browserItemRequestFinished, this, &DeviceManagerImplementation::browserItemRequestFinished); connect(pluginIface, &DevicePlugin::browserItemExecutionFinished, this, &DeviceManagerImplementation::browserItemExecutionFinished); } diff --git a/libnymea-core/devices/devicemanagerimplementation.h b/libnymea-core/devices/devicemanagerimplementation.h index 0362567e..75141c5d 100644 --- a/libnymea-core/devices/devicemanagerimplementation.h +++ b/libnymea-core/devices/devicemanagerimplementation.h @@ -100,6 +100,7 @@ public: Device::DeviceError executeAction(const Action &action) override; Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) override; + Device::BrowserItemResult browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) override; Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) override; Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) override; diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 9a13983c..a5fc70db 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -292,6 +292,15 @@ DeviceHandler::DeviceHandler(QObject *parent) : returns.insert("items", QVariantList() << JsonTypes::browserItemRef()); setReturns("BrowseDevice", returns); + params.clear(); returns.clear(); + setDescription("GetBrowserItem", "Get a single item from the browser. This won't give any more info on an item than a regular browseDevice call, but it allows to fetch details of an item if only the ID is known."); + params.insert("deviceId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("o:itemId", JsonTypes::basicTypeToString(JsonTypes::String)); + setParams("GetBrowserItem", params); + returns.insert("deviceError", JsonTypes::deviceErrorRef()); + returns.insert("o:item", JsonTypes::browserItemRef()); + setReturns("GetBrowserItem", returns); + // Notifications params.clear(); returns.clear(); setDescription("StateChanged", "Emitted whenever a State of a device changes."); @@ -339,6 +348,7 @@ DeviceHandler::DeviceHandler(QObject *parent) : connect(NymeaCore::instance(), &NymeaCore::deviceReconfigurationFinished, this, &DeviceHandler::deviceReconfigurationFinished); connect(NymeaCore::instance(), &NymeaCore::pairingFinished, this, &DeviceHandler::pairingFinished); connect(NymeaCore::instance()->deviceManager(), &DeviceManager::browseRequestFinished, this, &DeviceHandler::browseRequestFinished); + connect(NymeaCore::instance()->deviceManager(), &DeviceManager::browserItemRequestFinished, this, &DeviceHandler::browserItemRequestFinished); } /*! Returns the name of the \l{DeviceHandler}. In this case \b Devices.*/ @@ -685,9 +695,9 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const if (result.status == Device::DeviceErrorAsync ) { JsonReply *reply = createAsyncReply("BrowseDevice"); - m_asyncBrowseRequests.insert(result.id, reply); + m_asyncBrowseRequests.insert(result.id(), reply); connect(reply, &JsonReply::finished, this, [this, result](){ - m_asyncBrowseRequests.remove(result.id); + m_asyncBrowseRequests.remove(result.id()); }); return reply; } @@ -697,6 +707,30 @@ JsonReply *DeviceHandler::BrowseDevice(const QVariantMap ¶ms) const return createReply(returns); } +JsonReply *DeviceHandler::GetBrowserItem(const QVariantMap ¶ms) const +{ + QVariantMap returns; + DeviceId deviceId = DeviceId(params.value("deviceId").toString()); + QString itemId = params.value("itemId").toString(); + + Device::BrowserItemResult result = NymeaCore::instance()->deviceManager()->browserItemDetails(deviceId, itemId, params.value("locale").toLocale()); + + if (result.status == Device::DeviceErrorAsync ) { + JsonReply *reply = createAsyncReply("GetBrowserItem"); + m_asyncBrowseRequests.insert(result.id(), reply); + connect(reply, &JsonReply::finished, this, [this, result](){ + m_asyncBrowseRequests.remove(result.id()); + }); + return reply; + } + + returns.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); + if (result.status == Device::DeviceErrorNoError) { + returns.insert("item", JsonTypes::packBrowserItem(result.item)); + } + return createReply(returns); +} + void DeviceHandler::pluginConfigChanged(const PluginId &id, const ParamList &config) { QVariantMap params; @@ -824,14 +858,31 @@ void DeviceHandler::pairingFinished(const PairingTransactionId &pairingTransacti void DeviceHandler::browseRequestFinished(const Device::BrowseResult &result) { - if (!m_asyncBrowseRequests.contains(result.id)) { + if (!m_asyncBrowseRequests.contains(result.id())) { qCWarning(dcJsonRpc()) << "No pending JsonRpc reply. Did it time out?"; return; } - JsonReply *reply = m_asyncBrowseRequests.take(result.id); + + JsonReply *reply = m_asyncBrowseRequests.take(result.id()); QVariantMap params; - params.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); params.insert("items", JsonTypes::packBrowserItems(result.items)); + params.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); + reply->setData(params); + reply->finished(); +} + +void DeviceHandler::browserItemRequestFinished(const Device::BrowserItemResult &result) +{ + if (m_asyncBrowseDetailsRequests.contains(result.id())) { + qCWarning(dcJsonRpc()) << "No pending JsonRpc reply. Did it time out?"; + return; + } + JsonReply *reply = m_asyncBrowseDetailsRequests.take(result.id()); + QVariantMap params; + if (result.status == Device::DeviceErrorNoError) { + params.insert("item", JsonTypes::packBrowserItem(result.item)); + } + params.insert("deviceError", JsonTypes::deviceErrorToString(result.status)); reply->setData(params); reply->finished(); } diff --git a/libnymea-core/jsonrpc/devicehandler.h b/libnymea-core/jsonrpc/devicehandler.h index 7ae2352a..a2a26fbf 100644 --- a/libnymea-core/jsonrpc/devicehandler.h +++ b/libnymea-core/jsonrpc/devicehandler.h @@ -58,6 +58,7 @@ public: Q_INVOKABLE JsonReply *GetStateValues(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply *BrowseDevice(const QVariantMap ¶ms) const; + Q_INVOKABLE JsonReply *GetBrowserItem(const QVariantMap ¶ms) const; signals: void PluginConfigurationChanged(const QVariantMap ¶ms); @@ -90,6 +91,8 @@ private slots: void browseRequestFinished(const Device::BrowseResult &result); + void browserItemRequestFinished(const Device::BrowserItemResult &result); + private: // A cache for async replies mutable QHash m_discoverRequests; @@ -97,6 +100,7 @@ private: mutable QHash m_asynDeviceEditAdditions; mutable QHash m_asyncPairingRequests; mutable QHash m_asyncBrowseRequests; + mutable QHash m_asyncBrowseDetailsRequests; }; } diff --git a/libnymea-core/jsonrpc/jsontypes.h b/libnymea-core/jsonrpc/jsontypes.h index 7f7e7f20..bc5f9878 100644 --- a/libnymea-core/jsonrpc/jsontypes.h +++ b/libnymea-core/jsonrpc/jsontypes.h @@ -27,6 +27,7 @@ #include "devices/devicemanager.h" #include "ruleengine/rule.h" #include "ruleengine/ruleengine.h" +#include "ruleengine/ruleactionparam.h" #include "nymeaconfiguration.h" #include "usermanager/usermanager.h" @@ -36,7 +37,6 @@ #include "types/actiontype.h" #include "types/paramtype.h" #include "types/paramdescriptor.h" -#include "types/ruleactionparam.h" #include "types/mediabrowseritem.h" #include "logging/logging.h" diff --git a/libnymea-core/ruleengine/rule.h b/libnymea-core/ruleengine/rule.h index c1828383..dacf9559 100644 --- a/libnymea-core/ruleengine/rule.h +++ b/libnymea-core/ruleengine/rule.h @@ -23,10 +23,10 @@ #define RULE_H #include "types/state.h" -#include "types/ruleaction.h" #include "types/eventdescriptor.h" -#include "devices/stateevaluator.h" #include "time/timedescriptor.h" +#include "ruleaction.h" +#include "stateevaluator.h" #include diff --git a/libnymea-core/ruleengine/ruleengine.cpp b/libnymea-core/ruleengine/ruleengine.cpp index 35f0e93d..916b7fc6 100644 --- a/libnymea-core/ruleengine/ruleengine.cpp +++ b/libnymea-core/ruleengine/ruleengine.cpp @@ -1254,6 +1254,69 @@ void RuleEngine::saveRuleActions(NymeaSettings *settings, const QList RuleEngine::loadRuleActions(NymeaSettings *settings) +{ + QList actions; + foreach (const QString &actionNumber, settings->childGroups()) { + settings->beginGroup(actionNumber); + + RuleActionParamList params; + foreach (QString paramTypeIdString, settings->childGroups()) { + if (paramTypeIdString.startsWith("RuleActionParam-")) { + settings->beginGroup(paramTypeIdString); + QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-")); + EventTypeId eventTypeId = EventTypeId(settings->value("eventTypeId", EventTypeId()).toString()); + ParamTypeId eventParamTypeId = ParamTypeId(settings->value("eventParamTypeId", ParamTypeId()).toString()); + DeviceId stateDeviceId = DeviceId(settings->value("stateDeviceId", DeviceId()).toString()); + StateTypeId stateTypeId = StateTypeId(settings->value("stateTypeId", StateTypeId()).toString()); + QVariant value = settings->value("value"); + if (settings->contains("valueType")) { + QVariant::Type valueType = static_cast(settings->value("valueType").toInt()); + // Note: only warn, and continue with the QVariant guessed type + if (valueType == QVariant::Invalid) { + qCWarning(dcRuleEngine()) << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant."; + } else if (!value.canConvert(static_cast(valueType))) { + qCWarning(dcRuleEngine()) << "Error loading rule action. Could not convert the rule action param value" << value << "to the stored type" << valueType; + } else { + value.convert(static_cast(valueType)); + } + } + + RuleActionParam param; + if (!ParamTypeId(strippedParamTypeIdString).isNull()) { + // By ParamTypeId + param = RuleActionParam(ParamTypeId(strippedParamTypeIdString), value); + } else { + // By param name + param = RuleActionParam(strippedParamTypeIdString, value); + } + param.setEventTypeId(eventTypeId); + param.setEventParamTypeId(eventParamTypeId); + param.setStateDeviceId(stateDeviceId); + param.setStateTypeId(stateTypeId); + params.append(param); + settings->endGroup(); + } + } + + if (settings->contains("actionTypeId") && settings->contains("deviceId")) { + RuleAction action = RuleAction(ActionTypeId(settings->value("actionTypeId").toString()), DeviceId(settings->value("deviceId").toString())); + action.setRuleActionParams(params); + actions.append(action); + } else if (settings->contains("deviceId") && settings->contains("browserItemId")) { + RuleAction action = RuleAction(DeviceId(settings->value("deviceId").toString()), settings->value("browserItemId").toString()); + actions.append(action); + } else if (settings->contains("interface") && settings->contains("interfaceAction")){ + RuleAction action = RuleAction(settings->value("interface").toString(), settings->value("interfaceAction").toString()); + action.setRuleActionParams(params); + actions.append(action); + } + + settings->endGroup(); + } + return actions; +} + void RuleEngine::init() { NymeaSettings settings(NymeaSettings::SettingsRoleRules); @@ -1413,109 +1476,13 @@ void RuleEngine::init() // Load actions QList actions; settings.beginGroup("ruleActions"); - foreach (const QString &actionNumber, settings.childGroups()) { - settings.beginGroup(actionNumber); - - RuleActionParamList params; - foreach (QString paramTypeIdString, settings.childGroups()) { - if (paramTypeIdString.startsWith("RuleActionParam-")) { - settings.beginGroup(paramTypeIdString); - QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-")); - EventTypeId eventTypeId = EventTypeId(settings.value("eventTypeId", EventTypeId()).toString()); - ParamTypeId eventParamTypeId = ParamTypeId(settings.value("eventParamTypeId", ParamTypeId()).toString()); - DeviceId stateDeviceId = DeviceId(settings.value("stateDeviceId", DeviceId()).toString()); - StateTypeId stateTypeId = StateTypeId(settings.value("stateTypeId", StateTypeId()).toString()); - QVariant value = settings.value("value"); - if (settings.contains("valueType")) { - QVariant::Type valueType = static_cast(settings.value("valueType").toInt()); - // Note: only warn, and continue with the QVariant guessed type - if (valueType == QVariant::Invalid) { - qCWarning(dcRuleEngine()) << name << idString << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant."; - } else if (!value.canConvert(static_cast(valueType))) { - qCWarning(dcRuleEngine()) << "Error loading rule" << name << idString << ". Could not convert the rule action param value" << value << "to the stored type" << valueType; - } else { - value.convert(static_cast(valueType)); - } - } - - RuleActionParam param; - if (!ParamTypeId(strippedParamTypeIdString).isNull()) { - // By ParamTypeId - param = RuleActionParam(ParamTypeId(strippedParamTypeIdString), value); - } else { - // By param name - param = RuleActionParam(strippedParamTypeIdString, value); - } - param.setEventTypeId(eventTypeId); - param.setEventParamTypeId(eventParamTypeId); - param.setStateDeviceId(stateDeviceId); - param.setStateTypeId(stateTypeId); - params.append(param); - settings.endGroup(); - } - } - - if (settings.contains("actionTypeId") && settings.contains("deviceId")) { - RuleAction action = RuleAction(ActionTypeId(settings.value("actionTypeId").toString()), DeviceId(settings.value("deviceId").toString())); - action.setRuleActionParams(params); - actions.append(action); - } else if (settings.contains("interface") && settings.contains("interfaceAction")){ - RuleAction action = RuleAction(settings.value("interface").toString(), settings.value("interfaceAction").toString()); - action.setRuleActionParams(params); - actions.append(action); - } - - settings.endGroup(); - } + actions = loadRuleActions(&settings); settings.endGroup(); // Load exit actions QList exitActions; settings.beginGroup("ruleExitActions"); - foreach (const QString &actionNumber, settings.childGroups()) { - settings.beginGroup(actionNumber); - - RuleActionParamList params; - foreach (QString paramTypeIdString, settings.childGroups()) { - if (paramTypeIdString.startsWith("RuleActionParam-")) { - settings.beginGroup(paramTypeIdString); - QString strippedParamTypeIdString = paramTypeIdString.remove(QRegExp("^RuleActionParam-")); - QVariant value = settings.value("value"); - if (settings.contains("valueType")) { - QVariant::Type valueType = static_cast(settings.value("valueType").toInt()); - // Note: only warn, and continue with the QVariant guessed type - if (valueType == QVariant::Invalid) { - qCWarning(dcRuleEngine()) << name << idString << "Could not load the value type of the rule action param " << strippedParamTypeIdString << ". The value type will be guessed by QVariant."; - } else if (!value.canConvert(static_cast(valueType))) { - qCWarning(dcRuleEngine()) << "Error loading rule" << name << idString << ". Could not convert the rule action param value" << value << "to the stored type" << valueType; - } else { - value.convert(static_cast(valueType)); - } - } - - if (!ParamTypeId(strippedParamTypeIdString).isNull()) { - RuleActionParam param(ParamTypeId(strippedParamTypeIdString), value); - params.append(param); - } else { - RuleActionParam param(strippedParamTypeIdString, value); - params.append(param); - } - settings.endGroup(); - } - } - - if (settings.contains("actionTypeId") && settings.contains("deviceId")) { - RuleAction action = RuleAction(ActionTypeId(settings.value("actionTypeId").toString()), DeviceId(settings.value("deviceId").toString())); - action.setRuleActionParams(params); - exitActions.append(action); - } else if (settings.contains("interface") && settings.contains("interfaceAction")) { - RuleAction action = RuleAction(settings.value("interface").toString(),settings.value("interfaceAction").toString()); - action.setRuleActionParams(params); - exitActions.append(action); - } - - settings.endGroup(); - } + exitActions = loadRuleActions(&settings); settings.endGroup(); Rule rule; diff --git a/libnymea-core/ruleengine/ruleengine.h b/libnymea-core/ruleengine/ruleengine.h index e2039fae..cd18e8f4 100644 --- a/libnymea-core/ruleengine/ruleengine.h +++ b/libnymea-core/ruleengine/ruleengine.h @@ -23,9 +23,9 @@ #define RULEENGINE_H #include "rule.h" +#include "stateevaluator.h" #include "types/event.h" #include "types/deviceclass.h" -#include "devices/stateevaluator.h" #include #include @@ -114,6 +114,7 @@ private: void appendRule(const Rule &rule); void saveRule(const Rule &rule); void saveRuleActions(NymeaSettings *settings, const QList &ruleActions); + QList loadRuleActions(NymeaSettings *settings); private: QList m_ruleIds; // Keeping a list of RuleIds to keep sorting order... diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index f9e5c5e0..89e54fa8 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -85,11 +85,23 @@ public: class BrowseResult { public: - QUuid id; Device::DeviceError status = Device::DeviceErrorNoError; BrowserItems items; + QUuid id() const { return m_id; } private: - BrowseResult(): id(QUuid::createUuid()) {} + QUuid m_id; + BrowseResult(): m_id(QUuid::createUuid()) {} + friend class DeviceManager; + }; + + class BrowserItemResult { + public: + Device::DeviceError status = Device::DeviceErrorNoError; + BrowserItem item; + QUuid id() const { return m_id; } + private: + QUuid m_id; + BrowserItemResult(): m_id(QUuid::createUuid()) {} friend class DeviceManager; }; diff --git a/libnymea/devices/devicemanager.cpp b/libnymea/devices/devicemanager.cpp index e9f0f5c0..9ccb6dc3 100644 --- a/libnymea/devices/devicemanager.cpp +++ b/libnymea/devices/devicemanager.cpp @@ -42,11 +42,10 @@ DeviceManager::DeviceManager(QObject *parent) : QObject(parent) Device::BrowseResult DeviceManager::createBrowseResult() { - Device::BrowseResult result = Device::BrowseResult(); - return result; + return Device::BrowseResult(); } -QUuid DeviceManager::browseResultId(const Device::BrowseResult &result) +Device::BrowserItemResult DeviceManager::createBrowserItemResult() { - return result.id; + return Device::BrowserItemResult(); } diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index 4b9d4464..6892340d 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -75,6 +75,7 @@ public: virtual Device::DeviceError executeAction(const Action &action) = 0; virtual Device::BrowseResult browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) = 0; + virtual Device::BrowserItemResult browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) = 0; virtual Device::DeviceError executeBrowserItem(const BrowserAction &browserAction) = 0; virtual Device::DeviceError executeBrowserItemAction(const BrowserItemAction &browserItemAction) = 0; @@ -95,12 +96,15 @@ signals: void pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId = DeviceId()); void actionExecutionFinished(const ActionId &actionId, Device::DeviceError status); void browseRequestFinished(const Device::BrowseResult &result); + void browserItemRequestFinished(const Device::BrowserItemResult &result); void browserItemExecutionFinished(const ActionId &actionId, Device::DeviceError status); void browserItemActionExecutionFinished(const ActionId &actionId, Device::DeviceError status); protected: + // BrowseResult/BrowserItemResult have private Ctors to make sure a plugin dev can not mess up the request ids. + // DeviceManager is a friend, but the implementation is not, expose this to the DeviceManager implementation. Device::BrowseResult createBrowseResult(); - QUuid browseResultId(const Device::BrowseResult &result); + Device::BrowserItemResult createBrowserItemResult(); }; #endif // DEVICEMANAGER_H diff --git a/libnymea/devices/deviceplugin.cpp b/libnymea/devices/deviceplugin.cpp index a4607b6f..5acecb7d 100644 --- a/libnymea/devices/deviceplugin.cpp +++ b/libnymea/devices/deviceplugin.cpp @@ -248,7 +248,7 @@ Device::DeviceError DevicePlugin::executeAction(Device *device, const Action &ac return Device::DeviceErrorNoError; } -/*! Implement this if your devices support browsing (besides settings browsable to true in the metadata). +/*! Implement this if your devices support browsing (set "browsable" to true in the metadata). * When the system calls this method, fill the \a result object's items list with entries from the browser. * If \a itemId is empty it means that the root node of the file system should be returned. Each item in * the result set shall be uniquely identifiable using its \l{BrowserItem::id}{id} property. @@ -270,6 +270,23 @@ Device::BrowseResult DevicePlugin::browseDevice(Device *device, Device::BrowseRe return result; } +/*! Implement this if your devices support browsing (set "browsable" to true in the metadata). + * When the system calls this method, fetch the item details required to create a BrowserItem + * for the item with the given \a id and append that one item to the \a result. + * When done, set the \l{BrowserResult::status}{result's status} field approprietly. Set the result's + * status to Device::DeviceErrorAsync if this operation requires async behavior and emit + * \l{browserItemRequestFinished} when done. + */ +Device::BrowserItemResult DevicePlugin::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) +{ + Q_UNUSED(device) + Q_UNUSED(itemId) + Q_UNUSED(locale) + + result.status = Device::DeviceErrorUnsupportedFeature; + return result; +} + /*! Implement this if your devices support browsing and execute the itemId defined in \a browserAction. * Return Device::DeviceErrorAsync if this operation requires async behavior and emit * \l{browserItemExecutionFinished} when done. diff --git a/libnymea/devices/deviceplugin.h b/libnymea/devices/deviceplugin.h index 2b425313..89e74682 100644 --- a/libnymea/devices/deviceplugin.h +++ b/libnymea/devices/deviceplugin.h @@ -80,7 +80,8 @@ public: virtual Device::DeviceError executeAction(Device *device, const Action &action); - virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &nodeId, const QLocale &locale); + virtual Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale); + virtual Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale); virtual Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction); virtual Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction); @@ -103,6 +104,7 @@ signals: void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); void autoDeviceDisappeared(const DeviceId &deviceId); void browseRequestFinished(const Device::BrowseResult &result); + void browserItemRequestFinished(const Device::BrowserItemResult &result); void browserItemExecutionFinished(const ActionId &actionid, Device::DeviceError status); void browserItemActionExecutionFinished(const ActionId &actionid, Device::DeviceError status); diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index efe64e7a..28f6b6a0 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -272,6 +272,20 @@ Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::Brow return result; } +Device::BrowserItemResult DevicePluginMock::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) +{ + Q_UNUSED(device) + Q_UNUSED(locale) + VirtualFsNode *node = m_virtualFs->findNode(itemId); + if (!node) { + result.status = Device::DeviceErrorItemNotFound; + return result; + } + result.item = node->item; + result.status = Device::DeviceErrorNoError; + return result; +} + Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action &action) { if (!myDevices().contains(device)) @@ -404,7 +418,7 @@ Device::DeviceError DevicePluginMock::executeAction(Device *device, const Action Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const BrowserAction &browserAction) { - qCDebug(dcMockDevice()) << "ExecuteBrowserItem called"; + qCDebug(dcMockDevice()) << "ExecuteBrowserItem called" << browserAction.itemId(); bool broken = device->paramValue(mockDeviceBrokenParamTypeId).toBool(); bool async = device->paramValue(mockDeviceAsyncParamTypeId).toBool(); @@ -657,31 +671,31 @@ void DevicePluginMock::generateBrowseItems() { m_virtualFs = new VirtualFsNode(BrowserItem()); - BrowserItem item = BrowserItem(QUuid::createUuid().toString(), "Item 0", true); + BrowserItem item = BrowserItem("001", "Item 0", true); item.setDescription("I'm a folder"); item.setIcon(BrowserItem::BrowserIconFolder); m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem(QUuid::createUuid().toString(), "Item 1", false, true); + item = BrowserItem("002", "Item 1", false, true); item.setDescription("I'm executable"); item.setIcon(BrowserItem::BrowserIconApplication); item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem(QUuid::createUuid().toString(), "Item 2", false, true); + item = BrowserItem("003", "Item 2", false, true); item.setDescription("I'm a file"); item.setIcon(BrowserItem::BrowserIconFile); item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem(QUuid::createUuid().toString(), "Item 3", false, true); + item = BrowserItem("004", "Item 3", false, true); item.setDescription("I have a nice thumbnail"); item.setIcon(BrowserItem::BrowserIconFile); item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png"); item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId}); m_virtualFs->addChild(new VirtualFsNode(item)); - item = BrowserItem(QUuid::createUuid().toString(), "Item 4", false, false); + item = BrowserItem("005", "Item 4", false, false); item.setDescription("I'm disabled"); item.setDisabled(true); item.setIcon(BrowserItem::BrowserIconFile); diff --git a/plugins/mock/devicepluginmock.h b/plugins/mock/devicepluginmock.h index 76cbd647..d5ec48e2 100644 --- a/plugins/mock/devicepluginmock.h +++ b/plugins/mock/devicepluginmock.h @@ -53,6 +53,7 @@ public: Device::DeviceError displayPin(const PairingTransactionId &pairingTransactionId, const DeviceDescriptor &deviceDescriptor) override; Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override; + Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) override; public slots: Device::DeviceError executeAction(Device *device, const Action &action) override; diff --git a/tests/auto/api.json b/tests/auto/api.json index 7f378ef9..a2af70d6 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -318,6 +318,17 @@ ] } }, + "Devices.GetBrowserItem": { + "description": "Get a single item from the browser. This won't give any more info on an item than a regular browseDevice call, but it allows to fetch details of an item if only the ID is known.", + "params": { + "deviceId": "Uuid", + "o:itemId": "String" + }, + "returns": { + "deviceError": "$ref:DeviceError", + "o:item": "$ref:BrowserItem" + } + }, "Devices.GetConfiguredDevices": { "description": "Returns a list of configured devices, optionally filtered by deviceId.", "params": { @@ -1731,6 +1742,7 @@ }, "RuleAction": { "o:actionTypeId": "Uuid", + "o:browserItemId": "String", "o:deviceId": "Uuid", "o:interface": "String", "o:interfaceAction": "String", From b5b38f287fa4d7c6c28269aecb255adfc5931801 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 18 Jul 2019 17:18:04 +0200 Subject: [PATCH 19/22] Add missing method --- libnymea/types/browseritem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libnymea/types/browseritem.cpp b/libnymea/types/browseritem.cpp index d4e54e28..14bb2d0d 100644 --- a/libnymea/types/browseritem.cpp +++ b/libnymea/types/browseritem.cpp @@ -37,6 +37,11 @@ QString BrowserItem::id() const return m_id; } +void BrowserItem::setId(const QString &id) +{ + m_id = id; +} + QString BrowserItem::displayName() const { return m_displayName; From 0b7176c6f4945d9aed2dcf87bee46405dae14f96 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 19 Jul 2019 14:40:14 +0200 Subject: [PATCH 20/22] Re-enable the default ctor for BrowseResults This opens up the possibility for the plugin dev to mess up the request id if [s]he creates a new one instead of using the one given by the api, but not being able to keep results in a Qt container is not a price I am willing to pay for this. After all it'S going to be very visible on the first test to the plugin dev if this deosn't match. --- libnymea-core/devices/devicemanagerimplementation.cpp | 4 ++-- libnymea/devices/device.h | 6 ++---- libnymea/devices/devicemanager.cpp | 10 ---------- libnymea/devices/devicemanager.h | 5 ----- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 85b2dff9..6b267d3e 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -712,7 +712,7 @@ Device::DeviceError DeviceManagerImplementation::removeConfiguredDevice(const De Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) { - Device::BrowseResult result = createBrowseResult(); + Device::BrowseResult result; Device *device = m_configuredDevices.value(deviceId); if (!device) { @@ -733,7 +733,7 @@ Device::BrowseResult DeviceManagerImplementation::browseDevice(const DeviceId &d Device::BrowserItemResult DeviceManagerImplementation::browserItemDetails(const DeviceId &deviceId, const QString &itemId, const QLocale &locale) { - Device::BrowserItemResult result = createBrowserItemResult(); + Device::BrowserItemResult result; Device *device = m_configuredDevices.value(deviceId); if (!device) { diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index 89e54fa8..bd83f3fe 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -85,24 +85,22 @@ public: class BrowseResult { public: + BrowseResult(): m_id(QUuid::createUuid()) {} Device::DeviceError status = Device::DeviceErrorNoError; BrowserItems items; QUuid id() const { return m_id; } private: QUuid m_id; - BrowseResult(): m_id(QUuid::createUuid()) {} - friend class DeviceManager; }; class BrowserItemResult { public: + BrowserItemResult(): m_id(QUuid::createUuid()) {} Device::DeviceError status = Device::DeviceErrorNoError; BrowserItem item; QUuid id() const { return m_id; } private: QUuid m_id; - BrowserItemResult(): m_id(QUuid::createUuid()) {} - friend class DeviceManager; }; DeviceId id() const; diff --git a/libnymea/devices/devicemanager.cpp b/libnymea/devices/devicemanager.cpp index 9ccb6dc3..a2bb77e4 100644 --- a/libnymea/devices/devicemanager.cpp +++ b/libnymea/devices/devicemanager.cpp @@ -39,13 +39,3 @@ DeviceManager::DeviceManager(QObject *parent) : QObject(parent) { } - -Device::BrowseResult DeviceManager::createBrowseResult() -{ - return Device::BrowseResult(); -} - -Device::BrowserItemResult DeviceManager::createBrowserItemResult() -{ - return Device::BrowserItemResult(); -} diff --git a/libnymea/devices/devicemanager.h b/libnymea/devices/devicemanager.h index 6892340d..893e03a6 100644 --- a/libnymea/devices/devicemanager.h +++ b/libnymea/devices/devicemanager.h @@ -100,11 +100,6 @@ signals: void browserItemExecutionFinished(const ActionId &actionId, Device::DeviceError status); void browserItemActionExecutionFinished(const ActionId &actionId, Device::DeviceError status); -protected: - // BrowseResult/BrowserItemResult have private Ctors to make sure a plugin dev can not mess up the request ids. - // DeviceManager is a friend, but the implementation is not, expose this to the DeviceManager implementation. - Device::BrowseResult createBrowseResult(); - Device::BrowserItemResult createBrowserItemResult(); }; #endif // DEVICEMANAGER_H From b4ae1919ba84189c4bdc14ba5ed50d689d2d875b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Jul 2019 01:41:55 +0200 Subject: [PATCH 21/22] cleanup fixes --- libnymea-core/devices/devicemanagerimplementation.cpp | 7 +++++++ libnymea-core/jsonrpc/actionhandler.cpp | 1 + libnymea-core/jsonrpc/devicehandler.cpp | 8 ++++---- libnymea-core/nymeacore.cpp | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 6b267d3e..37291ced 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -1052,6 +1052,7 @@ void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const Pl connect(pluginIface, &DevicePlugin::browseRequestFinished, this, &DeviceManagerImplementation::browseRequestFinished); connect(pluginIface, &DevicePlugin::browserItemRequestFinished, this, &DeviceManagerImplementation::browserItemRequestFinished); connect(pluginIface, &DevicePlugin::browserItemExecutionFinished, this, &DeviceManagerImplementation::browserItemExecutionFinished); + connect(pluginIface, &DevicePlugin::browserItemActionExecutionFinished, this, &DeviceManagerImplementation::browserItemActionExecutionFinished); } @@ -1108,6 +1109,12 @@ void DeviceManagerImplementation::loadConfiguredDevices() params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString))); } } + // Make sure all params are around. if they aren't initialize with default values + foreach (const ParamType ¶mType, deviceClass.paramTypes()) { + if (!params.hasParam(paramType.id())) { + params.append(Param(paramType.id(), paramType.defaultValue())); + } + } device->setParams(params); settings.endGroup(); // Params diff --git a/libnymea-core/jsonrpc/actionhandler.cpp b/libnymea-core/jsonrpc/actionhandler.cpp index 8ff4c73c..0055bea3 100644 --- a/libnymea-core/jsonrpc/actionhandler.cpp +++ b/libnymea-core/jsonrpc/actionhandler.cpp @@ -82,6 +82,7 @@ ActionHandler::ActionHandler(QObject *parent) : connect(NymeaCore::instance(), &NymeaCore::actionExecuted, this, &ActionHandler::actionExecuted); connect(NymeaCore::instance(), &NymeaCore::browserItemExecuted, this, &ActionHandler::browserItemExecuted); + connect(NymeaCore::instance(), &NymeaCore::browserItemActionExecuted, this, &ActionHandler::browserItemActionExecuted); } /*! Returns the name of the \l{ActionHandler}. In this case \b Actions.*/ diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index a5fc70db..4371bb56 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -717,9 +717,9 @@ JsonReply *DeviceHandler::GetBrowserItem(const QVariantMap ¶ms) const if (result.status == Device::DeviceErrorAsync ) { JsonReply *reply = createAsyncReply("GetBrowserItem"); - m_asyncBrowseRequests.insert(result.id(), reply); + m_asyncBrowseDetailsRequests.insert(result.id(), reply); connect(reply, &JsonReply::finished, this, [this, result](){ - m_asyncBrowseRequests.remove(result.id()); + m_asyncBrowseDetailsRequests.remove(result.id()); }); return reply; } @@ -873,8 +873,8 @@ void DeviceHandler::browseRequestFinished(const Device::BrowseResult &result) void DeviceHandler::browserItemRequestFinished(const Device::BrowserItemResult &result) { - if (m_asyncBrowseDetailsRequests.contains(result.id())) { - qCWarning(dcJsonRpc()) << "No pending JsonRpc reply. Did it time out?"; + if (!m_asyncBrowseDetailsRequests.contains(result.id())) { + qCWarning(dcJsonRpc()) << "No pending JsonRpc reply for result" << result.id() << ". Did it time out?"; return; } JsonReply *reply = m_asyncBrowseDetailsRequests.take(result.id()); diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 39489898..b5686a1b 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -202,6 +202,7 @@ void NymeaCore::init() { connect(m_deviceManager, &DeviceManagerImplementation::deviceDisappeared, this, &NymeaCore::onDeviceDisappeared); connect(m_deviceManager, &DeviceManagerImplementation::actionExecutionFinished, this, &NymeaCore::actionExecutionFinished); connect(m_deviceManager, &DeviceManagerImplementation::browserItemExecutionFinished, this, &NymeaCore::browserItemExecutionFinished); + connect(m_deviceManager, &DeviceManagerImplementation::browserItemActionExecutionFinished, this, &NymeaCore::browserItemActionExecutionFinished); connect(m_deviceManager, &DeviceManagerImplementation::devicesDiscovered, this, &NymeaCore::devicesDiscovered); connect(m_deviceManager, &DeviceManagerImplementation::deviceSetupFinished, this, &NymeaCore::deviceSetupFinished); connect(m_deviceManager, &DeviceManagerImplementation::deviceReconfigurationFinished, this, &NymeaCore::deviceReconfigurationFinished); From 98bdda1ef254a99a28d6a6841f165afae0d6118f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Jul 2019 17:37:17 +0200 Subject: [PATCH 22/22] Add tests --- libnymea/devices/device.h | 1 + plugins/mock/devicepluginmock.cpp | 32 ++++- tests/auto/api.json | 1 + tests/auto/devices/testdevices.cpp | 215 +++++++++++++++++++++++++++-- 4 files changed, 240 insertions(+), 9 deletions(-) diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h index bd83f3fe..97dd3f37 100644 --- a/libnymea/devices/device.h +++ b/libnymea/devices/device.h @@ -72,6 +72,7 @@ public: DeviceErrorPairingTransactionIdNotFound, DeviceErrorParameterNotWritable, DeviceErrorItemNotFound, + DeviceErrorItemNotExecutable, DeviceErrorUnsupportedFeature, }; Q_ENUM(DeviceError) diff --git a/plugins/mock/devicepluginmock.cpp b/plugins/mock/devicepluginmock.cpp index 28f6b6a0..a96482f9 100644 --- a/plugins/mock/devicepluginmock.cpp +++ b/plugins/mock/devicepluginmock.cpp @@ -251,6 +251,11 @@ Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::Brow result.status = Device::DeviceErrorHardwareFailure; } else { VirtualFsNode *node = m_virtualFs->findNode(itemId); + if (!node) { + result.status = Device::DeviceErrorItemNotFound; + emit browseRequestFinished(result); + return; + } foreach (VirtualFsNode *child, node->childs) { result.items.append(child->item); } @@ -263,6 +268,10 @@ Device::BrowseResult DevicePluginMock::browseDevice(Device *device, Device::Brow result.status = Device::DeviceErrorHardwareFailure; } else { VirtualFsNode *node = m_virtualFs->findNode(itemId); + if (!node) { + result.status = Device::DeviceErrorItemNotFound; + return result; + } foreach (VirtualFsNode *child, node->childs) { result.items.append(child->item); } @@ -422,6 +431,15 @@ Device::DeviceError DevicePluginMock::executeBrowserItem(Device *device, const B bool broken = device->paramValue(mockDeviceBrokenParamTypeId).toBool(); bool async = device->paramValue(mockDeviceAsyncParamTypeId).toBool(); + VirtualFsNode *node = m_virtualFs->findNode(browserAction.itemId()); + if (!node) { + return Device::DeviceErrorItemNotFound; + } + + if (!node->item.executable()) { + return Device::DeviceErrorItemNotExecutable; + } + if (!async){ if (broken) { return Device::DeviceErrorHardwareFailure; @@ -674,7 +692,8 @@ void DevicePluginMock::generateBrowseItems() BrowserItem item = BrowserItem("001", "Item 0", true); item.setDescription("I'm a folder"); item.setIcon(BrowserItem::BrowserIconFolder); - m_virtualFs->addChild(new VirtualFsNode(item)); + VirtualFsNode *folderNode = new VirtualFsNode(item); + m_virtualFs->addChild(folderNode); item = BrowserItem("002", "Item 1", false, true); item.setDescription("I'm executable"); @@ -705,4 +724,15 @@ void DevicePluginMock::generateBrowseItems() item.setDescription("Yay! I'm the best!"); item.setIcon(BrowserItem::BrowserIconFavorites); m_virtualFs->addChild(new VirtualFsNode(item)); + + item = BrowserItem("sub-001", "Item Subdir 1", false, true); + item.setDescription("I'm an item in a subdir"); + item.setIcon(BrowserItem::BrowserIconFile); + folderNode->addChild(new VirtualFsNode(item)); + + item = BrowserItem("sub-002", "Item Subdir 2", true, false); + item.setDescription("I'm a folder in a subdir"); + item.setIcon(BrowserItem::BrowserIconFile); + folderNode->addChild(new VirtualFsNode(item)); + } diff --git a/tests/auto/api.json b/tests/auto/api.json index a2af70d6..35343857 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1531,6 +1531,7 @@ "DeviceErrorPairingTransactionIdNotFound", "DeviceErrorParameterNotWritable", "DeviceErrorItemNotFound", + "DeviceErrorItemNotExecutable", "DeviceErrorUnsupportedFeature" ], "Event": { diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index fe7e708e..cc227f74 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -29,10 +29,13 @@ class TestDevices : public NymeaTestBase { Q_OBJECT -protected slots: - void initTestCase(); +private: + DeviceId m_mockDeviceAsyncId; private slots: + + void initTestCase(); + void getPlugins(); void getPluginConfig_data(); @@ -98,6 +101,16 @@ private slots: void reconfigureByDiscoveryAndPair(); void reconfigureAutodevice(); + void testBrowsing_data(); + void testBrowsing(); + + void testExecuteBrowserItem_data(); + void testExecuteBrowserItem(); + + void testExecuteBrowserItemAction_data(); + void testExecuteBrowserItemAction(); + + // Keep those at last as they will remove devices void removeDevice_data(); void removeDevice(); @@ -114,6 +127,31 @@ void TestDevices::initTestCase() "MockDevice.debug=true\n" ); + // Adding an async mock device to be used in tests below + QVariantMap params; + params.insert("deviceClassId", mockDeviceClassId); + params.insert("name", "Mock Device (Async)"); + + QVariantList deviceParams; + + QVariantMap asyncParam; + asyncParam.insert("paramTypeId", mockDeviceAsyncParamTypeId); + asyncParam.insert("value", true); + deviceParams.append(asyncParam); + + QVariantMap httpParam; + httpParam.insert("paramTypeId", mockDeviceHttpportParamTypeId); + httpParam.insert("value", 8765); + deviceParams.append(httpParam); + + params.insert("deviceParams", deviceParams); + + QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); + + m_mockDeviceAsyncId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toString()); + QVERIFY2(!m_mockDeviceAsyncId.isNull(), "Creating an async mock device failed"); + + qCDebug(dcTests()) << "Created Async mock device with ID" << m_mockDeviceAsyncId; } void TestDevices::getPlugins() @@ -335,7 +373,7 @@ void TestDevices::getConfiguredDevices() QVariant response = injectAndWait("Devices.GetConfiguredDevices"); QVariantList devices = response.toMap().value("params").toMap().value("devices").toList(); - QCOMPARE(devices.count(), 2); // There should be one auto created mock device and one created in initTestcase() + QCOMPARE(devices.count(), 3); // There should be: one auto created mock device, one created in NymeaTestBase::initTestcase() and one created in TestDevices::initTestCase() } void TestDevices::storedDevices() @@ -422,7 +460,7 @@ void TestDevices::discoverDevices() } // If we found something, lets try to add it - if (Device::DeviceErrorNoError) { + if (error == Device::DeviceErrorNoError) { DeviceDescriptorId descriptorId = DeviceDescriptorId(response.toMap().value("params").toMap().value("deviceDescriptors").toList().first().toMap().value("id").toString()); params.clear(); @@ -1448,7 +1486,8 @@ void TestDevices::removeAutoDevice() // First try to make a manually created device disappear. It must not go away QList devices = NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId); - QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test"); + int oldCount = devices.count(); + QVERIFY2(oldCount > 0, "There needs to be at least one configured Mock Device for this test"); Device *device = devices.first(); // trigger disappear signal in mock device @@ -1458,12 +1497,13 @@ void TestDevices::removeAutoDevice() spy.wait(); QCOMPARE(spy.count(), 1); reply->deleteLater(); - QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId).count() == 1, "Mock device has disappeared even though it shouldn't"); + QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId).count() == oldCount, "Mock device has disappeared even though it shouldn't"); // Ok, now do the same with an autocreated one. It should go away devices = NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId); - QVERIFY2(devices.count() > 0, "There needs to be at least one auto-created Mock Device for this test"); + oldCount = devices.count(); + QVERIFY2(oldCount > 0, "There needs to be at least one auto-created Mock Device for this test"); device = devices.first(); DeviceClass dc = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); @@ -1478,7 +1518,59 @@ void TestDevices::removeAutoDevice() QCOMPARE(spy.count(), 1); reply->deleteLater(); - QVERIFY2(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId).count() == 0, "Mock device has not disappeared even though it should have."); + // Make sure one mock device has disappeared + QCOMPARE(NymeaCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceAutoDeviceClassId).count(), oldCount - 1); +} + +void TestDevices::testBrowsing_data() +{ + QTest::addColumn("deviceId"); + + QTest::newRow("regular mock device") << m_mockDeviceId; + QTest::newRow("async mock device") << m_mockDeviceAsyncId; +} + +void TestDevices::testBrowsing() +{ + QFETCH(DeviceId, deviceId); + + // Check if mockdevice is browsable + QVariant response = injectAndWait("Devices.GetSupportedDevices"); + + QVariantMap mockDeviceClass; + foreach (const QVariant &deviceClassVariant, response.toMap().value("params").toMap().value("deviceClasses").toList()) { + if (DeviceClassId(deviceClassVariant.toMap().value("id").toString()) == mockDeviceClassId) { + mockDeviceClass = deviceClassVariant.toMap(); + } + } + + QVERIFY2(DeviceClassId(mockDeviceClass.value("id").toString()) == mockDeviceClassId, "Could not find mock device"); + QCOMPARE(mockDeviceClass.value("browsable").toBool(), true); + + + // Browse it + QVariantMap params; + params.insert("deviceId", deviceId); + response = injectAndWait("Devices.BrowseDevice", params); + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError")); + QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QVERIFY2(browserEntries.count() > 0, "BrowseDevice did not return any items."); + + // Browse item 001, it should be a folder with 2 items + params.insert("itemId", "001"); + response = injectAndWait("Devices.BrowseDevice", params); + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError")); + browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QVERIFY2(browserEntries.count() == 2, "BrowseDevice did not return 2 items as childs in folder with id 001."); + + // Browse a non-existent item + params["itemId"] = "this-does-not-exist"; + response = injectAndWait("Devices.BrowseDevice", params); + browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorItemNotFound")); + QCOMPARE(browserEntries.count(), 0); + + } void TestDevices::discoverDeviceParenting() @@ -1542,6 +1634,113 @@ void TestDevices::discoverDeviceParenting() } +void TestDevices::testExecuteBrowserItem_data() +{ + QTest::addColumn("deviceId"); + QTest::addColumn("itemId"); + QTest::addColumn("deviceError"); + + QTest::newRow("regular mock device") << m_mockDeviceId << "002" << "DeviceErrorNoError"; + QTest::newRow("regular mock device") << m_mockDeviceId << "001" << "DeviceErrorItemNotExecutable"; + QTest::newRow("async mock device") << m_mockDeviceAsyncId << "002" << "DeviceErrorNoError"; +} + +void TestDevices::testExecuteBrowserItem() +{ + QFETCH(DeviceId, deviceId); + QFETCH(QString, itemId); + QFETCH(QString, deviceError); + + QVariantMap params; + params.insert("deviceId", deviceId); + params.insert("itemId", itemId); + QVariant response = injectAndWait("Actions.ExecuteBrowserItem", params); + qCDebug(dcTests()) << "resp" << response; + + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), deviceError); +} + +void TestDevices::testExecuteBrowserItemAction_data() +{ + QTest::addColumn("deviceId"); + + QTest::newRow("regular mock device") << m_mockDeviceId; + QTest::newRow("async mock device") << m_mockDeviceAsyncId; +} + +void TestDevices::testExecuteBrowserItemAction() +{ + QFETCH(DeviceId, deviceId); + + QVariantMap getItemsParams; + getItemsParams.insert("deviceId", deviceId); + QVariant response = injectAndWait("Devices.BrowseDevice", getItemsParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + + QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QVERIFY(browserEntries.count() > 2); + + QVariantMap item002; // Find the item we need for this test + foreach (const QVariant &item, browserEntries) { + if (item.toMap().value("id").toString() == "002") { + item002 = item.toMap(); + break; + } + } + QVERIFY2(item002.value("id").toString() == QString("002"), "Item with context actions not found"); + QVERIFY2(item002.value("actionTypeIds").toList().count() > 0, "Item doesn't have actionTypeIds"); + QVERIFY2(ActionTypeId(item002.value("actionTypeIds").toList().first().toString()) == mockAddToFavoritesBrowserItemActionTypeId, "AddToFavorites action type id not found in item"); + + + // Browse favorites + // ID is "favorites" in mockDevice + // It should be ampty at this point + getItemsParams.insert("itemId", "favorites"); + response = injectAndWait("Devices.BrowseDevice", getItemsParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + + browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QVERIFY2(browserEntries.count() == 0, "Favorites should be empty at this point"); + + // Now add an item to the favorites + QVariantMap actionParams; + actionParams.insert("deviceId", deviceId); + actionParams.insert("itemId", "002"); + actionParams.insert("actionTypeId", mockAddToFavoritesBrowserItemActionTypeId); + response = injectAndWait("Actions.ExecuteBrowserItemAction", actionParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError")); + + qCDebug(dcTests()) << "res" << response; + + // Fetch the list again + response = injectAndWait("Devices.BrowseDevice", getItemsParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + + browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QCOMPARE(browserEntries.count(), 1); + + QString favoriteItemId = browserEntries.first().toMap().value("id").toString(); + QVERIFY2(!favoriteItemId.isEmpty(), "ItemId is empty in favorites list"); + + // Now remove the again from favorites + actionParams.clear(); + actionParams.insert("deviceId", deviceId); + actionParams.insert("itemId", favoriteItemId); + actionParams.insert("actionTypeId", mockRemoveFromFavoritesBrowserItemActionTypeId); + response = injectAndWait("Actions.ExecuteBrowserItemAction", actionParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("deviceError").toString(), QString("DeviceErrorNoError")); + + // Fetch the list again + response = injectAndWait("Devices.BrowseDevice", getItemsParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + + browserEntries = response.toMap().value("params").toMap().value("items").toList(); + QCOMPARE(browserEntries.count(), 0); + +} + #include "testdevices.moc" QTEST_MAIN(TestDevices)