# HG changeset patch # User Sebastien Jodogne # Date 1447335387 -3600 # Node ID 53e045b5a8ec3f5d2ded66cdcc7fbda74e85b44d # Parent 8790488ae98b9b9ab3f55cb5671e8b25d75e3af8 MIME content type can be associated to custom attachments (cf. "UserContentType") diff -r 8790488ae98b -r 53e045b5a8ec Core/EnumerationDictionary.h --- 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 diff -r 8790488ae98b -r 53e045b5a8ec Core/Enumerations.cpp --- 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) diff -r 8790488ae98b -r 53e045b5a8ec Core/Enumerations.h --- 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); diff -r 8790488ae98b -r 53e045b5a8ec Core/FileStorage/StorageAccessor.cpp --- 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); diff -r 8790488ae98b -r 53e045b5a8ec Core/FileStorage/StorageAccessor.h --- 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); }; } diff -r 8790488ae98b -r 53e045b5a8ec NEWS --- 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 ------- diff -r 8790488ae98b -r 53e045b5a8ec OrthancServer/OrthancInitialization.cpp --- 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; } } diff -r 8790488ae98b -r 53e045b5a8ec OrthancServer/ServerContext.cpp --- 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)); } diff -r 8790488ae98b -r 53e045b5a8ec OrthancServer/ServerEnumerations.cpp --- 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 namespace Orthanc { + typedef std::map MimeTypes; + static boost::mutex enumerationsMutex_; static Toolbox::EnumerationDictionary dictMetadataType_; static Toolbox::EnumerationDictionary dictContentType_; + static MimeTypes mimeTypes_; void InitializeServerEnumerations() { @@ -72,10 +76,26 @@ if (metadata < static_cast(MetadataType_StartUser) || metadata > static_cast(MetadataType_EndUser)) { + LOG(ERROR) << "A user content type must have index between " + << static_cast(MetadataType_StartUser) << " and " + << static_cast(MetadataType_EndUser) << ", but \"" + << name << "\" has index " << metadata; + throw OrthancException(ErrorCode_ParameterOutOfRange); } - dictMetadataType_.Add(static_cast(metadata), name); + MetadataType type = static_cast(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(FileContentType_StartUser) || contentType > static_cast(FileContentType_EndUser)) { + LOG(ERROR) << "A user content type must have index between " + << static_cast(FileContentType_StartUser) << " and " + << static_cast(FileContentType_EndUser) << ", but \"" + << name << "\" has index " << contentType; + throw OrthancException(ErrorCode_ParameterOutOfRange); } - dictContentType_.Add(static_cast(contentType), name); + FileContentType type = static_cast(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_); diff -r 8790488ae98b -r 53e045b5a8ec OrthancServer/ServerEnumerations.h --- 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); diff -r 8790488ae98b -r 53e045b5a8ec Resources/Configuration.json --- 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