make the devicemanager save all the states (unless opted out in plugin.json)

pull/135/head
Michael Zanetti 2017-09-27 20:15:14 +02:00
parent ec5bedf774
commit daf8f4533f
12 changed files with 172 additions and 140 deletions

View File

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

View File

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

View File

@ -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<State> 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();
}

View File

@ -196,6 +196,9 @@ private:
DeviceError addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QString &name, const ParamList &params, 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;

View File

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

View File

@ -39,7 +39,8 @@ public:
SettingsRoleDevices,
SettingsRoleRules,
SettingsRolePlugins,
SettingsRoleGlobal
SettingsRoleGlobal,
SettingsRoleDeviceStates
};
explicit GuhSettings(const SettingsRole &role = SettingsRoleNone, QObject *parent = 0);

View File

@ -346,6 +346,10 @@ QList<DeviceClass> DevicePlugin::supportedDevices() const
break;
}
}
if (st.contains("cached")) {
stateType.setCached(st.value("cached").toBool());
}
stateTypes.append(stateType);
// Events for state changed

View File

@ -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<StateType> &other)
{

View File

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

View File

@ -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": [

View File

@ -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();

View File

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