Merge PR #406: Add browsing support to python plugin API

This commit is contained in:
Jenkins nymea 2021-04-19 11:38:27 +02:00
commit 07327b8bb4
12 changed files with 690 additions and 5 deletions

View File

@ -0,0 +1,121 @@
#ifndef PYBROWSERACTIONINFO_H
#define PYBROWSERACTIONINFO_H
#include <Python.h>
#include "structmember.h"
#include "pything.h"
#include "integrations/browseractioninfo.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 PyBrowserActionInfo_setInfo() while holding the GIL to initialize
* stuff after constructing it. Also set info to nullptr while holding the GIL when the info object vanishes.
*
* The BrowserActionInfo class is not threadsafe and self->info is owned by nymeas main thread.
* So we must never directly access anything of it in here.
*
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
* Make sure to check if the info object is still valid (it might not be if nymea finished
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
*
* For reading access, we keep copies of the thing properties here and sync them
* over to the according py* members when they change.
*
*/
typedef struct {
PyObject_HEAD
BrowserActionInfo* info;
PyThing *pyThing;
PyObject *pyItemId;
} PyBrowserActionInfo;
static PyObject* PyBrowserActionInfo_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
PyBrowserActionInfo *self = (PyBrowserActionInfo*)type->tp_alloc(type, 0);
if (self == NULL) {
return nullptr;
}
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserActionInfo";
return (PyObject*)self;
}
void PyBrowserActionInfo_setInfo(PyBrowserActionInfo *self, BrowserActionInfo *info, PyThing *pyThing)
{
self->info = info;
self->pyThing = pyThing;
Py_INCREF(pyThing);
self->pyItemId = PyUnicode_FromString(info->browserAction().itemId().toUtf8());
}
static void PyBrowserActionInfo_dealloc(PyBrowserActionInfo * self)
{
qCDebug(dcPythonIntegrations()) << "--- PyBrowserActionInfo";
Py_DECREF(self->pyThing);
Py_DECREF(self->pyItemId);
Py_TYPE(self)->tp_free(self);
}
static PyObject * PyBrowserActionInfo_finish(PyBrowserActionInfo* self, PyObject* args) {
int status;
char *message = nullptr;
if (!PyArg_ParseTuple(args, "i|s", &status, &message)) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\")");
return nullptr;
}
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
QString displayMessage = message != nullptr ? QString(message) : QString();
if (self->info) {
QMetaObject::invokeMethod(self->info, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
}
Py_RETURN_NONE;
}
static PyMemberDef PyBrowserActionInfo_members[] = {
{"thing", T_OBJECT_EX, offsetof(PyBrowserActionInfo, pyThing), 0, "Thing this action is for"},
{"itemId", T_OBJECT_EX, offsetof(PyBrowserActionInfo, pyItemId), 0, "The browser item id to be executed"},
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
};
static PyMethodDef PyBrowserActionInfo_methods[] = {
{ "finish", (PyCFunction)PyBrowserActionInfo_finish, METH_VARARGS, "finish an action" },
{nullptr, nullptr, 0, nullptr} // sentinel
};
static PyTypeObject PyBrowserActionInfoType = {
PyVarObject_HEAD_INIT(NULL, 0)
"nymea.BrowserActionInfo", /* tp_name */
sizeof(PyBrowserActionInfo), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PyBrowserActionInfo_dealloc, /* tp_dealloc */
};
static void registerBrowserActionInfoType(PyObject *module)
{
PyBrowserActionInfoType.tp_new = (newfunc)PyBrowserActionInfo_new;
PyBrowserActionInfoType.tp_flags = Py_TPFLAGS_DEFAULT;
PyBrowserActionInfoType.tp_methods = PyBrowserActionInfo_methods;
PyBrowserActionInfoType.tp_members = PyBrowserActionInfo_members;
PyBrowserActionInfoType.tp_doc = "The BrowserActionInfo is used to execute browser items";
if (PyType_Ready(&PyBrowserActionInfoType) < 0) {
return;
}
PyModule_AddObject(module, "BrowserActionInfo", (PyObject *)&PyBrowserActionInfoType);
}
#pragma GCC diagnostic pop
#endif // PYBROWSERACTIONINFO_H

View File

@ -0,0 +1,171 @@
#ifndef PYBROWSERESULT_H
#define PYBROWSERESULT_H
#include <Python.h>
#include "structmember.h"
#include "pything.h"
#include "integrations/browseresult.h"
#include "pybrowseritem.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 PyBrowseResult_setBrowseResult() while holding the GIL to initialize
* stuff after constructing it. Also set broeseResult to nullptr while holding the GIL when the browseResult object vanishes.
*
* The BrowseResult class is not threadsafe and self->browseResult is owned by nymeas main thread.
* So we must never directly access anything of it in here.
*
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
* Make sure to check if the info object is still valid (it might not be if nymea finished
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
*
* For reading access, we keep copies of the BrowseResults properties here.
*
*/
typedef struct {
PyObject_HEAD
BrowseResult* browseResult;
PyThing *pyThing;
PyObject *pyItemId;
PyObject *pyLocale;
} PyBrowseResult;
static PyObject* PyBrowseResult_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
PyBrowseResult *self = (PyBrowseResult*)type->tp_alloc(type, 0);
if (self == NULL) {
return nullptr;
}
qCDebug(dcPythonIntegrations()) << "+++ PyBrowseResult";
return (PyObject*)self;
}
void PyBrowseResult_setBrowseResult(PyBrowseResult *self, BrowseResult *browseResult, PyThing *pyThing)
{
self->browseResult = browseResult;
self->pyThing = pyThing;
Py_INCREF(pyThing);
self->pyItemId = PyUnicode_FromString(browseResult->itemId().toUtf8());
self->pyLocale = PyUnicode_FromString(browseResult->locale().name().toUtf8());
}
static void PyBrowseResult_dealloc(PyBrowseResult* self)
{
qCDebug(dcPythonIntegrations()) << "--- PyBrowseResult";
Py_DECREF(self->pyThing);
Py_DECREF(self->pyItemId);
Py_DECREF(self->pyLocale);
Py_TYPE(self)->tp_free(self);
}
static PyObject* PyBrowseResult_addItem(PyBrowseResult *self, PyObject *args) {
Q_UNUSED(self)
PyObject *pyObj = nullptr;
if (!PyArg_ParseTuple(args, "O", &pyObj)) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments in addItem call. Expected: addItem(BrowserItem)");
return nullptr;
}
if (pyObj->ob_type != &PyBrowserItemType) {
PyErr_SetString(PyExc_ValueError, "Invalid argument to BrowseResult.addItem(BrowserItem). Not a BrowserItem.");
return nullptr;
}
PyBrowserItem *pyBrowserItem = (PyBrowserItem*)pyObj;
QString id;
if (pyBrowserItem->pyId) {
id = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyId));
}
QString displayName;
if (pyBrowserItem->pyDisplayName) {
displayName = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDisplayName));
}
BrowserItem browserItem(id, displayName);
if (pyBrowserItem->pyDescription) {
browserItem.setDescription(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDescription)));
}
if (pyBrowserItem->pyThumbnail) {
browserItem.setThumbnail(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyThumbnail)));
}
browserItem.setBrowsable(pyBrowserItem->browsable);
browserItem.setExecutable(pyBrowserItem->executable);
browserItem.setDisabled(pyBrowserItem->disabled);
browserItem.setIcon(static_cast<BrowserItem::BrowserIcon>(pyBrowserItem->icon));
if (self->browseResult) {
QMetaObject::invokeMethod(self->browseResult, "addItem", Qt::QueuedConnection, Q_ARG(BrowserItem, browserItem));
}
Py_RETURN_NONE;
}
static PyObject* PyBrowseResult_finish(PyBrowseResult* self, PyObject* args) {
int status;
char *message = nullptr;
if (!PyArg_ParseTuple(args, "i|s", &status, &message)) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\")");
return nullptr;
}
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
QString displayMessage = message != nullptr ? QString(message) : QString();
if (self->browseResult) {
QMetaObject::invokeMethod(self->browseResult, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
}
Py_RETURN_NONE;
}
static PyMemberDef PyBrowseResult_members[] = {
{"thing", T_OBJECT_EX, offsetof(PyBrowseResult, pyThing), 0, "Thing this browse request is for"},
{"itemId", T_OBJECT_EX, offsetof(PyBrowseResult, pyItemId), 0, "The itemId of the item that should be browsed. Empty if the root item is requested"},
{"locale", T_OBJECT_EX, offsetof(PyBrowseResult, pyLocale), 0, "The locale strings should be translated to."},
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
};
static PyMethodDef PyBrowseResult_methods[] = {
{ "addItem", (PyCFunction)PyBrowseResult_addItem, METH_VARARGS, "Add a browser item to the result"},
{ "finish", (PyCFunction)PyBrowseResult_finish, METH_VARARGS, "Finish a browse request" },
{nullptr, nullptr, 0, nullptr} // sentinel
};
static PyTypeObject PyBrowseResultType = {
PyVarObject_HEAD_INIT(NULL, 0)
"nymea.BrowseResult", /* tp_name */
sizeof(PyBrowseResult), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PyBrowseResult_dealloc, /* tp_dealloc */
};
static void registerBrowseResultType(PyObject *module)
{
PyBrowseResultType.tp_new = (newfunc)PyBrowseResult_new;
PyBrowseResultType.tp_flags = Py_TPFLAGS_DEFAULT;
PyBrowseResultType.tp_methods = PyBrowseResult_methods;
PyBrowseResultType.tp_members = PyBrowseResult_members;
PyBrowseResultType.tp_doc = "The BrowseResult is used fetch browser entries from things";
if (PyType_Ready(&PyBrowseResultType) < 0) {
return;
}
PyModule_AddObject(module, "BrowseResult", (PyObject *)&PyBrowseResultType);
}
#pragma GCC diagnostic pop
#endif // PYBROWSERESULT_H

View File

@ -0,0 +1,118 @@
#ifndef PYBROWSERITEM_H
#define PYBROWSERITEM_H
#include <Python.h>
#include "structmember.h"
#include <QMetaEnum>
#include "types/browseritem.h"
#include "loggingcategories.h"
#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
PyObject* pyId;
PyObject* pyDisplayName;
PyObject* pyDescription;
PyObject* pyThumbnail;
bool browsable = false;
bool executable = false;
bool disabled = false;
int icon = (int)BrowserItem::BrowserIconNone;
} PyBrowserItem;
static PyMemberDef PyBrowserItem_members[] = {
{"id", T_OBJECT_EX, offsetof(PyBrowserItem, pyId), 0, "BrowserItem id"},
{"displayName", T_OBJECT_EX, offsetof(PyBrowserItem, pyDisplayName), 0, "The name of this item"},
{"description", T_OBJECT_EX, offsetof(PyBrowserItem, pyDescription), 0, "The description of this item"},
{"tumbnail", T_OBJECT_EX, offsetof(PyBrowserItem, pyThumbnail), 0, "An URL pointing to the thumbnail"},
{"browsable", T_OBJECT_EX, offsetof(PyBrowserItem, browsable), 0, "A boolean if this item can be browsed (e.g. a folder)"},
{"executable", T_OBJECT_EX, offsetof(PyBrowserItem, executable), 0, "A boolean if this item can be launched"},
{"disabled", T_OBJECT_EX, offsetof(PyBrowserItem, disabled), 0, "A boolean if this item is disabled"},
{"icon", T_OBJECT_EX, offsetof(PyBrowserItem, icon), 0, "The icon to be used"},
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
};
static int PyBrowserItem_init(PyBrowserItem *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"id", "displayName", "description", "thumbnail", "browsable", "executable", "disabled", "icon", nullptr};
PyObject *id = nullptr, *displayName = nullptr, *description = nullptr, *thumbnail = nullptr;
bool browsable = false, executable = false, disabled = false;
int icon = (int)BrowserItem::BrowserIconNone;
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserItem";
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOObbbi", kwlist, &id, &displayName, &description, &thumbnail, &browsable, &executable, &disabled, &icon))
return -1;
if (id) {
Py_INCREF(id);
self->pyId = id;
}
if (displayName) {
Py_INCREF(displayName);
self->pyDisplayName = displayName;
}
if (description) {
Py_INCREF(description);
self->pyDescription = description;
}
if (thumbnail) {
Py_INCREF(thumbnail);
self->pyThumbnail = thumbnail;
}
self->browsable = browsable;
self->executable = executable;
self->disabled = disabled;
self->icon = icon;
return 0;
}
static void PyBrowserItem_dealloc(PyBrowserItem* self)
{
qCDebug(dcPythonIntegrations()) << "--- PyBrowserItem";
Py_XDECREF(self->pyId);
Py_XDECREF(self->pyDisplayName);
Py_XDECREF(self->pyDescription);
Py_XDECREF(self->pyThumbnail);
Py_TYPE(self)->tp_free(self);
}
static PyTypeObject PyBrowserItemType = {
PyVarObject_HEAD_INIT(NULL, 0)
"nymea.BrowserItem", /* tp_name */
sizeof(PyBrowserItem), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PyBrowserItem_dealloc, /* tp_dealloc */
};
static void registerBrowserItemType(PyObject *module)
{
PyBrowserItemType.tp_new = PyType_GenericNew;
PyBrowserItemType.tp_members = PyBrowserItem_members;
PyBrowserItemType.tp_init = reinterpret_cast<initproc>(PyBrowserItem_init);
PyBrowserItemType.tp_doc = "BrowserItems are used to return entries in a things browser to nymea.";
PyBrowserItemType.tp_flags = Py_TPFLAGS_DEFAULT;
if (PyType_Ready(&PyBrowserItemType) < 0) {
return;
}
PyModule_AddObject(module, "BrowserItem", reinterpret_cast<PyObject*>(&PyBrowserItemType));
QMetaEnum browserIconEnum = QMetaEnum::fromType<BrowserItem::BrowserIcon>();
for (int i = 0; i < browserIconEnum.keyCount(); i++) {
PyModule_AddObject(module, browserIconEnum.key(i), PyLong_FromLong(browserIconEnum.value(i)));
}
}
#pragma GCC diagnostic pop
#endif // PYBROWSERITEM_H

View File

@ -0,0 +1,164 @@
#ifndef PYBROWSERITEMRESULT_H
#define PYBROWSERITEMRESULT_H
#include <Python.h>
#include "structmember.h"
#include "pything.h"
#include "integrations/browseritemresult.h"
#include "pybrowseritem.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 PyBrowseResult_setBrowseResult() while holding the GIL to initialize
* stuff after constructing it. Also set broeseResult to nullptr while holding the GIL when the browseResult object vanishes.
*
* The BrowseResult class is not threadsafe and self->browseResult is owned by nymeas main thread.
* So we must never directly access anything of it in here.
*
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
* Make sure to check if the info object is still valid (it might not be if nymea finished
* the setup and destroyed it but the PyThingSetupInfo is not garbage collected yet.
*
* For reading access, we keep copies of the BrowseResults properties here.
*
*/
typedef struct {
PyObject_HEAD
BrowserItemResult* browserItemResult;
PyThing *pyThing;
PyObject *pyItemId;
PyObject *pyLocale;
} PyBrowserItemResult;
static PyObject* PyBrowserItemResult_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
PyBrowserItemResult *self = (PyBrowserItemResult*)type->tp_alloc(type, 0);
if (self == NULL) {
return nullptr;
}
qCDebug(dcPythonIntegrations()) << "+++ PyBrowserItemResult";
return (PyObject*)self;
}
void PyBrowserItemResult_setBrowserItemResult(PyBrowserItemResult *self, BrowserItemResult *browserItemResult, PyThing *pyThing)
{
self->browserItemResult = browserItemResult;
self->pyThing = pyThing;
Py_INCREF(pyThing);
self->pyItemId = PyUnicode_FromString(browserItemResult->itemId().toUtf8());
self->pyLocale = PyUnicode_FromString(browserItemResult->locale().name().toUtf8());
}
static void PyBrowserItemResult_dealloc(PyBrowserItemResult* self)
{
qCDebug(dcPythonIntegrations()) << "--- PyBrowserItemResult";
Py_DECREF(self->pyThing);
Py_DECREF(self->pyItemId);
Py_DECREF(self->pyLocale);
Py_TYPE(self)->tp_free(self);
}
static PyObject* PyBrowserItemResult_finish(PyBrowserItemResult* self, PyObject* args) {
PyObject *pyObj;
int status;
char *message = nullptr;
if (!PyArg_ParseTuple(args, "|Ois", &pyObj, &status, &message)) {
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(BrowserItem) or finish(ThingError, message = \"\")");
return nullptr;
}
if (pyObj != nullptr) {
if (pyObj->ob_type != &PyBrowserItemType) {
PyErr_SetString(PyExc_ValueError, "Invalid argument to BrowserItemResult.finish(BrowserItem). Not a BrowserItem.");
return nullptr;
}
PyBrowserItem *pyBrowserItem = (PyBrowserItem*)pyObj;
QString id;
if (pyBrowserItem->pyId) {
id = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyId));
}
QString displayName;
if (pyBrowserItem->pyDisplayName) {
displayName = QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDisplayName));
}
BrowserItem browserItem(id, displayName);
if (pyBrowserItem->pyDescription) {
browserItem.setDescription(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyDescription)));
}
if (pyBrowserItem->pyThumbnail) {
browserItem.setThumbnail(QString::fromUtf8(PyUnicode_AsUTF8(pyBrowserItem->pyThumbnail)));
}
browserItem.setBrowsable(pyBrowserItem->browsable);
browserItem.setExecutable(pyBrowserItem->executable);
browserItem.setDisabled(pyBrowserItem->disabled);
browserItem.setIcon(static_cast<BrowserItem::BrowserIcon>(pyBrowserItem->icon));
if (self->browserItemResult) {
QMetaObject::invokeMethod(self->browserItemResult, "finish", Qt::QueuedConnection, Q_ARG(BrowserItem, browserItem));
}
Py_RETURN_NONE;
}
Thing::ThingError thingError = static_cast<Thing::ThingError>(status);
QString displayMessage = message != nullptr ? QString(message) : QString();
if (self->browserItemResult) {
QMetaObject::invokeMethod(self->browserItemResult, "finish", Qt::QueuedConnection, Q_ARG(Thing::ThingError, thingError), Q_ARG(QString, displayMessage));
}
Py_RETURN_NONE;
}
static PyMemberDef PyBrowserItemResult_members[] = {
{"thing", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyThing), 0, "Thing this browse request is for"},
{"itemId", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyItemId), 0, "The itemId of the item to be returned."},
{"locale", T_OBJECT_EX, offsetof(PyBrowserItemResult, pyLocale), 0, "The locale strings should be translated to."},
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
};
static PyMethodDef PyBrowserItemResult_methods[] = {
{ "finish", (PyCFunction)PyBrowserItemResult_finish, METH_VARARGS, "Finish a browser item request" },
{nullptr, nullptr, 0, nullptr} // sentinel
};
static PyTypeObject PyBrowserItemResultType = {
PyVarObject_HEAD_INIT(NULL, 0)
"nymea.BrowserItemResult", /* tp_name */
sizeof(PyBrowserItemResult), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PyBrowserItemResult_dealloc, /* tp_dealloc */
};
static void registerBrowserItemResultType(PyObject *module)
{
PyBrowserItemResultType.tp_new = (newfunc)PyBrowserItemResult_new;
PyBrowserItemResultType.tp_flags = Py_TPFLAGS_DEFAULT;
PyBrowserItemResultType.tp_methods = PyBrowserItemResult_methods;
PyBrowserItemResultType.tp_members = PyBrowserItemResult_members;
PyBrowserItemResultType.tp_doc = "The BrowserItemResult is used fetch an individual entry from the thing browser";
if (PyType_Ready(&PyBrowserItemResultType) < 0) {
return;
}
PyModule_AddObject(module, "BrowserItemResult", (PyObject *)&PyBrowserItemResultType);
}
#pragma GCC diagnostic pop
#endif // PYBROWSERITEMRESULT_H

View File

@ -13,6 +13,10 @@
#include "pythingpairinginfo.h"
#include "pypluginstorage.h"
#include "pyapikeystorage.h"
#include "pybrowseresult.h"
#include "pybrowseritem.h"
#include "pybrowseractioninfo.h"
#include "pybrowseritemresult.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
@ -31,6 +35,10 @@ static int nymea_exec(PyObject *m) {
registerThingActionInfoType(m);
registerPluginStorageType(m);
registerApiKeyStorageType(m);
registerBrowseResultType(m);
registerBrowserItemType(m);
registerBrowserActionInfoType(m);
registerBrowserItemResultType(m);
return 0;
}

View File

@ -574,13 +574,11 @@ void PythonIntegrationPlugin::executeAction(ThingActionInfo *info)
PyEval_ReleaseThread(m_threadState);
connect(info, &ThingActionInfo::destroyed, this, [=](){
qCDebug(dcPythonIntegrations()) << "Info destroyed";
qCDebug(dcPythonIntegrations()) << "ThingActionInfo destroyed";
PyEval_RestoreThread(m_threadState);
qCDebug(dcPythonIntegrations()) << "Info destroyed2";
pyInfo->info = nullptr;
Py_DECREF(pyInfo);
PyEval_ReleaseThread(m_threadState);
qCDebug(dcPythonIntegrations()) << "Info destroyed3";
});
bool success = callPluginFunction("executeAction", reinterpret_cast<PyObject*>(pyInfo));
@ -595,6 +593,81 @@ void PythonIntegrationPlugin::thingRemoved(Thing *thing)
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
}
void PythonIntegrationPlugin::browseThing(BrowseResult *result)
{
PyThing *pyThing = m_things.value(result->thing());
PyEval_RestoreThread(m_threadState);
PyBrowseResult *pyBrowseResult = (PyBrowseResult*)PyObject_CallObject((PyObject*)&PyBrowseResultType, NULL);
PyBrowseResult_setBrowseResult(pyBrowseResult, result, pyThing);
PyEval_ReleaseThread(m_threadState);
connect(result, &BrowseResult::destroyed, this, [=](){
qCDebug(dcPythonIntegrations()) << "BrowseResult destroyed";
PyEval_RestoreThread(m_threadState);
pyBrowseResult->browseResult = nullptr;
Py_DECREF(pyBrowseResult);
PyEval_ReleaseThread(m_threadState);
});
bool success = callPluginFunction("browseThing", reinterpret_cast<PyObject*>(pyBrowseResult));
if (!success) {
result->finish(Thing::ThingErrorUnsupportedFeature);
}
}
void PythonIntegrationPlugin::executeBrowserItem(BrowserActionInfo *info)
{
PyThing *pyThing = m_things.value(info->thing());
PyEval_RestoreThread(m_threadState);
PyBrowserActionInfo *pyBrowserActionInfo = (PyBrowserActionInfo*)PyObject_CallObject((PyObject*)&PyBrowserActionInfoType, NULL);
PyBrowserActionInfo_setInfo(pyBrowserActionInfo, info, pyThing);
PyEval_ReleaseThread(m_threadState);
connect(info, &BrowserActionInfo::destroyed, this, [=](){
qCDebug(dcPythonIntegrations()) << "BrowserActionInfo destroyed";
PyEval_RestoreThread(m_threadState);
pyBrowserActionInfo->info = nullptr;
Py_DECREF(pyBrowserActionInfo);
PyEval_ReleaseThread(m_threadState);
});
bool success = callPluginFunction("executeBrowserItem", reinterpret_cast<PyObject*>(pyBrowserActionInfo));
if (!success) {
info->finish(Thing::ThingErrorUnsupportedFeature);
}
}
void PythonIntegrationPlugin::browserItem(BrowserItemResult *result)
{
PyThing *pyThing = m_things.value(result->thing());
PyEval_RestoreThread(m_threadState);
PyBrowserItemResult *pyBrowserItemResult = (PyBrowserItemResult*)PyObject_CallObject((PyObject*)&PyBrowserItemResultType, NULL);
PyBrowserItemResult_setBrowserItemResult(pyBrowserItemResult, result, pyThing);
PyEval_ReleaseThread(m_threadState);
connect(result, &BrowserItemResult::destroyed, this, [=](){
qCDebug(dcPythonIntegrations()) << "BrowseItemResult destroyed";
PyEval_RestoreThread(m_threadState);
pyBrowserItemResult->browserItemResult = nullptr;
Py_DECREF(pyBrowserItemResult);
PyEval_ReleaseThread(m_threadState);
});
bool success = callPluginFunction("browserItem", reinterpret_cast<PyObject*>(pyBrowserItemResult));
if (!success) {
result->finish(Thing::ThingErrorUnsupportedFeature);
}
}
void PythonIntegrationPlugin::exportIds()
{
qCDebug(dcThingManager()) << "Exporting plugin IDs:";

View File

@ -37,6 +37,9 @@ public:
void postSetupThing(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
void thingRemoved(Thing *thing) override;
void browseThing(BrowseResult *result) override;
void executeBrowserItem(BrowserActionInfo *info) override;
void browserItem(BrowserItemResult *result) override;
static PyObject* pyConfiguration(PyObject* self, PyObject* args);

View File

@ -47,6 +47,10 @@ HEADERS += nymeacore.h \
integrations/apikeysprovidersloader.h \
integrations/plugininfocache.h \
integrations/python/pyapikeystorage.h \
integrations/python/pybrowseractioninfo.h \
integrations/python/pybrowseresult.h \
integrations/python/pybrowseritem.h \
integrations/python/pybrowseritemresult.h \
integrations/python/pypluginstorage.h \
integrations/thingmanagerimplementation.h \
integrations/translator.h \

View File

@ -122,6 +122,7 @@ protected:
QList<ActionTypeId> m_actionTypeIds;
};
Q_DECLARE_METATYPE(BrowserItem)
Q_DECLARE_OPERATORS_FOR_FLAGS(BrowserItem::ExtendedPropertiesFlags)

View File

@ -1037,7 +1037,7 @@ void IntegrationPluginMock::generateBrowseItems()
item = BrowserItem("004", "Item 3", false, true);
item.setDescription("I have a nice thumbnail");
item.setIcon(BrowserItem::BrowserIconFile);
item.setThumbnail("https://github.com/guh/nymea/raw/master/icons/nymea-logo-256x256.png");
item.setThumbnail("https://github.com/nymea/nymea/raw/master/icons/nymea-logo-256x256.png");
item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
m_virtualFs->addChild(new VirtualFsNode(item));

View File

@ -49,6 +49,7 @@
"displayName": "Python mock thing",
"createMethods": ["user"],
"setupMethod": "justAdd",
"browsable": true,
"eventTypes": [
{
"id": "de6c2425-0dee-413f-8f4c-bb0929e83c0d",

View File

@ -7,7 +7,7 @@ loopRunning = False
def init():
global loopRunning
loopRunning = True
loopRunning = True
logger.log("Python mock plugin init")
logger.warn("Python mock warning")
@ -139,6 +139,27 @@ def thingSettingChanged(thing, paramTypeId, value):
logger.log("Thing setting changed:", thing.name, paramTypeId, value)
def browseThing(result):
logger.log("browseThing called", result.thing.name, result.itemId)
if result.itemId == "":
result.addItem(nymea.BrowserItem("001", "Item 0", "I'm a folder", browsable=True, icon=nymea.BrowserIconFolder))
result.addItem(nymea.BrowserItem("002", "Item 1", "I'm executable", executable=True, icon=nymea.BrowserIconApplication))
result.addItem(nymea.BrowserItem("003", "Item 2", "I'm a file", icon=nymea.BrowserIconFile))
result.addItem(nymea.BrowserItem("004", "Item 3", "I have a nice thumbnail", thumbnail="https://github.com/nymea/nymea/raw/master/icons/nymea-logo-256x256.png"))
result.addItem(nymea.BrowserItem("005", "Item 4", "I'm disabled", disabled=True, icon=nymea.BrowserIconFile))
result.addItem(nymea.BrowserItem("favorites", "Favorites", "I'm the best!", icon=nymea.BrowserIconFavorites))
if result.itemId == "001":
result.addItem(nymea.BrowserItem("011", "Item in subdir", "I'm in a subfolder", icon=nymea.BrowserIconFile))
result.finish(nymea.ThingErrorNoError)
def executeBrowserItem(info):
logger.log("executeBrowserItem called for thing", info.thing.name, "and item", info.itemId)
info.finish(nymea.ThingErrorNoError)
# Intentionally commented out to also have a test case for unimplmented functions
# def thingRemoved(thing):
# logger.log("thingRemoved for", thing.name)