mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Refactor Python
This commit is contained in:
		@@ -24,13 +24,13 @@ public:
 | 
			
		||||
 | 
			
		||||
	friend class EffectModule;
 | 
			
		||||
 | 
			
		||||
	Effect(Hyperion *hyperion
 | 
			
		||||
				, int priority
 | 
			
		||||
				, int timeout
 | 
			
		||||
				, const QString &script
 | 
			
		||||
				, const QString &name
 | 
			
		||||
				, const QJsonObject &args = QJsonObject()
 | 
			
		||||
				, const QString &imageData = ""
 | 
			
		||||
	Effect(Hyperion* hyperion
 | 
			
		||||
		, int priority
 | 
			
		||||
		, int timeout
 | 
			
		||||
		, const QString& script
 | 
			
		||||
		, const QString& name
 | 
			
		||||
		, const QJsonObject& args = QJsonObject()
 | 
			
		||||
		, const QString& imageData = ""
 | 
			
		||||
	);
 | 
			
		||||
	~Effect() override;
 | 
			
		||||
 | 
			
		||||
@@ -64,20 +64,20 @@ public:
 | 
			
		||||
	QString getScript() const { return _script; }
 | 
			
		||||
	QString getName() const { return _name; }
 | 
			
		||||
 | 
			
		||||
	int getTimeout() const {return _timeout; }
 | 
			
		||||
	int getTimeout() const { return _timeout; }
 | 
			
		||||
	bool isEndless() const { return _isEndless; }
 | 
			
		||||
 | 
			
		||||
	QJsonObject getArgs() const { return _args; }
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void setInput(int priority, const std::vector<ColorRgb> &ledColors, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInputImage(int priority, const Image<ColorRgb> &image, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInput(int priority, const std::vector<ColorRgb>& ledColors, int timeout_ms, bool clearEffect);
 | 
			
		||||
	void setInputImage(int priority, const Image<ColorRgb>& image, int timeout_ms, bool clearEffect);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void setModuleParameters();
 | 
			
		||||
	bool setModuleParameters();
 | 
			
		||||
	void addImage();
 | 
			
		||||
 | 
			
		||||
	Hyperion *_hyperion;
 | 
			
		||||
	Hyperion* _hyperion;
 | 
			
		||||
 | 
			
		||||
	const int _priority;
 | 
			
		||||
 | 
			
		||||
@@ -95,12 +95,12 @@ private:
 | 
			
		||||
	/// Buffer for colorData
 | 
			
		||||
	QVector<ColorRgb> _colors;
 | 
			
		||||
 | 
			
		||||
	Logger *_log;
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	// Reflects whenever this effects should interrupt (timeout or external request)
 | 
			
		||||
	std::atomic<bool> _interupt {};
 | 
			
		||||
	std::atomic<bool> _interupt{};
 | 
			
		||||
 | 
			
		||||
	QSize           _imageSize;
 | 
			
		||||
	QImage          _image;
 | 
			
		||||
	QPainter       *_painter;
 | 
			
		||||
	QPainter*       _painter;
 | 
			
		||||
	QVector<QImage> _imageStack;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -8,47 +8,41 @@
 | 
			
		||||
 | 
			
		||||
class Effect;
 | 
			
		||||
 | 
			
		||||
class EffectModule: public QObject
 | 
			
		||||
class EffectModule : public QObject
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	// Python 3 module def
 | 
			
		||||
	static struct PyModuleDef moduleDef;
 | 
			
		||||
 | 
			
		||||
	// Init module
 | 
			
		||||
	static PyObject* PyInit_hyperion();
 | 
			
		||||
 | 
			
		||||
	// Register module once
 | 
			
		||||
	static void registerHyperionExtensionModule();
 | 
			
		||||
 | 
			
		||||
	// json 2 python
 | 
			
		||||
	static PyObject * json2python(const QJsonValue & jsonData);
 | 
			
		||||
	static PyObject* json2python(const QJsonValue& jsonData);
 | 
			
		||||
 | 
			
		||||
	// Wrapper methods for Python interpreter extra buildin methods
 | 
			
		||||
	static PyMethodDef effectMethods[];
 | 
			
		||||
	static PyObject* wrapSetColor              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapSetImage              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapGetImage              (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapAbort                 (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageShow             (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageLinearGradient   (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageConicalGradient  (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageRadialGradient   (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSolidFill        (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawLine         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPoint        (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawRect         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPolygon      (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageDrawPie          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSetPixel         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageGetPixel         (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageSave             (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageMinSize          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageWidth            (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageHeight           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCRotate          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCOffset          (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageCShear           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapImageResetT           (PyObject *self, PyObject *args);
 | 
			
		||||
	static PyObject* wrapSetColor(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapSetImage(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapGetImage(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapAbort(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageShow(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageLinearGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageConicalGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageRadialGradient(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSolidFill(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawLine(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPoint(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawRect(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPolygon(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageDrawPie(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSetPixel(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageGetPixel(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageSave(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageMinSize(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageWidth(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageHeight(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCRotate(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCOffset(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageCShear(PyObject* self, PyObject* args);
 | 
			
		||||
	static PyObject* wrapImageResetT(PyObject* self, PyObject* args);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#undef slots
 | 
			
		||||
#include <Python.h>
 | 
			
		||||
#define slots Q_SLOTS
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// @brief Handle the PythonInit, module registers and DeInit
 | 
			
		||||
///
 | 
			
		||||
@@ -10,4 +14,6 @@ private:
 | 
			
		||||
 | 
			
		||||
	PythonInit();
 | 
			
		||||
	~PythonInit();
 | 
			
		||||
 | 
			
		||||
	void handlePythonError(PyStatus status, PyConfig& config);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@
 | 
			
		||||
#include "Python.h"
 | 
			
		||||
#define slots
 | 
			
		||||
 | 
			
		||||
#include <python/PythonUtils.h>
 | 
			
		||||
 | 
			
		||||
class Logger;
 | 
			
		||||
 | 
			
		||||
class PythonProgram
 | 
			
		||||
@@ -17,9 +19,15 @@ public:
 | 
			
		||||
	PythonProgram(const QString & name, Logger * log);
 | 
			
		||||
	~PythonProgram();
 | 
			
		||||
 | 
			
		||||
	operator PyThreadState* ()
 | 
			
		||||
	{
 | 
			
		||||
		return _tstate;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void execute(const QByteArray &python_code);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	QString _name;
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	PyThreadState* _tstate;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
// python utils
 | 
			
		||||
#include <python/PythonProgram.h>
 | 
			
		||||
 | 
			
		||||
Effect::Effect(Hyperion *hyperion, int priority, int timeout, const QString &script, const QString &name, const QJsonObject &args, const QString &imageData)
 | 
			
		||||
Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString& script, const QString& name, const QJsonObject& args, const QString& imageData)
 | 
			
		||||
	: QThread()
 | 
			
		||||
	, _hyperion(hyperion)
 | 
			
		||||
	, _priority(priority)
 | 
			
		||||
@@ -26,7 +26,7 @@ Effect::Effect(Hyperion *hyperion, int priority, int timeout, const QString &scr
 | 
			
		||||
	, _endTime(-1)
 | 
			
		||||
	, _interupt(false)
 | 
			
		||||
	, _imageSize(hyperion->getLedGridSize())
 | 
			
		||||
	, _image(_imageSize,QImage::Format_ARGB32_Premultiplied)
 | 
			
		||||
	, _image(_imageSize, QImage::Format_ARGB32_Premultiplied)
 | 
			
		||||
{
 | 
			
		||||
	_colors.resize(_hyperion->getLedCount());
 | 
			
		||||
	_colors.fill(ColorRgb::BLACK);
 | 
			
		||||
@@ -61,41 +61,81 @@ int Effect::getRemaining() const
 | 
			
		||||
 | 
			
		||||
	if (timeout >= 0)
 | 
			
		||||
	{
 | 
			
		||||
		timeout = static_cast<int>( _endTime - QDateTime::currentMSecsSinceEpoch());
 | 
			
		||||
		timeout = static_cast<int>(_endTime - QDateTime::currentMSecsSinceEpoch());
 | 
			
		||||
	}
 | 
			
		||||
	return timeout;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Effect::setModuleParameters()
 | 
			
		||||
bool Effect::setModuleParameters()
 | 
			
		||||
{
 | 
			
		||||
	// import the buildtin Hyperion module
 | 
			
		||||
	PyObject * module = PyImport_ImportModule("hyperion");
 | 
			
		||||
	PyObject* module = PyImport_ImportModule("hyperion");
 | 
			
		||||
 | 
			
		||||
	// add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function
 | 
			
		||||
	PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr));
 | 
			
		||||
	if (module == nullptr) {
 | 
			
		||||
		PyErr_Print();  // Print error if module import fails
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add ledCount variable to the interpreter
 | 
			
		||||
	// Add a capsule containing 'this' to the module for callback access
 | 
			
		||||
	PyObject* capsule = PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr);
 | 
			
		||||
	if (capsule == nullptr || PyModule_AddObject(module, "__effectObj", capsule) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if adding capsule fails
 | 
			
		||||
		Py_XDECREF(module);  // Clean up if capsule addition fails
 | 
			
		||||
		Py_XDECREF(capsule);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add ledCount variable to the interpreter
 | 
			
		||||
	int ledCount = 0;
 | 
			
		||||
	QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, ledCount));
 | 
			
		||||
	PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", ledCount));
 | 
			
		||||
	PyObject* ledCountObj = Py_BuildValue("i", ledCount);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "ledCount", ledCountObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(ledCountObj);
 | 
			
		||||
 | 
			
		||||
	// add minimumWriteTime variable to the interpreter
 | 
			
		||||
	// Add minimumWriteTime variable to the interpreter
 | 
			
		||||
	int latchTime = 0;
 | 
			
		||||
	QMetaObject::invokeMethod(_hyperion, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, latchTime));
 | 
			
		||||
	PyObject_SetAttrString(module, "latchTime", Py_BuildValue("i", latchTime));
 | 
			
		||||
	PyObject* latchTimeObj = Py_BuildValue("i", latchTime);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "latchTime", latchTimeObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(latchTimeObj);
 | 
			
		||||
 | 
			
		||||
	// add a args variable to the interpreter
 | 
			
		||||
	PyObject_SetAttrString(module, "args", EffectModule::json2python(_args));
 | 
			
		||||
	// Add args variable to the interpreter
 | 
			
		||||
	PyObject* argsObj = EffectModule::json2python(_args);
 | 
			
		||||
	if (PyObject_SetAttrString(module, "args", argsObj) < 0) {
 | 
			
		||||
		PyErr_Print();  // Print error if setting attribute fails
 | 
			
		||||
	}
 | 
			
		||||
	Py_XDECREF(argsObj);
 | 
			
		||||
 | 
			
		||||
	// decref the module
 | 
			
		||||
	// Decrement module reference
 | 
			
		||||
	Py_XDECREF(module);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Effect::run()
 | 
			
		||||
{
 | 
			
		||||
	PythonProgram program(_name, _log);
 | 
			
		||||
 | 
			
		||||
	setModuleParameters();
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(program);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	if (!setModuleParameters())
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "Failed to set Module parameters. Effect will not be executed.");
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	// Set the end time if applicable
 | 
			
		||||
	if (_timeout > 0)
 | 
			
		||||
@@ -104,7 +144,7 @@ void Effect::run()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run the effect script
 | 
			
		||||
	QFile file (_script);
 | 
			
		||||
	QFile file(_script);
 | 
			
		||||
	if (file.open(QIODevice::ReadOnly))
 | 
			
		||||
	{
 | 
			
		||||
		program.execute(file.readAll());
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -18,7 +18,7 @@
 | 
			
		||||
#include <HyperionConfig.h>
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
	#include <stdexcept>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define STRINGIFY2(x) #x
 | 
			
		||||
@@ -44,14 +44,14 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
	status = PyConfig_SetString(&config, &config.program_name, programName);
 | 
			
		||||
	if (PyStatus_Exception(status)) {
 | 
			
		||||
		goto exception;
 | 
			
		||||
		handlePythonError(status, config);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
#else
 | 
			
		||||
	Py_SetProgramName(programName);
 | 
			
		||||
#endif
 | 
			
		||||
	{
 | 
			
		||||
		// set Python module path when exists
 | 
			
		||||
		// set Python module path when it exists
 | 
			
		||||
		QString py_path = QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + "." + STRINGIFY(PYTHON_VERSION_MINOR));
 | 
			
		||||
		QString py_file = QDir::cleanPath(qApp->applicationDirPath() + "/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + STRINGIFY(PYTHON_VERSION_MINOR) + ".zip");
 | 
			
		||||
		QString py_framework = QDir::cleanPath(qApp->applicationDirPath() + "/../Frameworks/Python.framework/Versions/Current/lib/python" + STRINGIFY(PYTHON_VERSION_MAJOR) + "." + STRINGIFY(PYTHON_VERSION_MINOR));
 | 
			
		||||
@@ -59,21 +59,23 @@ PythonInit::PythonInit()
 | 
			
		||||
		if (QFile(py_file).exists() || QDir(py_path).exists() || QDir(py_framework).exists())
 | 
			
		||||
		{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x030C0000)
 | 
			
		||||
            config.site_import = 0;
 | 
			
		||||
			config.site_import = 0;
 | 
			
		||||
#else
 | 
			
		||||
            Py_NoSiteFlag++;
 | 
			
		||||
			Py_NoSiteFlag++;
 | 
			
		||||
#endif
 | 
			
		||||
			if (QFile(py_file).exists()) // Windows
 | 
			
		||||
			{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(py_file));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(py_file.toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				Py_SetPythonHome(Py_DecodeLocale(py_file.toLatin1().data(), nullptr));
 | 
			
		||||
@@ -85,18 +87,21 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(QDir::cleanPath(qApp->applicationDirPath() + "/../")));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_path).absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_path + "/lib-dynload").absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				QStringList python_paths;
 | 
			
		||||
@@ -114,18 +119,21 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
				status = PyConfig_SetBytesString(&config, &config.home, QSTRING_CSTR(QDir::cleanPath(qApp->applicationDirPath() + "/../Frameworks/Python.framework/Versions/Current")));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				config.module_search_paths_set = 1;
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_framework).absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				status = PyWideStringList_Append(&config.module_search_paths, const_cast<wchar_t*>(QDir(py_framework + "/lib-dynload").absolutePath().toStdWString().c_str()));
 | 
			
		||||
				if (PyStatus_Exception(status)) {
 | 
			
		||||
					goto exception;
 | 
			
		||||
					handlePythonError(status, config);
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
#else
 | 
			
		||||
				QStringList python_paths;
 | 
			
		||||
@@ -146,7 +154,8 @@ PythonInit::PythonInit()
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
	status = Py_InitializeFromConfig(&config);
 | 
			
		||||
	if (PyStatus_Exception(status)) {
 | 
			
		||||
		goto exception;
 | 
			
		||||
		handlePythonError(status, config);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	PyConfig_Clear(&config);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -154,7 +163,8 @@ PythonInit::PythonInit()
 | 
			
		||||
	// init Python
 | 
			
		||||
	Debug(Logger::getInstance("DAEMON"), "Initializing Python interpreter");
 | 
			
		||||
	Py_InitializeEx(0);
 | 
			
		||||
	if ( !Py_IsInitialized() )
 | 
			
		||||
 | 
			
		||||
	if (!Py_IsInitialized())
 | 
			
		||||
	{
 | 
			
		||||
		throw std::runtime_error("Initializing Python failed!");
 | 
			
		||||
	}
 | 
			
		||||
@@ -165,20 +175,25 @@ PythonInit::PythonInit()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	mainThreadState = PyEval_SaveThread();
 | 
			
		||||
	return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03080000)
 | 
			
		||||
exception:
 | 
			
		||||
// Error handling function to replace goto exception
 | 
			
		||||
void PythonInit::handlePythonError(PyStatus status, PyConfig& config)
 | 
			
		||||
{
 | 
			
		||||
	Error(Logger::getInstance("DAEMON"), "Initializing Python config failed with error [%s]", status.err_msg);
 | 
			
		||||
	PyConfig_Clear(&config);
 | 
			
		||||
 | 
			
		||||
	throw std::runtime_error("Initializing Python failed!");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PythonInit::~PythonInit()
 | 
			
		||||
{
 | 
			
		||||
	Debug(Logger::getInstance("DAEMON"), "Cleaning up Python interpreter");
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyEval_RestoreThread(mainThreadState);
 | 
			
		||||
	Py_Finalize();
 | 
			
		||||
#else
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	int rc = Py_FinalizeEx();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,173 +1,184 @@
 | 
			
		||||
#include <python/PythonProgram.h>
 | 
			
		||||
#include <python/PythonUtils.h>
 | 
			
		||||
 | 
			
		||||
#include <utils/Logger.h>
 | 
			
		||||
 | 
			
		||||
#include <QThread>
 | 
			
		||||
 | 
			
		||||
PyThreadState* mainThreadState;
 | 
			
		||||
 | 
			
		||||
PythonProgram::PythonProgram(const QString & name, Logger * log) :
 | 
			
		||||
	_name(name), _log(log), _tstate(nullptr)
 | 
			
		||||
PythonProgram::PythonProgram(const QString& name, Logger* log) :
 | 
			
		||||
	_name(name)
 | 
			
		||||
	, _log(log)
 | 
			
		||||
	, _tstate(nullptr)
 | 
			
		||||
{
 | 
			
		||||
	// we probably need to wait until mainThreadState is available
 | 
			
		||||
	while(mainThreadState == nullptr){};
 | 
			
		||||
	QThread::msleep(10);
 | 
			
		||||
	while (mainThreadState == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		QThread::msleep(10);  // Wait with delay to avoid busy waiting
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a new subinterpreter for this thread
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	// get global lock
 | 
			
		||||
	PyEval_RestoreThread(mainThreadState);
 | 
			
		||||
 | 
			
		||||
	// Initialize a new thread state
 | 
			
		||||
	_tstate = Py_NewInterpreter();
 | 
			
		||||
	if(_tstate == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03020000)
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
		PyEval_SaveThread();
 | 
			
		||||
#else
 | 
			
		||||
		PyEval_ReleaseLock();
 | 
			
		||||
	PyThreadState* prev = PyThreadState_Swap(NULL);
 | 
			
		||||
 | 
			
		||||
	// Create a new interpreter configuration object
 | 
			
		||||
	PyInterpreterConfig config{};
 | 
			
		||||
 | 
			
		||||
	// Set configuration options
 | 
			
		||||
	config.use_main_obmalloc = 0;
 | 
			
		||||
	config.allow_fork = 0;
 | 
			
		||||
	config.allow_exec = 0;
 | 
			
		||||
	config.allow_threads = 1;
 | 
			
		||||
	config.allow_daemon_threads = 0;
 | 
			
		||||
	config.check_multi_interp_extensions = 1;
 | 
			
		||||
	config.gil = PyInterpreterConfig_OWN_GIL;
 | 
			
		||||
	Py_NewInterpreterFromConfig(&_tstate, &config);
 | 
			
		||||
#endif
 | 
			
		||||
		Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name));
 | 
			
		||||
 | 
			
		||||
	if (_tstate == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyEval_SaveThread();
 | 
			
		||||
#endif
 | 
			
		||||
		Error(_log, "Failed to get thread state for %s", QSTRING_CSTR(_name));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(_tstate);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PythonProgram::~PythonProgram()
 | 
			
		||||
{
 | 
			
		||||
	if (!_tstate)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	// stop sub threads if needed
 | 
			
		||||
	for (PyThreadState* s = PyInterpreterState_ThreadHead(_tstate->interp), *old = nullptr; s;)
 | 
			
		||||
	{
 | 
			
		||||
		if (s == _tstate)
 | 
			
		||||
		{
 | 
			
		||||
			s = s->next;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (old != s)
 | 
			
		||||
		{
 | 
			
		||||
			Debug(_log,"ID %s: Waiting on thread %u", QSTRING_CSTR(_name), s->thread_id);
 | 
			
		||||
			old = s;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
		QThread::msleep(100);
 | 
			
		||||
		Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
		s = PyInterpreterState_ThreadHead(_tstate->interp);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(_tstate);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	// Clean up the thread state
 | 
			
		||||
	Py_EndInterpreter(_tstate);
 | 
			
		||||
#if (PY_VERSION_HEX >= 0x03020000)
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
	PyEval_SaveThread();
 | 
			
		||||
#else
 | 
			
		||||
	PyEval_ReleaseLock();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PythonProgram::execute(const QByteArray & python_code)
 | 
			
		||||
void PythonProgram::execute(const QByteArray& python_code)
 | 
			
		||||
{
 | 
			
		||||
	if (!_tstate)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PyObject *main_module = PyImport_ImportModule("__main__"); // New Reference
 | 
			
		||||
	PyObject *main_dict = PyModule_GetDict(main_module); // Borrowed reference
 | 
			
		||||
	Py_INCREF(main_dict); // Incref "main_dict" to use it in PyRun_String(), because PyModule_GetDict() has decref "main_dict"
 | 
			
		||||
	Py_DECREF(main_module); // // release "main_module" when done
 | 
			
		||||
	PyObject *result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict); // New Reference
 | 
			
		||||
	PyThreadState* prev_thread_state = PyThreadState_Swap(_tstate);
 | 
			
		||||
 | 
			
		||||
	PyObject* main_module = PyImport_ImportModule("__main__");
 | 
			
		||||
	if (!main_module)
 | 
			
		||||
	{
 | 
			
		||||
		// Restore the previous thread state
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
		PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#else
 | 
			
		||||
		PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PyObject* main_dict = PyModule_GetDict(main_module);  // Borrowed reference to globals
 | 
			
		||||
	PyObject* result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict);
 | 
			
		||||
	if (!result)
 | 
			
		||||
	{
 | 
			
		||||
		if (PyErr_Occurred()) // Nothing needs to be done for a borrowed reference
 | 
			
		||||
		if (PyErr_Occurred())
 | 
			
		||||
		{
 | 
			
		||||
			Error(_log,"###### PYTHON EXCEPTION ######");
 | 
			
		||||
			Error(_log,"## In effect '%s'", QSTRING_CSTR(_name));
 | 
			
		||||
			/* Objects all initialized to NULL for Py_XDECREF */
 | 
			
		||||
			PyObject *errorType = NULL, *errorValue = NULL, *errorTraceback = NULL;
 | 
			
		||||
			Error(_log, "###### PYTHON EXCEPTION ######");
 | 
			
		||||
			Error(_log, "## In effect '%s'", QSTRING_CSTR(_name));
 | 
			
		||||
 | 
			
		||||
			PyErr_Fetch(&errorType, &errorValue, &errorTraceback); // New Reference or NULL
 | 
			
		||||
			PyObject* errorType = NULL, * errorValue = NULL, * errorTraceback = NULL;
 | 
			
		||||
 | 
			
		||||
			PyErr_Fetch(&errorType, &errorValue, &errorTraceback);
 | 
			
		||||
			PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback);
 | 
			
		||||
 | 
			
		||||
			// Extract exception message from "errorValue"
 | 
			
		||||
			if(errorValue)
 | 
			
		||||
			if (errorValue)
 | 
			
		||||
			{
 | 
			
		||||
				QString message;
 | 
			
		||||
				if(PyObject_HasAttrString(errorValue, "__class__"))
 | 
			
		||||
				if (PyObject_HasAttrString(errorValue, "__class__"))
 | 
			
		||||
				{
 | 
			
		||||
					PyObject *classPtr = PyObject_GetAttrString(errorValue, "__class__"); // New Reference
 | 
			
		||||
					PyObject *class_name = NULL; /* Object "class_name" initialized to NULL for Py_XDECREF */
 | 
			
		||||
					class_name = PyObject_GetAttrString(classPtr, "__name__"); // New Reference or NULL
 | 
			
		||||
					PyObject* classPtr = PyObject_GetAttrString(errorValue, "__class__");
 | 
			
		||||
					PyObject* class_name = classPtr ? PyObject_GetAttrString(classPtr, "__name__") : NULL;
 | 
			
		||||
 | 
			
		||||
					if(class_name && PyUnicode_Check(class_name))
 | 
			
		||||
					if (class_name && PyUnicode_Check(class_name))
 | 
			
		||||
						message.append(PyUnicode_AsUTF8(class_name));
 | 
			
		||||
 | 
			
		||||
					Py_DECREF(classPtr); // release "classPtr" when done
 | 
			
		||||
					Py_XDECREF(class_name); // Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
					Py_XDECREF(class_name);
 | 
			
		||||
					Py_DECREF(classPtr);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Object "class_name" initialized to NULL for Py_XDECREF
 | 
			
		||||
				PyObject *valueString = NULL;
 | 
			
		||||
				valueString = PyObject_Str(errorValue); // New Reference or NULL
 | 
			
		||||
				PyObject* valueString = PyObject_Str(errorValue);
 | 
			
		||||
 | 
			
		||||
				if(valueString && PyUnicode_Check(valueString))
 | 
			
		||||
				if (valueString && PyUnicode_Check(valueString))
 | 
			
		||||
				{
 | 
			
		||||
					if(!message.isEmpty())
 | 
			
		||||
					if (!message.isEmpty())
 | 
			
		||||
						message.append(": ");
 | 
			
		||||
 | 
			
		||||
					message.append(PyUnicode_AsUTF8(valueString));
 | 
			
		||||
				}
 | 
			
		||||
				Py_XDECREF(valueString); // Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
				Py_XDECREF(valueString);
 | 
			
		||||
 | 
			
		||||
				Error(_log, "## %s", QSTRING_CSTR(message));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Extract exception message from "errorTraceback"
 | 
			
		||||
			if(errorTraceback)
 | 
			
		||||
			if (errorTraceback)
 | 
			
		||||
			{
 | 
			
		||||
				// Object "tracebackList" initialized to NULL for Py_XDECREF
 | 
			
		||||
				PyObject *tracebackModule = NULL, *methodName = NULL, *tracebackList = NULL;
 | 
			
		||||
				QString tracebackMsg;
 | 
			
		||||
				PyObject* tracebackModule = PyImport_ImportModule("traceback");
 | 
			
		||||
				PyObject* methodName = PyUnicode_FromString("format_exception");
 | 
			
		||||
				PyObject* tracebackList = tracebackModule && methodName
 | 
			
		||||
					? PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL)
 | 
			
		||||
					: NULL;
 | 
			
		||||
 | 
			
		||||
				tracebackModule = PyImport_ImportModule("traceback"); // New Reference or NULL
 | 
			
		||||
				methodName = PyUnicode_FromString("format_exception"); // New Reference or NULL
 | 
			
		||||
				tracebackList = PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL); // New Reference or NULL
 | 
			
		||||
 | 
			
		||||
				if(tracebackList)
 | 
			
		||||
				if (tracebackList)
 | 
			
		||||
				{
 | 
			
		||||
					PyObject* iterator = PyObject_GetIter(tracebackList); // New Reference
 | 
			
		||||
 | 
			
		||||
					PyObject* iterator = PyObject_GetIter(tracebackList);
 | 
			
		||||
					PyObject* item;
 | 
			
		||||
					while( (item = PyIter_Next(iterator)) ) // New Reference
 | 
			
		||||
					while ((item = PyIter_Next(iterator)))
 | 
			
		||||
					{
 | 
			
		||||
						Error(_log, "## %s",QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed()));
 | 
			
		||||
						Py_DECREF(item); // release "item" when done
 | 
			
		||||
						Error(_log, "## %s", QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed()));
 | 
			
		||||
						Py_DECREF(item);
 | 
			
		||||
					}
 | 
			
		||||
					Py_DECREF(iterator);  // release "iterator" when done
 | 
			
		||||
					Py_DECREF(iterator);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Use Py_XDECREF() to ignore NULL references
 | 
			
		||||
				Py_XDECREF(tracebackModule);
 | 
			
		||||
				Py_XDECREF(methodName);
 | 
			
		||||
				Py_XDECREF(tracebackList);
 | 
			
		||||
 | 
			
		||||
				// Give the exception back to python and print it to stderr in case anyone else wants it.
 | 
			
		||||
				Py_XINCREF(errorType);
 | 
			
		||||
				Py_XINCREF(errorValue);
 | 
			
		||||
				Py_XINCREF(errorTraceback);
 | 
			
		||||
 | 
			
		||||
				PyErr_Restore(errorType, errorValue, errorTraceback);
 | 
			
		||||
				//PyErr_PrintEx(0); // Remove this line to switch off stderr output
 | 
			
		||||
			}
 | 
			
		||||
			Error(_log,"###### EXCEPTION END ######");
 | 
			
		||||
			Error(_log, "###### EXCEPTION END ######");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Py_DECREF(result);  // release "result" when done
 | 
			
		||||
		Py_DECREF(result);  // Release result when done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Py_DECREF(main_dict);  // release "main_dict" when done
 | 
			
		||||
	Py_DECREF(main_module);
 | 
			
		||||
 | 
			
		||||
	// Restore the previous thread state
 | 
			
		||||
#if (PY_VERSION_HEX < 0x030C0000)
 | 
			
		||||
	PyThreadState_Swap(mainThreadState);
 | 
			
		||||
#else
 | 
			
		||||
	PyThreadState_Swap(prev_thread_state);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user