Files
GoldenCheetah/src/Python/PythonEmbed.cpp
Mark Liversedge 385b56ee47 Python GC.activity()
.. very basic API as we develop out, it just returns a dict
   for now. Need to expand to enable returning a list if
   compare mode is active (like R).

.. implemented in a new source file Resources/python/library.py
   which is loaded directly after the interpreter is loaded at
   startup.

.. also updated src.pro to add library.py and goldencheetah.sip
   to OTHER_FILES so they can be edited easily in QtCreator.
2017-12-09 19:52:59 +00:00

168 lines
5.4 KiB
C++

/*
* Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com)
*
* Additionally, for the original source used as a basis for this (RInside.cpp)
* Released under the same GNU public license.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PythonEmbed.h"
#include "Settings.h"
#include <stdexcept>
#include <QMessageBox>
// Python header - using a C preprocessor hack
// GC_PYTHON_INCDIR is from gcconfig.pri, typically python3.6
#ifdef slots // clashes with python headers
#undef slots
#endif
#include <Python.h>
// global instance of embedded python
PythonEmbed *python;
PyThreadState *mainThreadState;
// SIP module with GoldenCheetah Bindings
extern "C" {
extern PyObject *PyInit_goldencheetah(void);
};
PythonEmbed::~PythonEmbed()
{
}
PythonEmbed::PythonEmbed(const bool verbose, const bool interactive) : verbose(verbose), interactive(interactive)
{
loaded = false;
threadid=-1;
name = QString("GoldenCheetah");
// tell python our program name
Py_SetProgramName((wchar_t*) name.toStdString().c_str());
// our own module
PyImport_AppendInittab("goldencheetah", PyInit_goldencheetah);
// need to load the interpreter etc
Py_InitializeEx(0);
// set the module path in the same way the interpreter would
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
// get version
version = QString(Py_GetVersion());
version.replace("\n", " ");
fprintf(stderr, "Python loaded [%s]\n", version.toStdString().c_str());
// our base code - traps stdout and loads goldencheetan module
// mapping all the bindings to a GC object.
std::string stdOutErr = ("import sys\n"
"class CatchOutErr:\n"
" def __init__(self):\n"
" self.value = ''\n"
" def write(self, txt):\n"
" self.value += txt\n"
"catchOutErr = CatchOutErr()\n"
"sys.stdout = catchOutErr\n"
"sys.stderr = catchOutErr\n"
"import goldencheetah\n"
"GC=goldencheetah.Bindings()\n");
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
// now load the library
QFile lib(":python/library.py");
if (lib.open(QFile::ReadOnly)) {
QString libstring=lib.readAll();
lib.close();
PyRun_SimpleString(libstring.toLatin1().constData());
}
// setup trapping of output
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
catcher = static_cast<void*>(PyObject_GetAttrString(pModule,"catchOutErr"));
clear = static_cast<void*>(PyObject_GetAttrString(static_cast<PyObject*>(catcher), "__init__"));
PyErr_Print(); //make python print any errors
PyErr_Clear(); //and clear them !
// prepare for threaded processing
PyEval_InitThreads();
mainThreadState = PyEval_SaveThread();
loaded = true;
}
// run on called thread
void PythonEmbed::runline(Context *context, QString line)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
// Get current thread ID via Python thread functions
PyObject* thread = PyImport_ImportModule("_thread");
PyObject* get_ident = PyObject_GetAttrString(thread, "get_ident");
PyObject* ident = PyObject_CallObject(get_ident, 0);
Py_DECREF(get_ident);
threadid = PyLong_AsLong(ident);
Py_DECREF(ident);
// add to the thread/context map
contexts.insert(threadid, context);
// run and generate errors etc
messages.clear();
PyRun_SimpleString(line.toStdString().c_str());
PyErr_Print();
PyErr_Clear(); //and clear them !
// capture results
PyObject *output = PyObject_GetAttrString(static_cast<PyObject*>(catcher),"value"); //get the stdout and stderr from our catchOutErr object
if (output) {
// allocated as unicodeA
Py_ssize_t size;
wchar_t *string = PyUnicode_AsWideCharString(output, &size);
if (string) {
if (size) messages = QString::fromWCharArray(string).split("\n");
PyMem_Free(string);
if (messages.count()) messages << "\n"; // always add a newline after anything
}
// clear results
PyObject_CallFunction(static_cast<PyObject*>(clear), NULL);
}
PyGILState_Release(gstate);
threadid=-1;
}
void
PythonEmbed::cancel()
{
if (chart!=NULL && threadid != -1) {
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
// raise an exception to cancel the execution
PyThreadState_SetAsyncExc(threadid, PyExc_KeyboardInterrupt);
PyGILState_Release(gstate);
}
}