From 9d30be25680f95e43ddab8378bbc50ce9d89fe6f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Mar 2021 00:47:12 +0100 Subject: [PATCH] Add support for the plugin storage to python plugins --- .../integrations/python/pynymeamodule.h | 2 + .../integrations/python/pypluginstorage.h | 135 ++++++++++++++++++ .../integrations/python/pythingpairinginfo.h | 2 +- .../integrations/pythonintegrationplugin.cpp | 16 +++ .../integrations/pythonintegrationplugin.h | 4 + libnymea-core/integrations/translator.cpp | 2 +- libnymea-core/libnymea-core.pro | 1 + libnymea/integrations/thingpairinginfo.h | 2 +- 8 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 libnymea-core/integrations/python/pypluginstorage.h diff --git a/libnymea-core/integrations/python/pynymeamodule.h b/libnymea-core/integrations/python/pynymeamodule.h index b9f01844..1044fc29 100644 --- a/libnymea-core/integrations/python/pynymeamodule.h +++ b/libnymea-core/integrations/python/pynymeamodule.h @@ -11,6 +11,7 @@ #include "pyparam.h" #include "pythingactioninfo.h" #include "pythingpairinginfo.h" +#include "pypluginstorage.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winvalid-offsetof" @@ -27,6 +28,7 @@ static int nymea_exec(PyObject *m) { registerThingPairingInfoType(m); registerThingSetupInfoType(m); registerThingActionInfoType(m); + registerPluginStorageType(m); return 0; } diff --git a/libnymea-core/integrations/python/pypluginstorage.h b/libnymea-core/integrations/python/pypluginstorage.h new file mode 100644 index 00000000..7da97481 --- /dev/null +++ b/libnymea-core/integrations/python/pypluginstorage.h @@ -0,0 +1,135 @@ +#ifndef PYPLUGINSTORAGE_H +#define PYPLUGINSTORAGE_H + +#include +#include "structmember.h" +#include "pyutils.h" + +//#include "integrations/pythonintegrationplugin.h" +#include "loggingcategories.h" +#include "nymeasettings.h" +#include "typeutils.h" + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wwrite-strings" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + + +typedef struct { + PyObject_HEAD + QSettings *settings; +} PyPluginStorage; + + +static PyMemberDef PyPluginStorage_members[] = { + {nullptr, 0, 0, 0, nullptr} /* Sentinel */ +}; + +static PyObject * PyPluginStorage_value(PyPluginStorage* self, PyObject* args) { + char *keyStr = nullptr; + if (!PyArg_ParseTuple(args, "s", &keyStr)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in value call. Expected: value(key)"); + return nullptr; + } + + QVariant value = self->settings->value(keyStr); + return QVariantToPyObject(value); +}; + +static PyObject * PyPluginStorage_setValue(PyPluginStorage* self, PyObject* args) { + char *keyStr = nullptr; + PyObject *value = nullptr; + if (!PyArg_ParseTuple(args, "sO", &keyStr, &value)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in value call. Expected: value(key, value)"); + return nullptr; + } + + self->settings->setValue(keyStr, PyObjectToQVariant(value)); + Py_RETURN_NONE; +}; + +static PyObject * PyPluginStorage_beginGroup(PyPluginStorage* self, PyObject* args) { + char *groupStr = nullptr; + if (!PyArg_ParseTuple(args, "s", &groupStr)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in value call. Expected: beginGroup(group)"); + return nullptr; + } + + self->settings->beginGroup(groupStr); + Py_RETURN_NONE; +}; + +static PyObject * PyPluginStorage_endGroup(PyPluginStorage* self, PyObject* args) { + Q_UNUSED(args) + self->settings->endGroup(); + Py_RETURN_NONE; +}; + +static PyMethodDef PyPluginStorage_methods[] = { + { "value", (PyCFunction)PyPluginStorage_value, METH_VARARGS, "Get a value from the plugin storage" }, + { "setValue", (PyCFunction)PyPluginStorage_setValue, METH_VARARGS, "Set a value to the plugin storage"}, + { "beginGroup", (PyCFunction)PyPluginStorage_beginGroup, METH_VARARGS, "Begin a group in the plugin storage."}, + { "endGroup", (PyCFunction)PyPluginStorage_endGroup, METH_VARARGS, "End a group in the plugin storage."}, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static int PyPluginStorage_init(PyPluginStorage *self, PyObject *args, PyObject *kwds) +{ + Q_UNUSED(kwds) + char *pluginIdStr = nullptr; + if (!PyArg_ParseTuple(args, "s", &pluginIdStr)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in constructor. Expected: PluginStorage(pluginId)"); + return -1; + } + + if (!pluginIdStr) { + return -1; + } + PluginId pluginId(pluginIdStr); + if (pluginId.isNull()) { + return -1; + } + + self->settings = new QSettings(NymeaSettings::settingsPath() + "/pluginconfig-" + pluginId.toString().remove(QRegExp("[{}]")) + ".conf", QSettings::IniFormat); + + qCDebug(dcPythonIntegrations()) << "+++ PyPluginStorage"; + return 0; +} + +static void PyPluginStorage_dealloc(PyPluginStorage * self) +{ + qCDebug(dcPythonIntegrations()) << "--- PyPluginStorage"; + Py_TYPE(self)->tp_free(self); +} + +static PyTypeObject PyPluginStorageType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.PluginStorage", /* tp_name */ + sizeof(PyPluginStorage), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyPluginStorage_dealloc, /* tp_dealloc */ +}; + + + +static void registerPluginStorageType(PyObject *module) +{ + PyPluginStorageType.tp_new = PyType_GenericNew; + PyPluginStorageType.tp_members = PyPluginStorage_members; + PyPluginStorageType.tp_methods = PyPluginStorage_methods; + PyPluginStorageType.tp_init = reinterpret_cast(PyPluginStorage_init); + PyPluginStorageType.tp_doc = "PluginStorage can be used by plugins to store key-value pairs to a persistant place."; + PyPluginStorageType.tp_flags = Py_TPFLAGS_DEFAULT; + + if (PyType_Ready(&PyPluginStorageType) < 0) { + return; + } + PyModule_AddObject(module, "PluginStorage", reinterpret_cast(&PyPluginStorageType)); +} + +#pragma GCC diagnostic pop + +#endif // PYPLUGINSTORAGE_H diff --git a/libnymea-core/integrations/python/pythingpairinginfo.h b/libnymea-core/integrations/python/pythingpairinginfo.h index 7a9a8071..824ba64e 100644 --- a/libnymea-core/integrations/python/pythingpairinginfo.h +++ b/libnymea-core/integrations/python/pythingpairinginfo.h @@ -105,7 +105,7 @@ static PyObject * PyThingPairingInfo_finish(PyThingPairingInfo* self, PyObject* if (self->info) { if (self->pyOAuthUrl) { QString oAuthUrl = QString::fromUtf8(PyUnicode_AsUTF8AndSize(self->pyOAuthUrl, nullptr)); - QMetaObject::invokeMethod(self->info, "setOAuthUrl", Qt::QueuedConnection, Q_ARG(QString, oAuthUrl)); + QMetaObject::invokeMethod(self->info, "setOAuthUrl", Qt::QueuedConnection, Q_ARG(QUrl, oAuthUrl)); } QMetaObject::invokeMethod(self->info, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage)); } diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 5bb711f2..cd365aef 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -3,6 +3,7 @@ #include "pythonintegrationplugin.h" #include "python/pynymeamodule.h" #include "python/pystdouthandler.h" +#include "python/pypluginstorage.h" #include "loggingcategories.h" @@ -176,6 +177,14 @@ PyObject *PythonIntegrationPlugin::pyAutoThingDisappeared(PyObject *self, PyObje Py_RETURN_NONE; } +PyObject *PythonIntegrationPlugin::pyPluginStorage(PyObject *self, PyObject *args) +{ + Q_UNUSED(args) + PyObject *pluginStorage = s_plugins.key(self)->m_pyPluginStorage; + Py_INCREF(pluginStorage); + return pluginStorage; +} + static PyMethodDef plugin_methods[] = { {"configuration", PythonIntegrationPlugin::pyConfiguration, METH_VARARGS, "Get the plugin configuration."}, @@ -184,6 +193,7 @@ static PyMethodDef plugin_methods[] = {"myThings", PythonIntegrationPlugin::pyMyThings, METH_VARARGS, "Obtain a list of things owned by this plugin."}, {"autoThingsAppeared", PythonIntegrationPlugin::pyAutoThingsAppeared, METH_VARARGS, "Inform the system about auto setup things having appeared."}, {"autoThingDisappeared", PythonIntegrationPlugin::pyAutoThingDisappeared, METH_VARARGS, "Inform the system about auto setup things having disappeared."}, + {"pluginStorage", PythonIntegrationPlugin::pyPluginStorage, METH_VARARGS, "Obtain the plugin storage for this plugin."}, {nullptr, nullptr, 0, nullptr} // sentinel }; @@ -215,6 +225,7 @@ PythonIntegrationPlugin::~PythonIntegrationPlugin() s_plugins.take(this); Py_XDECREF(m_pluginModule); + Py_DECREF(m_pyPluginStorage); Py_DECREF(m_nymeaModule); Py_EndInterpreter(m_threadState); @@ -354,6 +365,11 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) Py_DECREF(logger); } + args = Py_BuildValue("(s)", pluginId().toString().toUtf8().data()); + m_pyPluginStorage = PyObject_CallObject((PyObject*)&PyPluginStorageType, args); + Py_DECREF(args); + Py_INCREF(m_pyPluginStorage); + // Export metadata ids into module exportIds(); diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h index 37bcaf28..06a2fcdb 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.h +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -45,6 +45,7 @@ public: static PyObject* pyMyThings(PyObject *self, PyObject* args); static PyObject* pyAutoThingsAppeared(PyObject *self, PyObject* args); static PyObject* pyAutoThingDisappeared(PyObject *self, PyObject* args); + static PyObject* pyPluginStorage(PyObject* self, PyObject* args); private: void exportIds(); @@ -89,6 +90,9 @@ private: // Need to keep a copy of plugin params and sync that in a thread-safe manner ParamList m_pluginConfigCopy; + // Keeping our own plugin storage for python + PyObject* m_pyPluginStorage = nullptr; + }; #endif // PYTHONINTEGRATIONPLUGIN_H diff --git a/libnymea-core/integrations/translator.cpp b/libnymea-core/integrations/translator.cpp index 22b3077e..552f60c0 100644 --- a/libnymea-core/integrations/translator.cpp +++ b/libnymea-core/integrations/translator.cpp @@ -127,7 +127,7 @@ void Translator::loadTranslator(IntegrationPlugin *plugin, const QLocale &locale } if (!loaded && locale.name() != "en_US") { - qCWarning(dcTranslations()) << "* Could not load translation" << locale.name() << "for plugin" << plugin->pluginName() << "(" << pluginId << ")"; + qCDebug(dcTranslations()) << "* Could not load translation" << locale.name() << "for plugin" << plugin->pluginName() << "(" << pluginId << ")"; } } diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index ece76d66..4feff683 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -46,6 +46,7 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ integrations/apikeysprovidersloader.h \ integrations/plugininfocache.h \ + integrations/python/pypluginstorage.h \ integrations/thingmanagerimplementation.h \ integrations/translator.h \ experiences/experiencemanager.h \ diff --git a/libnymea/integrations/thingpairinginfo.h b/libnymea/integrations/thingpairinginfo.h index ee229401..475448e9 100644 --- a/libnymea/integrations/thingpairinginfo.h +++ b/libnymea/integrations/thingpairinginfo.h @@ -53,13 +53,13 @@ public: ThingId parentId() const; QUrl oAuthUrl() const; - void setOAuthUrl(const QUrl &oAuthUrl); Thing::ThingError status() const; QString displayMessage() const; QString translatedDisplayMessage(const QLocale &locale) const; public slots: + void setOAuthUrl(const QUrl &oAuthUrl); void finish(Thing::ThingError status, const QString &displayMessage = QString()); signals: