From daf8f4533f67ec12ae5c0b3d38434274dcd3cb9d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 27 Sep 2017 20:15:14 +0200 Subject: [PATCH] make the devicemanager save all the states (unless opted out in plugin.json) --- guh.pri | 5 -- guh.pro | 5 -- libguh/devicemanager.cpp | 43 +++++++-- libguh/devicemanager.h | 3 + libguh/guhsettings.cpp | 134 ++++++++--------------------- libguh/guhsettings.h | 3 +- libguh/plugin/deviceplugin.cpp | 4 + libguh/types/statetype.cpp | 21 +++-- libguh/types/statetype.h | 13 +-- plugins/mock/devicepluginmock.json | 6 +- server/main.cpp | 19 ++-- tests/auto/states/teststates.cpp | 56 ++++++++++++ 12 files changed, 172 insertions(+), 140 deletions(-) diff --git a/guh.pri b/guh.pri index c0d4b503..4b74aed6 100644 --- a/guh.pri +++ b/guh.pri @@ -87,11 +87,6 @@ coverage { QMAKE_CLEAN += *.gcda *.gcno coverage.info coverage.xml } -# Ubuntu snappy -snappy { - DEFINES += SNAPPY -} - # Enable Radio 433 MHz for GPIO's enable433gpio { DEFINES += GPIO433 diff --git a/guh.pro b/guh.pro index 7092361d..b210ed84 100644 --- a/guh.pro +++ b/guh.pro @@ -62,11 +62,6 @@ contains(DEFINES, BLUETOOTH_LE) { message("Bluetooth LE disabled (Qt $${QT_VERSION} < 5.4.0).") } -# Ubuntu snappy -contains(DEFINES, SNAPPY) { - message("Building Ubuntu snappy package.") -} - # GPIO RF 433 MHz support contains(DEFINES, GPIO433) { message("Radio 433 for GPIO's enabled") diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index 77093651..c3875f24 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -248,6 +248,10 @@ DeviceManager::DeviceManager(const QLocale &locale, QObject *parent) : DeviceManager::~DeviceManager() { qCDebug(dcApplication) << "Shutting down \"Device Manager\""; + foreach (Device *device, m_configuredDevices) { + storeDeviceStates(device); + } + foreach (DevicePlugin *plugin, m_devicePlugins) { delete plugin; } @@ -265,9 +269,10 @@ QStringList DeviceManager::pluginSearchDirs() searchDirs << QCoreApplication::applicationDirPath() + "/../plugins/"; searchDirs << QCoreApplication::applicationDirPath() + "/../../../plugins/"; searchDirs << QString("%1").arg(GUH_PLUGINS_PATH); -#ifdef SNAPPY - searchDirs << QString("%1%2").arg(QString::fromUtf8(qgetenv("SNAP"))).arg(GUH_PLUGINS_PATH); -#endif + QString snapDir = QString::fromUtf8(qgetenv("SNAP")); + if (!snapDir.isEmpty()) { + searchDirs << QString("%1%2").arg(snapDir).arg(GUH_PLUGINS_PATH); + } return searchDirs; } @@ -1109,7 +1114,7 @@ void DeviceManager::loadConfiguredDevices() { GuhSettings settings(GuhSettings::SettingsRoleDevices); settings.beginGroup("DeviceConfig"); - qCDebug(dcDeviceManager) << "loading devices from" << settings.fileName(); + qCDebug(dcDeviceManager) << "Loading devices from" << settings.fileName(); 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); @@ -1517,10 +1522,10 @@ DeviceManager::DeviceSetupStatus DeviceManager::setupDevice(Device *device) QList states; foreach (const StateType &stateType, deviceClass.stateTypes()) { State state(stateType.id(), device->id()); - state.setValue(stateType.defaultValue()); states.append(state); } device->setStates(states); + loadDeviceStates(device); DeviceSetupStatus status = plugin->setupDevice(device); if (status != DeviceSetupStatusSuccess) { @@ -1554,3 +1559,31 @@ void DeviceManager::postSetupDevice(Device *device) plugin->postSetupDevice(device); } +void DeviceManager::loadDeviceStates(Device *device) +{ + GuhSettings settings(GuhSettings::SettingsRoleDeviceStates); + settings.beginGroup(device->id().toString()); + DeviceClass deviceClass = m_supportedDevices.value(device->deviceClassId()); + foreach (const StateType &stateType, deviceClass.stateTypes()) { + if (stateType.cached()) { + device->setStateValue(stateType.id(), settings.value(stateType.id().toString(), stateType.defaultValue())); + } else { + device->setStateValue(stateType.id(), stateType.defaultValue()); + } + } + settings.endGroup(); +} + +void DeviceManager::storeDeviceStates(Device *device) +{ + GuhSettings settings(GuhSettings::SettingsRoleDeviceStates); + settings.beginGroup(device->id().toString()); + DeviceClass deviceClass = m_supportedDevices.value(device->deviceClassId()); + foreach (const StateType &stateType, deviceClass.stateTypes()) { + if (stateType.cached()) { + settings.setValue(stateType.id().toString(), device->stateValue(stateType.id())); + } + } + settings.endGroup(); +} + diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index 13107595..aa05065b 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -196,6 +196,9 @@ private: DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList ¶ms, const DeviceId id = DeviceId::createDeviceId()); DeviceSetupStatus setupDevice(Device *device); void postSetupDevice(Device *device); + void storeDeviceStates(Device *device); + void loadDeviceStates(Device *device); + private: QLocale m_locale; diff --git a/libguh/guhsettings.cpp b/libguh/guhsettings.cpp index 709b80f3..20bc0d07 100644 --- a/libguh/guhsettings.cpp +++ b/libguh/guhsettings.cpp @@ -63,98 +63,40 @@ GuhSettings::GuhSettings(const SettingsRole &role, QObject *parent): QObject(parent), m_role(role) { - QString settingsFile; -#ifdef SNAPPY - QString settingsFilePath = QString(qgetenv("SNAP_DATA")); + QString settingsPrefix = QCoreApplication::instance()->organizationName() + "/"; + + QString basePath; + if (!qgetenv("SNAP").isEmpty()) { + basePath = QString(qgetenv("SNAP_DATA")); + } else if (settingsPrefix == "guh-test/") { + basePath = "/tmp/"; + } else if (isRoot()) { + basePath = "/etc/"; + } else { + basePath = QDir::homePath() + "/.config/"; + } + + QString fileName; switch (role) { case SettingsRoleNone: break; case SettingsRoleDevices: - settingsFile = settingsFilePath + "/devices.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); + fileName = "devices.conf"; break; case SettingsRoleRules: - settingsFile = settingsFilePath + "/rules.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); + fileName = "rules.conf"; break; case SettingsRolePlugins: - settingsFile = settingsFilePath + "/plugins.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); + fileName = "plugins.conf"; break; case SettingsRoleGlobal: - settingsFile = settingsFilePath + "/guhd.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); + fileName = "guhd.conf"; break; - default: + case SettingsRoleDeviceStates: + fileName = "devicestates.conf"; break; } -#else - QString settingsPrefix = QCoreApplication::instance()->organizationName(); - bool rootPrivilege = isRoot(); - - switch (role) { - case SettingsRoleNone: - break; - case SettingsRoleDevices: - // check if we are running a test - if (settingsPrefix == "guh-test") { - settingsFile = "/tmp/" + settingsPrefix + "/test-devices.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created test-devices settings" << m_settings->fileName(); - } else if (rootPrivilege) { - settingsFile = "/etc/" + settingsPrefix + "/devices.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); - //qCDebug(dcApplication) << "Created device settings" << m_settings->fileName(); - } else { - settingsFile = QDir::homePath() + "/.config/" + settingsPrefix + "/devices.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created device settings" << m_settings->fileName(); - } - break; - case SettingsRoleRules: - // check if we are running a test - if (settingsPrefix == "guh-test") { - settingsFile = "/tmp/" + settingsPrefix + "/test-rules.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created test-rules settings" << m_settings->fileName(); - } else if (rootPrivilege) { - settingsFile = "/etc/" + settingsPrefix + "/rules.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); - //qCDebug(dcApplication) << "Created rule settings" << m_settings->fileName(); - } else { - settingsFile = QDir::homePath() + "/.config/" + settingsPrefix + "/rules.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created rule settings" << m_settings->fileName(); - } - break; - case SettingsRolePlugins: - // check if we are running a test - if (settingsPrefix == "guh-test") { - settingsFile = "/tmp/" + settingsPrefix + "/test-plugins.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created test-plugins settings" << m_settings->fileName(); - } else if (rootPrivilege) { - settingsFile = "/etc/" + settingsPrefix + "/plugins.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); - //qCDebug(dcApplication) << "Created plugin settings" << m_settings->fileName(); - } else { - settingsFile = QDir::homePath() + "/.config/" + settingsPrefix + "/plugins.conf"; - m_settings = new QSettings(settingsFile, QSettings::NativeFormat, this); - //qCDebug(dcApplication) << "Created plugin settings" << m_settings->fileName(); - } - break; - case SettingsRoleGlobal: - // this file schould always be readable and should never be written - settingsFile = "/etc/guh/guhd.conf"; - m_settings = new QSettings(settingsFile, QSettings::IniFormat, this); - qCDebug(dcApplication) << "Created test guhd settings" << m_settings->fileName(); - break; - default: - break; - } - -#endif // SNAPPY - + m_settings = new QSettings(basePath + settingsPrefix + fileName, QSettings::IniFormat, this); } /*! Destructor of the GuhSettings.*/ @@ -186,12 +128,11 @@ bool GuhSettings::isRoot() QString GuhSettings::logPath() { QString logPath; -#ifdef SNAPPY - logPath = QString(qgetenv("SNAP_COMMON")) + "/guhd.sqlite"; -#else QString organisationName = QCoreApplication::instance()->organizationName(); - if (organisationName == "guh-test") { + if (!qgetenv("SNAP").isEmpty()) { + logPath = QString(qgetenv("SNAP_COMMON")) + "/guhd.sqlite"; + } else if (organisationName == "guh-test") { logPath = "/tmp/" + organisationName + "/guhd-test.sqlite"; } else if (GuhSettings::isRoot()) { logPath = "/var/log/guhd.sqlite"; @@ -199,7 +140,6 @@ QString GuhSettings::logPath() logPath = QDir::homePath() + "/.config/" + organisationName + "/guhd.sqlite"; } -#endif // SNAPPY return logPath; } @@ -207,47 +147,43 @@ QString GuhSettings::logPath() QString GuhSettings::settingsPath() { QString path; -#ifdef SNAPPY - path = QString(qgetenv("SNAP_DATA")); -#else QString organisationName = QCoreApplication::instance()->organizationName(); - if (organisationName == "guh-test") { + if (!qgetenv("SNAP").isEmpty()) { + path = QString(qgetenv("SNAP_DATA")); + } else if (organisationName == "guh-test") { path = "/tmp/" + organisationName; } else if (GuhSettings::isRoot()) { path = "/etc/guh/"; } else { path = QDir::homePath() + "/.config/" + organisationName; } -#endif // SNAPPY return path; } /*! Returns the default system translation path \tt{/usr/share/guh/translations}. */ QString GuhSettings::translationsPath() { -#ifdef SNAPPY - return QString(qgetenv("SNAP") + "/usr/share/guh/translations"); -#else - return QString("/usr/share/guh/translations"); -#endif // SNAPPY + if (!qgetenv("SNAP").isEmpty()) { + return QString(qgetenv("SNAP") + "/usr/share/guh/translations"); + } else { + return QString("/usr/share/guh/translations"); + } } QString GuhSettings::storagePath() { QString path; -#ifdef SNAPPY - path = QString(qgetenv("SNAP_DATA")); -#else QString organisationName = QCoreApplication::instance()->organizationName(); - if (organisationName == "guh-test") { + if (!qgetenv("SNAP").isEmpty()) { + path = QString(qgetenv("SNAP_DATA")); + } else if (organisationName == "guh-test") { path = "/tmp/" + organisationName + "/"; } else if (GuhSettings::isRoot()) { path = "/var/lib/" + organisationName + "/"; } else { path = QDir::homePath() + "/.local/share/" + organisationName + "/"; } -#endif return path; } diff --git a/libguh/guhsettings.h b/libguh/guhsettings.h index ad4fadee..e55b5f99 100644 --- a/libguh/guhsettings.h +++ b/libguh/guhsettings.h @@ -39,7 +39,8 @@ public: SettingsRoleDevices, SettingsRoleRules, SettingsRolePlugins, - SettingsRoleGlobal + SettingsRoleGlobal, + SettingsRoleDeviceStates }; explicit GuhSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = 0); diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index bd0e55b3..a9ebea9e 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -346,6 +346,10 @@ QList DevicePlugin::supportedDevices() const break; } } + + if (st.contains("cached")) { + stateType.setCached(st.value("cached").toBool()); + } stateTypes.append(stateType); // Events for state changed diff --git a/libguh/types/statetype.cpp b/libguh/types/statetype.cpp index c2062d99..3e68e6ad 100644 --- a/libguh/types/statetype.cpp +++ b/libguh/types/statetype.cpp @@ -38,15 +38,7 @@ * When creating a \l{DevicePlugin} generate a new uuid for each StateType you define and * hardcode it into the plugin. */ StateType::StateType(const StateTypeId &id): - m_id(id), - m_index(0), - m_defaultValue(QVariant()), - m_minValue(QVariant()), - m_maxValue(QVariant()), - m_possibleValues(QVariantList()), - m_unit(Types::UnitNone), - m_ruleRelevant(true), - m_graphRelevant(false) + m_id(id) { } @@ -180,6 +172,17 @@ void StateType::setGraphRelevant(const bool &graphRelevant) m_graphRelevant = graphRelevant; } +/*! Returns true if this StateType is to be cached. This means, the last state value will be stored to disk upon shutdown and restored on reboot. If this is false, states will be initialized with the default value on each boot. By default all states are cached by the system. */ +bool StateType::cached() const +{ + return m_cached; +} + +/*! Sets whether this StateType should be cached or not. */ +void StateType::setCached(bool cached) +{ + m_cached = cached; +} StateTypes::StateTypes(const QList &other) { diff --git a/libguh/types/statetype.h b/libguh/types/statetype.h index 4036f636..222d4f8c 100644 --- a/libguh/types/statetype.h +++ b/libguh/types/statetype.h @@ -67,19 +67,22 @@ public: bool graphRelevant() const; void setGraphRelevant(const bool &graphRelevant); + bool cached() const; + void setCached(bool cached); + private: StateTypeId m_id; QString m_name; - int m_index; + int m_index = 0; QVariant::Type m_type; QVariant m_defaultValue; QVariant m_minValue; QVariant m_maxValue; QVariantList m_possibleValues; - Types::Unit m_unit; - bool m_ruleRelevant; - bool m_graphRelevant; - + Types::Unit m_unit = Types::UnitNone; + bool m_ruleRelevant = true; + bool m_graphRelevant = false; + bool m_cached = true; }; class StateTypes: public QList diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 862434b8..31368f01 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -97,7 +97,8 @@ "eventTypeName": "Dummy bool state changed", "index": 1, "defaultValue": false, - "type": "bool" + "type": "bool", + "cached": false } ], "eventTypes": [ @@ -230,7 +231,8 @@ "eventTypeName": "Dummy bool state changed", "index": 1, "defaultValue": false, - "type": "bool" + "type": "bool", + "cached": false } ], "eventTypes": [ diff --git a/server/main.cpp b/server/main.cpp index 3e9f89cd..b39dae68 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -245,15 +245,16 @@ int main(int argc, char *argv[]) qCDebug(dcApplication) << "====================================="; } -#ifdef SNAPPY - // Note: http://snapcraft.io/docs/reference/env - qCDebug(dcApplication) << "Snap name :" << qgetenv("SNAP_NAME"); - qCDebug(dcApplication) << "Snap version :" << qgetenv("SNAP_VERSION"); - qCDebug(dcApplication) << "Snap directory :" << qgetenv("SNAP"); - qCDebug(dcApplication) << "Snap app data :" << qgetenv("SNAP_DATA"); - qCDebug(dcApplication) << "Snap user data :" << qgetenv("SNAP_USER_DATA"); - qCDebug(dcApplication) << "Snap app common :" << qgetenv("SNAP_COMMON"); -#endif + // If running in a snappy environment, print out some details about it. + if (!qgetenv("SNAP").isEmpty()) { + // Note: http://snapcraft.io/docs/reference/env + qCDebug(dcApplication) << "Snap name :" << qgetenv("SNAP_NAME"); + qCDebug(dcApplication) << "Snap version :" << qgetenv("SNAP_VERSION"); + qCDebug(dcApplication) << "Snap directory :" << qgetenv("SNAP"); + qCDebug(dcApplication) << "Snap app data :" << qgetenv("SNAP_DATA"); + qCDebug(dcApplication) << "Snap user data :" << qgetenv("SNAP_USER_DATA"); + qCDebug(dcApplication) << "Snap app common :" << qgetenv("SNAP_COMMON"); + } // create core instance GuhCore::instance(); diff --git a/tests/auto/states/teststates.cpp b/tests/auto/states/teststates.cpp index ddfa28a0..57d1e131 100644 --- a/tests/auto/states/teststates.cpp +++ b/tests/auto/states/teststates.cpp @@ -43,6 +43,8 @@ private slots: void getStateValue_data(); void getStateValue(); + + void save_load_states(); }; void TestStates::getStateTypes() @@ -84,5 +86,59 @@ void TestStates::getStateValue() verifyDeviceError(response, error); } +void TestStates::save_load_states() +{ + DeviceClass mockDeviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(mockDeviceClassId); + + QVERIFY2(mockDeviceClass.getStateType(mockIntStateId).cached(), "Mock int state is not cached (required to be true for this test)"); + QVERIFY2(!mockDeviceClass.getStateType(mockBoolStateId).cached(), "Mock bool state is cached (required to be false for this test)"); + + Device* device = GuhCore::instance()->deviceManager()->findConfiguredDevices(mockDeviceClassId).first(); + int port = device->paramValue(httpportParamTypeId).toInt(); + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + + // First set the state values to something that is *not* the default + int newIntValue = mockDeviceClass.getStateType(mockIntStateId).defaultValue().toInt() + 1; + bool newBoolValue = !mockDeviceClass.getStateType(mockBoolStateId).defaultValue().toBool(); + + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateId.toString()).arg(newIntValue))); + QNetworkReply *reply = nam.get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + spy.wait(); + + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateId.toString()).arg(newBoolValue))); + reply = nam.get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + spy.wait(); + + // For completeness, verify through JSONRPC that they were actually yet. + QVariantMap params; + params.insert("deviceId", device->id()); + + params["stateTypeId"] = mockIntStateId; + QVariant response = injectAndWait("Devices.GetStateValue", params); + QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue); + + params["stateTypeId"] = mockBoolStateId; + response = injectAndWait("Devices.GetStateValue", params); + QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), newBoolValue); + + // Restart the server + restartServer(); + + // And check if the cached int state has successfully been restored + params["stateTypeId"] = mockIntStateId; + response = injectAndWait("Devices.GetStateValue", params); + QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue); + + // and that the non-cached bool state is back to its default + params["stateTypeId"] = mockBoolStateId; + response = injectAndWait("Devices.GetStateValue", params); + QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), mockDeviceClass.getStateType(mockBoolStateId).defaultValue().toBool()); +} + #include "teststates.moc" QTEST_MAIN(TestStates)