# HG changeset patch # User Sebastien Jodogne # Date 1447416741 -3600 # Node ID 776573e592da0dc5c1f899701aa8b618cf5ce58a # Parent 0f5c416969dc17b03627a4c3d17a6106b7665150 create media refactoring diff -r 0f5c416969dc -r 776573e592da OrthancServer/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Fri Nov 13 11:51:19 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Fri Nov 13 13:12:21 2015 +0100 @@ -54,197 +54,6 @@ { // Download of ZIP files ---------------------------------------------------- - static std::string GetDirectoryNameInArchive(const Json::Value& resource, - ResourceType resourceType) - { - std::string s; - const Json::Value& tags = resource["MainDicomTags"]; - - switch (resourceType) - { - case ResourceType_Patient: - { - std::string p = tags["PatientID"].asString(); - std::string n = tags["PatientName"].asString(); - s = p + " " + n; - break; - } - - case ResourceType_Study: - { - std::string p; - if (tags.isMember("AccessionNumber")) - { - p = tags["AccessionNumber"].asString() + " "; - } - - s = p + tags["StudyDescription"].asString(); - break; - } - - case ResourceType_Series: - { - std::string d = tags["SeriesDescription"].asString(); - std::string m = tags["Modality"].asString(); - s = m + " " + d; - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - // Get rid of special characters - return Toolbox::ConvertToAscii(s); - } - - static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, - ServerContext& context, - const Json::Value& resource, - ResourceType resourceType) - { - if (resourceType == ResourceType_Patient) - { - return true; - } - - ResourceType parentType = GetParentResourceType(resourceType); - Json::Value parent; - - switch (resourceType) - { - case ResourceType_Study: - { - if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) - { - return false; - } - - break; - } - - case ResourceType_Series: - if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || - !CreateRootDirectoryInArchive(writer, context, parent, parentType)) - { - return false; - } - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); - return true; - } - - static bool ArchiveInstance(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& instancePublicId, - const char* filename) - { - writer.OpenFile(filename); - - std::string dicom; - context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); - writer.Write(dicom); - - return true; - } - - static bool ArchiveInternal(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& publicId, - ResourceType resourceType, - bool isFirstLevel) - { - Json::Value resource; - if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) - { - return false; - } - - if (isFirstLevel && - !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) - { - return false; - } - - writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); - - switch (resourceType) - { - case ResourceType_Patient: - for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) - { - std::string studyId = resource["Studies"][i].asString(); - if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) - { - return false; - } - } - break; - - case ResourceType_Study: - for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) - { - std::string seriesId = resource["Series"][i].asString(); - if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) - { - return false; - } - } - break; - - case ResourceType_Series: - { - // Create a filename prefix, depending on the modality - char format[24] = "%08d.dcm"; - - if (resource["MainDicomTags"].isMember("Modality")) - { - std::string modality = resource["MainDicomTags"]["Modality"].asString(); - - if (modality.size() == 1) - { - snprintf(format, sizeof(format) - 1, "%c%%07d.dcm", toupper(modality[0])); - } - else if (modality.size() >= 2) - { - snprintf(format, sizeof(format) - 1, "%c%c%%06d.dcm", toupper(modality[0]), toupper(modality[1])); - } - } - - char filename[24]; - - for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) - { - snprintf(filename, sizeof(filename) - 1, format, i); - - std::string publicId = resource["Instances"][i].asString(); - - // This was the implementation up to Orthanc 0.7.0: - // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; - - if (!ArchiveInstance(writer, context, publicId, filename)) - { - return false; - } - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - writer.CloseDirectory(); - return true; - } - - static bool IsZip64Required(uint64_t uncompressedSize, unsigned int countInstances) { @@ -268,120 +77,6 @@ } - static bool IsZip64Required(ServerIndex& index, - const std::string& id) - { - uint64_t uncompressedSize; - uint64_t compressedSize; - unsigned int countStudies; - unsigned int countSeries; - unsigned int countInstances; - - index.GetStatistics(compressedSize, uncompressedSize, - countStudies, countSeries, countInstances, id); - - return IsZip64Required(uncompressedSize, countInstances); - } - - - template - static void GetArchive(RestApiGetCall& call) - { - ServerContext& context = OrthancRestApi::GetContext(call); - - std::string id = call.GetUriComponent("id", ""); - bool isZip64 = IsZip64Required(context.GetIndex(), id); - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - - // Store the requested resource into the ZIP - if (!ArchiveInternal(writer, context, id, resourceType, true)) - { - return; - } - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII - } - - - static void GetMediaArchive(RestApiGetCall& call) - { - ServerContext& context = OrthancRestApi::GetContext(call); - - std::string id = call.GetUriComponent("id", ""); - bool isZip64 = IsZip64Required(context.GetIndex(), id); - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - writer.OpenDirectory("IMAGES"); - - // Create the DICOMDIR writer - DicomDirWriter dicomDir; - - // Retrieve the list of the instances - std::list instances; - context.GetIndex().GetChildInstances(instances, id); - - size_t pos = 0; - for (std::list::const_iterator - it = instances.begin(); it != instances.end(); ++it, ++pos) - { - // "DICOM restricts the filenames on DICOM media to 8 - // characters (some systems wrongly use 8.3, but this does not - // conform to the standard)." - std::string filename = "IM" + boost::lexical_cast(pos); - writer.OpenFile(filename.c_str()); - - std::string dicom; - context.ReadFile(dicom, *it, FileContentType_Dicom); - writer.Write(dicom); - - ParsedDicomFile parsed(dicom); - dicomDir.Add("IMAGES", filename, parsed); - } - - // Add the DICOMDIR - writer.CloseDirectory(); - writer.OpenFile("DICOMDIR"); - std::string s; - dicomDir.Encode(s); - writer.Write(s); - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath()); - sender.SetContentType("application/zip"); - sender.SetContentFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerStream(sender); - - // The temporary file is automatically removed thanks to the RAII - } - - - - namespace { class ResourceIdentifiers @@ -726,10 +421,255 @@ out_ << " " << instanceId << std::endl; } }; + + + class ArchiveWriterVisitor : public IArchiveVisitor + { + private: + HierarchicalZipWriter& writer_; + ServerContext& context_; + char instanceFormat_[24]; + unsigned int countInstances_; + + static std::string GetTag(const DicomMap& tags, + const DicomTag& tag) + { + const DicomValue* v = tags.TestAndGetValue(tag); + if (v != NULL && + !v->IsBinary() && + !v->IsNull()) + { + return v->GetContent(); + } + else + { + return ""; + } + } + + public: + ArchiveWriterVisitor(HierarchicalZipWriter& writer, + ServerContext& context) : + writer_(writer), + context_(context) + { + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + std::string path; + + DicomMap tags; + if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) + { + switch (level) + { + case ResourceType_Patient: + path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); + break; + + case ResourceType_Study: + path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); + break; + + case ResourceType_Series: + { + std::string modality = GetTag(tags, DICOM_TAG_MODALITY); + path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); + + if (modality.size() == 0) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); + } + else if (modality.size() == 1) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", + toupper(modality[0])); + } + else if (modality.size() >= 2) + { + snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", + toupper(modality[0]), toupper(modality[1])); + } + + countInstances_ = 0; + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); + + if (path.empty()) + { + path = std::string("Unknown ") + EnumerationToString(level); + } + + writer_.OpenDirectory(path.c_str()); + } + + virtual void Close() + { + writer_.CloseDirectory(); + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + std::string content; + context_.ReadFile(content, dicom); + + char filename[24]; + snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_); + countInstances_ ++; + + writer_.OpenFile(filename); + writer_.Write(content); + } + + static void Apply(RestApiOutput& output, + ServerContext& context, + ArchiveIndex& archive, + const std::string& filename) + { + archive.Expand(context.GetIndex()); + + StatisticsVisitor stats; + archive.Apply(stats); + + const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); + + // Create a RAII for the temporary file to manage the ZIP file + Toolbox::TemporaryFile tmp; + + { + // Create a ZIP writer + HierarchicalZipWriter writer(tmp.GetPath().c_str()); + writer.SetZip64(isZip64); + + ArchiveWriterVisitor v(writer, context); + archive.Apply(v); + } + + // Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp.GetPath()); + sender.SetContentType("application/zip"); + sender.SetContentFilename(filename); + + // Send the ZIP + output.AnswerStream(sender); + + // The temporary file is automatically removed thanks to the RAII + } + }; + + + class MediaWriterVisitor : public IArchiveVisitor + { + private: + HierarchicalZipWriter& writer_; + DicomDirWriter dicomDir_; + ServerContext& context_; + unsigned int countInstances_; + + public: + MediaWriterVisitor(HierarchicalZipWriter& writer, + ServerContext& context) : + writer_(writer), + context_(context), + countInstances_(0) + { + } + + void EncodeDicomDir(std::string& result) + { + dicomDir_.Encode(result); + } + + virtual void Open(ResourceType level, + const std::string& publicId) + { + } + + virtual void Close() + { + } + + virtual void AddInstance(const std::string& instanceId, + const FileInfo& dicom) + { + // "DICOM restricts the filenames on DICOM media to 8 + // characters (some systems wrongly use 8.3, but this does not + // conform to the standard)." + std::string filename = "IM" + boost::lexical_cast(countInstances_); + writer_.OpenFile(filename.c_str()); + + std::string content; + context_.ReadFile(content, dicom); + writer_.Write(content); + + ParsedDicomFile parsed(content); + dicomDir_.Add("IMAGES", filename, parsed); + + countInstances_ ++; + } + + static void Apply(RestApiOutput& output, + ServerContext& context, + ArchiveIndex& archive, + const std::string& filename) + { + archive.Expand(context.GetIndex()); + + StatisticsVisitor stats; + archive.Apply(stats); + + const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); + + // Create a RAII for the temporary file to manage the ZIP file + Toolbox::TemporaryFile tmp; + + { + // Create a ZIP writer + HierarchicalZipWriter writer(tmp.GetPath().c_str()); + writer.SetZip64(isZip64); + writer.OpenDirectory("IMAGES"); + + // Create the DICOMDIR writer + DicomDirWriter dicomDir; + + MediaWriterVisitor v(writer, context); + archive.Apply(v); + + // Add the DICOMDIR + writer.CloseDirectory(); + writer.OpenFile("DICOMDIR"); + std::string s; + v.EncodeDicomDir(s); + writer.Write(s); + } + + // Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp.GetPath()); + sender.SetContentType("application/zip"); + sender.SetContentFilename(filename); + + // Send the ZIP + output.AnswerStream(sender); + + // The temporary file is automatically removed thanks to the RAII + } + }; } - static void CreateBatchArchive(RestApiPostCall& call) + static bool AddResourcesOfInterest(ArchiveIndex& archive, + RestApiPostCall& call) { ServerIndex& index = OrthancRestApi::GetIndex(call); @@ -737,43 +677,99 @@ if (call.ParseJsonRequest(resources) && resources.type() == Json::arrayValue) { - ArchiveIndex archive(ResourceType_Patient); // root - for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) { if (resources[i].type() != Json::stringValue) { - return; // Bad request + return false; // Bad request } ResourceIdentifiers resource(index, resources[i].asString()); archive.Add(index, resource); } - archive.Expand(index); + return true; + } + else + { + return false; + } + } - PrintVisitor v(std::cout); - archive.Apply(v); + + static void CreateBatchArchive(RestApiPostCall& call) + { + ArchiveIndex archive(ResourceType_Patient); // root - StatisticsVisitor s; - archive.Apply(s); + if (AddResourcesOfInterest(archive, call)) + { + ArchiveWriterVisitor::Apply(call.GetOutput(), + OrthancRestApi::GetContext(call), + archive, + "Archive.zip"); + } + } + - std::cout << s.GetUncompressedSize() << " " << s.GetInstancesCount() << std::endl; + static void CreateBatchMedia(RestApiPostCall& call) + { + ArchiveIndex archive(ResourceType_Patient); // root + + if (AddResourcesOfInterest(archive, call)) + { + MediaWriterVisitor::Apply(call.GetOutput(), + OrthancRestApi::GetContext(call), + archive, + "Archive.zip"); } } + static void CreateArchive(RestApiGetCall& call) + { + ServerIndex& index = OrthancRestApi::GetIndex(call); + + std::string id = call.GetUriComponent("id", ""); + ResourceIdentifiers resource(index, id); + + ArchiveIndex archive(ResourceType_Patient); // root + archive.Add(OrthancRestApi::GetIndex(call), resource); + + ArchiveWriterVisitor::Apply(call.GetOutput(), + OrthancRestApi::GetContext(call), + archive, + id + ".zip"); + } + + + static void CreateMedia(RestApiGetCall& call) + { + ServerIndex& index = OrthancRestApi::GetIndex(call); + + std::string id = call.GetUriComponent("id", ""); + ResourceIdentifiers resource(index, id); + + ArchiveIndex archive(ResourceType_Patient); // root + archive.Add(OrthancRestApi::GetIndex(call), resource); + + MediaWriterVisitor::Apply(call.GetOutput(), + OrthancRestApi::GetContext(call), + archive, + id + ".zip"); + } + void OrthancRestApi::RegisterArchive() { - Register("/patients/{id}/archive", GetArchive); - Register("/studies/{id}/archive", GetArchive); - Register("/series/{id}/archive", GetArchive); + Register("/patients/{id}/archive", CreateArchive); + Register("/studies/{id}/archive", CreateArchive); + Register("/series/{id}/archive", CreateArchive); - Register("/patients/{id}/media", GetMediaArchive); - Register("/studies/{id}/media", GetMediaArchive); - Register("/series/{id}/media", GetMediaArchive); + Register("/patients/{id}/media", CreateMedia); + Register("/studies/{id}/media", CreateMedia); + Register("/series/{id}/media", CreateMedia); - Register("/tools/archive", CreateBatchArchive); + Register("/tools/create-archive", CreateBatchArchive); + Register("/tools/create-media", CreateBatchMedia); } } diff -r 0f5c416969dc -r 776573e592da OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Fri Nov 13 11:51:19 2015 +0100 +++ b/OrthancServer/ServerContext.cpp Fri Nov 13 13:12:21 2015 +0100 @@ -407,6 +407,14 @@ } + void ServerContext::ReadFile(std::string& result, + const FileInfo& file) + { + StorageAccessor accessor(area_); + accessor.Read(result, file); + } + + IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId) { std::string content; diff -r 0f5c416969dc -r 776573e592da OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Fri Nov 13 11:51:19 2015 +0100 +++ b/OrthancServer/ServerContext.h Fri Nov 13 13:12:21 2015 +0100 @@ -201,6 +201,9 @@ FileContentType content, bool uncompressIfNeeded = true); + void ReadFile(std::string& result, + const FileInfo& file); + void SetStoreMD5ForAttachments(bool storeMD5); bool IsStoreMD5ForAttachments() const