From 3164b5c118747b57e46b7c009bd79213a96ff334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 9 Feb 2018 16:48:02 +0100 Subject: [PATCH] Embed Jupyter using Python/C API --- .gitignore | 3 ++ src/CMakeLists.txt | 5 ++ src/python/cutter_jupyter.py | 58 ++++++++++++++++++++++ src/utils/JupyterConnection.cpp | 86 +++++++++++---------------------- src/utils/JupyterConnection.h | 15 ++++-- 5 files changed, 106 insertions(+), 61 deletions(-) create mode 100644 src/python/cutter_jupyter.py diff --git a/.gitignore b/.gitignore index d1eea7aa..5265e4d3 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,6 @@ ninja.exe #Mesonbuild src/subprojects/ + +# Python +__pycache__ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11e8abd8..8dfbc646 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/python/cutter_jupyter.py b/src/python/cutter_jupyter.py new file mode 100644 index 00000000..535f9798 --- /dev/null +++ b/src/python/cutter_jupyter.py @@ -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 diff --git a/src/utils/JupyterConnection.cpp b/src/utils/JupyterConnection.cpp index 08266e61..fe250809 100644 --- a/src/utils/JupyterConnection.cpp +++ b/src/utils/JupyterConnection.cpp @@ -1,4 +1,6 @@ +#include + #include #include #include @@ -9,59 +11,42 @@ JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent) { - process = nullptr; } JupyterConnection::~JupyterConnection() { - cmdServer->stop(); - process->terminate(); -} + if (cutterNotebookAppInstance) + { + PyEval_RestoreThread(pyThreadState); -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()"; + auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop"); + PyObject_CallObject(stopFunc, nullptr); + Py_DECREF(cutterNotebookAppInstance); + + Py_FinalizeEx(); + } + + 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; }); @@ -70,17 +55,4 @@ void JupyterConnection::start() connect(cmdServer, SIGNAL (finished()), cmdServer, SLOT (deleteLater())); 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); } \ No newline at end of file diff --git a/src/utils/JupyterConnection.h b/src/utils/JupyterConnection.h index 04a6bf2d..021ae1cd 100644 --- a/src/utils/JupyterConnection.h +++ b/src/utils/JupyterConnection.h @@ -4,6 +4,12 @@ #include #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