# HG changeset patch # User Sebastien Jodogne # Date 1628783537 -7200 # Node ID 0685515201232264aeb825a77fad1ae5c4a53bc0 # Parent 2b9d82366e3aefc935e547d687dc708eb166f2f0 New Python function: "orthanc.RegisterStorageArea()" diff -r 2b9d82366e3a -r 068551520123 CMakeLists.txt --- a/CMakeLists.txt Thu Aug 12 16:05:49 2021 +0200 +++ b/CMakeLists.txt Thu Aug 12 17:52:17 2021 +0200 @@ -166,6 +166,7 @@ add_library(OrthancPython SHARED Sources/Autogenerated/sdk.cpp Sources/DicomScpCallbacks.cpp + Sources/ICallbackRegistration.cpp Sources/IncomingHttpRequestFilter.cpp Sources/OnChangeCallback.cpp Sources/OnStoredInstanceCallback.cpp @@ -176,7 +177,7 @@ Sources/PythonObject.cpp Sources/PythonString.cpp Sources/RestCallbacks.cpp - Sources/ICallbackRegistration.cpp + Sources/StorageArea.cpp # Third-party sources ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp diff -r 2b9d82366e3a -r 068551520123 NEWS --- a/NEWS Thu Aug 12 16:05:49 2021 +0200 +++ b/NEWS Thu Aug 12 17:52:17 2021 +0200 @@ -1,6 +1,7 @@ Pending changes in the mainline =============================== +* New Python function: "orthanc.RegisterStorageArea()" * Custom exception "orthanc.OrthancException" is raised instead of "ValueError" diff -r 2b9d82366e3a -r 068551520123 Sources/Plugin.cpp --- a/Sources/Plugin.cpp Thu Aug 12 16:05:49 2021 +0200 +++ b/Sources/Plugin.cpp Thu Aug 12 17:52:17 2021 +0200 @@ -28,6 +28,7 @@ #include "IncomingHttpRequestFilter.h" #include "OnChangeCallback.h" #include "OnStoredInstanceCallback.h" +#include "StorageArea.h" #include "RestCallbacks.h" #include "PythonModule.h" @@ -363,6 +364,16 @@ /** + * New in release 3.3 + **/ + + { + PyMethodDef f = { "RegisterStorageArea", RegisterStorageArea, METH_VARARGS, "" }; + functions.push_back(f); + } + + + /** * Append all the global functions that were automatically generated **/ diff -r 2b9d82366e3a -r 068551520123 Sources/StorageArea.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/StorageArea.cpp Thu Aug 12 17:52:17 2021 +0200 @@ -0,0 +1,243 @@ +/** + * Python plugin for Orthanc + * Copyright (C) 2020-2021 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "StorageArea.h" + +#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" +#include "PythonString.h" + + +static PyObject* createCallback_ = NULL; +static PyObject* readCallback_ = NULL; +static PyObject* removeCallback_ = NULL; + + +static OrthancPluginErrorCode RunCallback(PythonLock& lock, + PyObject* callback, + PythonObject& args, + const std::string& name) +{ + PythonObject result(lock, PyObject_CallObject(callback, args.GetPyObject())); + + std::string traceback; + if (lock.HasErrorOccurred(traceback)) + { + OrthancPlugins::LogError("Error in the Python " + name + " callback, traceback:\n" + traceback); + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } +} + + +static OrthancPluginErrorCode StorageCreate(const char *uuid, + const void *content, + int64_t size, + OrthancPluginContentType type) +{ + try + { + if (createCallback_ == NULL) + { + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError); + } + + PythonLock lock; + + PythonObject args(lock, PyTuple_New(3)); + + PythonString str(lock, uuid); + PyTuple_SetItem(args.GetPyObject(), 0, str.Release()); + PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type)); + PyTuple_SetItem(args.GetPyObject(), 2, PyBytes_FromStringAndSize(reinterpret_cast(content), size)); + + return RunCallback(lock, createCallback_, args, "StorageCreate"); + } + catch (OrthancPlugins::PluginException& e) + { + return e.GetErrorCode(); + } +} + + +static OrthancPluginErrorCode StorageRead(void **content, + int64_t *size, + const char *uuid, + OrthancPluginContentType type) +{ + try + { + if (readCallback_ == NULL) + { + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError); + } + + PythonLock lock; + + PythonObject args(lock, PyTuple_New(2)); + + PythonString str(lock, uuid); + PyTuple_SetItem(args.GetPyObject(), 0, str.Release()); + PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type)); + + PythonObject result(lock, PyObject_CallObject(readCallback_, args.GetPyObject())); + + std::string traceback; + if (lock.HasErrorOccurred(traceback)) + { + OrthancPlugins::LogError("Error in the Python StorageRead callback, traceback:\n" + traceback); + return OrthancPluginErrorCode_Plugin; + } + else if (!PyBytes_Check(result.GetPyObject())) + { + OrthancPlugins::LogError("The Python StorageRead callback has not returned a byte array as expected"); + return OrthancPluginErrorCode_Plugin; + } + else + { + char* pythonBuffer = NULL; + Py_ssize_t pythonSize = 0; + if (PyBytes_AsStringAndSize(result.GetPyObject(), &pythonBuffer, &pythonSize) == 1) + { + OrthancPlugins::LogError("Cannot access the byte buffer returned by the Python StorageRead callback"); + return OrthancPluginErrorCode_Plugin; + } + else + { + if (pythonSize == 0) + { + *content = NULL; + *size = 0; + } + else + { + *content = malloc(pythonSize); + *size = pythonSize; + if (*content == NULL) + { + return OrthancPluginErrorCode_NotEnoughMemory; + } + + memcpy(*content, pythonBuffer, pythonSize); + } + + return OrthancPluginErrorCode_Success; + } + } + } + catch (OrthancPlugins::PluginException& e) + { + return e.GetErrorCode(); + } +} + + +static OrthancPluginErrorCode StorageRemove(const char *uuid, + OrthancPluginContentType type) +{ + try + { + if (removeCallback_ == NULL) + { + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError); + } + + PythonLock lock; + + PythonObject args(lock, PyTuple_New(2)); + + PythonString str(lock, uuid); + PyTuple_SetItem(args.GetPyObject(), 0, str.Release()); + PyTuple_SetItem(args.GetPyObject(), 1, PyLong_FromLong(type)); + + return RunCallback(lock, removeCallback_, args, "StorageRemove"); + } + catch (OrthancPlugins::PluginException& e) + { + return e.GetErrorCode(); + } +} + + +PyObject* RegisterStorageArea(PyObject* module, PyObject* args) +{ + // The GIL is locked at this point (no need to create "PythonLock") + + PyObject* a = NULL; + PyObject* b = NULL; + PyObject* c = NULL; + + if (!PyArg_ParseTuple(args, "OOO", &a, &b, &c) || + a == NULL || + b == NULL || + c == NULL) + { + PyErr_SetString(PyExc_ValueError, "Expected three callback functions to register a custom storage area"); + return NULL; + } + else if (createCallback_ != NULL || + readCallback_ != NULL || + removeCallback_ != NULL) + { + PyErr_SetString(PyExc_RuntimeError, "Cannot register twice a custom storage area"); + return NULL; + } + else + { + OrthancPlugins::LogInfo("Registering a custom storage area in Python"); + + OrthancPluginRegisterStorageArea(OrthancPlugins::GetGlobalContext(), + StorageCreate, StorageRead, StorageRemove); + + createCallback_ = a; + Py_XINCREF(createCallback_); + + readCallback_ = b; + Py_XINCREF(readCallback_); + + removeCallback_ = c; + Py_XINCREF(removeCallback_); + + Py_INCREF(Py_None); + return Py_None; + } +} + + +void FinalizeStorageArea() +{ + PythonLock lock; + + if (createCallback_ != NULL) + { + Py_XDECREF(createCallback_); + } + + if (readCallback_ != NULL) + { + Py_XDECREF(readCallback_); + } + + if (removeCallback_ != NULL) + { + Py_XDECREF(removeCallback_); + } +} diff -r 2b9d82366e3a -r 068551520123 Sources/StorageArea.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/StorageArea.h Thu Aug 12 17:52:17 2021 +0200 @@ -0,0 +1,26 @@ +/** + * Python plugin for Orthanc + * Copyright (C) 2020-2021 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "PythonHeaderWrapper.h" + +PyObject* RegisterStorageArea(PyObject* module, PyObject* args); + +void FinalizeStorageArea();