changeset 250:f23318b11b39

creation of zip files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Dec 2012 12:29:10 +0100
parents 5694365ecb96
children 4dc9d00c359c
files Core/Compression/HierarchicalZipWriter.cpp NEWS OrthancExplorer/explorer.html OrthancExplorer/explorer.js OrthancServer/OrthancRestApi.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h
diffstat 7 files changed, 298 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Compression/HierarchicalZipWriter.cpp	Wed Dec 05 09:28:06 2012 +0100
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Wed Dec 05 12:29:10 2012 +0100
@@ -48,10 +48,14 @@
     result.reserve(source.size());
     for (size_t i = 0; i < source.size(); i++)
     {
-      if (source[i] < 128 && 
-          source[i] >= 0)
+      char c = source[i];
+      if (c == '^')
+        c = ' ';
+
+      if (c < 128 && 
+          c >= 0)
       {
-        if (isspace(source[i])) 
+        if (isspace(c)) 
         {
           if (!lastSpace)
           {
@@ -59,11 +63,11 @@
             result.push_back(' ');
           }
         }
-        else if (isalnum(source[i]) || 
-                 source[i] == '.' || 
-                 source[i] == '_')
+        else if (isalnum(c) || 
+                 c == '.' || 
+                 c == '_')
         {
-          result.push_back(source[i]);
+          result.push_back(c);
           lastSpace = false;
         }
       }
--- a/NEWS	Wed Dec 05 09:28:06 2012 +0100
+++ b/NEWS	Wed Dec 05 12:29:10 2012 +0100
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* Download archives of patients, studies and serie as ZIP files
+
 
 Version 0.3.0 (2012/11/30)
 ==========================
--- a/OrthancExplorer/explorer.html	Wed Dec 05 09:28:06 2012 +0100
+++ b/OrthancExplorer/explorer.html	Wed Dec 05 12:29:10 2012 +0100
@@ -84,6 +84,7 @@
               <p>
                 <a href="#find-patients" data-role="button" data-icon="search">Go to patient finder</a>
                 <a href="#" data-role="button" data-icon="delete" id="patient-delete">Delete this patient</a>
+                <a href="#" data-role="button" data-icon="gear" id="patient-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -115,6 +116,7 @@
               </ul>
               <p>
                 <a href="#" data-role="button" data-icon="delete" id="study-delete">Delete this study</a>
+                <a href="#" data-role="button" data-icon="gear" id="study-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -148,8 +150,9 @@
               </ul>
               <p>
                 <a href="#" data-role="button" data-icon="delete" id="series-delete">Delete this series</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="series-preview">Preview this series</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="series-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="search" id="series-preview">Preview this series</a>
+                <a href="#" data-role="button" data-icon="forward" id="series-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="gear" id="series-archive">Download ZIP</a>
               </p>
             </div>
           </div>
@@ -185,8 +188,8 @@
                 <a href="#" data-role="button" data-icon="delete" id="instance-delete">Delete this instance</a>
                 <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-dicom">Download the DICOM file</a>
                 <a href="#" data-role="button" data-icon="arrow-d" id="instance-download-json">Download the JSON file</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-preview">Preview the instance</a>
-                <a href="#" data-role="button" data-icon="arrow-d" id="instance-store">Store in another DICOM modality</a>
+                <a href="#" data-role="button" data-icon="search" id="instance-preview">Preview the instance</a>
+                <a href="#" data-role="button" data-icon="forward" id="instance-store">Store in another DICOM modality</a>
               </p>
             </div>
           </div>
--- a/OrthancExplorer/explorer.js	Wed Dec 05 09:28:06 2012 +0100
+++ b/OrthancExplorer/explorer.js	Wed Dec 05 12:29:10 2012 +0100
@@ -626,6 +626,7 @@
 });
 
 
+
 $('#instance-preview').live('click', function(e) {
   if ($.mobile.pageData) {
     GetSingleResource('instances', $.mobile.pageData.uuid + '/frames', function(frames) {
@@ -768,3 +769,20 @@
   else
     $('.tag-name').hide();
 });
+
+
+$('#patient-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../patients/' + $.mobile.pageData.uuid + '/archive';
+});
+
+$('#study-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../studies/' + $.mobile.pageData.uuid + '/archive';
+});
+
+$('#series-archive').live('click', function(e) {
+  e.preventDefault();  //stop the browser from following
+  window.location.href = '../series/' + $.mobile.pageData.uuid + '/archive';
+});
+
--- a/OrthancServer/OrthancRestApi.cpp	Wed Dec 05 09:28:06 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Wed Dec 05 12:29:10 2012 +0100
@@ -296,7 +296,6 @@
  
   static void GetSystemInformation(RestApi::GetCall& call)
   {
-    RETRIEVE_CONTEXT(call);
     Json::Value result = Json::objectValue;
 
     result["Version"] = ORTHANC_VERSION;
@@ -353,57 +352,182 @@
 
   // Download of ZIP files ----------------------------------------------------
  
-  static void GetPatientArchive(RestApi::GetCall& call)
+
+  static std::string GetDirectoryNameInArchive(const Json::Value& resource,
+                                               ResourceType resourceType)
+  {
+    switch (resourceType)
+    {
+      case ResourceType_Patient:
+      {
+        std::string p = resource["MainDicomTags"]["PatientID"].asString();
+        std::string n = resource["MainDicomTags"]["PatientName"].asString();
+        return p + " " + n;
+      }
+
+      case ResourceType_Study:
+        return resource["MainDicomTags"]["StudyDescription"].asString();
+        
+      case ResourceType_Series:
+        return resource["MainDicomTags"]["SeriesDescription"].asString();
+        
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+  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)
+  {
+    Json::Value instance;
+    if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance))
+    {
+      return false;
+    }
+
+    std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm";
+    writer.OpenFile(filename.c_str());
+
+    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 (size_t 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 (size_t 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:
+        for (size_t i = 0; i < resource["Instances"].size(); i++)
+        {
+          if (!ArchiveInstance(writer, context, resource["Instances"][i].asString()))
+          {
+            return false;
+          }
+        }
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    writer.CloseDirectory();
+    return true;
+  }                                 
+
+  template <enum ResourceType resourceType>
+  static void GetArchive(RestApi::GetCall& call)
   {
     RETRIEVE_CONTEXT(call);
 
-    Json::Value patient;
-    if (!context.GetIndex().LookupResource(patient, call.GetUriComponent("id", ""), ResourceType_Patient))
-    {
-      return;
-    }
-
+    // Create a RAII for the temporary file to manage the ZIP file
     Toolbox::TemporaryFile tmp;
+    std::string id = call.GetUriComponent("id", "");
 
     {
+      // Create a ZIP writer
       HierarchicalZipWriter writer(tmp.GetPath().c_str());
-      
-      for (size_t i = 0; i < patient["Studies"].size(); i++)
-      {
-        Json::Value study;
-        if (context.GetIndex().LookupResource(study, patient["Studies"][i].asString(), ResourceType_Study))
-        {
-          writer.OpenDirectory(study["MainDicomTags"]["StudyDescription"].asString().c_str());
 
-          for (size_t i = 0; i < study["Series"].size(); i++)
-          {
-            Json::Value series;
-            if (context.GetIndex().LookupResource(series, study["Series"][i].asString(), ResourceType_Series))
-            {
-              std::string m = series["MainDicomTags"]["Modality"].asString();
-              std::string s = series["MainDicomTags"]["SeriesDescription"].asString();
-              writer.OpenDirectory((m + " " + s).c_str());
-
-              for (size_t i = 0; i < series["Instances"].size(); i++)
-              {
-                Json::Value instance;
-                if (context.GetIndex().LookupResource(instance, series["Instances"][i].asString(), ResourceType_Instance))
-                {
-                  writer.OpenFile(instance["MainDicomTags"]["SOPInstanceUID"].asString().c_str());
-                }
-              }
-
-              writer.CloseDirectory();
-            }
-          }
-
-          writer.CloseDirectory();
-        }
+      // 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().c_str());
+    sender.SetContentType("application/zip");
+    sender.SetDownloadFilename(id + ".zip");
+
+    // Send the ZIP
     call.GetOutput().AnswerFile(sender);
+
+    // The temporary file is automatically removed thanks to the RAII
   }
 
 
@@ -710,7 +834,10 @@
     Register("/series/{id}", GetSingleResource<ResourceType_Series>);
     Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>);
     Register("/studies/{id}", GetSingleResource<ResourceType_Study>);
-    Register("/patients/{id}/archive", GetPatientArchive);
+
+    Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>);
+    Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
+    Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
 
     Register("/instances/{id}/file", GetInstanceFile);
     Register("/instances/{id}/tags", GetInstanceTags<false>);
--- a/OrthancServer/ServerEnumerations.cpp	Wed Dec 05 09:28:06 2012 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Dec 05 12:29:10 2012 +0100
@@ -39,20 +39,20 @@
   {
     switch (type)
     {
-    case ResourceType_Patient:
-      return "Patient";
+      case ResourceType_Patient:
+        return "Patient";
 
-    case ResourceType_Study:
-      return "Study";
+      case ResourceType_Study:
+        return "Study";
 
-    case ResourceType_Series:
-      return "Series";
+      case ResourceType_Series:
+        return "Series";
 
-    case ResourceType_Instance:
-      return "Instance";
+      case ResourceType_Instance:
+        return "Instance";
       
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -61,20 +61,20 @@
   {
     switch (type)
     {
-    case ResourceType_Patient:
-      return "/patients/" + publicId;
+      case ResourceType_Patient:
+        return "/patients/" + publicId;
 
-    case ResourceType_Study:
-      return "/studies/" + publicId;
+      case ResourceType_Study:
+        return "/studies/" + publicId;
 
-    case ResourceType_Series:
-      return "/series/" + publicId;
+      case ResourceType_Series:
+        return "/series/" + publicId;
 
-    case ResourceType_Instance:
-      return "/instances/" + publicId;
+      case ResourceType_Instance:
+        return "/instances/" + publicId;
       
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -82,20 +82,20 @@
   {
     switch (status)
     {
-    case SeriesStatus_Complete:
-      return "Complete";
+      case SeriesStatus_Complete:
+        return "Complete";
 
-    case SeriesStatus_Missing:
-      return "Missing";
+      case SeriesStatus_Missing:
+        return "Missing";
 
-    case SeriesStatus_Inconsistent:
-      return "Inconsistent";
+      case SeriesStatus_Inconsistent:
+        return "Inconsistent";
 
-    case SeriesStatus_Unknown:
-      return "Unknown";
+      case SeriesStatus_Unknown:
+        return "Unknown";
 
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -103,17 +103,17 @@
   {
     switch (status)
     {
-    case StoreStatus_Success:
-      return "Success";
+      case StoreStatus_Success:
+        return "Success";
 
-    case StoreStatus_AlreadyStored:
-      return "AlreadyStored";
+      case StoreStatus_AlreadyStored:
+        return "AlreadyStored";
 
-    case StoreStatus_Failure:
-      return "Failure";
+      case StoreStatus_Failure:
+        return "Failure";
 
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
@@ -122,23 +122,61 @@
   {
     switch (type)
     {
-    case ChangeType_CompletedSeries:
-      return "CompletedSeries";
+      case ChangeType_CompletedSeries:
+        return "CompletedSeries";
+
+      case ChangeType_NewInstance:
+        return "NewInstance";
+
+      case ChangeType_NewPatient:
+        return "NewPatient";
+
+      case ChangeType_NewSeries:
+        return "NewSeries";
 
-    case ChangeType_NewInstance:
-      return "NewInstance";
+      case ChangeType_NewStudy:
+        return "NewStudy";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 
-    case ChangeType_NewPatient:
-      return "NewPatient";
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
 
-    case ChangeType_NewSeries:
-      return "NewSeries";
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
 
-    case ChangeType_NewStudy:
-      return "NewStudy";
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
 
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      case ResourceType_Study:
+        return ResourceType_Series;
+
+      case ResourceType_Series:
+        return ResourceType_Instance;
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 }
--- a/OrthancServer/ServerEnumerations.h	Wed Dec 05 09:28:06 2012 +0100
+++ b/OrthancServer/ServerEnumerations.h	Wed Dec 05 12:29:10 2012 +0100
@@ -97,4 +97,8 @@
   const char* ToString(StoreStatus status);
 
   const char* ToString(ChangeType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  ResourceType GetChildResourceType(ResourceType type);
 }