Embed Jupyter using Python/C API

This commit is contained in:
Florian Märkl 2018-02-09 16:48:02 +01:00 committed by xarkes
parent cea55dfdb1
commit 3164b5c118
5 changed files with 106 additions and 61 deletions

3
.gitignore vendored
View File

@ -79,3 +79,6 @@ ninja.exe
#Mesonbuild
src/subprojects/
# Python
__pycache__

View File

@ -35,6 +35,10 @@ include_directories(${RADARE2_INCLUDE_DIRS})
link_directories(${RADARE2_LIBRARY_DIRS})
find_package(PythonLibs 3 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
add_definitions(-Wall -Wextra)
@ -67,4 +71,5 @@ add_definitions("-DAPP_VERSION=\"${CUTTER_VERSION_FULL}\"")
add_executable(cutter ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES})
qt5_use_modules(cutter Core Widgets Gui Svg WebEngineWidgets)
target_link_libraries(cutter ${RADARE2_LIBRARIES})
target_link_libraries(cutter ${PYTHON_LIBRARIES})

View File

@ -0,0 +1,58 @@
import sys
import threading
import time
from notebook.notebookapp import *
class CutterNotebookApp(NotebookApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.thread = None
def start(self):
""" see NotebookApp.start() """
super(NotebookApp, self).start()
self.write_server_info_file()
class NotebookThread(threading.Thread):
def __init__(self, notebook_app):
super().__init__()
self.notebook_app = notebook_app
def run(self):
self.notebook_app.run()
self.thread = NotebookThread(self)
self.thread.start()
def run(self):
self.io_loop = ioloop.IOLoop.current()
if sys.platform.startswith('win'):
# add no-op to wake every 5s
# to handle signals that may be ignored by the inner loop
pc = ioloop.PeriodicCallback(lambda: None, 5000)
pc.start()
try:
self.io_loop.start()
except KeyboardInterrupt:
self.log.info(_("Interrupted..."))
finally:
self.remove_server_info_file()
self.cleanup_kernels()
def stop(self):
super().stop()
self.thread.join()
@property
def url_with_token(self):
return url_concat(self.connection_url, {'token': self.token})
def start_jupyter():
app = CutterNotebookApp()
app.initialize()
app.start()
return app

View File

@ -1,4 +1,6 @@
#include <Python.h>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
@ -9,59 +11,42 @@
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
{
process = nullptr;
}
JupyterConnection::~JupyterConnection()
{
cmdServer->stop();
process->terminate();
if (cutterNotebookAppInstance)
{
PyEval_RestoreThread(pyThreadState);
auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop");
PyObject_CallObject(stopFunc, nullptr);
Py_DECREF(cutterNotebookAppInstance);
Py_FinalizeEx();
}
const char *jupyterPyCode = "import sys\n"
"from notebook.notebookapp import *\n"
"\n"
"\n"
"class CutterNotebookApp(NotebookApp):\n"
" def start(self):\n"
" \"\"\" see NotebookApp.start() \"\"\"\n"
"\n"
" super(NotebookApp, self).start()\n"
"\n"
" self.write_server_info_file()\n"
"\n"
" if self.token and self._token_generated:\n"
" url = url_concat(self.connection_url, {'token': self.token})\n"
" sys.stdout.write(url + \"\\n\")\n"
" sys.stdout.flush()\n"
"\n"
" self.io_loop = ioloop.IOLoop.current()\n"
" if sys.platform.startswith('win'):\n"
" # add no-op to wake every 5s\n"
" # to handle signals that may be ignored by the inner loop\n"
" pc = ioloop.PeriodicCallback(lambda: None, 5000)\n"
" pc.start()\n"
" try:\n"
" self.io_loop.start()\n"
" except KeyboardInterrupt:\n"
" self.log.info(_(\"Interrupted...\"))\n"
" finally:\n"
" self.remove_server_info_file()\n"
" self.cleanup_kernels()\n"
"\n"
"\n"
"if __name__ == \"__main__\":\n"
" CutterNotebookApp.launch_instance()";
cmdServer->stop();
}
void JupyterConnection::start()
{
process = new QProcess(this);
connect(process, &QProcess::readyReadStandardError, this, &JupyterConnection::readStandardError);
connect(process, &QProcess::readyReadStandardOutput, this, &JupyterConnection::readStandardOutput);
connect(process, &QProcess::errorOccurred, this, [](QProcess::ProcessError error){ qWarning() << "Jupyter error occurred:" << error; });
process->start("python3", {"-c", jupyterPyCode});
Py_Initialize();
PyEval_InitThreads();
QThread *cmdServerThread = new QThread(this);
cutterJupyterModule = PyImport_ImportModule("cutter_jupyter");
auto startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter");
cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr);
auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token");
auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken);
emit urlReceived(QString::fromUtf8(PyBytes_AsString(asciiBytes)));
Py_DECREF(asciiBytes);
Py_DECREF(urlWithToken);
pyThreadState = PyEval_SaveThread();
cmdServerThread = new QThread(this);
cmdServer = new CommandServer();
cmdServer->moveToThread(cmdServerThread);
connect(cmdServer, &CommandServer::error, this, [](QString err){ qWarning() << "CmdServer error:" << err; });
@ -71,16 +56,3 @@ void JupyterConnection::start()
connect(cmdServerThread, SIGNAL (finished()), cmdServerThread, SLOT (deleteLater()));
cmdServerThread->start();
}
void JupyterConnection::readStandardError()
{
auto data = process->readAllStandardError();
printf("Jupyter stderr: %s\n", data.constData());
}
void JupyterConnection::readStandardOutput()
{
auto data = process->readAllStandardOutput();
printf("Jupyter stdout: %s\n", data.constData());
emit urlReceived(data);
}

View File

@ -4,6 +4,12 @@
#include <QProcess>
#include "CommandServer.h"
struct _object;
typedef _object PyObject;
struct _ts;
typedef _ts PyThreadState;
class JupyterConnection : public QObject
{
Q_OBJECT
@ -18,12 +24,13 @@ signals:
void urlReceived(const QString &url);
private:
QProcess *process;
CommandServer *cmdServer;
QThread *cmdServerThread;
private slots:
void readStandardError();
void readStandardOutput();
PyObject *cutterJupyterModule = nullptr;
PyObject *cutterNotebookAppInstance = nullptr;
PyThreadState *pyThreadState = nullptr;
};
#endif //JUPYTERCONNECTION_H