changeset 1549:e5e975e9b738

refactoring and simplification of StorageAccessor
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 17 Aug 2015 10:47:04 +0200
parents e9325f3ac496
children 2c7d5eb588e6
files CMakeLists.txt Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.h Core/FileStorage/FileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.h Core/FileStorage/StorageAccessor.cpp Core/FileStorage/StorageAccessor.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/main.cpp UnitTestsSources/FileStorageTests.cpp UnitTestsSources/ServerIndexTests.cpp
diffstat 12 files changed, 203 insertions(+), 599 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Aug 14 11:52:03 2015 +0200
+++ b/CMakeLists.txt	Mon Aug 17 10:47:04 2015 +0200
@@ -97,8 +97,6 @@
   Core/Enumerations.cpp
   Core/FileStorage/FilesystemStorage.cpp
   Core/FileStorage/StorageAccessor.cpp
-  Core/FileStorage/CompressedFileStorageAccessor.cpp
-  Core/FileStorage/FileStorageAccessor.cpp
   Core/HttpClient.cpp
   Core/HttpServer/BufferHttpSender.cpp
   Core/HttpServer/EmbeddedResourceHttpHandler.cpp
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "CompressedFileStorageAccessor.h"
-
-#include "../HttpServer/BufferHttpSender.h"
-#include "../Logging.h"
-#include "../OrthancException.h"
-#include "../Uuid.h"
-#include "FileStorageAccessor.h"
-
-#include <memory>
-
-namespace Orthanc
-{
-  FileInfo CompressedFileStorageAccessor::WriteInternal(const void* data,
-                                                        size_t size,
-                                                        FileContentType type)
-  {
-    std::string uuid = Toolbox::GenerateUuid();
-
-    std::string md5;
-
-    if (storeMD5_)
-    {
-      Toolbox::ComputeMD5(md5, data, size);
-    }
-
-    switch (compressionType_)
-    {
-    case CompressionType_None:
-    {
-      GetStorageArea().Create(uuid.c_str(), data, size, type);
-      return FileInfo(uuid, type, size, md5);
-    }
-
-    case CompressionType_ZlibWithSize:
-    {
-      std::string compressed;
-      zlib_.Compress(compressed, data, size);
-
-      std::string compressedMD5;
-      
-      if (storeMD5_)
-      {
-        Toolbox::ComputeMD5(compressedMD5, compressed);
-      }
-
-      if (compressed.size() > 0)
-      {
-        GetStorageArea().Create(uuid.c_str(), &compressed[0], compressed.size(), type);
-      }
-      else
-      {
-        GetStorageArea().Create(uuid.c_str(), NULL, 0, type);
-      }
-
-      return FileInfo(uuid, type, size, md5,
-                      CompressionType_ZlibWithSize, compressed.size(), compressedMD5);
-    }
-
-    default:
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  CompressedFileStorageAccessor::CompressedFileStorageAccessor() : 
-    storage_(NULL),
-    compressionType_(CompressionType_None)
-  {
-  }
-
-
-  CompressedFileStorageAccessor::CompressedFileStorageAccessor(IStorageArea& storage) : 
-    storage_(&storage),
-    compressionType_(CompressionType_None)
-  {
-  }
-
-
-  IStorageArea& CompressedFileStorageAccessor::GetStorageArea()
-  {
-    if (storage_ == NULL)
-    {
-      LOG(ERROR) << "No storage area is currently available";
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    return *storage_;
-  }
-
-
-  void CompressedFileStorageAccessor::Read(std::string& content,
-                                           const std::string& uuid,
-                                           FileContentType type)
-  {
-    switch (compressionType_)
-    {
-    case CompressionType_None:
-      GetStorageArea().Read(content, uuid, type);
-      break;
-
-    case CompressionType_ZlibWithSize:
-    {
-      std::string compressed;
-      GetStorageArea().Read(compressed, uuid, type);
-      IBufferCompressor::Uncompress(content, zlib_, compressed);
-      break;
-    }
-
-    default:
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-  HttpFileSender* CompressedFileStorageAccessor::ConstructHttpFileSender(const std::string& uuid,
-                                                                         FileContentType type)
-  {
-    switch (compressionType_)
-    {
-    case CompressionType_None:
-    {
-      FileStorageAccessor uncompressedAccessor(GetStorageArea());
-      return uncompressedAccessor.ConstructHttpFileSender(uuid, type);
-    }
-
-    case CompressionType_ZlibWithSize:
-    {
-      std::string compressed;
-      GetStorageArea().Read(compressed, uuid, type);
-
-      std::auto_ptr<BufferHttpSender> sender(new BufferHttpSender);
-      IBufferCompressor::Uncompress(sender->GetBuffer(), zlib_, compressed);
-
-      return sender.release();
-    }        
-
-    default:
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void  CompressedFileStorageAccessor::Remove(const std::string& uuid,
-                                              FileContentType type)
-  {
-    GetStorageArea().Remove(uuid, type);
-  }
-}
--- a/Core/FileStorage/CompressedFileStorageAccessor.h	Fri Aug 14 11:52:03 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IStorageArea.h"
-#include "StorageAccessor.h"
-#include "../Compression/ZlibCompressor.h"
-
-namespace Orthanc
-{
-  class CompressedFileStorageAccessor : public StorageAccessor
-  {
-  private:
-    IStorageArea* storage_;
-    ZlibCompressor zlib_;
-    CompressionType compressionType_;
-
-  protected:
-    virtual FileInfo WriteInternal(const void* data,
-                                   size_t size,
-                                   FileContentType type);
-
-  public: 
-    CompressedFileStorageAccessor();
-
-    CompressedFileStorageAccessor(IStorageArea& storage);
-
-    void SetStorageArea(IStorageArea& storage)
-    {
-      storage_ = &storage;
-    }
-
-    bool HasStorageArea() const
-    {
-      return storage_ != NULL;
-    }
-
-    IStorageArea& GetStorageArea();
-
-    void SetCompressionForNextOperations(CompressionType compression)
-    {
-      compressionType_ = compression;
-    }
-    
-    CompressionType GetCompressionForNextOperations()
-    {
-      return compressionType_;
-    }
-
-    virtual void Read(std::string& content,
-                      const std::string& uuid,
-                      FileContentType type);
-
-    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid,
-                                                    FileContentType type);
-
-    virtual void Remove(const std::string& uuid,
-                        FileContentType type);
-  };
-}
--- a/Core/FileStorage/FileStorageAccessor.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeaders.h"
-#include "FileStorageAccessor.h"
-
-#include "../HttpServer/BufferHttpSender.h"
-#include "../Uuid.h"
-
-#include <memory>
-#include <stdio.h>
-
-namespace Orthanc
-{
-  FileInfo FileStorageAccessor::WriteInternal(const void* data,
-                                              size_t size,
-                                              FileContentType type)
-  {
-    std::string md5;
-
-    if (storeMD5_)
-    {
-      Toolbox::ComputeMD5(md5, data, size);
-    }
-
-    std::string uuid = Toolbox::GenerateUuid();
-    storage_.Create(uuid.c_str(), data, size, type);
-
-    return FileInfo(uuid, type, size, md5);
-  }
-
-
-  HttpFileSender* FileStorageAccessor::ConstructHttpFileSender(const std::string& uuid,
-                                                               FileContentType type)
-  {
-    std::auto_ptr<BufferHttpSender> sender(new BufferHttpSender);
-
-    storage_.Read(sender->GetBuffer(), uuid, type);
-      
-    return sender.release();
-  }
-
-}
--- a/Core/FileStorage/FileStorageAccessor.h	Fri Aug 14 11:52:03 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "StorageAccessor.h"
-#include "IStorageArea.h"
-
-namespace Orthanc
-{
-  class FileStorageAccessor : public StorageAccessor
-  {
-  private:
-    IStorageArea& storage_;
-    
-  protected:
-    virtual FileInfo WriteInternal(const void* data,
-                                   size_t size,
-                                   FileContentType type);
-
-  public:
-    FileStorageAccessor(IStorageArea& storage) : storage_(storage)
-    {
-    }
-
-    virtual void Read(std::string& content,
-                      const std::string& uuid,
-                      FileContentType type)
-    {
-      storage_.Read(content, uuid, type);
-    }
-
-    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid,
-                                                    FileContentType type);
-
-    virtual void Remove(const std::string& uuid,
-                        FileContentType type)
-    {
-      storage_.Remove(uuid, type);
-    }
-  };
-}
--- a/Core/FileStorage/StorageAccessor.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ b/Core/FileStorage/StorageAccessor.cpp	Mon Aug 17 10:47:04 2015 +0200
@@ -33,31 +33,126 @@
 #include "../PrecompiledHeaders.h"
 #include "StorageAccessor.h"
 
+#include "../Compression/ZlibCompressor.h"
+#include "../OrthancException.h"
+#include "../HttpServer/HttpStreamTranscoder.h"
+
 namespace Orthanc
 {
-  FileInfo StorageAccessor::Write(const std::vector<uint8_t>& content,
-                                  FileContentType type)
+  FileInfo StorageAccessor::Write(const void* data,
+                                  size_t size,
+                                  FileContentType type,
+                                  CompressionType compression,
+                                  bool storeMd5)
   {
-    if (content.size() == 0)
+    std::string uuid = Toolbox::GenerateUuid();
+
+    std::string md5;
+
+    if (storeMd5)
+    {
+      Toolbox::ComputeMD5(md5, data, size);
+    }
+
+    switch (compression)
     {
-      return WriteInternal(NULL, 0, type);
-    }
-    else
-    {
-      return WriteInternal(&content[0], content.size(), type);
+      case CompressionType_None:
+      {
+        area_.Create(uuid, data, size, type);
+        return FileInfo(uuid, type, size, md5);
+      }
+
+      case CompressionType_ZlibWithSize:
+      {
+        ZlibCompressor zlib;
+
+        std::string compressed;
+        zlib.Compress(compressed, data, size);
+
+        std::string compressedMD5;
+      
+        if (storeMd5)
+        {
+          Toolbox::ComputeMD5(compressedMD5, compressed);
+        }
+
+        if (compressed.size() > 0)
+        {
+          area_.Create(uuid, &compressed[0], compressed.size(), type);
+        }
+        else
+        {
+          area_.Create(uuid, NULL, 0, type);
+        }
+
+        return FileInfo(uuid, type, size, md5,
+                        CompressionType_ZlibWithSize, compressed.size(), compressedMD5);
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
     }
   }
 
-  FileInfo StorageAccessor::Write(const std::string& content,
-                                  FileContentType type)
+
+  void StorageAccessor::Read(std::string& content,
+                             const FileInfo& info)
   {
-    if (content.size() == 0)
+    switch (info.GetCompressionType())
     {
-      return WriteInternal(NULL, 0, type);
+      case CompressionType_None:
+      {
+        area_.Read(content, info.GetUuid(), info.GetContentType());
+        break;
+      }
+
+      case CompressionType_ZlibWithSize:
+      {
+        ZlibCompressor zlib;
+
+        std::string compressed;
+        area_.Read(compressed, info.GetUuid(), info.GetContentType());
+        IBufferCompressor::Uncompress(content, zlib, compressed);
+        break;
+      }
+
+      default:
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
     }
-    else
-    {
-      return WriteInternal(&content[0], content.size(), type);
-    }
+
+    // TODO Check the validity of the uncompressed MD5?
+  }
+
+
+  void StorageAccessor::SetupSender(BufferHttpSender& sender,
+                                    const FileInfo& info)
+  {
+    Read(sender.GetBuffer(), info);
+    sender.SetContentType(GetMimeType(info.GetContentType()));
+    sender.SetContentFilename(info.GetUuid() + std::string(GetFileExtension(info.GetContentType())));
+  }
+
+
+  void StorageAccessor::AnswerFile(HttpOutput& output,
+                                   const FileInfo& info)
+  {
+    BufferHttpSender sender;
+    SetupSender(sender, info);
+  
+    HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
+    output.Answer(transcoder);
+  }
+
+
+  void StorageAccessor::AnswerFile(RestApiOutput& output,
+                                   const FileInfo& info)
+  {
+    BufferHttpSender sender;
+    SetupSender(sender, info);
+  
+    HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
+    output.AnswerStream(transcoder);
   }
 }
--- a/Core/FileStorage/StorageAccessor.h	Fri Aug 14 11:52:03 2015 +0200
+++ b/Core/FileStorage/StorageAccessor.h	Mon Aug 17 10:47:04 2015 +0200
@@ -32,8 +32,10 @@
 
 #pragma once
 
+#include "IStorageArea.h"
 #include "FileInfo.h"
-#include "../HttpServer/HttpFileSender.h"
+#include "../HttpServer/BufferHttpSender.h"
+#include "../RestApi/RestApiOutput.h"
 
 #include <vector>
 #include <string>
@@ -44,54 +46,44 @@
 {
   class StorageAccessor : boost::noncopyable
   {
-  protected:
-    bool storeMD5_;
+  private:
+    IStorageArea&  area_;
 
-    virtual FileInfo WriteInternal(const void* data,
-                                   size_t size,
-                                   FileContentType type) = 0;
+    void SetupSender(BufferHttpSender& sender,
+                     const FileInfo& info);
 
   public:
-    StorageAccessor()
-    {
-      storeMD5_ = true;
-    }
-
-    virtual ~StorageAccessor()
+    StorageAccessor(IStorageArea& area) : area_(area)
     {
     }
 
-    void SetStoreMD5(bool storeMD5)
-    {
-      storeMD5_ = storeMD5;
-    }
-
-    bool IsStoreMD5() const
-    {
-      return storeMD5_;
-    }
-
     FileInfo Write(const void* data,
                    size_t size,
-                   FileContentType type)
+                   FileContentType type,
+                   CompressionType compression,
+                   bool storeMd5);
+
+    FileInfo Write(const std::string& data, 
+                   FileContentType type,
+                   CompressionType compression,
+                   bool storeMd5)
     {
-      return WriteInternal(data, size, type);
+      return Write((data.size() == 0 ? NULL : data.c_str()),
+                   data.size(), type, compression, storeMd5);
     }
 
-    FileInfo Write(const std::vector<uint8_t>& content,
-                   FileContentType type);
-
-    FileInfo Write(const std::string& content,
-                   FileContentType type);
+    void Read(std::string& content,
+              const FileInfo& info);
 
-    virtual void Read(std::string& content,
-                      const std::string& uuid,
-                      FileContentType type) = 0;
+    void Remove(const FileInfo& info)
+    {
+      area_.Remove(info.GetUuid(), info.GetContentType());
+    }
 
-    virtual void Remove(const std::string& uuid,
-                        FileContentType type) = 0;
+    void AnswerFile(HttpOutput& output,
+                    const FileInfo& info);
 
-    virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid,
-                                                    FileContentType type) = 0;
+    void AnswerFile(RestApiOutput& output,
+                    const FileInfo& info);
   };
 }
--- a/OrthancServer/ServerContext.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Mon Aug 17 10:47:04 2015 +0200
@@ -33,6 +33,7 @@
 #include "PrecompiledHeadersServer.h"
 #include "ServerContext.h"
 
+#include "../Core/FileStorage/StorageAccessor.h"
 #include "../Core/HttpServer/FilesystemHttpSender.h"
 #include "../Core/HttpServer/HttpStreamTranscoder.h"
 #include "../Core/Logging.h"
@@ -97,9 +98,12 @@
   }
 
 
-  ServerContext::ServerContext(IDatabaseWrapper& database) :
+  ServerContext::ServerContext(IDatabaseWrapper& database,
+                               IStorageArea& area) :
     index_(*this, database),
+    area_(area),
     compressionEnabled_(false),
+    storeMD5_(true),
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE),
     scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10)),
@@ -164,10 +168,11 @@
     compressionEnabled_ = enabled;
   }
 
+
   void ServerContext::RemoveFile(const std::string& fileUuid,
                                  FileContentType type)
   {
-    accessor_.Remove(fileUuid, type);
+    area_.Remove(fileUuid, type);
   }
 
 
@@ -176,6 +181,8 @@
   {
     try
     {
+      StorageAccessor accessor(area_);
+
       DicomInstanceHasher hasher(dicom.GetSummary());
       resultPublicId = hasher.HashInstance();
 
@@ -213,18 +220,13 @@
         return StoreStatus_FilteredOut;
       }
 
-      if (compressionEnabled_)
-      {
-        // TODO Should we use "gzip" instead?
-        accessor_.SetCompressionForNextOperations(CompressionType_ZlibWithSize);
-      }
-      else
-      {
-        accessor_.SetCompressionForNextOperations(CompressionType_None);
-      }      
+      // TODO Should we use "gzip" instead?
+      CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None);
 
-      FileInfo dicomInfo = accessor_.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom);
-      FileInfo jsonInfo = accessor_.Write(dicom.GetJson().toStyledString(), FileContentType_DicomAsJson);
+      FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(), 
+                                          FileContentType_Dicom, compression, storeMD5_);
+      FileInfo jsonInfo = accessor.Write(dicom.GetJson().toStyledString(), 
+                                         FileContentType_DicomAsJson, compression, storeMD5_);
 
       ServerIndex::Attachments attachments;
       attachments.push_back(dicomInfo);
@@ -247,8 +249,8 @@
             
       if (status != StoreStatus_Success)
       {
-        accessor_.Remove(dicomInfo.GetUuid(), FileContentType_Dicom);
-        accessor_.Remove(jsonInfo.GetUuid(), FileContentType_DicomAsJson);
+        accessor.Remove(dicomInfo);
+        accessor.Remove(jsonInfo);
       }
 
       switch (status)
@@ -314,15 +316,8 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    IStorageArea& area = accessor_.GetStorageArea();
-
-    BufferHttpSender sender;
-    area.Read(sender.GetBuffer(), attachment.GetUuid(), content);
-    sender.SetContentType(GetMimeType(content));
-    sender.SetContentFilename(attachment.GetUuid() + std::string(GetFileExtension(content)));
-  
-    HttpStreamTranscoder transcoder(sender, attachment.GetCompressionType());
-    output.AnswerStream(transcoder);
+    StorageAccessor accessor(area_);
+    accessor.AnswerFile(output, attachment);
   }
 
 
@@ -350,17 +345,9 @@
     {
       throw OrthancException(ErrorCode_InternalError);
     }
-
-    if (uncompressIfNeeded)
-    {
-      accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
-    }
-    else
-    {
-      accessor_.SetCompressionForNextOperations(CompressionType_None);
-    }
-
-    accessor_.Read(result, attachment.GetUuid(), attachment.GetContentType());
+    
+    StorageAccessor accessor(area_);
+    accessor.Read(result, attachment);
   }
 
 
@@ -395,7 +382,7 @@
   void ServerContext::SetStoreMD5ForAttachments(bool storeMD5)
   {
     LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
-    accessor_.SetStoreMD5(storeMD5);
+    storeMD5_ = storeMD5;
   }
 
 
@@ -406,22 +393,16 @@
   {
     LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId;
     
-    if (compressionEnabled_)
-    {
-      // TODO Should we use "gzip" instead?
-      accessor_.SetCompressionForNextOperations(CompressionType_ZlibWithSize);
-    }
-    else
-    {
-      accessor_.SetCompressionForNextOperations(CompressionType_None);
-    }      
+    // TODO Should we use "gzip" instead?
+    CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None);
 
-    FileInfo info = accessor_.Write(data, size, attachmentType);
-    StoreStatus status = index_.AddAttachment(info, resourceId);
+    StorageAccessor accessor(area_);
+    FileInfo attachment = accessor.Write(data, size, attachmentType, compression, storeMD5_);
 
+    StoreStatus status = index_.AddAttachment(attachment, resourceId);
     if (status != StoreStatus_Success)
     {
-      accessor_.Remove(info.GetUuid(), info.GetContentType());
+      accessor.Remove(attachment);
       return false;
     }
     else
--- a/OrthancServer/ServerContext.h	Fri Aug 14 11:52:03 2015 +0200
+++ b/OrthancServer/ServerContext.h	Mon Aug 17 10:47:04 2015 +0200
@@ -35,7 +35,6 @@
 #include "../Core/MultiThreading/SharedMessageQueue.h"
 #include "../Core/Cache/MemoryCache.h"
 #include "../Core/Cache/SharedArchive.h"
-#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 #include "../Core/FileStorage/IStorageArea.h"
 #include "../Core/Lua/LuaContext.h"
 #include "../Core/RestApi/RestApiOutput.h"
@@ -108,8 +107,10 @@
 
 
     ServerIndex index_;
-    CompressedFileStorageAccessor accessor_;
+    IStorageArea& area_;
+
     bool compressionEnabled_;
+    bool storeMD5_;
     
     DicomCacheProvider provider_;
     boost::mutex dicomCacheMutex_;
@@ -150,15 +151,11 @@
       }
     };
 
-    ServerContext(IDatabaseWrapper& database);
+    ServerContext(IDatabaseWrapper& database,
+                  IStorageArea& area);
 
     ~ServerContext();
 
-    void SetStorageArea(IStorageArea& storage)
-    {
-      accessor_.SetStorageArea(storage);
-    }
-
     ServerIndex& GetIndex()
     {
       return index_;
@@ -199,7 +196,7 @@
 
     bool IsStoreMD5ForAttachments() const
     {
-      return accessor_.IsStoreMD5();
+      return storeMD5_;
     }
 
     ReusableDicomUserConnection& GetReusableDicomUserConnection()
--- a/OrthancServer/main.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ b/OrthancServer/main.cpp	Mon Aug 17 10:47:04 2015 +0200
@@ -521,8 +521,7 @@
                                    IStorageArea& storageArea,
                                    OrthancPlugins *plugins)
 {
-  ServerContext context(database);
-  context.SetStorageArea(storageArea);
+  ServerContext context(database, storageArea);
 
   context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
   context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
--- a/UnitTestsSources/FileStorageTests.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ b/UnitTestsSources/FileStorageTests.cpp	Mon Aug 17 10:47:04 2015 +0200
@@ -36,15 +36,14 @@
 #include <ctype.h>
 
 #include "../Core/FileStorage/FilesystemStorage.h"
-#include "../OrthancServer/ServerIndex.h"
+#include "../Core/FileStorage/StorageAccessor.h"
+#include "../Core/HttpServer/BufferHttpSender.h"
+#include "../Core/HttpServer/FilesystemHttpSender.h"
 #include "../Core/Logging.h"
+#include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
-#include "../Core/OrthancException.h"
 #include "../Core/Uuid.h"
-#include "../Core/HttpServer/FilesystemHttpSender.h"
-#include "../Core/HttpServer/BufferHttpSender.h"
-#include "../Core/FileStorage/FileStorageAccessor.h"
-#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
+#include "../OrthancServer/ServerIndex.h"
 
 using namespace Orthanc;
 
@@ -123,106 +122,64 @@
 }
 
 
-TEST(FileStorageAccessor, Simple)
+TEST(StorageAccessor, NoCompression)
 {
   FilesystemStorage s("UnitTestsStorage");
-  FileStorageAccessor accessor(s);
+  StorageAccessor accessor(s);
 
   std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  FileInfo info = accessor.Write(data, FileContentType_Dicom, CompressionType_None, true);
   
   std::string r;
-  accessor.Read(r, info.GetUuid(), FileContentType_Unknown);
-
-  ASSERT_EQ(data, r);
-  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(11u, info.GetCompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, NoCompression)
-{
-  FilesystemStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid(), FileContentType_Unknown);
+  accessor.Read(r, info);
 
   ASSERT_EQ(data, r);
   ASSERT_EQ(CompressionType_None, info.GetCompressionType());
   ASSERT_EQ(11u, info.GetUncompressedSize());
   ASSERT_EQ(11u, info.GetCompressedSize());
   ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+  ASSERT_EQ("3e25960a79dbc69b674cd4ec67a72c62", info.GetUncompressedMD5());
+  ASSERT_EQ(info.GetUncompressedMD5(), info.GetCompressedMD5());
 }
 
 
-TEST(FileStorageAccessor, NoCompression2)
+TEST(StorageAccessor, Compression)
 {
   FilesystemStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
+  StorageAccessor accessor(s);
 
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  std::vector<uint8_t> data;
-  StringToVector(data, "Hello world");
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_DicomAsJson, CompressionType_ZlibWithSize, true);
   
   std::string r;
-  accessor.Read(r, info.GetUuid(), FileContentType_Unknown);
-
-  ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size()));
-  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(11u, info.GetCompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, Compression)
-{
-  FilesystemStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  accessor.SetCompressionForNextOperations(CompressionType_ZlibWithSize);
-  std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid(), FileContentType_Unknown);
+  accessor.Read(r, info);
 
   ASSERT_EQ(data, r);
   ASSERT_EQ(CompressionType_ZlibWithSize, info.GetCompressionType());
   ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+  ASSERT_EQ(FileContentType_DicomAsJson, info.GetContentType());
+  ASSERT_EQ("3e25960a79dbc69b674cd4ec67a72c62", info.GetUncompressedMD5());
+  ASSERT_NE(info.GetUncompressedMD5(), info.GetCompressedMD5());
 }
 
 
-TEST(FileStorageAccessor, Mix)
+TEST(StorageAccessor, Mix)
 {
   FilesystemStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
+  StorageAccessor accessor(s);
 
   std::string r;
   std::string compressedData = "Hello";
   std::string uncompressedData = "HelloWorld";
 
-  accessor.SetCompressionForNextOperations(CompressionType_ZlibWithSize);
-  FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom);
+  FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom, CompressionType_ZlibWithSize, false);  
+  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom, CompressionType_None, false);
   
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom);
-  
-  accessor.SetCompressionForNextOperations(CompressionType_ZlibWithSize);
-  accessor.Read(r, compressedInfo.GetUuid(), FileContentType_Unknown);
+  accessor.Read(r, compressedInfo);
   ASSERT_EQ(compressedData, r);
 
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  accessor.Read(r, compressedInfo.GetUuid(), FileContentType_Unknown);
+  accessor.Read(r, uncompressedInfo);
+  ASSERT_EQ(uncompressedData, r);
   ASSERT_NE(compressedData, r);
 
   /*
--- a/UnitTestsSources/ServerIndexTests.cpp	Fri Aug 14 11:52:03 2015 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Mon Aug 17 10:47:04 2015 +0200
@@ -660,8 +660,7 @@
   Toolbox::RemoveFile(path + "/index");
   FilesystemStorage storage(path);
   DatabaseWrapper db;   // The SQLite DB is in memory
-  ServerContext context(db);
-  context.SetStorageArea(storage);
+  ServerContext context(db, storage);
   ServerIndex& index = context.GetIndex();
 
   ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
@@ -729,8 +728,7 @@
   Toolbox::RemoveFile(path + "/index");
   FilesystemStorage storage(path);
   DatabaseWrapper db;   // The SQLite DB is in memory
-  ServerContext context(db);
-  context.SetStorageArea(storage);
+  ServerContext context(db, storage);
   ServerIndex& index = context.GetIndex();
 
   index.SetMaximumStorageSize(10);