diff --git a/libguh-core/guhcore.cpp b/libguh-core/guhcore.cpp index ebde582d..ff71b8f2 100644 --- a/libguh-core/guhcore.cpp +++ b/libguh-core/guhcore.cpp @@ -146,10 +146,15 @@ QPair > GuhCore::removeConfiguredDevic return QPair > (DeviceManager::DeviceErrorDeviceIsChild, QList()); } + if (device->autoCreated()) { + qCWarning(dcDeviceManager) << "This device has been auto-created and cannot be deleted manually."; + return QPair >(DeviceManager::DeviceErrorCreationMethodNotSupported, {}); + } + // Check if this device has child devices QList devicesToRemove; devicesToRemove.append(device); - QList childDevices = m_deviceManager->findChildDevices(device); + QList childDevices = m_deviceManager->findChildDevices(deviceId); if (!childDevices.isEmpty()) { foreach (Device *child, childDevices) { devicesToRemove.append(child); @@ -236,10 +241,15 @@ DeviceManager::DeviceError GuhCore::removeConfiguredDevice(const DeviceId &devic return DeviceManager::DeviceErrorDeviceIsChild; } + if (device->autoCreated()) { + qCWarning(dcDeviceManager) << "This device has been auto-created and cannot be deleted manually."; + return DeviceManager::DeviceErrorCreationMethodNotSupported; + } + // Check if this device has child devices QList devicesToRemove; devicesToRemove.append(device); - QList childDevices = m_deviceManager->findChildDevices(device); + QList childDevices = m_deviceManager->findChildDevices(deviceId); if (!childDevices.isEmpty()) { foreach (Device *child, childDevices) { devicesToRemove.append(child); @@ -437,6 +447,7 @@ void GuhCore::init() { connect(m_deviceManager, &DeviceManager::deviceAdded, this, &GuhCore::deviceAdded); connect(m_deviceManager, &DeviceManager::deviceChanged, this, &GuhCore::deviceChanged); connect(m_deviceManager, &DeviceManager::deviceRemoved, this, &GuhCore::deviceRemoved); + connect(m_deviceManager, &DeviceManager::deviceDisappeared, this, &GuhCore::onDeviceDisappeared); connect(m_deviceManager, &DeviceManager::actionExecutionFinished, this, &GuhCore::actionExecutionFinished); connect(m_deviceManager, &DeviceManager::devicesDiscovered, this, &GuhCore::devicesDiscovered); connect(m_deviceManager, &DeviceManager::deviceSetupFinished, this, &GuhCore::deviceSetupFinished); @@ -566,4 +577,58 @@ void GuhCore::actionExecutionFinished(const ActionId &id, DeviceManager::DeviceE m_logger->logAction(action, status == DeviceManager::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, status); } +void GuhCore::onDeviceDisappeared(const DeviceId &deviceId) +{ + Device *device = m_deviceManager->findConfiguredDevice(deviceId); + if (!device) { + return; + } + + // Check if this device has child devices + QList devicesToRemove; + devicesToRemove.append(device); + QList childDevices = m_deviceManager->findChildDevices(deviceId); + if (!childDevices.isEmpty()) { + foreach (Device *child, childDevices) { + devicesToRemove.append(child); + } + } + + // check devices + QList offendingRules; + qCDebug(dcDeviceManager) << "Devices to remove:"; + foreach (Device *d, devicesToRemove) { + qCDebug(dcDeviceManager) << " -> " << d->name() << d->id().toString(); + + // Check if device is in a rule + foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) { + qCDebug(dcDeviceManager) << " -> in rule:" << ruleId.toString(); + if (!offendingRules.contains(ruleId)) { + offendingRules.append(ruleId); + } + } + } + + // update involved rules + foreach (const RuleId &ruleId, offendingRules) { + foreach (Device *d, devicesToRemove) { + m_ruleEngine->removeDeviceFromRule(ruleId, d->id()); + } + } + + // remove the child devices + foreach (Device *d, childDevices) { + DeviceManager::DeviceError removeError = m_deviceManager->removeConfiguredDevice(d->id()); + if (removeError == DeviceManager::DeviceErrorNoError) { + m_logger->removeDeviceLogs(d->id()); + } + } + + // delete the device + DeviceManager::DeviceError removeError = m_deviceManager->removeConfiguredDevice(deviceId); + if (removeError == DeviceManager::DeviceErrorNoError) { + m_logger->removeDeviceLogs(deviceId); + } +} + } diff --git a/libguh-core/guhcore.h b/libguh-core/guhcore.h index 7bb60b22..1c9f1c3c 100644 --- a/libguh-core/guhcore.h +++ b/libguh-core/guhcore.h @@ -122,6 +122,7 @@ private slots: void onDateTimeChanged(const QDateTime &dateTime); void onLocaleChanged(); void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status); + void onDeviceDisappeared(const DeviceId &deviceId); }; diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index f798495b..77093651 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -741,7 +741,7 @@ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const Devi break; } - m_configuredDevices.append(device); + m_configuredDevices.insert(device->id(), device); storeConfiguredDevices(); postSetupDevice(device); @@ -755,12 +755,10 @@ DeviceManager::DeviceError DeviceManager::addConfiguredDeviceInternal(const Devi * Returns \l{DeviceError} to inform about the result. */ DeviceManager::DeviceError DeviceManager::removeConfiguredDevice(const DeviceId &deviceId) { - Device *device = findConfiguredDevice(deviceId); + Device *device = m_configuredDevices.take(deviceId); if (!device) { return DeviceErrorDeviceNotFound; } - - m_configuredDevices.removeAll(device); m_devicePlugins.value(device->pluginId())->deviceRemoved(device); // check if this plugin still needs the guhTimer call @@ -806,7 +804,7 @@ Device *DeviceManager::findConfiguredDevice(const DeviceId &id) const /*! Returns all configured \{Device}{Devices} in the system. */ QList DeviceManager::configuredDevices() const { - return m_configuredDevices; + return m_configuredDevices.values(); } /*! Returns all \l{Device}{Devices} matching the \l{DeviceClass} referred by \a deviceClassId. */ @@ -822,11 +820,11 @@ QList DeviceManager::findConfiguredDevices(const DeviceClassId &device } /*! Returns all child \l{Device}{Devices} of the given \a device. */ -QList DeviceManager::findChildDevices(Device *device) const +QList DeviceManager::findChildDevices(const DeviceId &id) const { QList ret; foreach (Device *d, m_configuredDevices) { - if (d->parentId() == device->id()) { + if (d->parentId() == id) { ret.append(d); } } @@ -1101,7 +1099,8 @@ void DeviceManager::loadPlugins() connect(pluginIface, &DevicePlugin::deviceSetupFinished, this, &DeviceManager::slotDeviceSetupFinished); connect(pluginIface, &DevicePlugin::actionExecutionFinished, this, &DeviceManager::actionExecutionFinished); connect(pluginIface, &DevicePlugin::pairingFinished, this, &DeviceManager::slotPairingFinished); - connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManager::autoDevicesAppeared); + connect(pluginIface, &DevicePlugin::autoDevicesAppeared, this, &DeviceManager::onAutoDevicesAppeared); + connect(pluginIface, &DevicePlugin::autoDeviceDisappeared, this, &DeviceManager::onAutoDeviceDisappeared); } } } @@ -1114,6 +1113,7 @@ void DeviceManager::loadConfiguredDevices() foreach (const QString &idString, settings.childGroups()) { settings.beginGroup(idString); Device *device = new Device(PluginId(settings.value("pluginid").toString()), DeviceId(idString), DeviceClassId(settings.value("deviceClassId").toString()), this); + device->m_autoCreated = settings.value("autoCreated").toBool(); device->setName(settings.value("devicename").toString()); device->setParentId(DeviceId(settings.value("parentid", QUuid()).toString())); @@ -1130,7 +1130,7 @@ void DeviceManager::loadConfiguredDevices() // it means that it was working at some point so lets still add it as there might // be rules associated with this device. Device::setupCompleted() will be false. DeviceSetupStatus status = setupDevice(device); - m_configuredDevices.append(device); + m_configuredDevices.insert(device->id(), device); if (status == DeviceSetupStatus::DeviceSetupStatusSuccess) postSetupDevice(device); @@ -1144,6 +1144,7 @@ void DeviceManager::storeConfiguredDevices() settings.beginGroup("DeviceConfig"); foreach (Device *device, m_configuredDevices) { settings.beginGroup(device->id().toString()); + settings.setValue("autoCreated", device->autoCreated()); settings.setValue("devicename", device->name()); settings.setValue("deviceClassId", device->deviceClassId().toString()); settings.setValue("pluginid", device->pluginId().toString()); @@ -1202,7 +1203,7 @@ void DeviceManager::slotDeviceSetupFinished(Device *device, DeviceManager::Devic } if (status == DeviceSetupStatusFailure) { - if (m_configuredDevices.contains(device)) { + if (m_configuredDevices.contains(device->id())) { if (m_asyncDeviceReconfiguration.contains(device)) { m_asyncDeviceReconfiguration.removeAll(device); qCWarning(dcDeviceManager) << QString("Error in device setup after reconfiguration. Device %1 (%2) will not be functional.").arg(device->name()).arg(device->id().toString()); @@ -1226,8 +1227,8 @@ void DeviceManager::slotDeviceSetupFinished(Device *device, DeviceManager::Devic // A device might be in here already if loaded from storedDevices. If it's not in the configuredDevices, // lets add it now. - if (!m_configuredDevices.contains(device)) { - m_configuredDevices.append(device); + if (!m_configuredDevices.contains(device->id())) { + m_configuredDevices.insert(device->id(), device); emit deviceAdded(device); storeConfiguredDevices(); } @@ -1335,14 +1336,14 @@ void DeviceManager::slotPairingFinished(const PairingTransactionId &pairingTrans break; } - m_configuredDevices.append(device); + m_configuredDevices.insert(device->id(), device); emit deviceAdded(device); storeConfiguredDevices(); emit deviceSetupFinished(device, DeviceError::DeviceErrorNoError); postSetupDevice(device); } -void DeviceManager::autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors) +void DeviceManager::onAutoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors) { DeviceClass deviceClass = findDeviceClass(deviceClassId); if (!deviceClass.isValid()) { @@ -1356,6 +1357,7 @@ void DeviceManager::autoDevicesAppeared(const DeviceClassId &deviceClassId, cons foreach (const DeviceDescriptor &deviceDescriptor, deviceDescriptors) { Device *device = new Device(plugin->pluginId(), deviceClassId, this); + device->m_autoCreated = true; device->setName(deviceClass.name()); device->setParams(deviceDescriptor.params()); @@ -1370,7 +1372,7 @@ void DeviceManager::autoDevicesAppeared(const DeviceClassId &deviceClassId, cons break; case DeviceSetupStatusSuccess: qCDebug(dcDeviceManager) << "Device setup complete."; - m_configuredDevices.append(device); + m_configuredDevices.insert(device->id(), device); storeConfiguredDevices(); emit deviceSetupFinished(device, DeviceError::DeviceErrorNoError); emit deviceAdded(device); @@ -1380,6 +1382,31 @@ void DeviceManager::autoDevicesAppeared(const DeviceClassId &deviceClassId, cons } } +void DeviceManager::onAutoDeviceDisappeared(const DeviceId &deviceId) +{ + DevicePlugin *plugin = static_cast(sender()); + Device *device = m_configuredDevices.value(deviceId); + + if (!device) { + qWarning(dcDeviceManager) << "Received an autoDeviceDisappeared signal but don't know this device:" << deviceId; + return; + } + + DeviceClass deviceClass = m_supportedDevices.value(device->deviceClassId()); + + if (deviceClass.pluginId() != plugin->pluginId()) { + qWarning(dcDeviceManager) << "Received a autoDeviceDisappeared signal but emitting plugin does not own the device"; + return; + } + + if (!device->autoCreated()) { + qWarning(dcDeviceManager) << "Received an autoDeviceDisappeared signal but device creationMethod is not CreateMothodAuto"; + return; + } + + emit deviceDisappeared(deviceId); +} + void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) { Device *device = qobject_cast(sender()); diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index 9af3006f..13107595 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -138,7 +138,7 @@ public: Device* findConfiguredDevice(const DeviceId &id) const; QList findConfiguredDevices(const DeviceClassId &deviceClassId) const; - QList findChildDevices(Device *device) const; + QList findChildDevices(const DeviceId &id) const; DeviceClass findDeviceClass(const DeviceClassId &deviceClassId) const; DeviceError verifyParams(const QList paramTypes, ParamList ¶ms, bool requireAll = true); @@ -152,6 +152,7 @@ signals: void eventTriggered(const Event &event); void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); void deviceRemoved(const DeviceId &deviceId); + void deviceDisappeared(const DeviceId &deviceId); void deviceAdded(Device *device); void deviceChanged(Device *device); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &devices); @@ -172,7 +173,8 @@ private slots: void slotDevicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); void slotDeviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status); void slotPairingFinished(const PairingTransactionId &pairingTransactionId, DeviceManager::DeviceSetupStatus status); - void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); + void onAutoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); + void onAutoDeviceDisappeared(const DeviceId &deviceId); // Only connect this to Devices. It will query the sender() void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value); @@ -200,7 +202,7 @@ private: QHash m_supportedVendors; QHash > m_vendorDeviceMap; QHash m_supportedDevices; - QList m_configuredDevices; + QHash m_configuredDevices; QHash m_discoveredDevices; QHash m_devicePlugins; diff --git a/libguh/plugin/device.cpp b/libguh/plugin/device.cpp index b4f5dcea..15b2fc43 100644 --- a/libguh/plugin/device.cpp +++ b/libguh/plugin/device.cpp @@ -50,8 +50,7 @@ Device::Device(const PluginId &pluginId, const DeviceId &id, const DeviceClassId QObject(parent), m_id(id), m_deviceClassId(deviceClassId), - m_pluginId(pluginId), - m_setupComplete(false) + m_pluginId(pluginId) { } @@ -61,8 +60,7 @@ Device::Device(const PluginId &pluginId, const DeviceClassId &deviceClassId, QOb QObject(parent), m_id(DeviceId::createDeviceId()), m_deviceClassId(deviceClassId), - m_pluginId(pluginId), - m_setupComplete(false) + m_pluginId(pluginId) { } @@ -233,6 +231,12 @@ bool Device::setupComplete() const return m_setupComplete; } +/*! Returns true if this device has been auto-created (not created by the user) */ +bool Device::autoCreated() const +{ + return m_autoCreated; +} + void Device::setSetupComplete(const bool &complete) { m_setupComplete = complete; diff --git a/libguh/plugin/device.h b/libguh/plugin/device.h index c0499555..8bae8005 100644 --- a/libguh/plugin/device.h +++ b/libguh/plugin/device.h @@ -69,6 +69,7 @@ public: void setParentId(const DeviceId &parentId); bool setupComplete() const; + bool autoCreated() const; signals: void stateValueChanged(const QUuid &stateTypeId, const QVariant &value); @@ -88,7 +89,8 @@ private: QString m_name; ParamList m_params; QList m_states; - bool m_setupComplete; + bool m_setupComplete = false; + bool m_autoCreated = false; }; #endif diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index dbfcf6f7..bd0e55b3 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -120,6 +120,14 @@ will be in \a deviceDescriptors. This signal can only emitted from devices with the \l{DeviceClass}{CreateMethodAuto}. */ +/*! + \fn void DevicePlugin::autoDeviceDisappeared(const DeviceId &id) + Emit this signal when a device which was created by \l{DevicePlugin::autoDevicesAppeared} has been removed from the system. + Be careful with this, as this will completely remove the device from the system and with it all the associated rules. Only + emit this if you are sure that a device will never come back. This signal should not be emitted for child auto devices + when the parent who created them is removed. The system will automatically remove all child devices in such a case. +*/ + /*! \fn void DevicePlugin::emitEvent(const Event &event) To produce a new event in the system, create a new \l{Event} and emit it with \a event. diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 38d3fa7c..7472fc09 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -108,6 +108,7 @@ signals: void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status); void configValueChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); + void autoDeviceDisappeared(const DeviceId &deviceId); protected: DeviceManager *deviceManager() const; diff --git a/tests/auto/devices/testdevices.cpp b/tests/auto/devices/testdevices.cpp index ba4a9fa2..198c7ee3 100644 --- a/tests/auto/devices/testdevices.cpp +++ b/tests/auto/devices/testdevices.cpp @@ -1175,6 +1175,7 @@ void TestDevices::removeDevice_data() QTest::newRow("Existing Device") << m_mockDeviceId << DeviceManager::DeviceErrorNoError; QTest::newRow("Not existing Device") << DeviceId::createDeviceId() << DeviceManager::DeviceErrorDeviceNotFound; + QTest::newRow("Auto device") << m_mockDeviceAutoId << DeviceManager::DeviceErrorCreationMethodNotSupported; } void TestDevices::removeDevice() diff --git a/tests/auto/guhtestbase.cpp b/tests/auto/guhtestbase.cpp index a48ebb7f..c1f229e1 100644 --- a/tests/auto/guhtestbase.cpp +++ b/tests/auto/guhtestbase.cpp @@ -203,6 +203,13 @@ void GuhTestBase::initTestCase() m_mockDeviceId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toString()); QVERIFY2(!m_mockDeviceId.isNull(), "Newly created mock device must not be null."); + + response = injectAndWait("Devices.GetConfiguredDevices", {}); + foreach (const QVariant &device, response.toMap().value("params").toMap().value("devices").toList()) { + if (device.toMap().value("deviceClassId").toUuid() == mockDeviceAutoClassId) { + m_mockDeviceAutoId = DeviceId(device.toMap().value("id").toString()); + } + } } void GuhTestBase::cleanupTestCase() diff --git a/tests/auto/guhtestbase.h b/tests/auto/guhtestbase.h index e66f9728..21a4044c 100644 --- a/tests/auto/guhtestbase.h +++ b/tests/auto/guhtestbase.h @@ -188,6 +188,7 @@ protected: int m_mockDevice2Port; DeviceId m_mockDeviceId; + DeviceId m_mockDeviceAutoId; QByteArray m_apiToken; };