diff --git a/libnymea-core/integrations/python/pyapikeystorage.h b/libnymea-core/integrations/python/pyapikeystorage.h new file mode 100644 index 00000000..0f023b42 --- /dev/null +++ b/libnymea-core/integrations/python/pyapikeystorage.h @@ -0,0 +1,155 @@ +#ifndef PYAPIKEYSTORAGE_H +#define PYAPIKEYSTORAGE_H + +#include +#include "structmember.h" +#include "pyutils.h" + +#include "loggingcategories.h" +#include "nymeasettings.h" +#include "typeutils.h" +#include "network/apikeys/apikeystorage.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wwrite-strings" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + +/* Note: + * When using this, make sure to call PyApiKeyStorage_setApiKeyStorage() while holding the GIL to initialize + * stuff after constructing it. + * + * The apiKeyStorage() pointer is owned by the C++ plugin class, however, without an actual C++ + * plugin it will never be accessed by anyone. So we pass it into the python context and use it in + * there. + * + */ + +typedef struct { + PyObject_HEAD + ApiKey *apiKey; +} PyApiKey; + +static int PyApiKey_init(PyApiKey */*self*/, PyObject */*args*/, PyObject */*kwds*/) +{ + qCDebug(dcPythonIntegrations()) << "+++ PyApiKey"; + return 0; +} + +void PyApiKey_setApiKey(PyApiKey *self, const ApiKey &apiKey) +{ + self->apiKey = new ApiKey(apiKey); +} + +static void PyApiKey_dealloc(PyApiKey* self) +{ + qCDebug(dcPythonIntegrations()) << "--- PyApiKey"; + delete self->apiKey; + Py_TYPE(self)->tp_free(self); +} + +static PyObject * PyApiKey_data(PyApiKey* self, PyObject* args) { + char *keyStr = nullptr; + if (!PyArg_ParseTuple(args, "s", &keyStr)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in value call. Expected: requestKey(key)"); + return nullptr; + } + + QByteArray data = self->apiKey->data(QString(keyStr)); + return QVariantToPyObject(data); +}; + +static PyMethodDef PyApiKey_methods[] = { + { "data", (PyCFunction)PyApiKey_data, METH_VARARGS, "Get data from the API key." }, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyTypeObject PyApiKeyType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.ApiKey", /* tp_name */ + sizeof(PyApiKey), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyApiKey_dealloc, /* tp_dealloc */ +}; + + + + + +typedef struct { + PyObject_HEAD + ApiKeyStorage *apiKeyStorage; +} PyApiKeyStorage; + +static int PyApiKeyStorage_init(PyApiKeyStorage */*self*/, PyObject */*args*/, PyObject */*kwds*/) +{ + qCDebug(dcPythonIntegrations()) << "+++ PyApiKeyStorage"; + return 0; +} + +void PyApiKeyStorage_setApiKeyStorage(PyApiKeyStorage *self, ApiKeyStorage *apiKeyStorage) +{ + self->apiKeyStorage = apiKeyStorage; +} + +static void PyApiKeyStorage_dealloc(PyApiKeyStorage * self) +{ + qCDebug(dcPythonIntegrations()) << "--- PyApiKeyStorage"; + Py_TYPE(self)->tp_free(self); +} + +static PyObject * PyApiKeyStorage_requestKey(PyApiKeyStorage* self, PyObject* args) { + char *nameStr = nullptr; + if (!PyArg_ParseTuple(args, "s", &nameStr)) { + PyErr_SetString(PyExc_TypeError, "Invalid arguments in value call. Expected: requestKey(name)"); + return nullptr; + } + + PyApiKey *pyApiKey = (PyApiKey*)PyObject_CallObject((PyObject*)&PyApiKeyType, args); + PyApiKey_setApiKey(pyApiKey, self->apiKeyStorage->requestKey(nameStr)); + return (PyObject*)pyApiKey; +}; + +static PyMethodDef PyApiKeyStorage_methods[] = { + { "requestKey", (PyCFunction)PyApiKeyStorage_requestKey, METH_VARARGS, "Get an API key from the API key storage." }, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyTypeObject PyApiKeyStorageType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.ApiKeyStorage", /* tp_name */ + sizeof(PyApiKeyStorage), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyApiKeyStorage_dealloc, /* tp_dealloc */ +}; + + + +static void registerApiKeyStorageType(PyObject *module) +{ + PyApiKeyStorageType.tp_new = PyType_GenericNew; + PyApiKeyStorageType.tp_methods = PyApiKeyStorage_methods; + PyApiKeyStorageType.tp_init = reinterpret_cast(PyApiKeyStorage_init); + PyApiKeyStorageType.tp_doc = "ApiKeyStorage holds API keys. API keys need to be requested in the plugin JSON file."; + PyApiKeyStorageType.tp_flags = Py_TPFLAGS_DEFAULT; + + if (PyType_Ready(&PyApiKeyStorageType) < 0) { + return; + } + PyModule_AddObject(module, "ApiKeyStorage", reinterpret_cast(&PyApiKeyStorageType)); + + PyApiKeyType.tp_new = PyType_GenericNew; + PyApiKeyType.tp_methods = PyApiKey_methods; + PyApiKeyType.tp_init = reinterpret_cast(PyApiKey_init); + PyApiKeyType.tp_doc = "ApiKey holds an API keys data as key-value pairs."; + PyApiKeyType.tp_flags = Py_TPFLAGS_DEFAULT; + + if (PyType_Ready(&PyApiKeyType) < 0) { + return; + } + PyModule_AddObject(module, "ApiKey", reinterpret_cast(&PyApiKeyType)); +} + +#pragma GCC diagnostic pop + +#endif // PYAPIKEYSTORAGE_H diff --git a/libnymea-core/integrations/python/pynymeamodule.h b/libnymea-core/integrations/python/pynymeamodule.h index b9f01844..8936ee65 100644 --- a/libnymea-core/integrations/python/pynymeamodule.h +++ b/libnymea-core/integrations/python/pynymeamodule.h @@ -11,6 +11,8 @@ #include "pyparam.h" #include "pythingactioninfo.h" #include "pythingpairinginfo.h" +#include "pypluginstorage.h" +#include "pyapikeystorage.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winvalid-offsetof" @@ -27,6 +29,8 @@ static int nymea_exec(PyObject *m) { registerThingPairingInfoType(m); registerThingSetupInfoType(m); registerThingActionInfoType(m); + registerPluginStorageType(m); + registerApiKeyStorageType(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..1355470b --- /dev/null +++ b/libnymea-core/integrations/python/pypluginstorage.h @@ -0,0 +1,126 @@ +#ifndef PYPLUGINSTORAGE_H +#define PYPLUGINSTORAGE_H + +#include +#include "structmember.h" +#include "pyutils.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" + +/* Note: + * When using this, make sure to call PyPluginStorage_setPluginStorage() while holding the GIL to initialize + * stuff after constructing it. + * + * The pluginStorage() pointer is owned by the C++ plugin class, however, without an actual C++ + * plugin it will never be accessed by anyone. So we pass it into the python context and use it in + * there. + * + */ + +typedef struct { + PyObject_HEAD + QSettings *pluginStorage; +} PyPluginStorage; + +static int PyPluginStorage_init(PyPluginStorage */*self*/, PyObject */*args*/, PyObject */*kwds*/) +{ + qCDebug(dcPythonIntegrations()) << "+++ PyPluginStorage"; + return 0; +} + +void PyPluginStorage_setPluginStorage(PyPluginStorage *self, QSettings* pluginStorage) +{ + self->pluginStorage = pluginStorage; +} + +static void PyPluginStorage_dealloc(PyPluginStorage * self) +{ + qCDebug(dcPythonIntegrations()) << "--- PyPluginStorage"; + Py_TYPE(self)->tp_free(self); +} + +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->pluginStorage->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->pluginStorage->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->pluginStorage->beginGroup(groupStr); + Py_RETURN_NONE; +}; + +static PyObject * PyPluginStorage_endGroup(PyPluginStorage* self, PyObject* args) { + Q_UNUSED(args) + self->pluginStorage->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 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_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..ee3b334f 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -3,6 +3,8 @@ #include "pythonintegrationplugin.h" #include "python/pynymeamodule.h" #include "python/pystdouthandler.h" +#include "python/pypluginstorage.h" +#include "python/pyapikeystorage.h" #include "loggingcategories.h" @@ -176,6 +178,24 @@ PyObject *PythonIntegrationPlugin::pyAutoThingDisappeared(PyObject *self, PyObje Py_RETURN_NONE; } +PyObject *PythonIntegrationPlugin::pyPluginStorage(PyObject *self, PyObject */*args*/) +{ + // Note: Passing the pluginsStorage() pointer directly into python. Implies that it must not be + // accessed in the main thread without obtaining the GIL + PyObject *pluginStorage = PyObject_CallObject((PyObject*)&PyPluginStorageType, nullptr); + PyPluginStorage_setPluginStorage((PyPluginStorage*)pluginStorage, s_plugins.key(self)->pluginStorage()); + return pluginStorage; +} + +PyObject *PythonIntegrationPlugin::pyApiKeyStorage(PyObject *self, PyObject *args) +{ + // Note: Passing the apiKeyStorage() pointer directly into python. Implies that it must not be + // accessed in the main thread without obtaining the GIL + PyObject *pyApiKeyStorage = PyObject_CallObject((PyObject*)&PyApiKeyStorageType, args); + PyApiKeyStorage_setApiKeyStorage((PyApiKeyStorage*)pyApiKeyStorage, s_plugins.key(self)->apiKeyStorage()); + return pyApiKeyStorage; +} + static PyMethodDef plugin_methods[] = { {"configuration", PythonIntegrationPlugin::pyConfiguration, METH_VARARGS, "Get the plugin configuration."}, @@ -184,6 +204,8 @@ 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."}, + {"apiKeyStorage", PythonIntegrationPlugin::pyApiKeyStorage, METH_VARARGS, "Obtain the API key storage for this plugin."}, {nullptr, nullptr, 0, nullptr} // sentinel }; diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h index 37bcaf28..d57c1305 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.h +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -45,6 +45,8 @@ 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); + static PyObject* pyApiKeyStorage(PyObject* self, PyObject* args); private: void exportIds(); @@ -88,7 +90,6 @@ private: // Need to keep a copy of plugin params and sync that in a thread-safe manner ParamList m_pluginConfigCopy; - }; #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..a98f1598 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -46,6 +46,8 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ integrations/apikeysprovidersloader.h \ integrations/plugininfocache.h \ + integrations/python/pyapikeystorage.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: