changeset 3026:c358bdb37c13

more plugin helpers
author amazy
date Tue, 18 Dec 2018 12:07:52 +0100
parents c9c2faf76bec
children ff65c925f57a
files Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h
diffstat 2 files changed, 1801 insertions(+), 1745 deletions(-) [+]
line wrap: on
line diff
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon Dec 17 10:22:51 2018 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue Dec 18 12:07:52 2018 +0100
@@ -39,1951 +39,1993 @@
 
 namespace OrthancPlugins
 {
-  static OrthancPluginContext* globalContext_ = NULL;
+static OrthancPluginContext* globalContext_ = NULL;
 
-  
-  void SetGlobalContext(OrthancPluginContext* context)
-  {
-    if (context == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
-    }        
-    else if (globalContext_ == NULL)
-    {
-      globalContext_ = context;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);      
-    }
-  }
 
-  
-  bool HasGlobalContext()
+void SetGlobalContext(OrthancPluginContext* context)
+{
+  if (context == NULL)
   {
-    return globalContext_ != NULL;
+    ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
   }
-  
-
-  OrthancPluginContext* GetGlobalContext()
+  else if (globalContext_ == NULL)
   {
-    if (globalContext_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);      
-    }
-    else
-    {
-      return globalContext_;
-    }
+    globalContext_ = context;
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
   }
+}
+
+
+bool HasGlobalContext()
+{
+  return globalContext_ != NULL;
+}
 
 
-  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+OrthancPluginContext* GetGlobalContext()
+{
+  if (globalContext_ == NULL)
   {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
   }
+  else
+  {
+    return globalContext_;
+  }
+}
 
 
-  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+void MemoryBuffer::Check(OrthancPluginErrorCode code)
+{
+  if (code != OrthancPluginErrorCode_Success)
   {
-    if (code != OrthancPluginErrorCode_Success)
-    {
-      // Prevent using garbage information
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (code == OrthancPluginErrorCode_UnknownResource ||
-             code == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-    }
+    // Prevent using garbage information
+    buffer_.data = NULL;
+    buffer_.size = 0;
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
   }
+}
 
 
-  MemoryBuffer::MemoryBuffer()
+bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+{
+  if (code != OrthancPluginErrorCode_Success)
   {
+    // Prevent using garbage information
     buffer_.data = NULL;
     buffer_.size = 0;
   }
 
+  if (code == OrthancPluginErrorCode_Success)
+  {
+    return true;
+  }
+  else if (code == OrthancPluginErrorCode_UnknownResource ||
+           code == OrthancPluginErrorCode_InexistentItem)
+  {
+    return false;
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+  }
+}
 
-  void MemoryBuffer::Clear()
+
+MemoryBuffer::MemoryBuffer()
+{
+  buffer_.data = NULL;
+  buffer_.size = 0;
+}
+
+
+void MemoryBuffer::Clear()
+{
+  if (buffer_.data != NULL)
   {
-    if (buffer_.data != NULL)
-    {
-      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_);
-      buffer_.data = NULL;
-      buffer_.size = 0;
-    }
+    OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_);
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+}
+
+
+void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
+{
+  Clear();
+
+  buffer_.data = other.data;
+  buffer_.size = other.size;
+
+  other.data = NULL;
+  other.size = 0;
+}
+
+
+OrthancPluginMemoryBuffer MemoryBuffer::Release()
+{
+  OrthancPluginMemoryBuffer result = buffer_;
+
+  buffer_.data = NULL;
+  buffer_.size = 0;
+
+  return result;
+}
+
+
+void MemoryBuffer::ToString(std::string& target) const
+{
+  if (buffer_.size == 0)
+  {
+    target.clear();
+  }
+  else
+  {
+    target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
+  }
+}
+
+
+void MemoryBuffer::ToJson(Json::Value& target) const
+{
+  if (buffer_.data == NULL ||
+      buffer_.size == 0)
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
   }
 
+  const char* tmp = reinterpret_cast<const char*>(buffer_.data);
 
-  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
+  Json::Reader reader;
+  if (!reader.parse(tmp, tmp + buffer_.size, target))
+  {
+    LogError("Cannot convert some memory buffer to JSON");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+}
+
+
+bool MemoryBuffer::RestApiGet(const std::string& uri,
+                              bool applyPlugins)
+{
+  Clear();
+
+  if (applyPlugins)
+  {
+    return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
+  }
+  else
+  {
+    return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
+  }
+}
+
+bool MemoryBuffer::RestApiGet(const std::string& uri,
+                              const std::map<std::string, std::string>& httpHeaders,
+                              bool applyPlugins)
+{
+  Clear();
+
+  std::vector<const char*> headersKeys;
+  std::vector<const char*> headersValues;
+  for (std::map<std::string, std::string>::const_iterator it = httpHeaders.begin(); it != httpHeaders.end(); it++)
+  {
+    headersKeys.push_back(it->first.c_str());
+    headersValues.push_back(it->second.c_str());
+  }
+
+  return CheckHttp(OrthancPluginRestApiGet2(GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), headersKeys.data(), headersValues.data(), applyPlugins));
+}
+
+bool MemoryBuffer::RestApiPost(const std::string& uri,
+                               const char* body,
+                               size_t bodySize,
+                               bool applyPlugins)
+{
+  Clear();
+
+  if (applyPlugins)
+  {
+    return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+  }
+  else
+  {
+    return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+  }
+}
+
+
+bool MemoryBuffer::RestApiPut(const std::string& uri,
+                              const char* body,
+                              size_t bodySize,
+                              bool applyPlugins)
+{
+  Clear();
+
+  if (applyPlugins)
+  {
+    return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+  }
+  else
+  {
+    return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+  }
+}
+
+
+bool MemoryBuffer::RestApiPost(const std::string& uri,
+                               const Json::Value& body,
+                               bool applyPlugins)
+{
+  Json::FastWriter writer;
+  return RestApiPost(uri, writer.write(body), applyPlugins);
+}
+
+
+bool MemoryBuffer::RestApiPut(const std::string& uri,
+                              const Json::Value& body,
+                              bool applyPlugins)
+{
+  Json::FastWriter writer;
+  return RestApiPut(uri, writer.write(body), applyPlugins);
+}
+
+
+void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                               OrthancPluginCreateDicomFlags flags)
+{
+  Clear();
+
+  Json::FastWriter writer;
+  std::string s = writer.write(tags);
+
+  Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+}
+
+void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                               const OrthancImage& pixelData,
+                               OrthancPluginCreateDicomFlags flags)
+{
+  Clear();
+
+  Json::FastWriter writer;
+  std::string s = writer.write(tags);
+
+  Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+}
+
+
+void MemoryBuffer::ReadFile(const std::string& path)
+{
+  Clear();
+  Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+}
+
+
+void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+{
+  Clear();
+  Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+}
+
+
+void OrthancString::Assign(char* str)
+{
+  if (str == NULL)
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+  else
   {
     Clear();
+    str_ = str;
+  }
+}
 
-    buffer_.data = other.data;
-    buffer_.size = other.size;
+
+void OrthancString::Clear()
+{
+  if (str_ != NULL)
+  {
+    OrthancPluginFreeString(GetGlobalContext(), str_);
+    str_ = NULL;
+  }
+}
+
+
+void OrthancString::ToString(std::string& target) const
+{
+  if (str_ == NULL)
+  {
+    target.clear();
+  }
+  else
+  {
+    target.assign(str_);
+  }
+}
+
+
+void OrthancString::ToJson(Json::Value& target) const
+{
+  if (str_ == NULL)
+  {
+    LogError("Cannot convert an empty memory buffer to JSON");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+
+  Json::Reader reader;
+  if (!reader.parse(str_, target))
+  {
+    LogError("Cannot convert some memory buffer to JSON");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+}
+
+
+void MemoryBuffer::DicomToJson(Json::Value& target,
+                               OrthancPluginDicomToJsonFormat format,
+                               OrthancPluginDicomToJsonFlags flags,
+                               uint32_t maxStringLength)
+{
+  OrthancString str;
+  str.Assign(OrthancPluginDicomBufferToJson
+             (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+  str.ToJson(target);
+}
+
+
+bool MemoryBuffer::HttpGet(const std::string& url,
+                           const std::string& username,
+                           const std::string& password)
+{
+  Clear();
+  return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
+                                        username.empty() ? NULL : username.c_str(),
+                                        password.empty() ? NULL : password.c_str()));
+}
+
+
+bool MemoryBuffer::HttpPost(const std::string& url,
+                            const std::string& body,
+                            const std::string& username,
+                            const std::string& password)
+{
+  Clear();
+  return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
+                                         body.c_str(), body.size(),
+                                         username.empty() ? NULL : username.c_str(),
+                                         password.empty() ? NULL : password.c_str()));
+}
+
+
+bool MemoryBuffer::HttpPut(const std::string& url,
+                           const std::string& body,
+                           const std::string& username,
+                           const std::string& password)
+{
+  Clear();
+  return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
+                                        body.empty() ? NULL : body.c_str(),
+                                        body.size(),
+                                        username.empty() ? NULL : username.c_str(),
+                                        password.empty() ? NULL : password.c_str()));
+}
+
+
+void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
+{
+  Clear();
+  Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
+}
+
+
+bool HttpDelete(const std::string& url,
+                const std::string& username,
+                const std::string& password)
+{
+  OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
 
-    other.data = NULL;
-    other.size = 0;
+  if (error == OrthancPluginErrorCode_Success)
+  {
+    return true;
+  }
+  else if (error == OrthancPluginErrorCode_UnknownResource ||
+           error == OrthancPluginErrorCode_InexistentItem)
+  {
+    return false;
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+  }
+}
+
+
+void LogError(const std::string& message)
+{
+  if (HasGlobalContext())
+  {
+    OrthancPluginLogError(GetGlobalContext(), message.c_str());
+  }
+}
+
+
+void LogWarning(const std::string& message)
+{
+  if (HasGlobalContext())
+  {
+    OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
+  }
+}
+
+
+void LogInfo(const std::string& message)
+{
+  if (HasGlobalContext())
+  {
+    OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
+  }
+}
+
+
+OrthancConfiguration::OrthancConfiguration()
+{
+  OrthancString str;
+  str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
+
+  if (str.GetContent() == NULL)
+  {
+    LogError("Cannot access the Orthanc configuration");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+
+  str.ToJson(configuration_);
+
+  if (configuration_.type() != Json::objectValue)
+  {
+    LogError("Unable to read the Orthanc configuration");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
   }
+}
+
+
+std::string OrthancConfiguration::GetPath(const std::string& key) const
+{
+  if (path_.empty())
+  {
+    return key;
+  }
+  else
+  {
+    return path_ + "." + key;
+  }
+}
+
+
+bool OrthancConfiguration::IsSection(const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  return (configuration_.isMember(key) &&
+          configuration_[key].type() == Json::objectValue);
+}
+
+
+void OrthancConfiguration::GetSection(OrthancConfiguration& target,
+                                      const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  target.path_ = GetPath(key);
+
+  if (!configuration_.isMember(key))
+  {
+    target.configuration_ = Json::objectValue;
+  }
+  else
+  {
+    if (configuration_[key].type() != Json::objectValue)
+    {
+      LogError("The configuration section \"" + target.path_ +
+               "\" is not an associative array as expected");
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    target.configuration_ = configuration_[key];
+  }
+}
+
+
+bool OrthancConfiguration::LookupStringValue(std::string& target,
+                                             const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  if (!configuration_.isMember(key))
+  {
+    return false;
+  }
+
+  if (configuration_[key].type() != Json::stringValue)
+  {
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a string as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+  target = configuration_[key].asString();
+  return true;
+}
 
 
-  OrthancPluginMemoryBuffer MemoryBuffer::Release()
+bool OrthancConfiguration::LookupIntegerValue(int& target,
+                                              const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  if (!configuration_.isMember(key))
+  {
+    return false;
+  }
+
+  switch (configuration_[key].type())
+  {
+  case Json::intValue:
+    target = configuration_[key].asInt();
+    return true;
+
+  case Json::uintValue:
+    target = configuration_[key].asUInt();
+    return true;
+
+  default:
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not an integer as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+}
+
+
+bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                      const std::string& key) const
+{
+  int tmp;
+  if (!LookupIntegerValue(tmp, key))
   {
-    OrthancPluginMemoryBuffer result = buffer_;
+    return false;
+  }
+
+  if (tmp < 0)
+  {
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a positive integer as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+  else
+  {
+    target = static_cast<unsigned int>(tmp);
+    return true;
+  }
+}
+
+
+bool OrthancConfiguration::LookupBooleanValue(bool& target,
+                                              const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  if (!configuration_.isMember(key))
+  {
+    return false;
+  }
+
+  if (configuration_[key].type() != Json::booleanValue)
+  {
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a Boolean as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
 
-    buffer_.data = NULL;
-    buffer_.size = 0;
-    
-    return result;
+  target = configuration_[key].asBool();
+  return true;
+}
+
+
+bool OrthancConfiguration::LookupFloatValue(float& target,
+                                            const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  if (!configuration_.isMember(key))
+  {
+    return false;
+  }
+
+  switch (configuration_[key].type())
+  {
+  case Json::realValue:
+    target = configuration_[key].asFloat();
+    return true;
+
+  case Json::intValue:
+    target = static_cast<float>(configuration_[key].asInt());
+    return true;
+
+  case Json::uintValue:
+    target = static_cast<float>(configuration_[key].asUInt());
+    return true;
+
+  default:
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not an integer as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+}
+
+
+bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                               const std::string& key,
+                                               bool allowSingleString) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  target.clear();
+
+  if (!configuration_.isMember(key))
+  {
+    return false;
+  }
+
+  switch (configuration_[key].type())
+  {
+  case Json::arrayValue:
+  {
+    bool ok = true;
+
+    for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+    {
+      if (configuration_[key][i].type() == Json::stringValue)
+      {
+        target.push_back(configuration_[key][i].asString());
+      }
+      else
+      {
+        ok = false;
+      }
+    }
+
+    if (ok)
+    {
+      return true;
+    }
+
+    break;
   }
 
+  case Json::stringValue:
+    if (allowSingleString)
+    {
+      target.push_back(configuration_[key].asString());
+      return true;
+    }
 
-  void MemoryBuffer::ToString(std::string& target) const
+    break;
+
+  default:
+    break;
+  }
+
+  LogError("The configuration option \"" + GetPath(key) +
+           "\" is not a list of strings as expected");
+
+  ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+}
+
+
+bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                              const std::string& key,
+                                              bool allowSingleString) const
+{
+  std::list<std::string> lst;
+
+  if (LookupListOfStrings(lst, key, allowSingleString))
+  {
+    target.clear();
+
+    for (std::list<std::string>::const_iterator
+         it = lst.begin(); it != lst.end(); ++it)
+    {
+      target.insert(*it);
+    }
+
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+std::string OrthancConfiguration::GetStringValue(const std::string& key,
+                                                 const std::string& defaultValue) const
+{
+  std::string tmp;
+  if (LookupStringValue(tmp, key))
+  {
+    return tmp;
+  }
+  else
+  {
+    return defaultValue;
+  }
+}
+
+
+int OrthancConfiguration::GetIntegerValue(const std::string& key,
+                                          int defaultValue) const
+{
+  int tmp;
+  if (LookupIntegerValue(tmp, key))
+  {
+    return tmp;
+  }
+  else
   {
-    if (buffer_.size == 0)
+    return defaultValue;
+  }
+}
+
+
+unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
+                                                           unsigned int defaultValue) const
+{
+  unsigned int tmp;
+  if (LookupUnsignedIntegerValue(tmp, key))
+  {
+    return tmp;
+  }
+  else
+  {
+    return defaultValue;
+  }
+}
+
+
+bool OrthancConfiguration::GetBooleanValue(const std::string& key,
+                                           bool defaultValue) const
+{
+  bool tmp;
+  if (LookupBooleanValue(tmp, key))
+  {
+    return tmp;
+  }
+  else
+  {
+    return defaultValue;
+  }
+}
+
+
+float OrthancConfiguration::GetFloatValue(const std::string& key,
+                                          float defaultValue) const
+{
+  float tmp;
+  if (LookupFloatValue(tmp, key))
+  {
+    return tmp;
+  }
+  else
+  {
+    return defaultValue;
+  }
+}
+
+
+void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
+                                         const std::string& key) const
+{
+  assert(configuration_.type() == Json::objectValue);
+
+  target.clear();
+
+  if (!configuration_.isMember(key))
+  {
+    return;
+  }
+
+  if (configuration_[key].type() != Json::objectValue)
+  {
+    LogError("The configuration option \"" + GetPath(key) +
+             "\" is not a string as expected");
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+  Json::Value::Members members = configuration_[key].getMemberNames();
+
+  for (size_t i = 0; i < members.size(); i++)
+  {
+    const Json::Value& value = configuration_[key][members[i]];
+
+    if (value.type() == Json::stringValue)
     {
-      target.clear();
+      target[members[i]] = value.asString();
     }
     else
     {
-      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
-    }
-  }
-
+      LogError("The configuration option \"" + GetPath(key) +
+               "\" is not a dictionary mapping strings to strings");
 
-  void MemoryBuffer::ToJson(Json::Value& target) const
-  {
-    if (buffer_.data == NULL ||
-        buffer_.size == 0)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
-
-    Json::Reader reader;
-    if (!reader.parse(tmp, tmp + buffer_.size, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
       ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
+}
+
+
+void OrthancImage::Clear()
+{
+  if (image_ != NULL)
+  {
+    OrthancPluginFreeImage(GetGlobalContext(), image_);
+    image_ = NULL;
+  }
+}
+
+
+void OrthancImage::CheckImageAvailable()
+{
+  if (image_ == NULL)
+  {
+    LogError("Trying to access a NULL image");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+}
+
+
+OrthancImage::OrthancImage() :
+  image_(NULL)
+{
+}
+
+
+OrthancImage::OrthancImage(OrthancPluginImage*  image) :
+  image_(image)
+{
+}
+
+
+OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                           uint32_t                  width,
+                           uint32_t                  height)
+{
+  image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
+
+  if (image_ == NULL)
+  {
+    LogError("Cannot create an image");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+}
+
+
+OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
+                           uint32_t                  width,
+                           uint32_t                  height,
+                           uint32_t                  pitch,
+                           void*                     buffer)
+{
+  image_ = OrthancPluginCreateImageAccessor
+      (GetGlobalContext(), format, width, height, pitch, buffer);
+
+  if (image_ == NULL)
+  {
+    LogError("Cannot create an image accessor");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+}
+
+void OrthancImage::UncompressPngImage(const void* data,
+                                      size_t size)
+{
+  Clear();
+
+  image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
+
+  if (image_ == NULL)
+  {
+    LogError("Cannot uncompress a PNG image");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+}
+
+
+void OrthancImage::UncompressJpegImage(const void* data,
+                                       size_t size)
+{
+  Clear();
+  image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
+  if (image_ == NULL)
+  {
+    LogError("Cannot uncompress a JPEG image");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+}
+
+
+void OrthancImage::DecodeDicomImage(const void* data,
+                                    size_t size,
+                                    unsigned int frame)
+{
+  Clear();
+  image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
+  if (image_ == NULL)
+  {
+    LogError("Cannot uncompress a DICOM image");
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+}
+
+
+OrthancPluginPixelFormat OrthancImage::GetPixelFormat()
+{
+  CheckImageAvailable();
+  return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
+}
 
 
-  bool MemoryBuffer::RestApiGet(const std::string& uri,
-                                bool applyPlugins)
-  {
-    Clear();
+unsigned int OrthancImage::GetWidth()
+{
+  CheckImageAvailable();
+  return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
+}
+
+
+unsigned int OrthancImage::GetHeight()
+{
+  CheckImageAvailable();
+  return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
+}
+
+
+unsigned int OrthancImage::GetPitch()
+{
+  CheckImageAvailable();
+  return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
+}
+
+
+const void* OrthancImage::GetBuffer()
+{
+  CheckImageAvailable();
+  return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
+}
+
 
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str()));
-    }
-  }
+void OrthancImage::CompressPngImage(MemoryBuffer& target)
+{
+  CheckImageAvailable();
+
+  OrthancPluginMemoryBuffer tmp;
+  OrthancPluginCompressPngImage(GetGlobalContext(), &tmp, GetPixelFormat(),
+                                GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+  target.Assign(tmp);
+}
+
+
+void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                     uint8_t quality)
+{
+  CheckImageAvailable();
+
+  OrthancPluginMemoryBuffer tmp;
+  OrthancPluginCompressJpegImage(GetGlobalContext(), &tmp, GetPixelFormat(),
+                                 GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+
+  target.Assign(tmp);
+}
+
+
+void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output)
+{
+  CheckImageAvailable();
+  OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
+                                         GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+}
+
 
-  
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const char* body,
-                                 size_t bodySize,
-                                 bool applyPlugins)
+void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                   uint8_t quality)
+{
+  CheckImageAvailable();
+  OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
+                                          GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+}
+
+
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
+  matcher_(NULL),
+  worklist_(worklist)
+{
+  if (worklist_ == NULL)
   {
-    Clear();
+    ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+  }
+}
+
+
+void FindMatcher::SetupDicom(const void*  query,
+                             uint32_t     size)
+{
+  worklist_ = NULL;
 
-    if (applyPlugins)
-    {
-      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
-    }
-    else
-    {
-      return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
-    }
+  matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
+  if (matcher_ == NULL)
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+}
+
+
+FindMatcher::~FindMatcher()
+{
+  // The "worklist_" field
+
+  if (matcher_ != NULL)
+  {
+    OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
+  }
+}
+
+
+
+bool FindMatcher::IsMatch(const void*  dicom,
+                          uint32_t     size) const
+{
+  int32_t result;
+
+  if (matcher_ != NULL)
+  {
+    result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
+  }
+  else if (worklist_ != NULL)
+  {
+    result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
   }
 
+  if (result == 0)
+  {
+    return false;
+  }
+  else if (result == 1)
+  {
+    return true;
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  }
+}
 
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const char* body,
-                                size_t bodySize,
-                                bool applyPlugins)
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+void AnswerJson(const Json::Value& value,
+                OrthancPluginRestOutput* output
+                )
+{
+  Json::StyledWriter writer;
+  std::string bodyString = writer.write(value);
+
+  OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
+}
+
+void AnswerString(const std::string& answer,
+                  const char* mimeType,
+                  OrthancPluginRestOutput* output
+                  )
+{
+  OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
+}
+
+void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
+{
+  OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
+}
+
+void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
+{
+  OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
+}
+
+bool RestApiGetString(std::string& result,
+                      const std::string& uri,
+                      bool applyPlugins)
+{
+  MemoryBuffer answer;
+  if (!answer.RestApiGet(uri, applyPlugins))
   {
-    Clear();
+    return false;
+  }
+  else
+  {
+    answer.ToString(result);
+    return true;
+  }
+}
+
+bool RestApiGetString(std::string& result,
+                      const std::string& uri,
+                      const std::map<std::string, std::string>& httpHeaders,
+                      bool applyPlugins)
+{
+  MemoryBuffer answer;
+  if (!answer.RestApiGet(uri, httpHeaders, applyPlugins))
+  {
+    return false;
+  }
+  else
+  {
+    answer.ToString(result);
+    return true;
+  }
+}
+
+
+
+bool RestApiGet(Json::Value& result,
+                const std::string& uri,
+                bool applyPlugins)
+{
+  MemoryBuffer answer;
 
-    if (applyPlugins)
+  if (!answer.RestApiGet(uri, applyPlugins))
+  {
+    return false;
+  }
+  else
+  {
+    if (!answer.IsEmpty())
     {
-      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+      answer.ToJson(result);
+    }
+    return true;
+  }
+}
+
+
+bool RestApiPost(Json::Value& result,
+                 const std::string& uri,
+                 const char* body,
+                 size_t bodySize,
+                 bool applyPlugins)
+{
+  MemoryBuffer answer;
+
+  if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
+  {
+    return false;
+  }
+  else
+  {
+    if (!answer.IsEmpty())
+    {
+      answer.ToJson(result);
     }
-    else
+    return true;
+  }
+}
+
+
+bool RestApiPost(Json::Value& result,
+                 const std::string& uri,
+                 const Json::Value& body,
+                 bool applyPlugins)
+{
+  Json::FastWriter writer;
+  return RestApiPost(result, uri, writer.write(body), applyPlugins);
+}
+
+
+bool RestApiPut(Json::Value& result,
+                const std::string& uri,
+                const char* body,
+                size_t bodySize,
+                bool applyPlugins)
+{
+  MemoryBuffer answer;
+
+  if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
+  {
+    return false;
+  }
+  else
+  {
+    if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthand returns an empty response
     {
-      return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), body, bodySize));
+      answer.ToJson(result);
     }
+    return true;
+  }
+}
+
+
+bool RestApiPut(Json::Value& result,
+                const std::string& uri,
+                const Json::Value& body,
+                bool applyPlugins)
+{
+  Json::FastWriter writer;
+  return RestApiPut(result, uri, writer.write(body), applyPlugins);
+}
+
+
+bool RestApiDelete(const std::string& uri,
+                   bool applyPlugins)
+{
+  OrthancPluginErrorCode error;
+
+  if (applyPlugins)
+  {
+    error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
+  }
+  else
+  {
+    error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
   }
 
+  if (error == OrthancPluginErrorCode_Success)
+  {
+    return true;
+  }
+  else if (error == OrthancPluginErrorCode_UnknownResource ||
+           error == OrthancPluginErrorCode_InexistentItem)
+  {
+    return false;
+  }
+  else
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+  }
+}
 
-  bool MemoryBuffer::RestApiPost(const std::string& uri,
-                                 const Json::Value& body,
-                                 bool applyPlugins)
+
+void ReportMinimalOrthancVersion(unsigned int major,
+                                 unsigned int minor,
+                                 unsigned int revision)
+{
+  LogError("Your version of the Orthanc core (" +
+           std::string(GetGlobalContext()->orthancVersion) +
+           ") is too old to run this plugin (version " +
+           boost::lexical_cast<std::string>(major) + "." +
+           boost::lexical_cast<std::string>(minor) + "." +
+           boost::lexical_cast<std::string>(revision) +
+           " is required)");
+}
+
+
+bool CheckMinimalOrthancVersion(unsigned int major,
+                                unsigned int minor,
+                                unsigned int revision)
+{
+  if (!HasGlobalContext())
   {
-    Json::FastWriter writer;
-    return RestApiPost(uri, writer.write(body), applyPlugins);
+    LogError("Bad Orthanc context in the plugin");
+    return false;
   }
 
+  if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
+  {
+    // Assume compatibility with the mainline
+    return true;
+  }
 
-  bool MemoryBuffer::RestApiPut(const std::string& uri,
-                                const Json::Value& body,
-                                bool applyPlugins)
+  // Parse the version of the Orthanc core
+  int aa, bb, cc;
+  if (
+    #ifdef _MSC_VER
+      sscanf_s
+    #else
+      sscanf
+    #endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
   {
-    Json::FastWriter writer;
-    return RestApiPut(uri, writer.write(body), applyPlugins);
+    return false;
+  }
+
+  unsigned int a = static_cast<unsigned int>(aa);
+  unsigned int b = static_cast<unsigned int>(bb);
+  unsigned int c = static_cast<unsigned int>(cc);
+
+  // Check the major version number
+
+  if (a > major)
+  {
+    return true;
+  }
+
+  if (a < major)
+  {
+    return false;
   }
 
 
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
+  // Check the minor version number
+  assert(a == major);
 
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-    
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags));
+  if (b > minor)
+  {
+    return true;
+  }
+
+  if (b < minor)
+  {
+    return false;
   }
 
-  void MemoryBuffer::CreateDicom(const Json::Value& tags,
-                                 const OrthancImage& pixelData,
-                                 OrthancPluginCreateDicomFlags flags)
-  {
-    Clear();
+  // Check the patch level version number
+  assert(a == major && b == minor);
 
-    Json::FastWriter writer;
-    std::string s = writer.write(tags);
-
-    Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags));
+  if (c >= revision)
+  {
+    return true;
   }
+  else
+  {
+    return false;
+  }
+}
 
 
-  void MemoryBuffer::ReadFile(const std::string& path)
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
+const char* AutodetectMimeType(const std::string& path)
+{
+  const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+
+  if (mime == NULL)
+  {
+    // Should never happen, just for safety
+    return "application/octet-stream";
+  }
+  else
   {
-    Clear();
-    Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str()));
+    return mime;
   }
+}
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_PEERS == 1
+size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+{
+  size_t index;
+  if (LookupName(index, name))
+  {
+    return index;
+  }
+  else
+  {
+    LogError("Inexistent peer: " + name);
+    ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
+  }
+}
 
 
-  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+OrthancPeers::OrthancPeers() :
+  peers_(NULL),
+  timeout_(0)
+{
+  peers_ = OrthancPluginGetPeers(GetGlobalContext());
+
+  if (peers_ == NULL)
   {
-    Clear();
-    Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query));
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
   }
 
+  uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
+
+  for (uint32_t i = 0; i < count; i++)
+  {
+    const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
+    if (name == NULL)
+    {
+      OrthancPluginFreePeers(GetGlobalContext(), peers_);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+
+    index_[name] = i;
+  }
+}
+
 
-  void OrthancString::Assign(char* str)
+OrthancPeers::~OrthancPeers()
+{
+  if (peers_ != NULL)
   {
-    if (str == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-    else
-    {
-      Clear();
-      str_ = str;
-    }
+    OrthancPluginFreePeers(GetGlobalContext(), peers_);
   }
+}
 
 
-  void OrthancString::Clear()
+bool OrthancPeers::LookupName(size_t& target,
+                              const std::string& name) const
+{
+  Index::const_iterator found = index_.find(name);
+
+  if (found == index_.end())
   {
-    if (str_ != NULL)
-    {
-      OrthancPluginFreeString(GetGlobalContext(), str_);
-      str_ = NULL;
-    }
+    return false;
   }
+  else
+  {
+    target = found->second;
+    return true;
+  }
+}
 
 
-  void OrthancString::ToString(std::string& target) const
+std::string OrthancPeers::GetPeerName(size_t index) const
+{
+  if (index >= index_.size())
   {
-    if (str_ == NULL)
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+  }
+  else
+  {
+    const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+    if (s == NULL)
     {
-      target.clear();
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
     }
     else
     {
-      target.assign(str_);
+      return s;
     }
   }
-
-
-  void OrthancString::ToJson(Json::Value& target) const
-  {
-    if (str_ == NULL)
-    {
-      LogError("Cannot convert an empty memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Reader reader;
-    if (!reader.parse(str_, target))
-    {
-      LogError("Cannot convert some memory buffer to JSON");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-  
-  void MemoryBuffer::DicomToJson(Json::Value& target,
-                                 OrthancPluginDicomToJsonFormat format,
-                                 OrthancPluginDicomToJsonFlags flags,
-                                 uint32_t maxStringLength)
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginDicomBufferToJson
-               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
-    str.ToJson(target);
-  }
+}
 
 
-  bool MemoryBuffer::HttpGet(const std::string& url,
-                             const std::string& username,
-                             const std::string& password)
+std::string OrthancPeers::GetPeerUrl(size_t index) const
+{
+  if (index >= index_.size())
   {
-    Clear();
-    return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
   }
-
-  
-  bool MemoryBuffer::HttpPost(const std::string& url,
-                              const std::string& body,
-                              const std::string& username,
-                              const std::string& password)
-  {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(),
-                                           body.c_str(), body.size(),
-                                           username.empty() ? NULL : username.c_str(),
-                                           password.empty() ? NULL : password.c_str()));
-  }
-  
-
-  bool MemoryBuffer::HttpPut(const std::string& url,
-                             const std::string& body,
-                             const std::string& username,
-                             const std::string& password)
+  else
   {
-    Clear();
-    return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(),
-                                          body.empty() ? NULL : body.c_str(),
-                                          body.size(),
-                                          username.empty() ? NULL : username.c_str(),
-                                          password.empty() ? NULL : password.c_str()));
+    const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+    }
+    else
+    {
+      return s;
+    }
   }
-  
+}
 
-  void MemoryBuffer::GetDicomInstance(const std::string& instanceId)
-  {
-    Clear();
-    Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str()));
-  }
 
-  
-  bool HttpDelete(const std::string& url,
-                  const std::string& username,
-                  const std::string& password)
+std::string OrthancPeers::GetPeerUrl(const std::string& name) const
+{
+  return GetPeerUrl(GetPeerIndex(name));
+}
+
+
+bool OrthancPeers::LookupUserProperty(std::string& value,
+                                      size_t index,
+                                      const std::string& key) const
+{
+  if (index >= index_.size())
   {
-    OrthancPluginErrorCode error = OrthancPluginHttpDelete
-        (GetGlobalContext(), url.c_str(),
-         username.empty() ? NULL : username.c_str(),
-         password.empty() ? NULL : password.c_str());
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+  }
+  else
+  {
+    const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
+    if (s == NULL)
     {
       return false;
     }
     else
     {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
-  }
-  
-
-  void LogError(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogError(GetGlobalContext(), message.c_str());
-    }
-  }
-
-  
-  void LogWarning(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogWarning(GetGlobalContext(), message.c_str());
-    }
-  }
-
-  
-  void LogInfo(const std::string& message)
-  {
-    if (HasGlobalContext())
-    {
-      OrthancPluginLogInfo(GetGlobalContext(), message.c_str());
-    }
-  }
-
-
-  OrthancConfiguration::OrthancConfiguration()
-  {
-    OrthancString str;
-    str.Assign(OrthancPluginGetConfiguration(GetGlobalContext()));
-
-    if (str.GetContent() == NULL)
-    {
-      LogError("Cannot access the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    str.ToJson(configuration_);
-
-    if (configuration_.type() != Json::objectValue)
-    {
-      LogError("Unable to read the Orthanc configuration");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  std::string OrthancConfiguration::GetPath(const std::string& key) const
-  {
-    if (path_.empty())
-    {
-      return key;
-    }
-    else
-    {
-      return path_ + "." + key;
-    }
-  }
-
-
-  bool OrthancConfiguration::IsSection(const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    return (configuration_.isMember(key) &&
-            configuration_[key].type() == Json::objectValue);
-  }
-
-
-  void OrthancConfiguration::GetSection(OrthancConfiguration& target,
-                                        const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.path_ = GetPath(key);
-
-    if (!configuration_.isMember(key))
-    {
-      target.configuration_ = Json::objectValue;
-    }
-    else
-    {
-      if (configuration_[key].type() != Json::objectValue)
-      {
-        LogError("The configuration section \"" + target.path_ +
-                 "\" is not an associative array as expected");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-      }
-
-      target.configuration_ = configuration_[key];
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupStringValue(std::string& target,
-                                               const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    if (configuration_[key].type() != Json::stringValue)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    target = configuration_[key].asString();
-    return true;
-  }
-
-
-  bool OrthancConfiguration::LookupIntegerValue(int& target,
-                                                const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-    case Json::intValue:
-      target = configuration_[key].asInt();
-      return true;
-
-    case Json::uintValue:
-      target = configuration_[key].asUInt();
-      return true;
-
-    default:
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not an integer as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
-                                                        const std::string& key) const
-  {
-    int tmp;
-    if (!LookupIntegerValue(tmp, key))
-    {
-      return false;
-    }
-
-    if (tmp < 0)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a positive integer as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-    else
-    {
-      target = static_cast<unsigned int>(tmp);
-      return true;
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupBooleanValue(bool& target,
-                                                const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    if (configuration_[key].type() != Json::booleanValue)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a Boolean as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    target = configuration_[key].asBool();
-    return true;
-  }
-
-
-  bool OrthancConfiguration::LookupFloatValue(float& target,
-                                              const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-    case Json::realValue:
-      target = configuration_[key].asFloat();
-      return true;
-
-    case Json::intValue:
-      target = static_cast<float>(configuration_[key].asInt());
-      return true;
-
-    case Json::uintValue:
-      target = static_cast<float>(configuration_[key].asUInt());
-      return true;
-
-    default:
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not an integer as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-  }
-
-
-  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
-                                                 const std::string& key,
-                                                 bool allowSingleString) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return false;
-    }
-
-    switch (configuration_[key].type())
-    {
-    case Json::arrayValue:
-    {
-      bool ok = true;
-
-      for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
-      {
-        if (configuration_[key][i].type() == Json::stringValue)
-        {
-          target.push_back(configuration_[key][i].asString());
-        }
-        else
-        {
-          ok = false;
-        }
-      }
-
-      if (ok)
-      {
-        return true;
-      }
-
-      break;
-    }
-
-    case Json::stringValue:
-      if (allowSingleString)
-      {
-        target.push_back(configuration_[key].asString());
-        return true;
-      }
-
-      break;
-
-    default:
-      break;
-    }
-
-    LogError("The configuration option \"" + GetPath(key) +
-             "\" is not a list of strings as expected");
-
-    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-  }
-
-
-  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
-                                                const std::string& key,
-                                                bool allowSingleString) const
-  {
-    std::list<std::string> lst;
-
-    if (LookupListOfStrings(lst, key, allowSingleString))
-    {
-      target.clear();
-
-      for (std::list<std::string>::const_iterator
-           it = lst.begin(); it != lst.end(); ++it)
-      {
-        target.insert(*it);
-      }
-
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  std::string OrthancConfiguration::GetStringValue(const std::string& key,
-                                                   const std::string& defaultValue) const
-  {
-    std::string tmp;
-    if (LookupStringValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  int OrthancConfiguration::GetIntegerValue(const std::string& key,
-                                            int defaultValue) const
-  {
-    int tmp;
-    if (LookupIntegerValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
-                                                             unsigned int defaultValue) const
-  {
-    unsigned int tmp;
-    if (LookupUnsignedIntegerValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
-                                             bool defaultValue) const
-  {
-    bool tmp;
-    if (LookupBooleanValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  float OrthancConfiguration::GetFloatValue(const std::string& key,
-                                            float defaultValue) const
-  {
-    float tmp;
-    if (LookupFloatValue(tmp, key))
-    {
-      return tmp;
-    }
-    else
-    {
-      return defaultValue;
-    }
-  }
-
-
-  void OrthancConfiguration::GetDictionary(std::map<std::string, std::string>& target,
-                                           const std::string& key) const
-  {
-    assert(configuration_.type() == Json::objectValue);
-
-    target.clear();
-
-    if (!configuration_.isMember(key))
-    {
-      return;
-    }
-
-    if (configuration_[key].type() != Json::objectValue)
-    {
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not a string as expected");
-
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-    }
-
-    Json::Value::Members members = configuration_[key].getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& value = configuration_[key][members[i]];
-
-      if (value.type() == Json::stringValue)
-      {
-        target[members[i]] = value.asString();
-      }
-      else
-      {
-        LogError("The configuration option \"" + GetPath(key) +
-                 "\" is not a dictionary mapping strings to strings");
-
-        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
-      }
-    }
-  }
-
-
-  void OrthancImage::Clear()
-  {
-    if (image_ != NULL)
-    {
-      OrthancPluginFreeImage(GetGlobalContext(), image_);
-      image_ = NULL;
-    }
-  }
-
-
-  void OrthancImage::CheckImageAvailable()
-  {
-    if (image_ == NULL)
-    {
-      LogError("Trying to access a NULL image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancImage::OrthancImage() :
-    image_(NULL)
-  {
-  }
-
-
-  OrthancImage::OrthancImage(OrthancPluginImage*  image) :
-    image_(image)
-  {
-  }
-  
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height)
-  {
-    image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-  
-
-  OrthancImage::OrthancImage(OrthancPluginPixelFormat  format,
-                             uint32_t                  width,
-                             uint32_t                  height,
-                             uint32_t                  pitch,
-                             void*                     buffer)
-  {
-    image_ = OrthancPluginCreateImageAccessor
-      (GetGlobalContext(), format, width, height, pitch, buffer);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot create an image accessor");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-  void OrthancImage::UncompressPngImage(const void* data,
-                                        size_t size)
-  {
-    Clear();
-    
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png);
-
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a PNG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::UncompressJpegImage(const void* data,
-                                         size_t size)
-  {
-    Clear();
-    image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a JPEG image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void OrthancImage::DecodeDicomImage(const void* data,
-                                      size_t size,
-                                      unsigned int frame)
-  {
-    Clear();
-    image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame);
-    if (image_ == NULL)
-    {
-      LogError("Cannot uncompress a DICOM image");
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  OrthancPluginPixelFormat OrthancImage::GetPixelFormat()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetWidth()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageWidth(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetHeight()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageHeight(GetGlobalContext(), image_);
-  }
-
-
-  unsigned int OrthancImage::GetPitch()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImagePitch(GetGlobalContext(), image_);
-  }
-
-
-  const void* OrthancImage::GetBuffer()
-  {
-    CheckImageAvailable();
-    return OrthancPluginGetImageBuffer(GetGlobalContext(), image_);
-  }
-
-
-  void OrthancImage::CompressPngImage(MemoryBuffer& target)
-  {
-    CheckImageAvailable();
-    
-    OrthancPluginMemoryBuffer tmp;
-    OrthancPluginCompressPngImage(GetGlobalContext(), &tmp, GetPixelFormat(),
-                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-
-    target.Assign(tmp);
-  }
-
-
-  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
-                                       uint8_t quality)
-  {
-    CheckImageAvailable();
-    
-    OrthancPluginMemoryBuffer tmp;
-    OrthancPluginCompressJpegImage(GetGlobalContext(), &tmp, GetPixelFormat(),
-                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-    
-    target.Assign(tmp);
-  }
-
-
-  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output)
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(),
-                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
-  }
-
-
-  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
-                                     uint8_t quality)
-  {
-    CheckImageAvailable();
-    OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
-                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
-  }
-
-
-
-#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
-  FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) :
-    matcher_(NULL),
-    worklist_(worklist)
-  {
-    if (worklist_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
-    }
-  }
-
-
-  void FindMatcher::SetupDicom(const void*  query,
-                               uint32_t     size)
-  {
-    worklist_ = NULL;
-
-    matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size);
-    if (matcher_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-
-  FindMatcher::~FindMatcher()
-  {
-    // The "worklist_" field
-
-    if (matcher_ != NULL)
-    {
-      OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_);
-    }
-  }
-
-
-
-  bool FindMatcher::IsMatch(const void*  dicom,
-                            uint32_t     size) const
-  {
-    int32_t result;
-
-    if (matcher_ != NULL)
-    {
-      result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size);
-    }
-    else if (worklist_ != NULL)
-    {
-      result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size);
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    if (result == 0)
-    {
-      return false;
-    }
-    else if (result == 1)
-    {
-      return true;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-  }
-
-#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
-
-  void AnswerJson(const Json::Value& value,
-                  OrthancPluginRestOutput* output
-                  )
-  {
-    Json::StyledWriter writer;
-    std::string bodyString = writer.write(value);
-
-    OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json");
-  }
-
-  void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output)
-  {
-    OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError);
-  }
-
-  void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods)
-  {
-    OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
-  }
-
-  bool RestApiGetString(std::string& result,
-                        const std::string& uri,
-                        bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      answer.ToString(result);
-      return true;
-    }
-  }
-
-
-  bool RestApiGet(Json::Value& result,
-                  const std::string& uri,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    
-    if (!answer.RestApiGet(uri, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const char* body,
-                   size_t bodySize,
-                   bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    
-    if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty())
-      {
-        answer.ToJson(result);
-      }
+      value.assign(s);
       return true;
     }
   }
-
-
-  bool RestApiPost(Json::Value& result,
-                   const std::string& uri,
-                   const Json::Value& body,
-                   bool applyPlugins)
-  {
-    Json::FastWriter writer;
-    return RestApiPost(result, uri, writer.write(body), applyPlugins);
-  }
+}
 
 
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const char* body,
-                  size_t bodySize,
-                  bool applyPlugins)
-  {
-    MemoryBuffer answer;
-    
-    if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
-    {
-      return false;
-    }
-    else
-    {
-      if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthand returns an empty response
-      {
-        answer.ToJson(result);
-      }
-      return true;
-    }
-  }
+bool OrthancPeers::LookupUserProperty(std::string& value,
+                                      const std::string& peer,
+                                      const std::string& key) const
+{
+  return LookupUserProperty(value, GetPeerIndex(peer), key);
+}
 
 
-  bool RestApiPut(Json::Value& result,
-                  const std::string& uri,
-                  const Json::Value& body,
-                  bool applyPlugins)
+bool OrthancPeers::DoGet(MemoryBuffer& target,
+                         size_t index,
+                         const std::string& uri) const
+{
+  if (index >= index_.size())
   {
-    Json::FastWriter writer;
-    return RestApiPut(result, uri, writer.write(body), applyPlugins);
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
   }
 
-
-  bool RestApiDelete(const std::string& uri,
-                     bool applyPlugins)
-  {
-    OrthancPluginErrorCode error;
+  OrthancPluginMemoryBuffer answer;
+  uint16_t status;
+  OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), &answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
 
-    if (applyPlugins)
-    {
-      error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str());
-    }
-    else
-    {
-      error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str());
-    }
+  if (code == OrthancPluginErrorCode_Success)
+  {
+    target.Assign(answer);
+    return (status == 200);
+  }
+  else
+  {
+    return false;
+  }
+}
+
 
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
-    }
+bool OrthancPeers::DoGet(MemoryBuffer& target,
+                         const std::string& name,
+                         const std::string& uri) const
+{
+  size_t index;
+  return (LookupName(index, name) &&
+          DoGet(target, index, uri));
+}
+
+
+bool OrthancPeers::DoGet(Json::Value& target,
+                         size_t index,
+                         const std::string& uri) const
+{
+  MemoryBuffer buffer;
+
+  if (DoGet(buffer, index, uri))
+  {
+    buffer.ToJson(target);
+    return true;
   }
+  else
+  {
+    return false;
+  }
+}
 
 
-  void ReportMinimalOrthancVersion(unsigned int major,
-                                   unsigned int minor,
-                                   unsigned int revision)
+bool OrthancPeers::DoGet(Json::Value& target,
+                         const std::string& name,
+                         const std::string& uri) const
+{
+  MemoryBuffer buffer;
+
+  if (DoGet(buffer, name, uri))
+  {
+    buffer.ToJson(target);
+    return true;
+  }
+  else
   {
-    LogError("Your version of the Orthanc core (" +
-             std::string(GetGlobalContext()->orthancVersion) +
-             ") is too old to run this plugin (version " +
-             boost::lexical_cast<std::string>(major) + "." +
-             boost::lexical_cast<std::string>(minor) + "." +
-             boost::lexical_cast<std::string>(revision) +
-             " is required)");
+    return false;
   }
+}
+
+
+bool OrthancPeers::DoPost(MemoryBuffer& target,
+                          const std::string& name,
+                          const std::string& uri,
+                          const std::string& body) const
+{
+  size_t index;
+  return (LookupName(index, name) &&
+          DoPost(target, index, uri, body));
+}
+
+
+bool OrthancPeers::DoPost(Json::Value& target,
+                          size_t index,
+                          const std::string& uri,
+                          const std::string& body) const
+{
+  MemoryBuffer buffer;
+
+  if (DoPost(buffer, index, uri, body))
+  {
+    buffer.ToJson(target);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
 
 
-  bool CheckMinimalOrthancVersion(unsigned int major,
-                                  unsigned int minor,
-                                  unsigned int revision)
+bool OrthancPeers::DoPost(Json::Value& target,
+                          const std::string& name,
+                          const std::string& uri,
+                          const std::string& body) const
+{
+  MemoryBuffer buffer;
+
+  if (DoPost(buffer, name, uri, body))
   {
-    if (!HasGlobalContext())
-    {
-      LogError("Bad Orthanc context in the plugin");
-      return false;
-    }
+    buffer.ToJson(target);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
 
-    if (!strcmp(GetGlobalContext()->orthancVersion, "mainline"))
-    {
-      // Assume compatibility with the mainline
-      return true;
-    }
 
-    // Parse the version of the Orthanc core
-    int aa, bb, cc;
-    if (
-    #ifdef _MSC_VER
-        sscanf_s
-    #else
-        sscanf
-    #endif
-        (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
-        aa < 0 ||
-        bb < 0 ||
-        cc < 0)
-    {
-      return false;
-    }
+bool OrthancPeers::DoPost(MemoryBuffer& target,
+                          size_t index,
+                          const std::string& uri,
+                          const std::string& body) const
+{
+  if (index >= index_.size())
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
+  }
 
-    unsigned int a = static_cast<unsigned int>(aa);
-    unsigned int b = static_cast<unsigned int>(bb);
-    unsigned int c = static_cast<unsigned int>(cc);
-
-    // Check the major version number
+  OrthancPluginMemoryBuffer answer;
+  uint16_t status;
+  OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), &answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
-    if (a > major)
-    {
-      return true;
-    }
-
-    if (a < major)
-    {
-      return false;
-    }
+  if (code == OrthancPluginErrorCode_Success)
+  {
+    target.Assign(answer);
+    return (status == 200);
+  }
+  else
+  {
+    return false;
+  }
+}
 
 
-    // Check the minor version number
-    assert(a == major);
-
-    if (b > minor)
-    {
-      return true;
-    }
-
-    if (b < minor)
-    {
-      return false;
-    }
-
-    // Check the patch level version number
-    assert(a == major && b == minor);
-
-    if (c >= revision)
-    {
-      return true;
-    }
-    else
-    {
-      return false;
-    }
+bool OrthancPeers::DoPut(size_t index,
+                         const std::string& uri,
+                         const std::string& body) const
+{
+  if (index >= index_.size())
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
   }
 
-  
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
-  const char* AutodetectMimeType(const std::string& path)
+  OrthancPluginMemoryBuffer answer;
+  uint16_t status;
+  OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), &answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+
+  if (code == OrthancPluginErrorCode_Success)
+  {
+    OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &answer);
+    return (status == 200);
+  }
+  else
   {
-    const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str());
+    return false;
+  }
+}
+
+
+bool OrthancPeers::DoPut(const std::string& name,
+                         const std::string& uri,
+                         const std::string& body) const
+{
+  size_t index;
+  return (LookupName(index, name) &&
+          DoPut(index, uri, body));
+}
+
 
-    if (mime == NULL)
-    {
-      // Should never happen, just for safety
-      return "application/octet-stream";
-    }
-    else
-    {
-      return mime;
-    }
+bool OrthancPeers::DoDelete(size_t index,
+                            const std::string& uri) const
+{
+  if (index >= index_.size())
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
   }
+
+  OrthancPluginMemoryBuffer answer;
+  uint16_t status;
+  OrthancPluginErrorCode code = OrthancPluginCallPeerApi
+      (GetGlobalContext(), &answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
+
+  if (code == OrthancPluginErrorCode_Success)
+  {
+    OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &answer);
+    return (status == 200);
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+bool OrthancPeers::DoDelete(const std::string& name,
+                            const std::string& uri) const
+{
+  size_t index;
+  return (LookupName(index, name) &&
+          DoDelete(index, uri));
+}
 #endif
 
 
-#if HAS_ORTHANC_PLUGIN_PEERS == 1
-  size_t OrthancPeers::GetPeerIndex(const std::string& name) const
+
+#if HAS_ORTHANC_PLUGIN_JOB == 1
+void OrthancJob::CallbackFinalize(void* job)
+{
+  if (job != NULL)
   {
-    size_t index;
-    if (LookupName(index, name))
-    {
-      return index;
-    }
-    else
-    {
-      LogError("Inexistent peer: " + name);
-      ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource);
-    }
+    delete reinterpret_cast<OrthancJob*>(job);
   }
+}
+
+
+float OrthancJob::CallbackGetProgress(void* job)
+{
+  assert(job != NULL);
+
+  try
+  {
+    return reinterpret_cast<OrthancJob*>(job)->progress_;
+  }
+  catch (...)
+  {
+    return 0;
+  }
+}
 
 
-  OrthancPeers::OrthancPeers() :
-    peers_(NULL),
-    timeout_(0)
-  {
-    peers_ = OrthancPluginGetPeers(GetGlobalContext());
-
-    if (peers_ == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-
-    uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_);
-
-    for (uint32_t i = 0; i < count; i++)
-    {
-      const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i);
-      if (name == NULL)
-      {
-        OrthancPluginFreePeers(GetGlobalContext(), peers_);
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-
-      index_[name] = i;
-    }
-  }
-
-  
-  OrthancPeers::~OrthancPeers()
-  {
-    if (peers_ != NULL)
-    {
-      OrthancPluginFreePeers(GetGlobalContext(), peers_);
-    }
-  }
-
-  
-  bool OrthancPeers::LookupName(size_t& target,
-                                const std::string& name) const
-  {
-    Index::const_iterator found = index_.find(name);
+const char* OrthancJob::CallbackGetContent(void* job)
+{
+  assert(job != NULL);
 
-    if (found == index_.end())
-    {
-      return false;
-    }
-    else
-    {
-      target = found->second;
-      return true;
-    }
-  }
-
-  
-  std::string OrthancPeers::GetPeerName(size_t index) const
+  try
   {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
+    return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
   }
-  
-
-  std::string OrthancPeers::GetPeerUrl(size_t index) const
+  catch (...)
   {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast<uint32_t>(index));
-      if (s == NULL)
-      {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-      }
-      else
-      {
-        return s;
-      }
-    }
+    return 0;
   }
-
-  
-  std::string OrthancPeers::GetPeerUrl(const std::string& name) const
-  {
-    return GetPeerUrl(GetPeerIndex(name));
-  }
+}
 
 
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        size_t index,
-                                        const std::string& key) const
+const char* OrthancJob::CallbackGetSerialized(void* job)
+{
+  assert(job != NULL);
+
+  try
   {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast<uint32_t>(index), key.c_str());
-      if (s == NULL)
-      {
-        return false;
-      }
-      else
-      {
-        value.assign(s);
-        return true;
-      }
-    }
-  }
+    const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
 
-
-  bool OrthancPeers::LookupUserProperty(std::string& value,
-                                        const std::string& peer,
-                                        const std::string& key) const
-  {
-    return LookupUserProperty(value, GetPeerIndex(peer), key);
-  }
-
-
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    if (index >= index_.size())
+    if (tmp.hasSerialized_)
     {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPluginMemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), &answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-         0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Assign(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool OrthancPeers::DoGet(MemoryBuffer& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoGet(target, index, uri));
-  }
-  
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           size_t index,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, index, uri))
-    {
-      buffer.ToJson(target);
-      return true;
+      return tmp.serialized_.c_str();
     }
     else
     {
-      return false;
-    }
-  }
-  
-
-  bool OrthancPeers::DoGet(Json::Value& target,
-                           const std::string& name,
-                           const std::string& uri) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoGet(buffer, name, uri))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPost(target, index, uri, body));
-  }
-  
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, index, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-  
-
-  bool OrthancPeers::DoPost(Json::Value& target,
-                            const std::string& name,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    MemoryBuffer buffer;
-
-    if (DoPost(buffer, name, uri, body))
-    {
-      buffer.ToJson(target);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool OrthancPeers::DoPost(MemoryBuffer& target,
-                            size_t index,
-                            const std::string& uri,
-                            const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPluginMemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), &answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-         0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      target.Assign(answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-  
-
-  bool OrthancPeers::DoPut(size_t index,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPluginMemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), &answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-         0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-  
-
-  bool OrthancPeers::DoPut(const std::string& name,
-                           const std::string& uri,
-                           const std::string& body) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoPut(index, uri, body));
-  }
-  
-
-  bool OrthancPeers::DoDelete(size_t index,
-                              const std::string& uri) const
-  {
-    if (index >= index_.size())
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    OrthancPluginMemoryBuffer answer;
-    uint16_t status;
-    OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), &answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-         0, NULL, NULL, NULL, 0, timeout_);
-
-    if (code == OrthancPluginErrorCode_Success)
-    {
-      OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &answer);
-      return (status == 200);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  
-  bool OrthancPeers::DoDelete(const std::string& name,
-                              const std::string& uri) const
-  {
-    size_t index;
-    return (LookupName(index, name) &&
-            DoDelete(index, uri));
-  }
-#endif
-
-
-  
-#if HAS_ORTHANC_PLUGIN_JOB == 1
-  void OrthancJob::CallbackFinalize(void* job)
-  {
-    if (job != NULL)
-    {
-      delete reinterpret_cast<OrthancJob*>(job);
-    }
-  }
-  
-
-  float OrthancJob::CallbackGetProgress(void* job)
-  {
-    assert(job != NULL);
-
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->progress_;
-    }
-    catch (...)
-    {
-      return 0;
+      return NULL;
     }
   }
-  
+  catch (...)
+  {
+    return 0;
+  }
+}
+
+
+OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+{
+  assert(job != NULL);
 
-  const char* OrthancJob::CallbackGetContent(void* job)
+  try
+  {
+    return reinterpret_cast<OrthancJob*>(job)->Step();
+  }
+  catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
   {
-    assert(job != NULL);
+    return OrthancPluginJobStepStatus_Failure;
+  }
+  catch (...)
+  {
+    return OrthancPluginJobStepStatus_Failure;
+  }
+}
 
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
-    }
-    catch (...)
-    {
-      return 0;
-    }
+
+OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
+                                                OrthancPluginJobStopReason reason)
+{
+  assert(job != NULL);
+
+  try
+  {
+    reinterpret_cast<OrthancJob*>(job)->Stop(reason);
+    return OrthancPluginErrorCode_Success;
   }
-  
-
-  const char* OrthancJob::CallbackGetSerialized(void* job)
+  catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+  {
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+  }
+  catch (...)
   {
-    assert(job != NULL);
+    return OrthancPluginErrorCode_Plugin;
+  }
+}
 
-    try
-    {
-      const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(job);
+
+OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
+{
+  assert(job != NULL);
 
-      if (tmp.hasSerialized_)
-      {
-        return tmp.serialized_.c_str();
-      }
-      else
-      {
-        return NULL;
-      }
-    }
-    catch (...)
-    {
-      return 0;
-    }
+  try
+  {
+    reinterpret_cast<OrthancJob*>(job)->Reset();
+    return OrthancPluginErrorCode_Success;
+  }
+  catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+  {
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
   }
-  
+  catch (...)
+  {
+    return OrthancPluginErrorCode_Plugin;
+  }
+}
+
+
+void OrthancJob::ClearContent()
+{
+  Json::Value empty = Json::objectValue;
+  UpdateContent(empty);
+}
+
 
-  OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
+void OrthancJob::UpdateContent(const Json::Value& content)
+{
+  if (content.type() != Json::objectValue)
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+  }
+  else
   {
-    assert(job != NULL);
+    Json::FastWriter writer;
+    content_ = writer.write(content);
+  }
+}
+
+
+void OrthancJob::ClearSerialized()
+{
+  hasSerialized_ = false;
+  serialized_.clear();
+}
+
 
-    try
-    {
-      return reinterpret_cast<OrthancJob*>(job)->Step();
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
-    catch (...)
-    {
-      return OrthancPluginJobStepStatus_Failure;
-    }
+void OrthancJob::UpdateSerialized(const Json::Value& serialized)
+{
+  if (serialized.type() != Json::objectValue)
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
+  }
+  else
+  {
+    Json::FastWriter writer;
+    serialized_ = writer.write(serialized);
+    hasSerialized_ = true;
+  }
+}
+
+
+void OrthancJob::UpdateProgress(float progress)
+{
+  if (progress < 0 ||
+      progress > 1)
+  {
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
   }
 
-  
-  OrthancPluginErrorCode OrthancJob::CallbackStop(void* job,
-                                                  OrthancPluginJobStopReason reason)
-  {
-    assert(job != NULL);
+  progress_ = progress;
+}
 
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Stop(reason);
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-  
-
-  OrthancPluginErrorCode OrthancJob::CallbackReset(void* job)
-  {
-    assert(job != NULL);
 
-    try
-    {
-      reinterpret_cast<OrthancJob*>(job)->Reset();
-      return OrthancPluginErrorCode_Success;
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-  
+OrthancJob::OrthancJob(const std::string& jobType) :
+  jobType_(jobType),
+  progress_(0)
+{
+  ClearContent();
+  ClearSerialized();
+}
 
-  void OrthancJob::ClearContent()
+
+OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
+{
+  if (job == NULL)
   {
-    Json::Value empty = Json::objectValue;
-    UpdateContent(empty);
-  }
-
-  
-  void OrthancJob::UpdateContent(const Json::Value& content)
-  {
-    if (content.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      content_ = writer.write(content);
-    }
-  }
-  
-
-  void OrthancJob::ClearSerialized()
-  {
-    hasSerialized_ = false;
-    serialized_.clear();
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
   }
 
-  
-  void OrthancJob::UpdateSerialized(const Json::Value& serialized)
-  {
-    if (serialized.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
-    }
-    else
-    {
-      Json::FastWriter writer;
-      serialized_ = writer.write(serialized);
-      hasSerialized_ = true;
-    }
-  }
+  OrthancPluginJob* orthanc = OrthancPluginCreateJob(
+        GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+        CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+        CallbackStep, CallbackStop, CallbackReset);
 
-  
-  void OrthancJob::UpdateProgress(float progress)
+  if (orthanc == NULL)
   {
-    if (progress < 0 ||
-        progress > 1)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
-    }
-
-    progress_ = progress;
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
   }
-
-
-  OrthancJob::OrthancJob(const std::string& jobType) :
-    jobType_(jobType),
-    progress_(0)
+  else
   {
-    ClearContent();
-    ClearSerialized();
+    return orthanc;
   }
+}
 
 
-  OrthancPluginJob* OrthancJob::Create(OrthancJob* job)
-  {
-    if (job == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
-    }
+std::string OrthancJob::Submit(OrthancJob* job,
+                               int priority)
+{
+  OrthancPluginJob* orthanc = Create(job);
 
-    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-      CallbackStep, CallbackStop, CallbackReset);
-
-    if (orthanc == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      return orthanc;
-    }
-  }
+  char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
 
-  
-  std::string OrthancJob::Submit(OrthancJob* job,
-                                 int priority)
+  if (id == NULL)
   {
-    OrthancPluginJob* orthanc = Create(job);
-    
-    char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority);
+    LogError("Plugin cannot submit job");
+    OrthancPluginFreeJob(GetGlobalContext(), orthanc);
+    ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
+  }
+  else
+  {
+    std::string tmp(id);
+    tmp.assign(id);
+    OrthancPluginFreeString(GetGlobalContext(), id);
 
-    if (id == NULL)
-    {
-      LogError("Plugin cannot submit job");
-      OrthancPluginFreeJob(GetGlobalContext(), orthanc);
-      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin);
-    }
-    else
-    {
-      std::string tmp(id);
-      tmp.assign(id);
-      OrthancPluginFreeString(GetGlobalContext(), id);
-
-      return tmp;
-    }
+    return tmp;
   }
+}
 #endif
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon Dec 17 10:22:51 2018 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue Dec 18 12:07:52 2018 +0100
@@ -39,6 +39,7 @@
 #include <boost/noncopyable.hpp>
 #include <boost/lexical_cast.hpp>
 #include <json/value.h>
+#include <vector>
 #include <list>
 #include <set>
 #include <map>
@@ -154,6 +155,10 @@
     bool RestApiGet(const std::string& uri,
                     bool applyPlugins);
 
+    bool RestApiGet(const std::string& uri,
+                    const std::map<std::string, std::string>& httpHeaders,
+                    bool applyPlugins);
+
     bool RestApiPost(const std::string& uri,
                      const char* body,
                      size_t bodySize,
@@ -428,6 +433,11 @@
                         const std::string& uri,
                         bool applyPlugins);
 
+  bool RestApiGetString(std::string& result,
+                        const std::string& uri,
+                        const std::map<std::string, std::string>& httpHeaders,
+                        bool applyPlugins);
+
   bool RestApiPost(Json::Value& result,
                    const std::string& uri,
                    const char* body,
@@ -487,6 +497,10 @@
   void AnswerJson(const Json::Value& value,
                   OrthancPluginRestOutput* output);
 
+  void AnswerString(const std::string& answer,
+                    const char* mimeType,
+                    OrthancPluginRestOutput* output);
+
   void AnswerHttpError(uint16_t httpError,
                        OrthancPluginRestOutput* output);