changeset 1772:53e045b5a8ec

MIME content type can be associated to custom attachments (cf. "UserContentType")
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 12 Nov 2015 14:36:27 +0100
parents 8790488ae98b
children 613df4362575
files Core/EnumerationDictionary.h Core/Enumerations.cpp Core/Enumerations.h Core/FileStorage/StorageAccessor.cpp Core/FileStorage/StorageAccessor.h NEWS OrthancServer/OrthancInitialization.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h Resources/Configuration.json
diffstat 11 files changed, 148 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/Core/EnumerationDictionary.h	Tue Nov 10 17:18:42 2015 +0100
+++ b/Core/EnumerationDictionary.h	Thu Nov 12 14:36:27 2015 +0100
@@ -60,6 +60,11 @@
         stringToEnumeration_.clear();
       }
 
+      bool Contains(Enumeration value) const
+      {
+        return enumerationToString_.find(value) != enumerationToString_.end();
+      }
+
       void Add(Enumeration value, const std::string& str)
       {
         // Check if these values are free
--- a/Core/Enumerations.cpp	Tue Nov 10 17:18:42 2015 +0100
+++ b/Core/Enumerations.cpp	Thu Nov 12 14:36:27 2015 +0100
@@ -980,39 +980,6 @@
   }
 
 
-  const char* GetMimeType(FileContentType type)
-  {
-    switch (type)
-    {
-      case FileContentType_Dicom:
-        return "application/dicom";
-
-      case FileContentType_DicomAsJson:
-        return "application/json";
-
-      default:
-        return "application/octet-stream";
-    }
-  }
-
-
-  const char* GetFileExtension(FileContentType type)
-  {
-    switch (type)
-    {
-      case FileContentType_Dicom:
-        return ".dcm";
-
-      case FileContentType_DicomAsJson:
-        return ".json";
-
-      default:
-        // Unknown file type
-        return "";
-    }
-  }
-
-
   ResourceType GetChildResourceType(ResourceType type)
   {
     switch (type)
--- a/Core/Enumerations.h	Tue Nov 10 17:18:42 2015 +0100
+++ b/Core/Enumerations.h	Thu Nov 12 14:36:27 2015 +0100
@@ -454,10 +454,6 @@
   bool GetDicomEncoding(Encoding& encoding,
                         const char* specificCharacterSet);
 
-  const char* GetMimeType(FileContentType type);
-
-  const char* GetFileExtension(FileContentType type);
-
   ResourceType GetChildResourceType(ResourceType type);
 
   ResourceType GetParentResourceType(ResourceType type);
--- a/Core/FileStorage/StorageAccessor.cpp	Tue Nov 10 17:18:42 2015 +0100
+++ b/Core/FileStorage/StorageAccessor.cpp	Thu Nov 12 14:36:27 2015 +0100
@@ -141,19 +141,38 @@
 
 
   void StorageAccessor::SetupSender(BufferHttpSender& sender,
-                                    const FileInfo& info)
+                                    const FileInfo& info,
+                                    const std::string& mime)
   {
     area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType());
-    sender.SetContentType(GetMimeType(info.GetContentType()));
-    sender.SetContentFilename(info.GetUuid() + std::string(GetFileExtension(info.GetContentType())));
+    sender.SetContentType(mime);
+
+    const char* extension;
+    switch (info.GetContentType())
+    {
+      case FileContentType_Dicom:
+        extension = ".dcm";
+        break;
+
+      case FileContentType_DicomAsJson:
+        extension = ".json";
+        break;
+
+      default:
+        // Non-standard content type
+        extension = "";
+    }
+
+    sender.SetContentFilename(info.GetUuid() + std::string(extension));
   }
 
 
   void StorageAccessor::AnswerFile(HttpOutput& output,
-                                   const FileInfo& info)
+                                   const FileInfo& info,
+                                   const std::string& mime)
   {
     BufferHttpSender sender;
-    SetupSender(sender, info);
+    SetupSender(sender, info, mime);
   
     HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
     output.Answer(transcoder);
@@ -161,10 +180,11 @@
 
 
   void StorageAccessor::AnswerFile(RestApiOutput& output,
-                                   const FileInfo& info)
+                                   const FileInfo& info,
+                                   const std::string& mime)
   {
     BufferHttpSender sender;
-    SetupSender(sender, info);
+    SetupSender(sender, info, mime);
   
     HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
     output.AnswerStream(transcoder);
--- a/Core/FileStorage/StorageAccessor.h	Tue Nov 10 17:18:42 2015 +0100
+++ b/Core/FileStorage/StorageAccessor.h	Thu Nov 12 14:36:27 2015 +0100
@@ -51,7 +51,8 @@
     IStorageArea&  area_;
 
     void SetupSender(BufferHttpSender& sender,
-                     const FileInfo& info);
+                     const FileInfo& info,
+                     const std::string& mime);
 
   public:
     StorageAccessor(IStorageArea& area) : area_(area)
@@ -85,9 +86,11 @@
     }
 
     void AnswerFile(HttpOutput& output,
-                    const FileInfo& info);
+                    const FileInfo& info,
+                    const std::string& mime);
 
     void AnswerFile(RestApiOutput& output,
-                    const FileInfo& info);
+                    const FileInfo& info,
+                    const std::string& mime);
   };
 }
--- a/NEWS	Tue Nov 10 17:18:42 2015 +0100
+++ b/NEWS	Thu Nov 12 14:36:27 2015 +0100
@@ -10,6 +10,7 @@
 * New URI "/tools/shutdown" to stop Orthanc from the REST API
 * New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed"
 * New configuration option: "Dictionary" to declare custom DICOM tags
+* MIME content type can be associated to custom attachments (cf. "UserContentType")
 
 Plugins
 -------
--- a/OrthancServer/OrthancInitialization.cpp	Tue Nov 10 17:18:42 2015 +0100
+++ b/OrthancServer/OrthancInitialization.cpp	Thu Nov 12 14:36:27 2015 +0100
@@ -252,24 +252,26 @@
       Json::Value::Members members = parameter.getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
       {
-        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
-        LOG(INFO) << "Registering user-defined metadata: " << info;
+        const std::string& name = members[i];
 
-        if (!parameter[members[i]].asBool())
+        if (!parameter[name].isInt())
         {
-          LOG(ERROR) << "Not a number in this user-defined metadata: " << info;
+          LOG(ERROR) << "Not a number in this user-defined metadata: " << name;
           throw OrthancException(ErrorCode_BadParameterType);
         }
 
-        int metadata = parameter[members[i]].asInt();
+        int metadata = parameter[name].asInt();        
+
+        LOG(INFO) << "Registering user-defined metadata: " << name << " (index " 
+                  << metadata << ")";
 
         try
         {
-          RegisterUserMetadata(metadata, members[i]);
+          RegisterUserMetadata(metadata, name);
         }
         catch (OrthancException&)
         {
-          LOG(ERROR) << "Cannot register this user-defined metadata: " << info;
+          LOG(ERROR) << "Cannot register this user-defined metadata: " << name;
           throw;
         }
       }
@@ -286,24 +288,39 @@
       Json::Value::Members members = parameter.getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
       {
-        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
-        LOG(INFO) << "Registering user-defined attachment type: " << info;
+        const std::string& name = members[i];
+        std::string mime = "application/octet-stream";
+
+        const Json::Value& value = parameter[name];
+        int contentType;
 
-        if (!parameter[members[i]].asBool())
+        if (value.isArray() &&
+            value.size() == 2 &&
+            value[0].isInt() &&
+            value[1].isString())
         {
-          LOG(ERROR) << "Not a number in this user-defined attachment type: " << info;
+          contentType = value[0].asInt();
+          mime = value[1].asString();
+        }
+        else if (value.isInt())
+        {
+          contentType = value.asInt();
+        }
+        else
+        {
+          LOG(ERROR) << "Not a number in this user-defined attachment type: " << name;
           throw OrthancException(ErrorCode_BadParameterType);
         }
 
-        int contentType = parameter[members[i]].asInt();
+        LOG(INFO) << "Registering user-defined attachment type: " << name << " (index " 
+                  << contentType << ") with MIME type \"" << mime << "\"";
 
         try
         {
-          RegisterUserContentType(contentType, members[i]);
+          RegisterUserContentType(contentType, name, mime);
         }
         catch (OrthancException&)
         {
-          LOG(ERROR) << "Cannot register this user-defined attachment type: " << info;
           throw;
         }
       }
--- a/OrthancServer/ServerContext.cpp	Tue Nov 10 17:18:42 2015 +0100
+++ b/OrthancServer/ServerContext.cpp	Thu Nov 12 14:36:27 2015 +0100
@@ -318,7 +318,7 @@
     }
 
     StorageAccessor accessor(area_);
-    accessor.AnswerFile(output, attachment);
+    accessor.AnswerFile(output, attachment, GetFileContentMime(content));
   }
 
 
--- a/OrthancServer/ServerEnumerations.cpp	Tue Nov 10 17:18:42 2015 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Nov 12 14:36:27 2015 +0100
@@ -35,15 +35,19 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/EnumerationDictionary.h"
+#include "../Core/Logging.h"
 #include "../Core/Toolbox.h"
 
 #include <boost/thread.hpp>
 
 namespace Orthanc
 {
+  typedef std::map<FileContentType, std::string>  MimeTypes;
+
   static boost::mutex enumerationsMutex_;
   static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
   static Toolbox::EnumerationDictionary<FileContentType> dictContentType_;
+  static MimeTypes  mimeTypes_;
 
   void InitializeServerEnumerations()
   {
@@ -72,10 +76,26 @@
     if (metadata < static_cast<int>(MetadataType_StartUser) ||
         metadata > static_cast<int>(MetadataType_EndUser))
     {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(MetadataType_StartUser) << " and "
+                 << static_cast<int>(MetadataType_EndUser) << ", but \""
+                 << name << "\" has index " << metadata;
+        
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    dictMetadataType_.Add(static_cast<MetadataType>(metadata), name);
+    MetadataType type = static_cast<MetadataType>(metadata);
+
+    if (dictMetadataType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << metadata 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictMetadataType_.Add(type, name);
   }
 
   std::string EnumerationToString(MetadataType type)
@@ -93,17 +113,34 @@
   }
 
   void RegisterUserContentType(int contentType,
-                               const std::string& name)
+                               const std::string& name,
+                               const std::string& mime)
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
 
     if (contentType < static_cast<int>(FileContentType_StartUser) ||
         contentType > static_cast<int>(FileContentType_EndUser))
     {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(FileContentType_StartUser) << " and "
+                 << static_cast<int>(FileContentType_EndUser) << ", but \""
+                 << name << "\" has index " << contentType;
+        
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    dictContentType_.Add(static_cast<FileContentType>(contentType), name);
+    FileContentType type = static_cast<FileContentType>(contentType);
+    if (dictContentType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << contentType 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictContentType_.Add(type, name);
+    mimeTypes_[type] = mime;
   }
 
   std::string EnumerationToString(FileContentType type)
@@ -114,6 +151,33 @@
     return dictContentType_.Translate(type);
   }
 
+  std::string GetFileContentMime(FileContentType type)
+  {
+    if (type >= FileContentType_StartUser &&
+        type <= FileContentType_EndUser)
+    {
+      boost::mutex::scoped_lock lock(enumerationsMutex_);
+      
+      MimeTypes::const_iterator it = mimeTypes_.find(type);
+      if (it != mimeTypes_.end())
+      {
+        return it->second;
+      }
+    }
+
+    switch (type)
+    {
+      case FileContentType_Dicom:
+        return "application/dicom";
+
+      case FileContentType_DicomAsJson:
+        return "application/json";
+
+      default:
+        return "application/octet-stream";
+    }
+  }
+
   FileContentType StringToContentType(const std::string& str)
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
--- a/OrthancServer/ServerEnumerations.h	Tue Nov 10 17:18:42 2015 +0100
+++ b/OrthancServer/ServerEnumerations.h	Thu Nov 12 14:36:27 2015 +0100
@@ -198,12 +198,15 @@
   std::string EnumerationToString(MetadataType type);
 
   void RegisterUserContentType(int contentType,
-                               const std::string& name);
+                               const std::string& name,
+                               const std::string& mime);
 
   FileContentType StringToContentType(const std::string& str);
 
   std::string EnumerationToString(FileContentType type);
 
+  std::string GetFileContentMime(FileContentType type);
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId);
 
--- a/Resources/Configuration.json	Tue Nov 10 17:18:42 2015 +0100
+++ b/Resources/Configuration.json	Thu Nov 12 14:36:27 2015 +0100
@@ -190,17 +190,19 @@
    **/
 
   // Dictionary of symbolic names for the user-defined metadata. Each
-  // entry must map a number between 1024 and 65535 to an unique
-  // string.
+  // entry must map an unique string to an unique number between 1024
+  // and 65535.
   "UserMetadata" : {
     // "Sample" : 1024
   },
 
   // Dictionary of symbolic names for the user-defined types of
-  // attached files. Each entry must map a number between 1024 and
-  // 65535 to an unique string.
+  // attached files. Each entry must map an unique string to an unique
+  // number between 1024 and 65535. Optionally, a second argument can
+  // provided to specify a MIME content type for the attachment.
   "UserContentType" : {
     // "sample" : 1024
+    // "sample2" : [ 1025, "application/pdf" ]
   },
 
   // Number of seconds without receiving any instance before a