changeset 2996:d547f998c947 db-changes

integration mainline->db-changes
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 10 Dec 2018 20:33:13 +0100
parents eff50153a7b3 (current diff) 657d77bb1343 (diff)
children c0a766e68d6c
files
diffstat 25 files changed, 502 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomParsing/ParsedDicomFile.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -1105,7 +1105,8 @@
 
       default:
         throw OrthancException(ErrorCode_NotImplemented,
-                               "Unsupported MIME type for the content of a new DICOM file: " + mime);
+                               "Unsupported MIME type for the content of a new DICOM file: " +
+                               std::string(EnumerationToString(mime)));
     }
 
     return true;
--- a/Core/Enumerations.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/Enumerations.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -51,8 +51,12 @@
   static const char* const MIME_HTML = "text/html";
   static const char* const MIME_JAVASCRIPT = "application/javascript";
   static const char* const MIME_JPEG2000 = "image/jp2";
+  static const char* const MIME_NACL = "application/x-nacl";
   static const char* const MIME_PLAIN_TEXT = "text/plain";
+  static const char* const MIME_PNACL = "application/x-pnacl";
+  static const char* const MIME_SVG = "image/svg+xml";
   static const char* const MIME_WEB_ASSEMBLY = "application/wasm";
+  static const char* const MIME_WOFF = "application/x-font-woff";
   static const char* const MIME_XML_2 = "text/xml";
   static const char* const MIME_ZIP = "application/zip";
 
@@ -1088,6 +1092,18 @@
       case MimeType_Zip:
         return MIME_ZIP;
                 
+      case MimeType_NaCl:
+        return MIME_NACL;
+                
+      case MimeType_PNaCl:
+        return MIME_PNACL;
+                
+      case MimeType_Svg:
+        return MIME_SVG;
+                
+      case MimeType_Woff:
+        return MIME_WOFF;
+                
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1680,6 +1696,22 @@
     {
       return MimeType_Zip;
     }
+    else if (mime == MIME_NACL)
+    {
+      return MimeType_NaCl;
+    }
+    else if (mime == MIME_PNACL)
+    {
+      return MimeType_PNaCl;
+    }
+    else if (mime == MIME_SVG)
+    {
+      return MimeType_Svg;
+    }
+    else if (mime == MIME_WOFF)
+    {
+      return MimeType_Woff;
+    }
     else
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/Core/Enumerations.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/Enumerations.h	Mon Dec 10 20:33:13 2018 +0100
@@ -86,21 +86,25 @@
   enum MimeType
   {
     MimeType_Binary,
+    MimeType_Css,
     MimeType_Dicom,
+    MimeType_Gif,
+    MimeType_Gzip,
     MimeType_Html,
+    MimeType_JavaScript,
     MimeType_Jpeg,
     MimeType_Jpeg2000,
     MimeType_Json,
+    MimeType_NaCl,
+    MimeType_PNaCl,
     MimeType_Pam,
     MimeType_Pdf,
     MimeType_PlainText,
     MimeType_Png,
+    MimeType_Svg,
+    MimeType_WebAssembly,
     MimeType_Xml,
-    MimeType_Gzip,
-    MimeType_JavaScript,
-    MimeType_Css,
-    MimeType_WebAssembly,
-    MimeType_Gif,
+    MimeType_Woff,  // Web Open Font Format
     MimeType_Zip
   };
 
--- a/Core/IDynamicObject.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/IDynamicObject.h	Mon Dec 10 20:33:13 2018 +0100
@@ -50,27 +50,27 @@
     {
     }
   };
+  
 
   /**
-   * This class is a simple implementation of a IDynamicObject that stores a single typed value
+   * This class is a simple implementation of a IDynamicObject that
+   * stores a single typed value.
    */
   template <typename T>
-  class SingleValueObject : public Orthanc::IDynamicObject
+  class SingleValueObject : public IDynamicObject
   {
   private:
-    T                  value_;
+    T  value_;
+    
   public:
-    SingleValueObject(const T& value) :
+    explicit SingleValueObject(const T& value) :
       value_(value)
     {
     }
-    virtual ~SingleValueObject()
-    {
-    }
 
     const T& GetValue() const
     {
-        return value_;
+      return value_;
     }
   };
 }
--- a/Core/OrthancException.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/OrthancException.h	Mon Dec 10 20:33:13 2018 +0100
@@ -52,7 +52,7 @@
     ErrorCode  errorCode_;
     HttpStatus httpStatus_;
 
-    // New in Orthanc 1.4.3
+    // New in Orthanc 1.5.0
     std::auto_ptr<std::string>  details_;
     
   public:
--- a/Core/SerializationToolbox.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/SerializationToolbox.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -36,10 +36,33 @@
 
 #include "OrthancException.h"
 
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "DicomParsing/FromDcmtkBridge.h"
+#endif
+
 namespace Orthanc
 {
   namespace SerializationToolbox
   {
+    static bool ParseTagInternal(DicomTag& tag,
+                                 const char* name)
+    {
+#if ORTHANC_ENABLE_DCMTK == 1
+      try
+      {
+        tag = FromDcmtkBridge::ParseTag(name);
+        return true;
+      }
+      catch (OrthancException& e)
+      {
+        return false;
+      }
+#else
+      return DicomTag::ParseHexadecimal(tag, name);
+#endif   
+    }
+
+    
     std::string ReadString(const Json::Value& value,
                            const std::string& field)
     {
@@ -191,7 +214,7 @@
         DicomTag tag(0, 0);
 
         if (arr[i].type() != Json::stringValue ||
-            !DicomTag::ParseHexadecimal(tag, arr[i].asCString()))
+            !ParseTagInternal(tag, arr[i].asCString()))
         {
           throw OrthancException(ErrorCode_BadFileFormat,
                                  "Set of DICOM tags expected in field: " + field);
@@ -263,7 +286,7 @@
 
         DicomTag tag(0, 0);
 
-        if (!DicomTag::ParseHexadecimal(tag, members[i].c_str()) ||
+        if (!ParseTagInternal(tag, members[i].c_str()) ||
             tmp.type() != Json::stringValue)
         {
           throw OrthancException(ErrorCode_BadFileFormat,
--- a/Core/SystemToolbox.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Core/SystemToolbox.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -649,7 +649,8 @@
     {
       return MimeType_JavaScript;
     }
-    else if (extension == ".json")
+    else if (extension == ".json" ||
+             extension == ".nmf"  /* manifest */)
     {
       return MimeType_Json;
     }
@@ -661,6 +662,14 @@
     {
       return MimeType_WebAssembly;
     }
+    else if (extension == ".nexe")
+    {
+      return MimeType_NaCl;
+    }
+    else if (extension == ".pexe")
+    {
+      return MimeType_PNaCl;
+    }
 
     // Images types
     else if (extension == ".jpg" ||
@@ -680,8 +689,21 @@
     {
       return MimeType_Pam;
     }
+    else if (extension == ".svg")
+    {
+      return MimeType_Svg;
+    }
+
+    // Various types
+    else if (extension == ".woff")
+    {
+      return MimeType_Woff;
+    }
+
+    // Default type
     else
     {
+      LOG(INFO) << "Unknown MIME type for extension \"" << extension << "\"";
       return MimeType_Binary;
     }
   }
--- a/NEWS	Thu Dec 06 15:58:08 2018 +0100
+++ b/NEWS	Mon Dec 10 20:33:13 2018 +0100
@@ -2,6 +2,9 @@
 ===============================
 
 
+Version 1.5.0 (2018-12-10)
+==========================
+
 General
 -------
 
@@ -25,14 +28,21 @@
 * New URI: "/studies/.../merge" to merge a study
 * New URI: "/studies/.../split" to split a study
 * POST-ing a DICOM file to "/instances" also answers the patient/study/series ID
-* GET "/modalities/..." now returns a JSON object instead of a JSON array
+* GET "/modalities/?expand" now returns a JSON object instead of a JSON array
+* New "Details" field in HTTP answers on error (cf. "HttpDescribeErrors" option)
 * New options to URI "/queries/.../answers": "?expand" and "?simplify"
-* New "Details" field in HTTP answers on error (cf. "HttpDescribeErrors" option)
+* New URIs to launch new C-FIND to explore the hierarchy of a C-FIND answer:
+  - "/queries/.../answers/.../query-instances" to C-FIND child instances
+  - "/queries/.../answers/.../query-series" to C-FIND child series
+  - "/queries/.../answers/.../query-studies" to C-FIND child studies
+* New "DicomDiskSize" and "DicomUncompressedSize" fields in statistics about resources
 
 Plugins
 -------
 
-* New function in the SDK: "OrthancPluginSetHttpErrorDetails()"
+* New functions in the SDK:
+  - "OrthancPluginSetHttpErrorDetails()"
+  - "OrthancPluginAutodetectMimeType()"
 
 Maintenance
 -----------
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -47,6 +47,11 @@
 
 namespace Orthanc
 {
+  static const char* const KEY_LEVEL = "Level";
+  static const char* const KEY_QUERY = "Query";
+  static const char* const KEY_RESOURCES = "Resources";
+
+  
   static RemoteModalityParameters MyGetModalityUsingSymbolicName(const std::string& name)
   {
     OrthancConfiguration::ReaderLock lock;
@@ -408,6 +413,27 @@
    * DICOM C-Find and C-Move SCU => Recommended since Orthanc 0.9.0
    ***************************************************************************/
 
+  static void AnswerQueryHandler(RestApiPostCall& call,
+                                 std::auto_ptr<QueryRetrieveHandler>& handler)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    if (handler.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    handler->Run();
+    
+    std::string s = context.GetQueryRetrieveArchive().Add(handler.release());
+    Json::Value result = Json::objectValue;
+    result["ID"] = s;
+    result["Path"] = "/queries/" + s;
+    
+    call.GetOutput().AnswerJson(result);
+  }
+
+  
   static void DicomQuery(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
@@ -415,31 +441,27 @@
 
     if (call.ParseJsonRequest(request) &&
         request.type() == Json::objectValue &&
-        request.isMember("Level") && request["Level"].type() == Json::stringValue &&
-        (!request.isMember("Query") || request["Query"].type() == Json::objectValue))
+        request.isMember(KEY_LEVEL) && request[KEY_LEVEL].type() == Json::stringValue &&
+        (!request.isMember(KEY_QUERY) || request[KEY_QUERY].type() == Json::objectValue))
     {
       std::auto_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
 
       handler->SetModality(call.GetUriComponent("id", ""));
-      handler->SetLevel(StringToResourceType(request["Level"].asCString()));
+      handler->SetLevel(StringToResourceType(request[KEY_LEVEL].asCString()));
 
-      if (request.isMember("Query"))
+      if (request.isMember(KEY_QUERY))
       {
-        Json::Value::Members tags = request["Query"].getMemberNames();
-        for (size_t i = 0; i < tags.size(); i++)
+        std::map<DicomTag, std::string> query;
+        SerializationToolbox::ReadMapOfTags(query, request, KEY_QUERY);
+
+        for (std::map<DicomTag, std::string>::const_iterator
+               it = query.begin(); it != query.end(); ++it)
         {
-          handler->SetQuery(FromDcmtkBridge::ParseTag(tags[i].c_str()),
-                            request["Query"][tags[i]].asString());
+          handler->SetQuery(it->first, it->second);
         }
       }
 
-      handler->Run();
-
-      std::string s = context.GetQueryRetrieveArchive().Add(handler.release());
-      Json::Value result = Json::objectValue;
-      result["ID"] = s;
-      result["Path"] = "/queries/" + s;
-      call.GetOutput().AnswerJson(result);
+      AnswerQueryHandler(call, handler);
     }
   }
 
@@ -469,19 +491,28 @@
     private:
       ServerContext&            context_;
       SharedArchive::Accessor   accessor_;
-      QueryRetrieveHandler&     handler_;
+      QueryRetrieveHandler*     handler_;
 
     public:
       QueryAccessor(RestApiCall& call) :
         context_(OrthancRestApi::GetContext(call)),
         accessor_(context_.GetQueryRetrieveArchive(), call.GetUriComponent("id", "")),
-        handler_(dynamic_cast<QueryRetrieveHandler&>(accessor_.GetItem()))
+        handler_(NULL)
       {
+        if (accessor_.IsValid())
+        {
+          handler_ = &dynamic_cast<QueryRetrieveHandler&>(accessor_.GetItem());
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
       }                     
 
       QueryRetrieveHandler& GetHandler() const
       {
-        return handler_;
+        assert(handler_ != NULL);
+        return *handler_;
       }
     };
 
@@ -509,7 +540,7 @@
     {
       if (expand)
       {
-        // New in Orthanc 1.4.3
+        // New in Orthanc 1.5.0
         DicomMap value;
         query.GetHandler().GetAnswer(value, i);
         
@@ -652,10 +683,127 @@
     DicomMap map;
     query.GetHandler().GetAnswer(map, index);
 
-    RestApi::AutoListChildren(call);
+    Json::Value answer = Json::arrayValue;
+    answer.append("content");
+    answer.append("retrieve");
+
+    switch (query.GetHandler().GetLevel())
+    {
+      case ResourceType_Patient:
+        answer.append("query-study");
+
+      case ResourceType_Study:
+        answer.append("query-series");
+
+      case ResourceType_Series:
+        answer.append("query-instances");
+        break;
+
+      default:
+        break;
+    }
+    
+    call.GetOutput().AnswerJson(answer);
   }
 
 
+  template <ResourceType CHILDREN_LEVEL>
+  static void QueryAnswerChildren(RestApiPostCall& call)
+  {
+    // New in Orthanc 1.5.0
+    assert(CHILDREN_LEVEL == ResourceType_Study ||
+           CHILDREN_LEVEL == ResourceType_Series ||
+           CHILDREN_LEVEL == ResourceType_Instance);
+    
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::auto_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
+      
+    {
+      const QueryAccessor parent(call);
+      const ResourceType level = parent.GetHandler().GetLevel();
+    
+      const size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", ""));
+
+      Json::Value request;
+
+      if (index >= parent.GetHandler().GetAnswersCount())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else if (CHILDREN_LEVEL == ResourceType_Study &&
+               level != ResourceType_Patient)
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+      else if (CHILDREN_LEVEL == ResourceType_Series &&
+               level != ResourceType_Patient &&
+               level != ResourceType_Study)
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }      
+      else if (CHILDREN_LEVEL == ResourceType_Instance &&
+               level != ResourceType_Patient &&
+               level != ResourceType_Study &&
+               level != ResourceType_Series)
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+      else if (!call.ParseJsonRequest(request))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object");
+      }
+      else
+      {
+        handler->SetModality(parent.GetHandler().GetModalitySymbolicName());
+        handler->SetLevel(CHILDREN_LEVEL);
+
+        if (request.isMember(KEY_QUERY))
+        {
+          std::map<DicomTag, std::string> query;
+          SerializationToolbox::ReadMapOfTags(query, request, KEY_QUERY);
+
+          for (std::map<DicomTag, std::string>::const_iterator
+                 it = query.begin(); it != query.end(); ++it)
+          {
+            handler->SetQuery(it->first, it->second);
+          }
+        }
+
+        DicomMap answer;
+        parent.GetHandler().GetAnswer(answer, index);
+
+        // This switch-case mimics "DicomUserConnection::Move()"
+        switch (parent.GetHandler().GetLevel())
+        {
+          case ResourceType_Patient:
+            handler->CopyStringTag(answer, DICOM_TAG_PATIENT_ID);
+            break;
+
+          case ResourceType_Study:
+            handler->CopyStringTag(answer, DICOM_TAG_STUDY_INSTANCE_UID);
+            break;
+
+          case ResourceType_Series:
+            handler->CopyStringTag(answer, DICOM_TAG_STUDY_INSTANCE_UID);
+            handler->CopyStringTag(answer, DICOM_TAG_SERIES_INSTANCE_UID);
+            break;
+
+          case ResourceType_Instance:
+            handler->CopyStringTag(answer, DICOM_TAG_STUDY_INSTANCE_UID);
+            handler->CopyStringTag(answer, DICOM_TAG_SERIES_INSTANCE_UID);
+            handler->CopyStringTag(answer, DICOM_TAG_SOP_INSTANCE_UID);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+      
+    AnswerQueryHandler(call, handler);
+  }
+  
 
 
   /***************************************************************************
@@ -701,12 +849,12 @@
     else
     {
       if (request.type() != Json::objectValue ||
-          !request.isMember("Resources"))
+          !request.isMember(KEY_RESOURCES))
       {
         return false;
       }
 
-      resources = &request["Resources"];
+      resources = &request[KEY_RESOURCES];
       if (!resources->isArray())
       {
         return false;
@@ -794,20 +942,17 @@
 
     Json::Value request;
 
-    static const char* RESOURCES = "Resources";
-    static const char* LEVEL = "Level";
-
     if (!call.ParseJsonRequest(request) ||
         request.type() != Json::objectValue ||
-        !request.isMember(RESOURCES) ||
-        !request.isMember(LEVEL) ||
-        request[RESOURCES].type() != Json::arrayValue ||
-        request[LEVEL].type() != Json::stringValue)
+        !request.isMember(KEY_RESOURCES) ||
+        !request.isMember(KEY_LEVEL) ||
+        request[KEY_RESOURCES].type() != Json::arrayValue ||
+        request[KEY_LEVEL].type() != Json::stringValue)
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
-    ResourceType level = StringToResourceType(request["Level"].asCString());
+    ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
     
     std::string localAet = Toolbox::GetJsonStringField
       (request, "LocalAet", context.GetDefaultLocalApplicationEntityTitle());
@@ -820,10 +965,10 @@
     DicomUserConnection connection(localAet, source);
     connection.Open();
     
-    for (Json::Value::ArrayIndex i = 0; i < request[RESOURCES].size(); i++)
+    for (Json::Value::ArrayIndex i = 0; i < request[KEY_RESOURCES].size(); i++)
     {
       DicomMap resource;
-      FromDcmtkBridge::FromJson(resource, request[RESOURCES][i]);
+      FromDcmtkBridge::FromJson(resource, request[KEY_RESOURCES][i]);
       
       connection.Move(targetAet, level, resource);
     }
@@ -1075,7 +1220,8 @@
       RemoteModalityParameters remote =
         MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-      std::auto_ptr<ParsedDicomFile> query(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0)));
+      std::auto_ptr<ParsedDicomFile> query
+        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0)));
 
       DicomFindAnswers answers(true);
 
@@ -1116,6 +1262,12 @@
     Register("/queries/{id}/answers/{index}", ListQueryAnswerOperations);
     Register("/queries/{id}/answers/{index}/content", GetQueryOneAnswer);
     Register("/queries/{id}/answers/{index}/retrieve", RetrieveOneAnswer);
+    Register("/queries/{id}/answers/{index}/query-instances",
+             QueryAnswerChildren<ResourceType_Instance>);
+    Register("/queries/{id}/answers/{index}/query-series",
+             QueryAnswerChildren<ResourceType_Series>);
+    Register("/queries/{id}/answers/{index}/query-studies",
+             QueryAnswerChildren<ResourceType_Study>);
     Register("/queries/{id}/level", GetQueryLevel);
     Register("/queries/{id}/modality", GetQueryModality);
     Register("/queries/{id}/query", GetQueryArguments);
--- a/OrthancServer/QueryRetrieveHandler.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -142,6 +142,24 @@
   }
 
 
+  void QueryRetrieveHandler::CopyStringTag(const DicomMap& from,
+                                           const DicomTag& tag)
+  {
+    const DicomValue* value = from.TestAndGetValue(tag);
+
+    if (value == NULL ||
+        value->IsNull() ||
+        value->IsBinary())
+    {
+      throw OrthancException(ErrorCode_InexistentTag);
+    }
+    else
+    {
+      SetQuery(tag, value->GetContent());
+    }
+  }
+
+
   size_t QueryRetrieveHandler::GetAnswersCount()
   {
     Run();
--- a/OrthancServer/QueryRetrieveHandler.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/QueryRetrieveHandler.h	Mon Dec 10 20:33:13 2018 +0100
@@ -86,6 +86,9 @@
       return query_;
     }
 
+    void CopyStringTag(const DicomMap& from,
+                       const DicomTag& tag);
+
     void Run();
 
     size_t GetAnswersCount();
--- a/OrthancServer/ServerEnumerations.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/ServerEnumerations.h	Mon Dec 10 20:33:13 2018 +0100
@@ -93,10 +93,10 @@
     GlobalProperty_FlushSleep = 2,
     GlobalProperty_AnonymizationSequence = 3,
     GlobalProperty_JobsRegistry = 5,
-    GlobalProperty_TotalCompressedSize = 6,     // Reserved for Orthanc > 1.4.3
-    GlobalProperty_TotalUncompressedSize = 7,   // Reserved for Orthanc > 1.4.3
-    GlobalProperty_Modalities = 20,             // New in Orthanc 1.4.3
-    GlobalProperty_Peers = 21,                  // New in Orthanc 1.4.3
+    GlobalProperty_TotalCompressedSize = 6,     // Reserved for Orthanc > 1.5.0
+    GlobalProperty_TotalUncompressedSize = 7,   // Reserved for Orthanc > 1.5.0
+    GlobalProperty_Modalities = 20,             // New in Orthanc 1.5.0
+    GlobalProperty_Peers = 21,                  // New in Orthanc 1.5.0
 
     // Reserved values for internal use by the database plugins
     GlobalProperty_DatabasePatchLevel = 4,
--- a/OrthancServer/ServerIndex.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/ServerIndex.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -1779,11 +1779,13 @@
   }
 
 
-  void ServerIndex::GetStatisticsInternal(/* out */ uint64_t& compressedSize, 
+  void ServerIndex::GetStatisticsInternal(/* out */ uint64_t& diskSize, 
                                           /* out */ uint64_t& uncompressedSize, 
                                           /* out */ unsigned int& countStudies, 
                                           /* out */ unsigned int& countSeries, 
                                           /* out */ unsigned int& countInstances, 
+                                          /* out */ uint64_t& dicomDiskSize, 
+                                          /* out */ uint64_t& dicomUncompressedSize, 
                                           /* in  */ int64_t id,
                                           /* in  */ ResourceType type)
   {
@@ -1793,8 +1795,10 @@
     countInstances = 0;
     countSeries = 0;
     countStudies = 0;
-    compressedSize = 0;
+    diskSize = 0;
     uncompressedSize = 0;
+    dicomDiskSize = 0;
+    dicomUncompressedSize = 0;
 
     while (!toExplore.empty())
     {
@@ -1813,7 +1817,13 @@
         FileInfo attachment;
         if (db_.LookupAttachment(attachment, resource, *it))
         {
-          compressedSize += attachment.GetCompressedSize();
+          if (attachment.GetContentType() == FileContentType_Dicom)
+          {
+            dicomDiskSize += attachment.GetCompressedSize();
+            dicomUncompressedSize += attachment.GetUncompressedSize();
+          }
+          
+          diskSize += attachment.GetCompressedSize();
           uncompressedSize += attachment.GetUncompressedSize();
         }
       }
@@ -1875,19 +1885,26 @@
     }
 
     uint64_t uncompressedSize;
-    uint64_t compressedSize;
+    uint64_t diskSize;
+    uint64_t dicomUncompressedSize;
+    uint64_t dicomDiskSize;
     unsigned int countStudies;
     unsigned int countSeries;
     unsigned int countInstances;
-    GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, 
-                          countSeries, countInstances, top, type);
+    GetStatisticsInternal(diskSize, uncompressedSize, countStudies, 
+                          countSeries, countInstances, dicomDiskSize, dicomUncompressedSize, top, type);
 
     target = Json::objectValue;
-    target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize);
-    target["DiskSizeMB"] = static_cast<unsigned int>(compressedSize / MEGA_BYTES);
+    target["DiskSize"] = boost::lexical_cast<std::string>(diskSize);
+    target["DiskSizeMB"] = static_cast<unsigned int>(diskSize / MEGA_BYTES);
     target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
     target["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
 
+    target["DicomDiskSize"] = boost::lexical_cast<std::string>(dicomDiskSize);
+    target["DicomDiskSizeMB"] = static_cast<unsigned int>(dicomDiskSize / MEGA_BYTES);
+    target["DicomUncompressedSize"] = boost::lexical_cast<std::string>(dicomUncompressedSize);
+    target["DicomUncompressedSizeMB"] = static_cast<unsigned int>(dicomUncompressedSize / MEGA_BYTES);
+
     switch (type)
     {
       // Do NOT add "break" below this point!
@@ -1907,11 +1924,13 @@
   }
 
 
-  void ServerIndex::GetStatistics(/* out */ uint64_t& compressedSize, 
+  void ServerIndex::GetStatistics(/* out */ uint64_t& diskSize, 
                                   /* out */ uint64_t& uncompressedSize, 
                                   /* out */ unsigned int& countStudies, 
                                   /* out */ unsigned int& countSeries, 
                                   /* out */ unsigned int& countInstances, 
+                                  /* out */ uint64_t& dicomDiskSize, 
+                                  /* out */ uint64_t& dicomUncompressedSize, 
                                   const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -1923,8 +1942,9 @@
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, 
-                          countSeries, countInstances, top, type);    
+    GetStatisticsInternal(diskSize, uncompressedSize, countStudies, 
+                          countSeries, countInstances, dicomDiskSize,
+                          dicomUncompressedSize, top, type);    
   }
 
 
--- a/OrthancServer/ServerIndex.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/OrthancServer/ServerIndex.h	Mon Dec 10 20:33:13 2018 +0100
@@ -97,11 +97,13 @@
                         Orthanc::ResourceType type,
                         const std::string& publicId);
 
-    void GetStatisticsInternal(/* out */ uint64_t& compressedSize, 
+    void GetStatisticsInternal(/* out */ uint64_t& diskSize, 
                                /* out */ uint64_t& uncompressedSize, 
                                /* out */ unsigned int& countStudies, 
                                /* out */ unsigned int& countSeries, 
                                /* out */ unsigned int& countInstances, 
+                               /* out */ uint64_t& dicomDiskSize, 
+                               /* out */ uint64_t& dicomUncompressedSize, 
                                /* in  */ int64_t id,
                                /* in  */ ResourceType type);
 
@@ -239,11 +241,13 @@
     void GetStatistics(Json::Value& target,
                        const std::string& publicId);
 
-    void GetStatistics(/* out */ uint64_t& compressedSize, 
+    void GetStatistics(/* out */ uint64_t& diskSize, 
                        /* out */ uint64_t& uncompressedSize, 
                        /* out */ unsigned int& countStudies, 
                        /* out */ unsigned int& countSeries, 
                        /* out */ unsigned int& countInstances, 
+                       /* out */ uint64_t& dicomDiskSize, 
+                       /* out */ uint64_t& dicomUncompressedSize, 
                        const std::string& publicId);
 
     void LookupIdentifierExact(std::list<std::string>& result,
--- a/Plugins/Engine/OrthancPlugins.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -3080,6 +3080,14 @@
         return true;
       }
 
+      case _OrthancPluginService_AutodetectMimeType:
+      {
+        const _OrthancPluginRetrieveStaticString& p =
+          *reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
+        *p.result = EnumerationToString(SystemToolbox::AutodetectMimeType(p.argument));
+        return true;
+      }
+
       default:
         return false;
     }
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Dec 10 20:33:13 2018 +0100
@@ -118,8 +118,8 @@
 #endif
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     4
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  3
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     5
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -423,7 +423,8 @@
     _OrthancPluginService_CallHttpClient2 = 27,
     _OrthancPluginService_GenerateUuid = 28,
     _OrthancPluginService_RegisterPrivateDictionaryTag = 29,
-
+    _OrthancPluginService_AutodetectMimeType = 30,
+    
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
     _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
@@ -6468,6 +6469,45 @@
 
 
 
+  typedef struct
+  {
+    const char** result;
+    const char*  argument;
+  } _OrthancPluginRetrieveStaticString;
+
+  /**
+   * @brief Detect the MIME type of a file.
+   *
+   * This function returns the MIME type of a file by inspecting its extension.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path Path to the file.
+   * @return The MIME type. This is a statically-allocated
+   * string, do not free it.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginAutodetectMimeType(
+    OrthancPluginContext*  context,
+    const char*            path)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginRetrieveStaticString params;
+    params.result = &result;
+    params.argument = path;
+
+    if (context->InvokeService(context, _OrthancPluginService_AutodetectMimeType, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -1356,59 +1356,23 @@
     }
   }
 
-  const char* GetMimeType(const std::string& path)
+  
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path)
   {
-    size_t dot = path.find_last_of('.');
-
-    std::string extension = (dot == std::string::npos) ? "" : path.substr(dot);
-    std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
+    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
 
-    if (extension == ".html")
-    {
-      return "text/html";
-    }
-    else if (extension == ".css")
-    {
-      return "text/css";
-    }
-    else if (extension == ".js")
-    {
-      return "application/javascript";
-    }
-    else if (extension == ".gif")
+    if (mime == NULL)
     {
-      return "image/gif";
-    }
-    else if (extension == ".svg")
-    {
-      return "image/svg+xml";
-    }
-    else if (extension == ".json")
-    {
-      return "application/json";
-    }
-    else if (extension == ".xml")
-    {
-      return "application/xml";
-    }
-    else if (extension == ".wasm")
-    {
-      return "application/wasm";
-    }
-    else if (extension == ".png")
-    {
-      return "image/png";
-    }
-    else if (extension == ".jpg" || extension == ".jpeg")
-    {
-      return "image/jpeg";
+      // Should never happen, just for safety
+      return "application/octet-stream";
     }
     else
     {
-      return "application/octet-stream";
+      return mime;
     }
   }
-
+#endif
 
 
 #if HAS_ORTHANC_PLUGIN_PEERS == 1
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Dec 06 15:58:08 2018 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon Dec 10 20:33:13 2018 +0100
@@ -71,7 +71,7 @@
 #  define HAS_ORTHANC_PLUGIN_JOB    0
 #endif
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 3)
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
 #  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  1
 #else
 #  define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS  0
@@ -492,8 +492,9 @@
 
   void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
 
-  const char* GetMimeType(const std::string& path);
-
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+  const char* AutodetectMimeType(const std::string& path);
+#endif
 
   void LogError(const std::string& message);
 
--- a/Resources/CMake/BoostConfiguration.cmake	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/CMake/BoostConfiguration.cmake	Mon Dec 10 20:33:13 2018 +0100
@@ -55,8 +55,8 @@
   
   set(BOOST_NAME boost_1_68_0)
   set(BOOST_VERSION 1.68.0)
-  set(BOOST_BCP_SUFFIX bcpdigest-1.4.3)
-  set(BOOST_MD5 "2d272566a72343766c523e2e32313c65")
+  set(BOOST_BCP_SUFFIX bcpdigest-1.5.0)
+  set(BOOST_MD5 "5297c45ffda809b2da84223bac591abe")
   set(BOOST_URL "http://www.orthanc-server.com/downloads/third-party/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
   set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
 
--- a/Resources/CMake/BoostConfiguration.sh	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/CMake/BoostConfiguration.sh	Mon Dec 10 20:33:13 2018 +0100
@@ -20,10 +20,10 @@
 ##   - Orthanc 1.3.1: Boost 1.65.1
 ##   - Orthanc 1.3.2: Boost 1.66.0
 ##   - Orthanc between 1.4.0 and 1.4.2: Boost 1.67.0
-##   - Orthanc >= 1.4.3: Boost 1.68.0
+##   - Orthanc >= 1.5.0: Boost 1.68.0
 
 BOOST_VERSION=1_68_0
-ORTHANC_VERSION=1.4.3
+ORTHANC_VERSION=1.5.0
 
 rm -rf /tmp/boost_${BOOST_VERSION}
 rm -rf /tmp/bcp/boost_${BOOST_VERSION}
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Mon Dec 10 20:33:13 2018 +0100
@@ -562,6 +562,34 @@
 endif()
 
 
+
+#####################################################################
+## Configuration of Orthanc versioning macros (new in Orthanc 1.5.0)
+#####################################################################
+
+if (ORTHANC_VERSION STREQUAL "mainline")
+  set(ORTHANC_VERSION_MAJOR "999")
+  set(ORTHANC_VERSION_MINOR "999")
+  set(ORTHANC_VERSION_REVISION "999")
+else()
+  string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)$" "\\1" ORTHANC_VERSION_MAJOR    ${ORTHANC_VERSION})
+  string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)$" "\\2" ORTHANC_VERSION_MINOR    ${ORTHANC_VERSION})
+  string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)$" "\\3" ORTHANC_VERSION_REVISION ${ORTHANC_VERSION})
+
+  if (NOT ORTHANC_VERSION STREQUAL
+      "${ORTHANC_VERSION_MAJOR}.${ORTHANC_VERSION_MINOR}.${ORTHANC_VERSION_REVISION}")
+    message(FATAL_ERROR "Error in the (x.y.z) format of the Orthanc version: ${ORTHANC_VERSION}")
+  endif()
+endif()
+
+add_definitions(
+  -DORTHANC_VERSION_MAJOR=${ORTHANC_VERSION_MAJOR}
+  -DORTHANC_VERSION_MINOR=${ORTHANC_VERSION_MINOR}
+  -DORTHANC_VERSION_REVISION=${ORTHANC_VERSION_REVISION}
+  )
+
+
+
 #####################################################################
 ## Gathering of all the source code
 #####################################################################
--- a/Resources/Configuration.json	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/Configuration.json	Mon Dec 10 20:33:13 2018 +0100
@@ -183,7 +183,7 @@
     /**
      * By default, the Orthanc SCP accepts all DICOM commands (C-GET,
      * C-STORE, C-FIND, C-MOVE) issued by the remote SCU
-     * modalities. Starting with Orthanc 1.4.3, it is possible to
+     * modalities. Starting with Orthanc 1.5.0, it is possible to
      * specify which DICOM commands are allowed, separately for each
      * remote modality, using the syntax below.
      **/
@@ -199,7 +199,7 @@
   },
 
   // Whether to store the DICOM modalities in the Orthanc database
-  // instead of in this configuration file (new in Orthanc 1.4.3)
+  // instead of in this configuration file (new in Orthanc 1.5.0)
   "DicomModalitiesInDatabase" : false,
 
   // Whether the Orthanc SCP allows incoming C-Echo requests, even
@@ -253,7 +253,7 @@
   },
 
   // Whether to store the Orthanc peers in the Orthanc database
-  // instead of in this configuration file (new in Orthanc 1.4.3)
+  // instead of in this configuration file (new in Orthanc 1.5.0)
   "OrthancPeersInDatabase" : false,
 
   // Parameters of the HTTP proxy to be used by Orthanc. If set to the
@@ -424,13 +424,13 @@
   // creating a new background job. Up to Orthanc 1.3.2, the implicit
   // behavior was to use synchronous C-Move. Between Orthanc 1.4.0 and
   // 1.4.2, the default behavior was set to asynchronous C-Move. Since
-  // Orthanc 1.4.3, the default behavior is synchronous C-Move
+  // Orthanc 1.5.0, the default behavior is synchronous C-Move
   // (backward compatibility with Orthanc <= 1.3.2).
   "SynchronousCMove" : true,
 
   // Maximum number of completed jobs that are kept in memory. A
   // processing job is considered as complete once it is tagged as
-  // "Success" or "Failure". Since Orthanc 1.4.3, a value of "0"
+  // "Success" or "Failure". Since Orthanc 1.5.0, a value of "0"
   // indicates to keep no job in memory (i.e. jobs are removed from
   // the history as soon as they are completed).
   "JobsHistorySize" : 10,
@@ -445,7 +445,7 @@
   // Maximum number of ZIP/media archives that are maintained by
   // Orthanc, as a response to the asynchronous creation of archives.
   // The least recently used archives get deleted as new archives are
-  // generated. This option was introduced in Orthanc 1.4.3, and has
+  // generated. This option was introduced in Orthanc 1.5.0, and has
   // no effect on the synchronous generation of archives.
   "MediaArchiveSize" : 1
 }
--- a/Resources/DownloadOrthancFramework.cmake	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/DownloadOrthancFramework.cmake	Mon Dec 10 20:33:13 2018 +0100
@@ -91,6 +91,8 @@
         set(ORTHANC_FRAMEWORK_MD5 "9b6f6114264b17ed421b574cd6476127")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.4.2")
         set(ORTHANC_FRAMEWORK_MD5 "d1ee84927dcf668e60eb5868d24b9394")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.0")
+        set(ORTHANC_FRAMEWORK_MD5 "4429d8d9dea4ff6648df80ec3c64d79e")
       endif()
     endif()
   endif()
--- a/Resources/WindowsResources.rc	Thu Dec 06 15:58:08 2018 +0100
+++ b/Resources/WindowsResources.rc	Mon Dec 10 20:33:13 2018 +0100
@@ -11,11 +11,11 @@
          BLOCK "${BLOCK}"
          BEGIN
             VALUE "Comments", "${RELEASE}"
-            VALUE "CompanyName", "University Hospital of Liege, Belgium"
+            VALUE "CompanyName", "Osimis SA, Belgium"
             VALUE "FileDescription", "${DESCRIPTION}"
             VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
             VALUE "InternalName", "${PRODUCT}"
-            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
+            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, and Osimis SA, Belgium"
             VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
             VALUE "OriginalFilename", "${FILENAME}"
             VALUE "ProductName", "${PRODUCT}"
--- a/UnitTestsSources/UnitTestsMain.cpp	Thu Dec 06 15:58:08 2018 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Mon Dec 10 20:33:13 2018 +0100
@@ -325,6 +325,30 @@
   ASSERT_EQ(MimeType_Jpeg, SystemToolbox::AutodetectMimeType("NOTES.jpg"));
   ASSERT_EQ(MimeType_Jpeg, SystemToolbox::AutodetectMimeType("NOTES.jpeg"));
   ASSERT_EQ(MimeType_Png, SystemToolbox::AutodetectMimeType("NOTES.png"));
+  ASSERT_EQ(MimeType_NaCl, SystemToolbox::AutodetectMimeType("NOTES.nexe"));
+  ASSERT_EQ(MimeType_Json, SystemToolbox::AutodetectMimeType("NOTES.nmf"));
+  ASSERT_EQ(MimeType_PNaCl, SystemToolbox::AutodetectMimeType("NOTES.pexe"));
+  ASSERT_EQ(MimeType_Svg, SystemToolbox::AutodetectMimeType("NOTES.svg"));
+  ASSERT_EQ(MimeType_Woff, SystemToolbox::AutodetectMimeType("NOTES.woff"));
+
+  // Test primitives from the "RegisterDefaultExtensions()" that was
+  // present in the sample "Serve Folders plugin" of Orthanc 1.4.2
+  ASSERT_STREQ("application/javascript", EnumerationToString(SystemToolbox::AutodetectMimeType(".js")));
+  ASSERT_STREQ("application/json", EnumerationToString(SystemToolbox::AutodetectMimeType(".json")));
+  ASSERT_STREQ("application/json", EnumerationToString(SystemToolbox::AutodetectMimeType(".nmf")));
+  ASSERT_STREQ("application/octet-stream", EnumerationToString(SystemToolbox::AutodetectMimeType("")));
+  ASSERT_STREQ("application/wasm", EnumerationToString(SystemToolbox::AutodetectMimeType(".wasm")));
+  ASSERT_STREQ("application/x-font-woff", EnumerationToString(SystemToolbox::AutodetectMimeType(".woff")));
+  ASSERT_STREQ("application/x-nacl", EnumerationToString(SystemToolbox::AutodetectMimeType(".nexe")));
+  ASSERT_STREQ("application/x-pnacl", EnumerationToString(SystemToolbox::AutodetectMimeType(".pexe")));
+  ASSERT_STREQ("application/xml", EnumerationToString(SystemToolbox::AutodetectMimeType(".xml")));
+  ASSERT_STREQ("image/gif", EnumerationToString(SystemToolbox::AutodetectMimeType(".gif")));
+  ASSERT_STREQ("image/jpeg", EnumerationToString(SystemToolbox::AutodetectMimeType(".jpeg")));
+  ASSERT_STREQ("image/jpeg", EnumerationToString(SystemToolbox::AutodetectMimeType(".jpg")));
+  ASSERT_STREQ("image/png", EnumerationToString(SystemToolbox::AutodetectMimeType(".png")));
+  ASSERT_STREQ("image/svg+xml", EnumerationToString(SystemToolbox::AutodetectMimeType(".svg")));
+  ASSERT_STREQ("text/css", EnumerationToString(SystemToolbox::AutodetectMimeType(".css")));
+  ASSERT_STREQ("text/html", EnumerationToString(SystemToolbox::AutodetectMimeType(".html")));
 }
 
 TEST(Toolbox, ComputeMD5)
@@ -711,23 +735,26 @@
   ASSERT_THROW(StringToJobState("nope"), OrthancException);
 
   ASSERT_EQ(MimeType_Binary, StringToMimeType(EnumerationToString(MimeType_Binary)));
+  ASSERT_EQ(MimeType_Css, StringToMimeType(EnumerationToString(MimeType_Css)));
   ASSERT_EQ(MimeType_Dicom, StringToMimeType(EnumerationToString(MimeType_Dicom)));
+  ASSERT_EQ(MimeType_Gif, StringToMimeType(EnumerationToString(MimeType_Gif)));
+  ASSERT_EQ(MimeType_Gzip, StringToMimeType(EnumerationToString(MimeType_Gzip)));
+  ASSERT_EQ(MimeType_Html, StringToMimeType(EnumerationToString(MimeType_Html)));
+  ASSERT_EQ(MimeType_JavaScript, StringToMimeType(EnumerationToString(MimeType_JavaScript)));
   ASSERT_EQ(MimeType_Jpeg, StringToMimeType(EnumerationToString(MimeType_Jpeg)));
   ASSERT_EQ(MimeType_Jpeg2000, StringToMimeType(EnumerationToString(MimeType_Jpeg2000)));
   ASSERT_EQ(MimeType_Json, StringToMimeType(EnumerationToString(MimeType_Json)));
+  ASSERT_EQ(MimeType_NaCl, StringToMimeType(EnumerationToString(MimeType_NaCl)));
+  ASSERT_EQ(MimeType_PNaCl, StringToMimeType(EnumerationToString(MimeType_PNaCl)));
+  ASSERT_EQ(MimeType_Pam, StringToMimeType(EnumerationToString(MimeType_Pam)));
   ASSERT_EQ(MimeType_Pdf, StringToMimeType(EnumerationToString(MimeType_Pdf)));
+  ASSERT_EQ(MimeType_PlainText, StringToMimeType(EnumerationToString(MimeType_PlainText)));
   ASSERT_EQ(MimeType_Png, StringToMimeType(EnumerationToString(MimeType_Png)));
-  ASSERT_EQ(MimeType_Xml, StringToMimeType(EnumerationToString(MimeType_Xml)));
+  ASSERT_EQ(MimeType_Svg, StringToMimeType(EnumerationToString(MimeType_Svg)));
+  ASSERT_EQ(MimeType_WebAssembly, StringToMimeType(EnumerationToString(MimeType_WebAssembly)));
   ASSERT_EQ(MimeType_Xml, StringToMimeType("application/xml"));
   ASSERT_EQ(MimeType_Xml, StringToMimeType("text/xml"));
-  ASSERT_EQ(MimeType_PlainText, StringToMimeType(EnumerationToString(MimeType_PlainText)));
-  ASSERT_EQ(MimeType_Pam, StringToMimeType(EnumerationToString(MimeType_Pam)));
-  ASSERT_EQ(MimeType_Html, StringToMimeType(EnumerationToString(MimeType_Html)));
-  ASSERT_EQ(MimeType_Gzip, StringToMimeType(EnumerationToString(MimeType_Gzip)));
-  ASSERT_EQ(MimeType_JavaScript, StringToMimeType(EnumerationToString(MimeType_JavaScript)));
-  ASSERT_EQ(MimeType_Gif, StringToMimeType(EnumerationToString(MimeType_Gif)));
-  ASSERT_EQ(MimeType_WebAssembly, StringToMimeType(EnumerationToString(MimeType_WebAssembly)));
-  ASSERT_EQ(MimeType_Css, StringToMimeType(EnumerationToString(MimeType_Css)));
+  ASSERT_EQ(MimeType_Xml, StringToMimeType(EnumerationToString(MimeType_Xml)));
   ASSERT_THROW(StringToMimeType("nope"), OrthancException);
 }