changeset 232:5368bbe813cf

refactoring of attachments
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 30 Nov 2012 14:22:27 +0100
parents 8098448bd827
children c11273198cef
files CMakeLists.txt Core/Enumerations.h Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.h Core/FileStorage/FileInfo.h Core/FileStorage/FileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.h Core/FileStorage/StorageAccessor.cpp Core/FileStorage/StorageAccessor.h NEWS OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/OrthancRestApi.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerEnumerations.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h UnitTests/FileStorage.cpp UnitTests/ServerIndex.cpp
diffstat 20 files changed, 381 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Nov 30 12:18:44 2012 +0100
+++ b/CMakeLists.txt	Fri Nov 30 14:22:27 2012 +0100
@@ -106,6 +106,7 @@
   Core/FileStorage.cpp
   Core/FileStorage/StorageAccessor.cpp
   Core/FileStorage/CompressedFileStorageAccessor.cpp
+  Core/FileStorage/FileStorageAccessor.cpp
   Core/HttpServer/EmbeddedResourceHttpHandler.cpp
   Core/HttpServer/FilesystemHttpHandler.cpp
   Core/HttpServer/HttpHandler.cpp
--- a/Core/Enumerations.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/Enumerations.h	Fri Nov 30 14:22:27 2012 +0100
@@ -64,9 +64,22 @@
     PixelFormat_Grayscale16
   };
 
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
   enum CompressionType
   {
     CompressionType_None = 1,
     CompressionType_Zlib = 2
   };
+
+  enum FileType
+  {
+    FileType_Dicom = 1,
+    FileType_Json = 2
+  };
 }
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -38,19 +38,25 @@
 
 namespace Orthanc
 {
-  std::string CompressedFileStorageAccessor::WriteInternal(const void* data,
-                                                           size_t size)
+  FileInfo CompressedFileStorageAccessor::WriteInternal(const void* data,
+                                                        size_t size,
+                                                        FileType type)
   {
     switch (compressionType_)
     {
     case CompressionType_None:
-      return storage_.Create(data, size);
+    {
+      std::string uuid = storage_.Create(data, size);
+      return FileInfo(uuid, type, size);
+    }
 
     case CompressionType_Zlib:
     {
       std::string compressed;
       zlib_.Compress(compressed, data, size);
-      return storage_.Create(compressed);
+      std::string uuid = storage_.Create(compressed);
+      return FileInfo(uuid, type, size, 
+                      CompressionType_Zlib, compressed.size());
     }
 
     default:
@@ -61,7 +67,7 @@
   CompressedFileStorageAccessor::CompressedFileStorageAccessor(FileStorage& storage) : 
     storage_(storage)
   {
-    compressionType_ = CompressionType_Zlib;
+    compressionType_ = CompressionType_None;
   }
 
   void CompressedFileStorageAccessor::Read(std::string& content,
--- a/Core/FileStorage/CompressedFileStorageAccessor.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/FileStorage/CompressedFileStorageAccessor.h	Fri Nov 30 14:22:27 2012 +0100
@@ -46,8 +46,9 @@
     CompressionType compressionType_;
 
   protected:
-    virtual std::string WriteInternal(const void* data,
-                                      size_t size);
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileType type);
 
   public: 
     CompressedFileStorageAccessor(FileStorage& storage);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileInfo.h	Fri Nov 30 14:22:27 2012 +0100
@@ -0,0 +1,110 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU 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 <string>
+#include <stdint.h>
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  struct FileInfo
+  {
+  private:
+    std::string uuid_;
+    FileType type_;
+    uint64_t uncompressedSize_;
+    CompressionType compression_;
+    uint64_t compressedSize_;
+
+  public:
+    FileInfo()
+    {
+    }
+
+    /**
+     * Constructor for an uncompressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileType type,
+             uint64_t size) :
+      uuid_(uuid),
+      type_(type),
+      uncompressedSize_(size),
+      compression_(CompressionType_None),
+      compressedSize_(size)
+    {
+    }
+
+    /**
+     * Constructor for a compressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileType type,
+             uint64_t uncompressedSize,
+             CompressionType compression,
+             uint64_t compressedSize) :
+      uuid_(uuid),
+      type_(type),
+      uncompressedSize_(uncompressedSize),
+      compression_(compression),
+      compressedSize_(compressedSize)
+    {
+    }
+
+    const std::string& GetUuid() const
+    {
+      return uuid_;
+    }
+
+    FileType GetFileType() const
+    {
+      return type_;
+    }
+
+    uint64_t GetUncompressedSize() const
+    {
+      return uncompressedSize_;
+    }
+
+    CompressionType GetCompressionType() const
+    {
+      return compression_;
+    }
+
+    uint64_t GetCompressedSize() const
+    {
+      return compressedSize_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/FileStorage/FileStorageAccessor.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -0,0 +1,43 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU 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 "FileStorageAccessor.h"
+
+namespace Orthanc
+{
+  FileInfo FileStorageAccessor::WriteInternal(const void* data,
+                                              size_t size,
+                                              FileType type)
+  {
+    return FileInfo(storage_.Create(data, size), type, size);
+  }
+}
--- a/Core/FileStorage/FileStorageAccessor.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/FileStorage/FileStorageAccessor.h	Fri Nov 30 14:22:27 2012 +0100
@@ -44,11 +44,9 @@
     FileStorage& storage_;
     
   protected:
-    virtual std::string WriteInternal(const void* data,
-                                      size_t size)
-    {
-      return storage_.Create(data, size);
-    }
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileType type);
 
   public:
     FileStorageAccessor(FileStorage& storage) : storage_(storage)
--- a/Core/FileStorage/StorageAccessor.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/FileStorage/StorageAccessor.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -34,27 +34,29 @@
 
 namespace Orthanc
 {
-  std::string StorageAccessor::Write(const std::vector<uint8_t>& content)
+  FileInfo StorageAccessor::Write(const std::vector<uint8_t>& content,
+                                  FileType type)
   {
     if (content.size() == 0)
     {
-      return WriteInternal(NULL, 0);
+      return WriteInternal(NULL, 0, type);
     }
     else
     {
-      return WriteInternal(&content[0], content.size());
+      return WriteInternal(&content[0], content.size(), type);
     }
   }
 
-  std::string StorageAccessor::Write(const std::string& content)
+  FileInfo StorageAccessor::Write(const std::string& content,
+                                  FileType type)
   {
     if (content.size() == 0)
     {
-      return WriteInternal(NULL, 0);
+      return WriteInternal(NULL, 0, type);
     }
     else
     {
-      return WriteInternal(&content[0], content.size());
+      return WriteInternal(&content[0], content.size(), type);
     }
   }
 }
--- a/Core/FileStorage/StorageAccessor.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/Core/FileStorage/StorageAccessor.h	Fri Nov 30 14:22:27 2012 +0100
@@ -32,6 +32,7 @@
 
 #pragma once
 
+#include "FileInfo.h"
 #include "../HttpServer/HttpFileSender.h"
 
 #include <vector>
@@ -44,23 +45,27 @@
   class StorageAccessor : boost::noncopyable
   {
   protected:
-    virtual std::string WriteInternal(const void* data,
-                                      size_t size) = 0;
+    virtual FileInfo WriteInternal(const void* data,
+                                   size_t size,
+                                   FileType type) = 0;
 
   public:
     virtual ~StorageAccessor()
     {
     }
 
-    std::string Write(const void* data,
-                      size_t size)
+    FileInfo Write(const void* data,
+                   size_t size,
+                   FileType type)
     {
-      return WriteInternal(data, size);
+      return WriteInternal(data, size, type);
     }
 
-    std::string Write(const std::vector<uint8_t>& content);
+    FileInfo Write(const std::vector<uint8_t>& content,
+                   FileType type);
 
-    std::string Write(const std::string& content);
+    FileInfo Write(const std::string& content,
+                   FileType type);
 
     virtual void Read(std::string& content,
                       const std::string& uuid) = 0;
--- a/NEWS	Fri Nov 30 12:18:44 2012 +0100
+++ b/NEWS	Fri Nov 30 14:22:27 2012 +0100
@@ -17,7 +17,7 @@
 * Generate a sample configuration file from command line
 * "CompletedSeries" event in the changes API
 * Thread to continuously flush DB to disk (SQLite checkpoints for
-  robustness against reboots)
+  improved robustness)
 
 
 Version 0.2.3 (2012/10/26)
--- a/OrthancServer/DatabaseWrapper.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -376,32 +376,25 @@
 
 
 
-  void DatabaseWrapper::AttachFile(int64_t id,
-                                   AttachedFileType contentType,
-                                   const std::string& fileUuid,
-                                   uint64_t compressedSize,
-                                   uint64_t uncompressedSize,
-                                   CompressionType compressionType)
+  void DatabaseWrapper::AddAttachment(int64_t id,
+                                      const FileInfo& attachment)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?)");
     s.BindInt(0, id);
-    s.BindInt(1, contentType);
-    s.BindString(2, fileUuid);
-    s.BindInt(3, compressedSize);
-    s.BindInt(4, uncompressedSize);
-    s.BindInt(5, compressionType);
+    s.BindInt(1, attachment.GetFileType());
+    s.BindString(2, attachment.GetUuid());
+    s.BindInt(3, attachment.GetCompressedSize());
+    s.BindInt(4, attachment.GetUncompressedSize());
+    s.BindInt(5, attachment.GetCompressionType());
     s.Run();
   }
 
-  bool DatabaseWrapper::LookupFile(int64_t id,
-                                   AttachedFileType contentType,
-                                   std::string& fileUuid,
-                                   uint64_t& compressedSize,
-                                   uint64_t& uncompressedSize,
-                                   CompressionType& compressionType)
+  bool DatabaseWrapper::LookupAttachment(FileInfo& attachment,
+                                         int64_t id,
+                                         FileType contentType)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT uuid, compressedSize, uncompressedSize, compressionType FROM AttachedFiles WHERE id=? AND fileType=?");
+                        "SELECT uuid, uncompressedSize, compressionType, compressedSize FROM AttachedFiles WHERE id=? AND fileType=?");
     s.BindInt(0, id);
     s.BindInt(1, contentType);
 
@@ -411,10 +404,11 @@
     }
     else
     {
-      fileUuid = s.ColumnString(0);
-      compressedSize = s.ColumnInt(1);
-      uncompressedSize = s.ColumnInt(2);
-      compressionType = static_cast<CompressionType>(s.ColumnInt(3));
+      attachment = FileInfo(s.ColumnString(0),
+                            contentType,
+                            s.ColumnInt(1),
+                            static_cast<CompressionType>(s.ColumnInt(2)),
+                            s.ColumnInt(3));
       return true;
     }
   }
--- a/OrthancServer/DatabaseWrapper.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Fri Nov 30 14:22:27 2012 +0100
@@ -35,6 +35,7 @@
 #include "../Core/SQLite/Connection.h"
 #include "../Core/SQLite/Transaction.h"
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
+#include "../Core/FileStorage/FileInfo.h"
 #include "IServerIndexListener.h"
 
 #include <list>
@@ -107,37 +108,12 @@
                               int64_t id,
                               MetadataType type);
 
-    void AttachFile(int64_t id,
-                    AttachedFileType contentType,
-                    const std::string& fileUuid,
-                    uint64_t compressedSize,
-                    uint64_t uncompressedSize,
-                    CompressionType compressionType);
-
-    void AttachFile(int64_t id,
-                    AttachedFileType contentType,
-                    const std::string& fileUuid,
-                    uint64_t fileSize)
-    {
-      AttachFile(id, contentType, fileUuid, fileSize, fileSize, CompressionType_None);
-    }
+    void AddAttachment(int64_t id,
+                       const FileInfo& attachment);
 
-    bool LookupFile(int64_t id,
-                    AttachedFileType contentType,
-                    std::string& fileUuid,
-                    uint64_t& compressedSize,
-                    uint64_t& uncompressedSize,
-                    CompressionType& compressionType);
-
-    bool LookupFile(int64_t id,
-                    AttachedFileType contentType,
-                    std::string& fileUuid,
-                    uint64_t& uncompressedSize)
-    {
-      uint64_t compressedSize;
-      CompressionType compressionType;
-      return LookupFile(id, contentType, fileUuid, compressedSize, uncompressedSize, compressionType);
-    }
+    bool LookupAttachment(FileInfo& attachment,
+                          int64_t id,
+                          FileType contentType);
 
     void SetMainDicomTags(int64_t id,
                           const DicomMap& tags);
--- a/OrthancServer/OrthancRestApi.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -258,7 +258,7 @@
       {
         std::string instanceId = found["Instances"][i].asString();
         std::string dicom;
-        context.ReadFile(dicom, instanceId, AttachedFileType_Dicom);
+        context.ReadFile(dicom, instanceId, FileType_Dicom);
         connection.Store(dicom);
       }
 
@@ -270,7 +270,7 @@
       context.GetIndex().LogExportedResource(resourceId, remote);
 
       std::string dicom;
-      context.ReadFile(dicom, resourceId, AttachedFileType_Dicom);
+      context.ReadFile(dicom, resourceId, FileType_Dicom);
       connection.Store(dicom);
 
       call.GetOutput().AnswerBuffer("{}", "application/json");
@@ -410,7 +410,7 @@
     RETRIEVE_CONTEXT(call);
 
     std::string publicId = call.GetUriComponent("id", "");
-    context.AnswerFile(call.GetOutput(), publicId, AttachedFileType_Dicom);
+    context.AnswerFile(call.GetOutput(), publicId, FileType_Dicom);
   }
 
 
@@ -485,7 +485,7 @@
 
     std::string publicId = call.GetUriComponent("id", "");
     std::string dicomContent, png;
-    context.ReadFile(dicomContent, publicId, AttachedFileType_Dicom);
+    context.ReadFile(dicomContent, publicId, FileType_Dicom);
 
     try
     {
--- a/OrthancServer/ServerContext.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/ServerContext.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -50,7 +50,8 @@
 {
   ServerContext::ServerContext(const boost::filesystem::path& path) :
     storage_(path.string()),
-    index_(*this, path.string())
+    index_(*this, path.string()),
+    accessor_(storage_)
   {
   }
 
@@ -65,14 +66,21 @@
                                    const Json::Value& dicomJson,
                                    const std::string& remoteAet)
   {
-    std::string fileUuid = storage_.Create(dicomFile, dicomSize);
-    std::string jsonUuid = storage_.Create(dicomJson.toStyledString());
-    StoreStatus status = index_.Store(dicomSummary, fileUuid, dicomSize, jsonUuid, remoteAet);
+    //accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
+
+    FileInfo dicomInfo = accessor_.Write(dicomFile, dicomSize, FileType_Dicom);
+    FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileType_Json);
+
+    ServerIndex::Attachments attachments;
+    attachments.push_back(dicomInfo);
+    attachments.push_back(jsonInfo);
+
+    StoreStatus status = index_.Store(dicomSummary, attachments, remoteAet);
 
     if (status != StoreStatus_Success)
     {
-      storage_.Remove(fileUuid);
-      storage_.Remove(jsonUuid);
+      storage_.Remove(dicomInfo.GetUuid());
+      storage_.Remove(jsonInfo.GetUuid());
     }
 
     switch (status)
@@ -96,18 +104,16 @@
   
   void ServerContext::AnswerFile(RestApiOutput& output,
                                  const std::string& instancePublicId,
-                                 AttachedFileType content)
+                                 FileType content)
   {
-    CompressionType compressionType;
-    std::string fileUuid;
+    FileInfo attachment;
+    if (index_.LookupAttachment(attachment, instancePublicId, FileType_Dicom))
+    {
+      assert(attachment.GetCompressionType() == CompressionType_None);
+      assert(attachment.GetFileType() == FileType_Dicom);
 
-    if (index_.GetFile(fileUuid, compressionType, 
-                       instancePublicId, AttachedFileType_Dicom))
-    {
-      assert(compressionType == CompressionType_None);
-
-      FilesystemHttpSender sender(storage_, fileUuid);
-      sender.SetDownloadFilename(fileUuid + ".dcm");
+      FilesystemHttpSender sender(storage_, attachment.GetUuid());
+      sender.SetDownloadFilename(attachment.GetUuid() + ".dcm");
       sender.SetContentType("application/dicom");
       output.AnswerFile(sender);
     }
@@ -118,7 +124,7 @@
                                const std::string& instancePublicId)
   {
     std::string s;
-    ReadFile(s, instancePublicId, AttachedFileType_Json);
+    ReadFile(s, instancePublicId, FileType_Json);
 
     Json::Reader reader;
     if (!reader.parse(s, result))
@@ -130,16 +136,15 @@
 
   void ServerContext::ReadFile(std::string& result,
                                const std::string& instancePublicId,
-                               AttachedFileType content)
+                               FileType content)
   {
-    CompressionType compressionType;
-    std::string fileUuid;
-    if (!index_.GetFile(fileUuid, compressionType, instancePublicId, content))
+    FileInfo attachment;
+    if (!index_.LookupAttachment(attachment, instancePublicId, content))
     {
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    assert(compressionType == CompressionType_None);
-    storage_.ReadFile(result, fileUuid);    
+    accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
+    accessor_.Read(result, attachment.GetUuid());
   }
 }
--- a/OrthancServer/ServerContext.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/ServerContext.h	Fri Nov 30 14:22:27 2012 +0100
@@ -35,6 +35,7 @@
 #include "ServerIndex.h"
 #include "../Core/FileStorage.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 
 namespace Orthanc
 {
@@ -48,6 +49,7 @@
   private:
     FileStorage storage_;
     ServerIndex index_;
+    CompressedFileStorageAccessor accessor_;
 
   public:
     ServerContext(const boost::filesystem::path& path);
@@ -67,7 +69,7 @@
 
     void AnswerFile(RestApiOutput& output,
                     const std::string& instancePublicId,
-                    AttachedFileType content);
+                    FileType content);
 
     void ReadJson(Json::Value& result,
                   const std::string& instancePublicId);
@@ -75,6 +77,6 @@
     // TODO CACHING MECHANISM AT THIS POINT
     void ReadFile(std::string& result,
                   const std::string& instancePublicId,
-                  AttachedFileType content);
+                  FileType content);
   };
 }
--- a/OrthancServer/ServerEnumerations.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/ServerEnumerations.h	Fri Nov 30 14:22:27 2012 +0100
@@ -35,11 +35,6 @@
 
 namespace Orthanc
 {
-  enum GlobalProperty
-  {
-    GlobalProperty_FlushSleep = 1
-  };
-
   enum SeriesStatus
   {
     SeriesStatus_Complete,
@@ -55,6 +50,18 @@
     StoreStatus_Failure
   };
 
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum GlobalProperty
+  {
+    GlobalProperty_FlushSleep = 1
+  };
+
   enum ResourceType
   {
     ResourceType_Patient = 1,
@@ -65,32 +72,26 @@
 
   enum MetadataType
   {
-    MetadataType_Instance_IndexInSeries = 2,
-    MetadataType_Instance_ReceptionDate = 4,
-    MetadataType_Instance_RemoteAet = 1,
-    MetadataType_Series_ExpectedNumberOfInstances = 3
+    MetadataType_Instance_IndexInSeries = 1,
+    MetadataType_Instance_ReceptionDate = 2,
+    MetadataType_Instance_RemoteAet = 3,
+    MetadataType_Series_ExpectedNumberOfInstances = 4
   };
 
   enum ChangeType
   {
     ChangeType_CompletedSeries = 1,
-    ChangeType_NewInstance = 3,
-    ChangeType_NewPatient = 4,
-    ChangeType_NewSeries = 2,
+    ChangeType_NewInstance = 2,
+    ChangeType_NewPatient = 3,
+    ChangeType_NewSeries = 4,
     ChangeType_NewStudy = 5
   };
 
-  enum AttachedFileType
-  {
-    AttachedFileType_Dicom = 1,
-    AttachedFileType_Json = 2
-  };
+  std::string GetBasePath(ResourceType type,
+                          const std::string& publicId);
 
   const char* ToString(ResourceType type);
 
-  std::string GetBasePath(ResourceType type,
-                          const std::string& publicId);
-
   const char* ToString(SeriesStatus status);
 
   const char* ToString(StoreStatus status);
--- a/OrthancServer/ServerIndex.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/ServerIndex.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -228,9 +228,7 @@
 
 
   StoreStatus ServerIndex::Store(const DicomMap& dicomSummary,
-                                 const std::string& fileUuid,
-                                 uint64_t uncompressedFileSize,
-                                 const std::string& jsonUuid,
+                                 const Attachments& attachments,
                                  const std::string& remoteAet)
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -305,8 +303,11 @@
       }
 
       // Attach the files to the newly created instance
-      db_->AttachFile(instance, AttachedFileType_Dicom, fileUuid, uncompressedFileSize);
-      db_->AttachFile(instance, AttachedFileType_Json, jsonUuid, 0);  // TODO "0"
+      for (Attachments::const_iterator it = attachments.begin();
+           it != attachments.end(); it++)
+      {
+        db_->AddAttachment(instance, *it);
+      }
 
       // Attach the metadata
       db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, Toolbox::GetNowIsoString());
@@ -546,15 +547,14 @@
     {
       result["Type"] = "Instance";
 
-      std::string fileUuid;
-      uint64_t uncompressedSize;
-      if (!db_->LookupFile(id, AttachedFileType_Dicom, fileUuid, uncompressedSize))
+      FileInfo attachment;
+      if (!db_->LookupAttachment(attachment, id, FileType_Dicom))
       {
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      result["FileSize"] = static_cast<unsigned int>(uncompressedSize);
-      result["FileUuid"] = fileUuid;
+      result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
+      result["FileUuid"] = attachment.GetUuid();
 
       int i;
       if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
@@ -577,10 +577,9 @@
   }
 
 
-  bool ServerIndex::GetFile(std::string& fileUuid,
-                            CompressionType& compressionType,
-                            const std::string& instanceUuid,
-                            AttachedFileType contentType)
+  bool ServerIndex::LookupAttachment(FileInfo& attachment,
+                                     const std::string& instanceUuid,
+                                     FileType contentType)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
@@ -592,9 +591,15 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    uint64_t compressedSize, uncompressedSize;
-
-    return db_->LookupFile(id, contentType, fileUuid, compressedSize, uncompressedSize, compressionType);
+    if (db_->LookupAttachment(attachment, id, contentType))
+    {
+      assert(attachment.GetFileType() == contentType);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
   }
 
 
--- a/OrthancServer/ServerIndex.h	Fri Nov 30 12:18:44 2012 +0100
+++ b/OrthancServer/ServerIndex.h	Fri Nov 30 14:22:27 2012 +0100
@@ -52,6 +52,7 @@
   }
 
 
+
   class ServerIndex : public boost::noncopyable
   {
   private:
@@ -67,15 +68,15 @@
     SeriesStatus GetSeriesStatus(int id);
 
   public:
+    typedef std::list<FileInfo> Attachments;
+
     ServerIndex(ServerContext& context,
                 const std::string& dbPath);
 
     ~ServerIndex();
 
     StoreStatus Store(const DicomMap& dicomSummary,
-                      const std::string& fileUuid,
-                      uint64_t uncompressedFileSize,
-                      const std::string& jsonUuid,
+                      const Attachments& attachments,
                       const std::string& remoteAet);
 
     uint64_t GetTotalCompressedSize();
@@ -86,10 +87,9 @@
                         const std::string& publicId,
                         ResourceType expectedType);
 
-    bool GetFile(std::string& fileUuid,
-                 CompressionType& compressionType,
-                 const std::string& instanceUuid,
-                 AttachedFileType contentType);
+    bool LookupAttachment(FileInfo& attachment,
+                          const std::string& instanceUuid,
+                          FileType contentType);
 
     void GetAllUuids(Json::Value& target,
                      ResourceType resourceType);
--- a/UnitTests/FileStorage.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/UnitTests/FileStorage.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -66,12 +66,16 @@
   FileStorageAccessor accessor(s);
 
   std::string data = "Hello world";
-  std::string id = accessor.Write(data);
+  FileInfo info = accessor.Write(data, FileType_Dicom);
   
   std::string r;
-  accessor.Read(r, id);
+  accessor.Read(r, info.GetUuid());
 
   ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileType_Dicom, info.GetFileType());
 }
 
 
@@ -82,12 +86,16 @@
 
   accessor.SetCompressionForNextOperations(CompressionType_None);
   std::string data = "Hello world";
-  std::string id = accessor.Write(data);
+  FileInfo info = accessor.Write(data, FileType_Dicom);
   
   std::string r;
-  accessor.Read(r, id);
+  accessor.Read(r, info.GetUuid());
 
   ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileType_Dicom, info.GetFileType());
 }
 
 
@@ -98,12 +106,15 @@
 
   accessor.SetCompressionForNextOperations(CompressionType_Zlib);
   std::string data = "Hello world";
-  std::string id = accessor.Write(data);
+  FileInfo info = accessor.Write(data, FileType_Dicom);
   
   std::string r;
-  accessor.Read(r, id);
+  accessor.Read(r, info.GetUuid());
 
   ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(FileType_Dicom, info.GetFileType());
 }
 
 
@@ -117,24 +128,24 @@
   std::string uncompressedData = "HelloWorld";
 
   accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  std::string compressedId = accessor.Write(compressedData);
+  FileInfo compressedInfo = accessor.Write(compressedData, FileType_Dicom);
   
   accessor.SetCompressionForNextOperations(CompressionType_None);
-  std::string uncompressedId = accessor.Write(uncompressedData);
+  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileType_Dicom);
   
   accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  accessor.Read(r, compressedId);
+  accessor.Read(r, compressedInfo.GetUuid());
   ASSERT_EQ(compressedData, r);
 
   accessor.SetCompressionForNextOperations(CompressionType_None);
-  accessor.Read(r, compressedId);
+  accessor.Read(r, compressedInfo.GetUuid());
   ASSERT_NE(compressedData, r);
-  
-#if defined(__linux)
+
+  /*
   // This test is too slow on Windows
   accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  ASSERT_THROW(accessor.Read(r, uncompressedId), OrthancException);
-#endif
+  ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException);
+  */
 }
 
 
--- a/UnitTests/ServerIndex.cpp	Fri Nov 30 12:18:44 2012 +0100
+++ b/UnitTests/ServerIndex.cpp	Fri Nov 30 14:22:27 2012 +0100
@@ -65,18 +65,18 @@
     Json::Value t;
     index.GetAllPublicIds(t, ResourceType_Patient);
 
-    ASSERT_EQ(1, t.size());
+    ASSERT_EQ(1u, t.size());
     ASSERT_EQ("a", t[0u].asString());
 
     index.GetAllPublicIds(t, ResourceType_Series);
-    ASSERT_EQ(1, t.size());
+    ASSERT_EQ(1u, t.size());
     ASSERT_EQ("c", t[0u].asString());
 
     index.GetAllPublicIds(t, ResourceType_Study);
-    ASSERT_EQ(2, t.size());
+    ASSERT_EQ(2u, t.size());
 
     index.GetAllPublicIds(t, ResourceType_Instance);
-    ASSERT_EQ(3, t.size());
+    ASSERT_EQ(3u, t.size());
   }
 
   index.SetGlobalProperty(GlobalProperty_FlushSleep, "World");
@@ -107,14 +107,14 @@
   ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);
 
   std::list<std::string> l;
-  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1, l.size()); ASSERT_EQ("b", l.front());
-  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1, l.size()); ASSERT_EQ("c", l.front());
-  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0, l.size()); 
-  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0, l.size()); 
-  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0, l.size()); 
-  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1, l.size()); ASSERT_EQ("f", l.front());
+  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
+  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
+  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
+  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
 
-  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2, l.size()); 
+  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
   if (l.front() == "d")
   {
     ASSERT_EQ("e", l.back());
@@ -125,13 +125,13 @@
     ASSERT_EQ("e", l.front());
   }
 
-  index.AttachFile(a[4], AttachedFileType_Json, "my json file", 21, 42, CompressionType_Zlib);
-  index.AttachFile(a[4], AttachedFileType_Dicom, "my dicom file", 42);
-  index.AttachFile(a[6], AttachedFileType_Dicom, "world", 44);
+  index.AddAttachment(a[4], FileInfo("my json file", FileType_Json, 42, CompressionType_Zlib, 21));
+  index.AddAttachment(a[4], FileInfo("my dicom file", FileType_Dicom, 42));
+  index.AddAttachment(a[6], FileInfo("world", FileType_Dicom, 44));
   index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
 
-  ASSERT_EQ(21 + 42 + 44, index.GetTotalCompressedSize());
-  ASSERT_EQ(42 + 42 + 44, index.GetTotalUncompressedSize());
+  ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize());
+  ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize());
 
   DicomMap m;
   m.SetValue(0x0010, 0x0010, "PatientName");
@@ -155,35 +155,34 @@
   ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep));
   ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None"));
 
-  uint64_t us, cs;
-  CompressionType ct;
-  ASSERT_TRUE(index.LookupFile(a[4], AttachedFileType_Json, s, cs, us, ct));
-  ASSERT_EQ("my json file", s);
-  ASSERT_EQ(21, cs);
-  ASSERT_EQ(42, us);
-  ASSERT_EQ(CompressionType_Zlib, ct);
+  FileInfo att;
+  ASSERT_TRUE(index.LookupAttachment(att, a[4], FileType_Json));
+  ASSERT_EQ("my json file", att.GetUuid());
+  ASSERT_EQ(21u, att.GetCompressedSize());
+  ASSERT_EQ(42u, att.GetUncompressedSize());
+  ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType());
 
   ASSERT_EQ(0u, listener.deletedFiles_.size());
-  ASSERT_EQ(7, index.GetTableRecordCount("Resources")); 
-  ASSERT_EQ(3, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(1, index.GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1, index.GetTableRecordCount("MainDicomTags"));
+  ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags"));
   index.DeleteResource(a[0]);
 
-  ASSERT_EQ(2, listener.deletedFiles_.size());
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
   ASSERT_FALSE(listener.deletedFiles_.find("my json file") == listener.deletedFiles_.end());
   ASSERT_FALSE(listener.deletedFiles_.find("my dicom file") == listener.deletedFiles_.end());
 
-  ASSERT_EQ(2, index.GetTableRecordCount("Resources"));
-  ASSERT_EQ(0, index.GetTableRecordCount("Metadata"));
-  ASSERT_EQ(1, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(0, index.GetTableRecordCount("MainDicomTags"));
+  ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags"));
   index.DeleteResource(a[5]);
-  ASSERT_EQ(0, index.GetTableRecordCount("Resources"));
-  ASSERT_EQ(0, index.GetTableRecordCount("AttachedFiles"));
-  ASSERT_EQ(1, index.GetTableRecordCount("GlobalProperties"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources"));
+  ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles"));
+  ASSERT_EQ(1u, index.GetTableRecordCount("GlobalProperties"));
 
-  ASSERT_EQ(3, listener.deletedFiles_.size());
+  ASSERT_EQ(3u, listener.deletedFiles_.size());
   ASSERT_FALSE(listener.deletedFiles_.find("world") == listener.deletedFiles_.end());
 }
 
@@ -217,25 +216,25 @@
   {
     Json::Value j;
     index.GetChildren(j, a[0]);
-    ASSERT_EQ(2, j.size());
+    ASSERT_EQ(2u, j.size());
     ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") ||
                 (j[1u] == "b" && j[0u] == "f"));
 
     index.GetChildren(j, a[1]);
-    ASSERT_EQ(2, j.size());
+    ASSERT_EQ(2u, j.size());
     ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") ||
                 (j[1u] == "c" && j[0u] == "g"));
 
     index.GetChildren(j, a[2]);
-    ASSERT_EQ(2, j.size());
+    ASSERT_EQ(2u, j.size());
     ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") ||
                 (j[1u] == "d" && j[0u] == "e"));
 
-    index.GetChildren(j, a[3]); ASSERT_EQ(0, j.size());
-    index.GetChildren(j, a[4]); ASSERT_EQ(0, j.size());
-    index.GetChildren(j, a[5]); ASSERT_EQ(1, j.size()); ASSERT_EQ("h", j[0u].asString());
-    index.GetChildren(j, a[6]); ASSERT_EQ(0, j.size());
-    index.GetChildren(j, a[7]); ASSERT_EQ(0, j.size());
+    index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString());
+    index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size());
+    index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size());
   }
 
   listener.Reset();