diff Framework/DownloadArea.cpp @ 0:95226b754d9e

initial release
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 17 Sep 2018 11:34:55 +0200
parents
children 4c3437217518
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/DownloadArea.cpp	Mon Sep 17 11:34:55 2018 +0200
@@ -0,0 +1,325 @@
+/**
+ * Transfers accelerator plugin for Orthanc
+ * Copyright (C) 2018 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DownloadArea.h"
+
+#include <Core/Compression/GzipCompressor.h>
+#include <Core/Logging.h>
+#include <Core/SystemToolbox.h>
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+namespace OrthancPlugins
+{
+  class DownloadArea::Instance::Writer : public boost::noncopyable
+  {
+  private:
+    boost::filesystem::ofstream stream_;
+        
+  public:
+    Writer(Orthanc::TemporaryFile& f,
+           bool create) 
+    {
+      if (create)
+      {
+        // Create the file.
+        stream_.open(f.GetPath(), std::ofstream::out | std::ofstream::binary);
+      }
+      else
+      {
+        // Open the existing file to modify it. The "in" mode is
+        // necessary, otherwise previous content is lost by
+        // truncation (as an ofstream defaults to std::ios::trunc,
+        // the flag to truncate the existing content).
+        stream_.open(f.GetPath(), std::ofstream::in | std::ofstream::out | std::ofstream::binary);
+      }
+
+      if (!stream_.good())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+      }
+    }
+
+    void Write(size_t offset,
+               const void* data,
+               size_t size)
+    {
+      stream_.seekp(offset);
+      stream_.write(reinterpret_cast<const char*>(data), size);
+    }
+  };
+
+
+  DownloadArea::Instance::Instance(const DicomInstanceInfo& info) :
+    info_(info)
+  {
+    Writer writer(file_, true);
+
+    // Create a sparse file of expected size
+    if (info_.GetSize() != 0)
+    {
+      writer.Write(info_.GetSize() - 1, "", 1);
+    }
+  }
+
+
+  void DownloadArea::Instance::WriteChunk(size_t offset,
+                                          const void* data,
+                                          size_t size)
+  {
+    if (offset + size > info_.GetSize())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else if (size > 0)
+    {
+      Writer writer(file_, false);
+      writer.Write(offset, data, size);
+    }
+  }
+
+  
+  void DownloadArea::Instance::Commit(OrthancPluginContext* context,
+                                      bool simulate) const
+  {
+    std::string content;
+    Orthanc::SystemToolbox::ReadFile(content, file_.GetPath());
+
+    std::string md5;
+    Orthanc::Toolbox::ComputeMD5(md5, content);
+
+    if (md5 == info_.GetMD5())
+    {
+      if (!simulate)
+      {
+        if (context == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+
+        Json::Value result;
+        if (!RestApiPost(result, context, "/instances", 
+                         content.empty() ? NULL : content.c_str(), content.size(),
+                         false))
+        {
+          LOG(ERROR) << "Cannot import a transfered DICOM instance into Orthanc: "
+                     << info_.GetId();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+        }
+      }
+    }
+    else
+    {
+      LOG(ERROR) << "Bad MD5 sum in a transfered DICOM instance: " << info_.GetId();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+    }
+  }
+
+
+  void DownloadArea::Clear()
+  {
+    for (Instances::iterator it = instances_.begin(); 
+         it != instances_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        delete it->second;
+        it->second = NULL;
+      }
+    }
+
+    instances_.clear();
+  }
+
+
+  DownloadArea::Instance& DownloadArea::LookupInstance(const std::string& id)
+  {
+    Instances::iterator it = instances_.find(id);
+
+    if (it == instances_.end())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+    else if (it->first != id ||
+             it->second == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    else
+    {
+      return *it->second;
+    }
+  }
+
+
+  void DownloadArea::WriteUncompressedBucket(const TransferBucket& bucket,
+                                             const void* data,
+                                             size_t size)
+  {
+    if (size != bucket.GetTotalSize())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    if (size == 0)
+    {
+      return;
+    }
+
+    size_t pos = 0;
+
+    for (size_t i = 0; i < bucket.GetChunksCount(); i++)
+    {
+      size_t chunkSize = bucket.GetChunkSize(i);
+      size_t offset = bucket.GetChunkOffset(i);
+
+      if (pos + chunkSize > size)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      Instance& instance = LookupInstance(bucket.GetChunkInstanceId(i));
+      instance.WriteChunk(offset, reinterpret_cast<const char*>(data) + pos, chunkSize);
+
+      pos += chunkSize;
+    }
+
+    if (pos != size)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  void DownloadArea::Setup(const std::vector<DicomInstanceInfo>& instances)
+  {
+    totalSize_ = 0;
+      
+    for (size_t i = 0; i < instances.size(); i++)
+    {
+      const std::string& id = instances[i].GetId();
+        
+      assert(instances_.find(id) == instances_.end());
+      instances_[id] = new Instance(instances[i]);
+
+      totalSize_ += instances[i].GetSize();
+    }
+  }
+    
+
+  void DownloadArea::CommitInternal(OrthancPluginContext* context,
+                                    bool simulate)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    for (Instances::iterator it = instances_.begin(); 
+         it != instances_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        it->second->Commit(context, simulate);
+        delete it->second;
+        it->second = NULL;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  DownloadArea::DownloadArea(const TransferScheduler& scheduler)
+  {
+    std::vector<DicomInstanceInfo> instances;
+    scheduler.ListInstances(instances);
+    Setup(instances);
+  }
+
+
+  void DownloadArea::WriteBucket(const TransferBucket& bucket,
+                                 const void* data,
+                                 size_t size,
+                                 BucketCompression compression)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    switch (compression)
+    {
+      case BucketCompression_None:
+        WriteUncompressedBucket(bucket, data, size);
+        break;
+          
+      case BucketCompression_Gzip:
+      {
+        std::string uncompressed;
+        Orthanc::GzipCompressor compressor;
+        compressor.Uncompress(uncompressed, data, size);
+        WriteUncompressedBucket(bucket, uncompressed.c_str(), uncompressed.size());
+        break;
+      }
+
+      default:          
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void DownloadArea::WriteInstance(const std::string& instanceId,
+                                   const void* data,
+                                   size_t size)
+  {
+    std::string md5;
+    Orthanc::Toolbox::ComputeMD5(md5, data, size);
+      
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      Instances::const_iterator it = instances_.find(instanceId);
+      if (it == instances_.end() ||
+          it->second == NULL ||
+          it->second->GetInfo().GetId() != instanceId ||
+          it->second->GetInfo().GetSize() != size ||
+          it->second->GetInfo().GetMD5() != md5)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+      }
+      else
+      {
+        it->second->WriteChunk(0, data, size);
+      }
+    }
+  }
+
+
+  void DownloadArea::CheckMD5()
+  {
+    LOG(INFO) << "Checking MD5 sum without committing (testing)";
+    CommitInternal(NULL, true);
+  }
+
+
+  void DownloadArea::Commit(OrthancPluginContext* context)
+  {
+    LOG(INFO) << "Importing transfered DICOM files from the temporary download area into Orthanc";
+    CommitInternal(context, false);
+  }
+}