changeset 1135:67c3c1e4a6e0

index-only mode, and custom storage area with plugins
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 09 Sep 2014 15:55:43 +0200
parents ba9fd42284d0
children 208dc67b9bab
files Core/Enumerations.h Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.cpp Core/FileStorage/FilesystemStorage.cpp Core/FileStorage/FilesystemStorage.h Core/FileStorage/IStorageArea.h Core/OrthancException.cpp NEWS OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/OrthancCPlugin/OrthancCPlugin.h Resources/Configuration.json UnitTestsSources/FileStorageTests.cpp
diffstat 14 files changed, 325 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Enumerations.h	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/Enumerations.h	Tue Sep 09 15:55:43 2014 +0200
@@ -73,7 +73,8 @@
     ErrorCode_ReadOnly,
     ErrorCode_IncompatibleImageFormat,
     ErrorCode_IncompatibleImageSize,
-    ErrorCode_SharedLibrary
+    ErrorCode_SharedLibrary,
+    ErrorCode_Plugin
   };
 
   /**
@@ -267,6 +268,8 @@
 
   enum FileContentType
   {
+    // If you add a value below, insert it in "PluginStorageArea" in
+    // the file "Plugins/Engine/OrthancPlugins.cpp"
     FileContentType_Unknown = 0,
     FileContentType_Dicom = 1,
     FileContentType_DicomAsJson = 2,
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -36,6 +36,7 @@
 #include "../OrthancException.h"
 #include "FileStorageAccessor.h"
 #include "../HttpServer/BufferHttpSender.h"
+#include "../Uuid.h"
 
 #include <memory>
 
@@ -45,6 +46,8 @@
                                                         size_t size,
                                                         FileContentType type)
   {
+    std::string uuid = Toolbox::GenerateUuid();
+
     std::string md5;
 
     if (storeMD5_)
@@ -56,7 +59,7 @@
     {
     case CompressionType_None:
     {
-      std::string uuid = GetStorageArea().Create(data, size, type);
+      GetStorageArea().Create(uuid.c_str(), data, size, type);
       return FileInfo(uuid, type, size, md5);
     }
 
@@ -72,14 +75,13 @@
         Toolbox::ComputeMD5(compressedMD5, compressed);
       }
 
-      std::string uuid;
       if (compressed.size() > 0)
       {
-        uuid = GetStorageArea().Create(&compressed[0], compressed.size(), type);
+        GetStorageArea().Create(uuid.c_str(), &compressed[0], compressed.size(), type);
       }
       else
       {
-        uuid = GetStorageArea().Create(NULL, 0, type);
+        GetStorageArea().Create(uuid.c_str(), NULL, 0, type);
       }
 
       return FileInfo(uuid, type, size, md5,
--- a/Core/FileStorage/FileStorageAccessor.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -34,9 +34,9 @@
 #include "FileStorageAccessor.h"
 
 #include "../HttpServer/BufferHttpSender.h"
+#include "../Uuid.h"
 
 #include <memory>
-
 #include <stdio.h>
 
 namespace Orthanc
@@ -52,7 +52,10 @@
       Toolbox::ComputeMD5(md5, data, size);
     }
 
-    return FileInfo(storage_.Create(data, size, type), type, size, md5);
+    std::string uuid = Toolbox::GenerateUuid();
+    storage_.Create(uuid.c_str(), data, size, type);
+
+    return FileInfo(uuid, type, size, md5);
   }
 
 
--- a/Core/FileStorage/FilesystemStorage.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/FileStorage/FilesystemStorage.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -85,26 +85,20 @@
     Toolbox::CreateDirectory(root);
   }
 
-  std::string FilesystemStorage::Create(const void* content, 
-                                        size_t size,
-                                        FileContentType /*type*/)
+  void FilesystemStorage::Create(const std::string& uuid,
+                                 const void* content, 
+                                 size_t size,
+                                 FileContentType /*type*/)
   {
-    std::string uuid;
     boost::filesystem::path path;
     
-    for (;;)
-    {
-      uuid = Toolbox::GenerateUuid();
-      path = GetPath(uuid);
+    path = GetPath(uuid);
 
-      if (!boost::filesystem::exists(path))
-      {
-        // OK, this is indeed a new file
-        break;
-      }
-
-      // Extremely improbable case: This Uuid has already been created
-      // in the past. Try again.
+    if (boost::filesystem::exists(path))
+    {
+      // Extremely unlikely case: This Uuid has already been created
+      // in the past.
+      throw OrthancException(ErrorCode_InternalError);
     }
 
     if (boost::filesystem::exists(path.parent_path()))
@@ -140,8 +134,6 @@
     }
 
     f.close();
-
-    return uuid;
   }
 
 
--- a/Core/FileStorage/FilesystemStorage.h	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/FileStorage/FilesystemStorage.h	Tue Sep 09 15:55:43 2014 +0200
@@ -53,9 +53,10 @@
   public:
     FilesystemStorage(std::string root);
 
-    virtual std::string Create(const void* content, 
-                               size_t size,
-                               FileContentType type);
+    virtual void Create(const std::string& uuid,
+                        const void* content, 
+                        size_t size,
+                        FileContentType type);
 
     virtual void Read(std::string& content,
                       const std::string& uuid,
--- a/Core/FileStorage/IStorageArea.h	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/FileStorage/IStorageArea.h	Tue Sep 09 15:55:43 2014 +0200
@@ -45,9 +45,10 @@
     {
     }
 
-    virtual std::string Create(const void* content, 
-                               size_t size,
-                               FileContentType type) = 0;
+    virtual void Create(const std::string& uuid,
+                        const void* content,
+                        size_t size,
+                        FileContentType type) = 0;
 
     virtual void Read(std::string& content,
                       const std::string& uuid,
--- a/Core/OrthancException.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/Core/OrthancException.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -127,6 +127,9 @@
       case ErrorCode_SystemCommand:
         return "Error while calling a system command";
 
+      case ErrorCode_Plugin:
+        return "Error encountered inside a plugin";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/NEWS	Tue Sep 09 12:53:49 2014 +0200
+++ b/NEWS	Tue Sep 09 15:55:43 2014 +0200
@@ -5,12 +5,14 @@
 -------
 
 * Creation of ZIP archives for media storage, with DICOMDIR
-* Refactoring of HttpOutput ("Content-Length" header is now always sent)
+* Support of index-only mode (using the "StoreDicom" option)
+* Plugins can implement a custom storage area
 
 Minor
 -----
 
 * Configuration option to enable HTTP Keep-Alive
+* Refactoring of HttpOutput ("Content-Length" header is now always sent)
 * "/tools/create-dicom" now accepts the "PatientID" DICOM tag (+ updated sample)
 * Upgrade to Mongoose 3.8
 * Fixes for Visual Studio 2013 and Windows 64bit
--- a/OrthancServer/main.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/OrthancServer/main.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -326,17 +326,14 @@
   {
   }
 
-  virtual std::string Create(const void* content, 
-                             size_t size,
-                             FileContentType type)
+  virtual void Create(const std::string& uuid,
+                      const void* content, 
+                      size_t size,
+                      FileContentType type)
   {
     if (type != FileContentType_Dicom)
     {
-      return storage_.Create(content, size, type);
-    }
-    else
-    {
-      return Toolbox::GenerateUuid();
+      storage_.Create(uuid, content, size, type);
     }
   }
 
@@ -368,18 +365,14 @@
 static bool StartOrthanc()
 {
   std::string storageDirectoryStr = Configuration::GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
-  boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr);
   boost::filesystem::path indexDirectory = Configuration::InterpretStringParameterAsPath(
     Configuration::GetGlobalStringParameter("IndexDirectory", storageDirectoryStr));
 
-  // TODO HERE
-  FilesystemStorage storage(storageDirectory.string());
-  //FilesystemStorageWithoutDicom storage(storageDirectory.string());
+  // "storage" must be declared BEFORE "ServerContext context", to
+  // avoid mess in the invokation order of the destructors.
+  std::auto_ptr<IStorageArea>  storage;
+  ServerContext context(indexDirectory);
 
-  ServerContext context(indexDirectory);
-  context.SetStorageArea(storage);
-
-  LOG(WARNING) << "Storage directory: " << storageDirectory;
   LOG(WARNING) << "Index directory: " << indexDirectory;
 
   context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
@@ -465,6 +458,31 @@
     orthancPlugins.SetOrthancRestApi(restApi);
     context.SetOrthancPlugins(orthancPlugins);
 
+
+    // Prepare the storage area
+    if (orthancPlugins.HasStorageArea())
+    {
+      LOG(WARNING) << "Using a custom storage area from plugins";
+      storage.reset(orthancPlugins.GetStorageArea());
+    }
+    else
+    {
+      boost::filesystem::path storageDirectory = Configuration::InterpretStringParameterAsPath(storageDirectoryStr);
+      LOG(WARNING) << "Storage directory: " << storageDirectory;
+      if (Configuration::GetGlobalBoolParameter("StoreDicom", true))
+      {
+        storage.reset(new FilesystemStorage(storageDirectory.string()));
+      }
+      else
+      {
+        LOG(WARNING) << "The DICOM files will not be stored, Orthanc running in index-only mode";
+        storage.reset(new FilesystemStorageWithoutDicom(storageDirectory.string()));
+      }
+    }
+    
+    context.SetStorageArea(*storage);
+
+
     // GO !!! Start the requested servers
     if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true))
     {
--- a/Plugins/Engine/OrthancPlugins.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -88,9 +88,15 @@
     RestCallbacks restCallbacks_;
     OrthancRestApi* restApi_;
     OnStoredCallbacks  onStoredCallbacks_;
+    bool hasStorageArea_;
+    _OrthancPluginRegisterStorageArea storageArea_;
 
-    PImpl(ServerContext& context) : context_(context), restApi_(NULL)
+    PImpl(ServerContext& context) : 
+      context_(context), 
+      restApi_(NULL),
+      hasStorageArea_(false)
     {
+      memset(&storageArea_, 0, sizeof(storageArea_));
     }
   };
 
@@ -805,6 +811,15 @@
         AccessDicomInstance(service, parameters);
         return true;
 
+      case _OrthancPluginService_RegisterStorageArea:
+      {
+        const _OrthancPluginRegisterStorageArea& p = 
+          *reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters);
+        
+        pimpl_->storageArea_ = p;
+        return true;
+      }
+
       default:
         return false;
     }
@@ -816,4 +831,108 @@
     pimpl_->restApi_ = &restApi;
   }
 
+  
+  bool OrthancPlugins::HasStorageArea() const
+  {
+    return pimpl_->hasStorageArea_;
+  }
+
+
+  namespace
+  {
+    class PluginStorageArea : public IStorageArea
+    {
+    private:
+      _OrthancPluginRegisterStorageArea params_;
+
+      void Free(void* buffer) const
+      {
+        if (buffer != NULL)
+        {
+          params_.free_(buffer);
+        }
+      }
+
+      OrthancPluginContentType Convert(FileContentType type) const
+      {
+        switch (type)
+        {
+          case FileContentType_Dicom:
+            return OrthancPluginContentType_Dicom;
+
+          case FileContentType_DicomAsJson:
+            return OrthancPluginContentType_DicomAsJson;
+
+          default:
+            return OrthancPluginContentType_Unknown;
+        }
+      }
+
+    public:
+      PluginStorageArea(const _OrthancPluginRegisterStorageArea& params) : params_(params)
+      {
+      }
+
+      virtual void Create(const std::string& uuid,
+                          const void* content, 
+                          size_t size,
+                          FileContentType type)
+      {
+        if (params_.create_(uuid.c_str(), content, size, Convert(type)) != 0)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+      }
+
+      virtual void Read(std::string& content,
+                        const std::string& uuid,
+                        FileContentType type) const
+      {
+        void* buffer = NULL;
+        int64_t size = 0;
+
+        if (params_.read_(&buffer, &size, uuid.c_str(), Convert(type)) != 0)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }        
+
+        try
+        {
+          content.resize(size);
+        }
+        catch (OrthancException&)
+        {
+          Free(buffer);
+          throw;
+        }
+
+        if (size > 0)
+        {
+          memcpy(&content[0], buffer, size);
+        }
+
+        Free(buffer);
+      }
+
+      virtual void Remove(const std::string& uuid,
+                          FileContentType type) 
+      {
+        if (params_.remove_(uuid.c_str(), Convert(type)) != 0)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }        
+      }
+    };
+  }
+
+
+  IStorageArea* OrthancPlugins::GetStorageArea()
+  {
+    if (!HasStorageArea())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return new PluginStorageArea(pimpl_->storageArea_);;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Tue Sep 09 12:53:49 2014 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue Sep 09 15:55:43 2014 +0200
@@ -47,7 +47,6 @@
   {
   private:
     struct PImpl;
-
     boost::shared_ptr<PImpl> pimpl_;
 
     void RegisterRestCallback(const void* parameters);
@@ -98,5 +97,9 @@
                               const std::string& instanceId);
 
     void SetOrthancRestApi(OrthancRestApi& restApi);
+
+    bool HasStorageArea() const;
+
+    IStorageArea* GetStorageArea();
   };
 }
--- a/Plugins/OrthancCPlugin/OrthancCPlugin.h	Tue Sep 09 12:53:49 2014 +0200
+++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h	Tue Sep 09 15:55:43 2014 +0200
@@ -15,6 +15,7 @@
  *      services of Orthanc.
  *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
  *    - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    This function is invoked by Orthanc during its shutdown. The plugin
  *    must free all its memory.
@@ -87,7 +88,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     8
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  1
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  3
 
 
 
@@ -240,6 +241,7 @@
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
     _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -321,6 +323,19 @@
   } OrthancPluginPixelFormat;
 
 
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2   /*!< JSON summary of a DICOM file */
+  } OrthancPluginContentType;
+
+
+
   /**
    * @brief A memory buffer allocated by the core system of Orthanc.
    *
@@ -378,16 +393,76 @@
 
 
   /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   **/
+  typedef int32_t (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
    * @brief Opaque structure that contains information about the Orthanc core.
    **/
   typedef struct _OrthancPluginContext_t
   {
-    void*        pluginsManager;
-    const char*  orthancVersion;
-    void       (*Free) (void* buffer);
-    int32_t    (*InvokeService) (struct _OrthancPluginContext_t* context,
-                                 _OrthancPluginService service,
-                                 const void* params);
+    void*              pluginsManager;
+    const char*        orthancVersion;
+    OrthancPluginFree  Free;
+    int32_t          (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                       _OrthancPluginService service,
+                                       const void* params);
   } OrthancPluginContext;
 
 
@@ -1404,6 +1479,40 @@
 
 
 
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create_;
+    OrthancPluginStorageRead    read_;
+    OrthancPluginStorageRemove  remove_;
+    OrthancPluginFree           free_;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param 
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create_ = create;
+    params.read_ = read;
+    params.remove_ = remove;
+    params.free_ = free;
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Resources/Configuration.json	Tue Sep 09 12:53:49 2014 +0200
+++ b/Resources/Configuration.json	Tue Sep 09 15:55:43 2014 +0200
@@ -191,5 +191,9 @@
 
   // Enable or disable HTTP Keep-Alive (experimental). Set this option
   // to "true" only in the case of high HTTP loads.
-  "KeepAlive" : false
+  "KeepAlive" : false,
+
+  // If this option is set to "false", Orthanc will run in index-only
+  // mode. The DICOM files will not be stored on the drive.
+  "StoreDicom" : true
 }
--- a/UnitTestsSources/FileStorageTests.cpp	Tue Sep 09 12:53:49 2014 +0200
+++ b/UnitTestsSources/FileStorageTests.cpp	Tue Sep 09 15:55:43 2014 +0200
@@ -63,7 +63,8 @@
   FilesystemStorage s("UnitTestsStorage");
 
   std::string data = Toolbox::GenerateUuid();
-  std::string uid = s.Create(&data[0], data.size(), FileContentType_Unknown);
+  std::string uid = Toolbox::GenerateUuid();
+  s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown);
   std::string d;
   s.Read(d, uid, FileContentType_Unknown);
   ASSERT_EQ(d.size(), data.size());
@@ -77,7 +78,8 @@
 
   std::vector<uint8_t> data;
   StringToVector(data, Toolbox::GenerateUuid());
-  std::string uid = s.Create(&data[0], data.size(), FileContentType_Unknown);
+  std::string uid = Toolbox::GenerateUuid();
+  s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown);
   std::string d;
   s.Read(d, uid, FileContentType_Unknown);
   ASSERT_EQ(d.size(), data.size());
@@ -94,7 +96,9 @@
   for (unsigned int i = 0; i < 10; i++)
   {
     std::string t = Toolbox::GenerateUuid();
-    u.push_back(s.Create(&t[0], t.size(), FileContentType_Unknown));
+    std::string uid = Toolbox::GenerateUuid();
+    s.Create(uid.c_str(), &t[0], t.size(), FileContentType_Unknown);
+    u.push_back(uid);
   }
 
   std::set<std::string> ss;