changeset 1066:bb82e5e818e9

OnStoredInstance callback in plugins
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 25 Jul 2014 18:39:02 +0200
parents 921532f67770
children ace99e272203
files OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/main.cpp Plugins/Engine/PluginsHttpHandler.cpp Plugins/Engine/PluginsHttpHandler.h Plugins/OrthancCPlugin/OrthancCPlugin.h Plugins/Samples/Basic/Plugin.c
diffstat 7 files changed, 360 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/ServerContext.cpp	Fri Jul 25 16:59:33 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Fri Jul 25 18:39:02 2014 +0200
@@ -50,7 +50,7 @@
 #include "Scheduler/StoreScuCommand.h"
 #include "Scheduler/StorePeerCommand.h"
 #include "OrthancRestApi/OrthancRestApi.h"
-
+#include "../Plugins/Engine/PluginsHttpHandler.h"
 
 
 #define ENABLE_DICOM_CACHE  1
@@ -79,7 +79,8 @@
     compressionEnabled_(false),
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE),
-    scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10))
+    scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10)),
+    plugins_(NULL)
   {
     scu_.SetLocalApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
     //scu_.SetMillisecondsBeforeClose(1);  // The connection is always released
@@ -206,9 +207,9 @@
   }
 
 
-  void ServerContext::ApplyOnStoredInstance(const std::string& instanceId,
-                                            const Json::Value& simplifiedDicom,
-                                            const Json::Value& metadata)
+  void ServerContext::ApplyLuaOnStoredInstance(const std::string& instanceId,
+                                               const Json::Value& simplifiedDicom,
+                                               const Json::Value& metadata)
   {
     LuaContextLocker locker(*this);
 
@@ -347,12 +348,24 @@
 
         try
         {
-          ApplyOnStoredInstance(resultPublicId, simplified, metadata);
+          ApplyLuaOnStoredInstance(resultPublicId, simplified, metadata);
         }
         catch (OrthancException& e)
         {
           LOG(ERROR) << "Error in OnStoredInstance callback (Lua): " << e.What();
         }
+
+        if (plugins_ != NULL)
+        {
+          try
+          {
+            plugins_->SignalStoredInstance(dicom, resultPublicId);
+          }
+          catch (OrthancException& e)
+          {
+            LOG(ERROR) << "Error in OnStoredInstance callback (Lua): " << e.What();
+          }
+        }
       }
 
       return status;
--- a/OrthancServer/ServerContext.h	Fri Jul 25 16:59:33 2014 +0200
+++ b/OrthancServer/ServerContext.h	Fri Jul 25 18:39:02 2014 +0200
@@ -45,6 +45,8 @@
 
 namespace Orthanc
 {
+  class PluginsHttpHandler;
+
   /**
    * This class is responsible for maintaining the storage area on the
    * filesystem (including compression), as well as the index of the
@@ -69,9 +71,9 @@
     bool ApplyReceivedInstanceFilter(const Json::Value& simplified,
                                      const std::string& remoteAet);
 
-    void ApplyOnStoredInstance(const std::string& instanceId,
-                               const Json::Value& simplifiedDicom,
-                               const Json::Value& metadata);
+    void ApplyLuaOnStoredInstance(const std::string& instanceId,
+                                  const Json::Value& simplifiedDicom,
+                                  const Json::Value& metadata);
 
     FileStorage storage_;
     ServerIndex index_;
@@ -86,6 +88,7 @@
 
     boost::mutex luaMutex_;
     LuaContext lua_;
+    PluginsHttpHandler* plugins_;  // TODO Turn it into a listener pattern (idem for Lua callbacks)
 
   public:
     class DicomCacheLocker : public boost::noncopyable
@@ -183,5 +186,10 @@
     {
       return scheduler_;
     }
+
+    void SetPluginsHttpHandler(PluginsHttpHandler& plugin)
+    {
+      plugins_ = &plugin;
+    }
   };
 }
--- a/OrthancServer/main.cpp	Fri Jul 25 16:59:33 2014 +0200
+++ b/OrthancServer/main.cpp	Fri Jul 25 18:39:02 2014 +0200
@@ -484,6 +484,7 @@
       httpServer.RegisterHandler(staticResources);
       httpServer.RegisterHandler(restApi);
       httpPlugins.SetOrthancRestApi(restApi);
+      context.SetPluginsHttpHandler(httpPlugins);
 
       // GO !!! Start the requested servers
       if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true))
--- a/Plugins/Engine/PluginsHttpHandler.cpp	Fri Jul 25 16:59:33 2014 +0200
+++ b/Plugins/Engine/PluginsHttpHandler.cpp	Fri Jul 25 18:39:02 2014 +0200
@@ -37,6 +37,7 @@
 #include "../../Core/Toolbox.h"
 #include "../../Core/HttpServer/HttpOutput.h"
 #include "../../Core/ImageFormats/PngWriter.h"
+#include "../../OrthancServer/ServerToolbox.h"
 
 #include <boost/regex.hpp> 
 #include <glog/logging.h>
@@ -79,12 +80,14 @@
 
   struct PluginsHttpHandler::PImpl
   {
-    typedef std::pair<boost::regex*, OrthancPluginRestCallback> Callback;
-    typedef std::list<Callback>  Callbacks;
+    typedef std::pair<boost::regex*, OrthancPluginRestCallback> RestCallback;
+    typedef std::list<RestCallback>  RestCallbacks;
+    typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
 
     ServerContext& context_;
-    Callbacks callbacks_;
+    RestCallbacks restCallbacks_;
     OrthancRestApi* restApi_;
+    OnStoredCallbacks  onStoredCallbacks_;
 
     PImpl(ServerContext& context) : context_(context), restApi_(NULL)
     {
@@ -92,6 +95,27 @@
   };
 
 
+  static char* CopyString(const std::string& str)
+  {
+    char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
+    if (result == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    if (str.size() == 0)
+    {
+      result[0] = '\0';
+    }
+    else
+    {
+      memcpy(result, &str[0], str.size() + 1);
+    }
+
+    return result;
+  }
+
+
   PluginsHttpHandler::PluginsHttpHandler(ServerContext& context)
   {
     pimpl_.reset(new PImpl(context));
@@ -100,8 +124,8 @@
   
   PluginsHttpHandler::~PluginsHttpHandler()
   {
-    for (PImpl::Callbacks::iterator it = pimpl_->callbacks_.begin(); 
-         it != pimpl_->callbacks_.end(); ++it)
+    for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin(); 
+         it != pimpl_->restCallbacks_.end(); ++it)
     {
       // Delete the regular expression associated with this callback
       delete it->first;
@@ -142,8 +166,8 @@
 
     // Loop over the callbacks registered by the plugins
     bool found = false;
-    for (PImpl::Callbacks::const_iterator it = pimpl_->callbacks_.begin(); 
-         it != pimpl_->callbacks_.end() && !found; ++it)
+    for (PImpl::RestCallbacks::const_iterator it = pimpl_->restCallbacks_.begin(); 
+         it != pimpl_->restCallbacks_.end() && !found; ++it)
     {
       // Check whether the regular expression associated to this
       // callback matches the URI
@@ -247,6 +271,20 @@
   }
 
 
+  void PluginsHttpHandler::SignalStoredInstance(DicomInstanceToStore& instance,
+                                                const std::string& instanceId)                                                  
+  {
+    for (PImpl::OnStoredCallbacks::const_iterator
+           callback = pimpl_->onStoredCallbacks_.begin(); 
+         callback != pimpl_->onStoredCallbacks_.end(); ++callback)
+    {
+      (*callback) (reinterpret_cast<OrthancPluginDicomInstance*>(&instance),
+                   instanceId.c_str());
+    }
+  }
+
+
+
   static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
                                  const void* data,
                                  size_t size)
@@ -293,7 +331,18 @@
       *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
 
     LOG(INFO) << "Plugin has registered a REST callback on: " << p.pathRegularExpression;
-    pimpl_->callbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback));
+    pimpl_->restCallbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback));
+  }
+
+
+
+  void PluginsHttpHandler::RegisterOnStoredInstanceCallback(const void* parameters)
+  {
+    const _OrthancPluginOnStoredInstanceCallback& p = 
+      *reinterpret_cast<const _OrthancPluginOnStoredInstanceCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered an OnStoredInstance callback";
+    pimpl_->onStoredCallbacks_.push_back(p.callback);
   }
 
 
@@ -546,24 +595,53 @@
   }
 
 
-  char* PluginsHttpHandler::CopyString(const std::string& str) const
+  static void AccessDicomInstance(_OrthancPluginService service,
+                                  const void* parameters)
   {
-    char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
-    if (result == NULL)
+    const _OrthancPluginAccessDicomInstance& p = 
+      *reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
+
+    DicomInstanceToStore& instance =
+      *reinterpret_cast<DicomInstanceToStore*>(p.instance);
+
+    switch (service)
     {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
+      case _OrthancPluginService_GetInstanceRemoteAet:
+        *p.resultString = instance.GetRemoteAet().c_str();
+        return;
+
+      case _OrthancPluginService_GetInstanceSize:
+        *p.resultInt64 = instance.GetBufferSize();
+        return;
+
+      case _OrthancPluginService_GetInstanceData:
+        *p.resultString = instance.GetBufferData();
+        return;
 
-    if (str.size() == 0)
-    {
-      result[0] = '\0';
+      case _OrthancPluginService_GetInstanceJson:
+      case _OrthancPluginService_GetInstanceSimplifiedJson:
+      {
+        Json::StyledWriter writer;
+        std::string s;
+
+        if (service == _OrthancPluginService_GetInstanceJson)
+        {
+          s = writer.write(instance.GetJson());
+        }
+        else
+        {
+          Json::Value simplified;
+          SimplifyTags(simplified, instance.GetJson());
+          s = writer.write(simplified);
+        }
+
+        *p.resultStringToFree = CopyString(s);
+        return;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
-    else
-    {
-      memcpy(result, &str[0], str.size() + 1);
-    }
-
-    return result;
   }
 
 
@@ -576,6 +654,10 @@
         RegisterRestCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterOnStoredInstanceCallback:
+        RegisterOnStoredInstanceCallback(parameters);
+        return true;
+
       case _OrthancPluginService_AnswerBuffer:
         AnswerBuffer(parameters);
         return true;
@@ -640,6 +722,14 @@
         LookupResource(ResourceType_Instance, parameters);
         return true;
 
+      case _OrthancPluginService_GetInstanceRemoteAet:
+      case _OrthancPluginService_GetInstanceSize:
+      case _OrthancPluginService_GetInstanceData:
+      case _OrthancPluginService_GetInstanceJson:
+      case _OrthancPluginService_GetInstanceSimplifiedJson:
+        AccessDicomInstance(service, parameters);
+        return true;
+
       default:
         return false;
     }
--- a/Plugins/Engine/PluginsHttpHandler.h	Fri Jul 25 16:59:33 2014 +0200
+++ b/Plugins/Engine/PluginsHttpHandler.h	Fri Jul 25 18:39:02 2014 +0200
@@ -50,9 +50,9 @@
 
     boost::shared_ptr<PImpl> pimpl_;
 
-    char* CopyString(const std::string& str) const;
+    void RegisterRestCallback(const void* parameters);
 
-    void RegisterRestCallback(const void* parameters);
+    void RegisterOnStoredInstanceCallback(const void* parameters);
 
     void AnswerBuffer(const void* parameters);
 
@@ -94,6 +94,9 @@
     virtual bool InvokeService(_OrthancPluginService service,
                                const void* parameters);
 
+    void SignalStoredInstance(DicomInstanceToStore& instance,
+                              const std::string& instanceId);
+
     void SetOrthancRestApi(OrthancRestApi& restApi);
   };
 }
--- a/Plugins/OrthancCPlugin/OrthancCPlugin.h	Fri Jul 25 16:59:33 2014 +0200
+++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h	Fri Jul 25 18:39:02 2014 +0200
@@ -238,6 +238,7 @@
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -257,7 +258,16 @@
     _OrthancPluginService_LookupPatient = 3005,
     _OrthancPluginService_LookupStudy = 3006,
     _OrthancPluginService_LookupSeries = 3007,
-    _OrthancPluginService_LookupInstance = 3008
+    _OrthancPluginService_LookupInstance = 3008,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004
+    
+    /* + METADATA !!! */
   } _OrthancPluginService;
 
 
@@ -338,6 +348,14 @@
   typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
 
 
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
   /**
    * @brief Signature of a callback function that answers to a REST request.
    **/
@@ -347,6 +365,16 @@
     const OrthancPluginHttpRequest* request);
 
 
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   **/
+  typedef int32_t (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
   /**
    * @brief Opaque structure that contains information about the Orthanc core.
    **/
@@ -551,6 +579,32 @@
 
   typedef struct
   {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
     OrthancPluginRestOutput* output;
     const char*              answer;
     uint32_t                 answerSize;
@@ -1045,6 +1099,134 @@
   }
 
 
+  typedef struct
+  {
+    char**                      resultStringToFree;
+    const char**                resultString;
+    int64_t*                    resultInt64;
+    const char*                 key;
+    OrthancPluginDicomInstance* instance;
+  } _OrthancPluginAccessDicomInstance;
+
+
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params))
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params))
+    {
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params))
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params))
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params))
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+
+  /*
+    TODO :  METADATA !!!
+    TODO : DOCUMENTATION !!!
+  */
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Basic/Plugin.c	Fri Jul 25 16:59:33 2014 +0200
+++ b/Plugins/Samples/Basic/Plugin.c	Fri Jul 25 18:39:02 2014 +0200
@@ -165,6 +165,33 @@
 }
 
 
+ORTHANC_PLUGINS_API int32_t OnStoredCallback(OrthancPluginDicomInstance* instance,
+                                             const char* instanceId)
+{
+  char buffer[256];
+  FILE* fp;
+  char* json;
+
+  sprintf(buffer, "Just received a DICOM instance of size %d and ID %s from AET %s", 
+          (int) OrthancPluginGetInstanceSize(context, instance), instanceId, 
+          OrthancPluginGetInstanceRemoteAet(context, instance));
+
+  OrthancPluginLogWarning(context, buffer);  
+
+  fp = fopen("PluginReceivedInstance.dcm", "wb");
+  fwrite(OrthancPluginGetInstanceData(context, instance),
+         OrthancPluginGetInstanceSize(context, instance), 1, fp);
+  fclose(fp);
+
+  json = OrthancPluginGetInstanceSimplifiedJson(context, instance);
+  printf("[%s]\n", json);
+  OrthancPluginFreeString(context, json);
+
+  return 0;
+}
+
+
+
 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
 {
   const char* pathLocator = "\"Path\" : \"";
@@ -193,8 +220,9 @@
   OrthancPluginRegisterRestCallback(context, "/(plu.*)/hello", Callback1);
   OrthancPluginRegisterRestCallback(context, "/plu.*/image", Callback2);
   OrthancPluginRegisterRestCallback(context, "/plugin/instances/([^/]+)/info", Callback3);
+  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
 
-  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
+  OrthancPluginRegisterOnStoredInstanceCallback(context, OnStoredCallback);
 
   /* Make REST requests to the built-in Orthanc API */
   OrthancPluginRestApiGet(context, &tmp, "/changes");