# HG changeset patch # User Sebastien Jodogne # Date 1544470393 -3600 # Node ID d547f998c947c2e8c46fedf7736716813a4d7513 # Parent eff50153a7b30d704091bb4937a25c21779fd7e9# Parent 657d77bb13439abc555096f3f1325d1d4a63f11c integration mainline->db-changes diff -r eff50153a7b3 -r d547f998c947 Core/DicomParsing/ParsedDicomFile.cpp --- 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; diff -r eff50153a7b3 -r d547f998c947 Core/Enumerations.cpp --- 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); diff -r eff50153a7b3 -r d547f998c947 Core/Enumerations.h --- 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 }; diff -r eff50153a7b3 -r d547f998c947 Core/IDynamicObject.h --- 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 - 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_; } }; } diff -r eff50153a7b3 -r d547f998c947 Core/OrthancException.h --- 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 details_; public: diff -r eff50153a7b3 -r d547f998c947 Core/SerializationToolbox.cpp --- 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, diff -r eff50153a7b3 -r d547f998c947 Core/SystemToolbox.cpp --- 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; } } diff -r eff50153a7b3 -r d547f998c947 NEWS --- 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 ----------- diff -r eff50153a7b3 -r d547f998c947 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- 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& 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 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 query; + SerializationToolbox::ReadMapOfTags(query, request, KEY_QUERY); + + for (std::map::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(accessor_.GetItem())) + handler_(NULL) { + if (accessor_.IsValid()) + { + handler_ = &dynamic_cast(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 + 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 handler(new QueryRetrieveHandler(context)); + + { + const QueryAccessor parent(call); + const ResourceType level = parent.GetHandler().GetLevel(); + + const size_t index = boost::lexical_cast(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 query; + SerializationToolbox::ReadMapOfTags(query, request, KEY_QUERY); + + for (std::map::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 query(ParsedDicomFile::CreateFromJson(json, static_cast(0))); + std::auto_ptr query + (ParsedDicomFile::CreateFromJson(json, static_cast(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); + Register("/queries/{id}/answers/{index}/query-series", + QueryAnswerChildren); + Register("/queries/{id}/answers/{index}/query-studies", + QueryAnswerChildren); Register("/queries/{id}/level", GetQueryLevel); Register("/queries/{id}/modality", GetQueryModality); Register("/queries/{id}/query", GetQueryArguments); diff -r eff50153a7b3 -r d547f998c947 OrthancServer/QueryRetrieveHandler.cpp --- 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(); diff -r eff50153a7b3 -r d547f998c947 OrthancServer/QueryRetrieveHandler.h --- 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(); diff -r eff50153a7b3 -r d547f998c947 OrthancServer/ServerEnumerations.h --- 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, diff -r eff50153a7b3 -r d547f998c947 OrthancServer/ServerIndex.cpp --- 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(compressedSize); - target["DiskSizeMB"] = static_cast(compressedSize / MEGA_BYTES); + target["DiskSize"] = boost::lexical_cast(diskSize); + target["DiskSizeMB"] = static_cast(diskSize / MEGA_BYTES); target["UncompressedSize"] = boost::lexical_cast(uncompressedSize); target["UncompressedSizeMB"] = static_cast(uncompressedSize / MEGA_BYTES); + target["DicomDiskSize"] = boost::lexical_cast(dicomDiskSize); + target["DicomDiskSizeMB"] = static_cast(dicomDiskSize / MEGA_BYTES); + target["DicomUncompressedSize"] = boost::lexical_cast(dicomUncompressedSize); + target["DicomUncompressedSizeMB"] = static_cast(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); } diff -r eff50153a7b3 -r d547f998c947 OrthancServer/ServerIndex.h --- 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& result, diff -r eff50153a7b3 -r d547f998c947 Plugins/Engine/OrthancPlugins.cpp --- 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(parameters); + *p.result = EnumerationToString(SystemToolbox::AutodetectMimeType(p.argument)); + return true; + } + default: return false; } diff -r eff50153a7b3 -r d547f998c947 Plugins/Include/orthanc/OrthancCPlugin.h --- 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, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + #ifdef __cplusplus } #endif diff -r eff50153a7b3 -r d547f998c947 Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- 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 diff -r eff50153a7b3 -r d547f998c947 Plugins/Samples/Common/OrthancPluginCppWrapper.h --- 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); diff -r eff50153a7b3 -r d547f998c947 Resources/CMake/BoostConfiguration.cmake --- 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}) diff -r eff50153a7b3 -r d547f998c947 Resources/CMake/BoostConfiguration.sh --- 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} diff -r eff50153a7b3 -r d547f998c947 Resources/CMake/OrthancFrameworkConfiguration.cmake --- 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 ##################################################################### diff -r eff50153a7b3 -r d547f998c947 Resources/CMake/OrthancFrameworkParameters.cmake diff -r eff50153a7b3 -r d547f998c947 Resources/Configuration.json --- 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 } diff -r eff50153a7b3 -r d547f998c947 Resources/DownloadOrthancFramework.cmake --- 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() diff -r eff50153a7b3 -r d547f998c947 Resources/WindowsResources.rc --- 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}" diff -r eff50153a7b3 -r d547f998c947 UnitTestsSources/UnitTestsMain.cpp --- 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); }