# HG changeset patch # User Sebastien Jodogne # Date 1355406777 -3600 # Node ID 727a6d766ddec51ed3cc080e440bc7c8c9bf354a # Parent e5402c368b21bbabf76ac03e7842a357e6e79866# Parent 4031f73fe0e41239507903aefbdc87bdbc7b31a1 merge diff -r e5402c368b21 -r 727a6d766dde CMakeLists.txt --- a/CMakeLists.txt Mon Dec 10 17:49:14 2012 +0100 +++ b/CMakeLists.txt Thu Dec 13 14:52:57 2012 +0100 @@ -99,6 +99,7 @@ ${AUTOGENERATED_SOURCES} ${THIRD_PARTY_SOURCES} + Core/Cache/MemoryCache.cpp Core/ChunkedBuffer.cpp Core/Compression/BufferCompressor.cpp Core/Compression/ZlibCompressor.cpp @@ -188,6 +189,7 @@ UnitTests/Versions.cpp UnitTests/Zip.cpp UnitTests/FileStorage.cpp + UnitTests/MemoryCache.cpp UnitTests/main.cpp ) target_link_libraries(UnitTests ServerLibrary CoreLibrary) diff -r e5402c368b21 -r 727a6d766dde Core/Cache/CacheIndex.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/CacheIndex.h Thu Dec 13 14:52:57 2012 +0100 @@ -0,0 +1,250 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include +#include +#include + +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + /** + * This class implements the index of a cache with least recently + * used (LRU) recycling policy. All the items of the cache index + * can be associated with a payload. + * Reference: http://stackoverflow.com/a/2504317 + **/ + template + class CacheIndex : public boost::noncopyable + { + private: + typedef std::list< std::pair > Queue; + typedef std::map Index; + + Index index_; + Queue queue_; + + /** + * Internal method for debug builds to check whether the internal + * data structures are not corrupted. + **/ + void CheckInvariants() const; + + public: + /** + * Add a new element to the cache index, and make it the most + * recent element. + * \param id The ID of the element. + * \param payload The payload of the element. + **/ + void Add(T id, Payload payload = Payload()); + + /** + * When accessing an element of the cache, this method tags the + * element as the most recently used. + * \param id The most recently accessed item. + **/ + void TagAsMostRecent(T id); + + /** + * Remove an element from the cache index. + * \param id The item to remove. + **/ + Payload Invalidate(T id); + + /** + * Get the oldest element in the cache and remove it. + * \return The oldest item. + **/ + T RemoveOldest() + { + Payload p; + return RemoveOldest(p); + } + + /** + * Get the oldest element in the cache, remove it and return the + * associated payload. + * \param payload Where to store the associated payload. + * \return The oldest item. + **/ + T RemoveOldest(Payload& payload); + + /** + * Check whether an element is contained in the cache. + * \param id The item. + * \return \c true iff the item is indexed by the cache. + **/ + bool Contains(T id) const + { + return index_.find(id) != index_.end(); + } + + bool Contains(T id, Payload& payload) const + { + typename Index::const_iterator it = index_.find(id); + if (it == index_.end()) + { + return false; + } + else + { + payload = it->second->second; + return true; + } + } + + /** + * Return the number of elements in the cache. + * \return The number of elements. + **/ + size_t GetSize() const + { + assert(index_.size() == queue_.size()); + return queue_.size(); + } + + /** + * Check whether the cache index is empty. + * \return \c true iff the cache is empty. + **/ + bool IsEmpty() const + { + return index_.empty(); + } + }; + + + + + /****************************************************************** + ** Implementation of the template + ******************************************************************/ + + template + void CacheIndex::CheckInvariants() const + { +#ifndef NDEBUG + assert(index_.size() == queue_.size()); + + for (typename Index::const_iterator + it = index_.begin(); it != index_.end(); it++) + { + assert(it->second != queue_.end()); + assert(it->second->first == it->first); + } +#endif + } + + + template + void CacheIndex::Add(T id, Payload payload) + { + if (Contains(id)) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + queue_.push_front(std::make_pair(id, payload)); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template + void CacheIndex::TagAsMostRecent(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + std::pair item = *(it->second); + + queue_.erase(it->second); + queue_.push_front(item); + index_[id] = queue_.begin(); + + CheckInvariants(); + } + + + template + Payload CacheIndex::Invalidate(T id) + { + if (!Contains(id)) + { + throw OrthancException(ErrorCode_InexistentItem); + } + + typename Index::iterator it = index_.find(id); + assert(it != index_.end()); + + Payload payload = it->second->second; + queue_.erase(it->second); + index_.erase(it); + + CheckInvariants(); + return payload; + } + + + template + T CacheIndex::RemoveOldest(Payload& payload) + { + if (IsEmpty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::pair item = queue_.back(); + T oldest = item.first; + payload = item.second; + + queue_.pop_back(); + assert(index_.find(oldest) != index_.end()); + index_.erase(oldest); + + CheckInvariants(); + + return oldest; + } +} diff -r e5402c368b21 -r 727a6d766dde Core/Cache/ICachePageProvider.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/ICachePageProvider.h Thu Dec 13 14:52:57 2012 +0100 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include "../IDynamicObject.h" + +namespace Orthanc +{ + class ICachePageProvider + { + public: + virtual ~ICachePageProvider() + { + } + + virtual IDynamicObject* Provide(const std::string& id) = 0; + }; +} diff -r e5402c368b21 -r 727a6d766dde Core/Cache/MemoryCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryCache.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "MemoryCache.h" + +namespace Orthanc +{ + MemoryCache::Page& MemoryCache::Load(const std::string& id) + { + // Reuse the cache entry if it already exists + Page* p = NULL; + if (index_.Contains(id, p)) + { + assert(p != NULL); + index_.TagAsMostRecent(id); + return *p; + } + + // The id is not in the cache yet. Make some room if the cache + // is full. + if (index_.GetSize() == cacheSize_) + { + index_.RemoveOldest(p); + delete p; + } + + // Create a new cache page + std::auto_ptr result(new Page); + result->id_ = id; + result->content_.reset(provider_.Provide(id)); + + // Add the newly create page to the cache + p = result.release(); + index_.Add(id, p); + return *p; + } + + MemoryCache::MemoryCache(ICachePageProvider& provider, + size_t cacheSize) : + provider_(provider), + cacheSize_(cacheSize) + { + } + + MemoryCache::~MemoryCache() + { + while (!index_.IsEmpty()) + { + Page* element = NULL; + index_.RemoveOldest(element); + assert(element != NULL); + delete element; + } + } + + IDynamicObject& MemoryCache::Access(const std::string& id) + { + return *Load(id).content_; + } +} diff -r e5402c368b21 -r 727a6d766dde Core/Cache/MemoryCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/MemoryCache.h Thu Dec 13 14:52:57 2012 +0100 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include "CacheIndex.h" +#include "ICachePageProvider.h" + +namespace Orthanc +{ + /** + * WARNING: This class is NOT thread-safe. + **/ + class MemoryCache + { + private: + struct Page + { + std::string id_; + std::auto_ptr content_; + }; + + ICachePageProvider& provider_; + size_t cacheSize_; + CacheIndex index_; + + Page& Load(const std::string& id); + + public: + MemoryCache(ICachePageProvider& provider, + size_t cacheSize); + + ~MemoryCache(); + + IDynamicObject& Access(const std::string& id); + }; +} diff -r e5402c368b21 -r 727a6d766dde Core/Enumerations.h --- a/Core/Enumerations.h Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/Enumerations.h Thu Dec 13 14:52:57 2012 +0100 @@ -47,6 +47,7 @@ ErrorCode_NotEnoughMemory, ErrorCode_BadParameterType, ErrorCode_BadSequenceOfCalls, + ErrorCode_InexistentItem, // Specific error codes ErrorCode_UriSyntax, diff -r e5402c368b21 -r 727a6d766dde Core/HttpServer/HttpOutput.h --- a/Core/HttpServer/HttpOutput.h Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/HttpServer/HttpOutput.h Thu Dec 13 14:52:57 2012 +0100 @@ -43,11 +43,6 @@ private: void SendHeaderInternal(Orthanc_HttpStatus status); - void SendOkHeader(const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename); - public: virtual ~HttpOutput() { @@ -55,6 +50,11 @@ virtual void Send(const void* buffer, size_t length) = 0; + void SendOkHeader(const char* contentType, + bool hasContentLength, + uint64_t contentLength, + const char* contentFilename); + void SendCustomOkHeader(const std::string& customHeader); void SendString(const std::string& s); diff -r e5402c368b21 -r 727a6d766dde Core/OrthancException.cpp --- a/Core/OrthancException.cpp Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/OrthancException.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -96,6 +96,9 @@ case ErrorCode_FullStorage: return "The file storage is full"; + case ErrorCode_InexistentItem: + return "Accessing an inexistent item"; + case ErrorCode_Custom: default: return "???"; diff -r e5402c368b21 -r 727a6d766dde Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/RestApi/RestApi.h Thu Dec 13 14:52:57 2012 +0100 @@ -70,6 +70,11 @@ { return *fullUri_; } + + const UriComponents& GetTrailingUri() const + { + return *trailing_; + } std::string GetUriComponent(const std::string& name, const std::string& defaultValue) const diff -r e5402c368b21 -r 727a6d766dde Core/RestApi/RestApiOutput.h --- a/Core/RestApi/RestApiOutput.h Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/RestApi/RestApiOutput.h Thu Dec 13 14:52:57 2012 +0100 @@ -52,6 +52,16 @@ ~RestApiOutput(); + HttpOutput& GetLowLevelOutput() + { + return output_; + } + + void MarkLowLevelOutputDone() + { + alreadySent_ = true; + } + void AnswerFile(HttpFileSender& sender); void AnswerJson(const Json::Value& value); diff -r e5402c368b21 -r 727a6d766dde Core/Toolbox.h --- a/Core/Toolbox.h Mon Dec 10 17:49:14 2012 +0100 +++ b/Core/Toolbox.h Thu Dec 13 14:52:57 2012 +0100 @@ -40,6 +40,10 @@ { typedef std::vector UriComponents; + class NullType + { + }; + namespace Toolbox { void ServerBarrier(); diff -r e5402c368b21 -r 727a6d766dde NEWS --- a/NEWS Mon Dec 10 17:49:14 2012 +0100 +++ b/NEWS Thu Dec 13 14:52:57 2012 +0100 @@ -3,6 +3,7 @@ * Recycling of disk space * Protection of patients against recycling (also in Orthanc Explorer) +* Raw access to the value of the DICOM tags in the REST API Version 0.3.1 (2012/12/05) diff -r e5402c368b21 -r 727a6d766dde OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Mon Dec 10 17:49:14 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -63,6 +63,193 @@ namespace Orthanc { + ParsedDicomFile::ParsedDicomFile(const std::string& content) + { + DcmInputBufferStream is; + if (content.size() > 0) + { + is.setBuffer(&content[0], content.size()); + } + is.setEos(); + + file_.reset(new DcmFileFormat); + if (!file_->read(is).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + static bool ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + if (tag.size() != 9 || + !isxdigit(tag[0]) || + !isxdigit(tag[1]) || + !isxdigit(tag[2]) || + !isxdigit(tag[3]) || + tag[4] != '-' || + !isxdigit(tag[5]) || + !isxdigit(tag[6]) || + !isxdigit(tag[7]) || + !isxdigit(tag[8])) + { + return false; + } + + uint16_t group = GetTagValue(tag.c_str()); + uint16_t element = GetTagValue(tag.c_str() + 5); + + key = DcmTagKey(group, element); + + return true; + } + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom) + { + DcmTagKey k; + if (!ParseTagAndGroup(k, tag)) + { + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && element != NULL) + { + if (element->getVR() == EVR_SQ) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + DcmSequenceOfItems& sequence = dynamic_cast(*element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast(i)); + } + + output.AnswerJson(v); + } + else + { + // This element is not a sequence + std::string buffer; + buffer.resize(65536); + Uint32 length = element->getLength(); + Uint32 offset = 0; + + output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + + while (offset < length) + { + Uint32 nbytes; + if (length - offset < buffer.size()) + { + nbytes = length - offset; + } + else + { + nbytes = buffer.size(); + } + + if (element->getPartialValue(&buffer[0], offset, nbytes).good()) + { + output.GetLowLevelOutput().Send(&buffer[0], nbytes); + offset += nbytes; + } + else + { + return; + } + } + + output.MarkLowLevelOutputDone(); + } + } + } + + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = file_->getDataset(); + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + if (!ParseTagAndGroup(k, uri[2 * pos]) || + !dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom); + } + } + + void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { target.Clear(); diff -r e5402c368b21 -r 727a6d766dde OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Mon Dec 10 17:49:14 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Thu Dec 13 14:52:57 2012 +0100 @@ -33,8 +33,13 @@ #pragma once #include "../Core/DicomFormat/DicomMap.h" +#include "../Core/RestApi/RestApiOutput.h" +#include "../Core/Toolbox.h" + #include +#include #include +#include namespace Orthanc { @@ -52,6 +57,23 @@ DicomRootLevel_Instance }; + class ParsedDicomFile : public IDynamicObject + { + private: + std::auto_ptr file_; + + public: + ParsedDicomFile(const std::string& content); + + DcmFileFormat& GetDicom() + { + return *file_; + } + + void SendPathValue(RestApiOutput& output, + const UriComponents& uri); + }; + class FromDcmtkBridge { public: diff -r e5402c368b21 -r 727a6d766dde OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Mon Dec 10 17:49:14 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -847,6 +847,23 @@ + // Raw access to the DICOM tags of an instance ------------------------------ + + static void GetRawContent(RestApi::GetCall& call) + { + // TODO IMPROVE MULTITHREADING + static boost::mutex mutex_; + boost::mutex::scoped_lock lock(mutex_); + + RETRIEVE_CONTEXT(call); + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); + } + + + + // Registration of the various REST handlers -------------------------------- OrthancRestApi::OrthancRestApi(ServerContext& context) : @@ -885,6 +902,7 @@ Register("/instances/{id}/tags", GetInstanceTags); Register("/instances/{id}/simplified-tags", GetInstanceTags); Register("/instances/{id}/frames", ListFrames); + Register("/instances/{id}/content/*", GetRawContent); Register("/instances/{id}/frames/{frame}/preview", GetImage); Register("/instances/{id}/frames/{frame}/image-uint8", GetImage); diff -r e5402c368b21 -r 727a6d766dde OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Dec 10 17:49:14 2012 +0100 +++ b/OrthancServer/ServerContext.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -37,6 +37,8 @@ #include +static const size_t DICOM_CACHE_SIZE = 2; + /** * IMPORTANT: We make the assumption that the same instance of * FileStorage can be accessed from multiple threads. This seems OK @@ -51,7 +53,9 @@ ServerContext::ServerContext(const boost::filesystem::path& path) : storage_(path.string()), index_(*this, path.string()), - accessor_(storage_) + accessor_(storage_), + provider_(*this), + dicomCache_(provider_, DICOM_CACHE_SIZE) { // TODO RECYCLING SETUP HERE //index_.SetMaximumPatientCount(4); @@ -165,4 +169,18 @@ accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); accessor_.Read(result, attachment.GetUuid()); } + + + IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId) + { + std::string content; + context_.ReadFile(content, instancePublicId, FileContentType_Dicom); + return new ParsedDicomFile(content); + } + + + ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId) + { + return dynamic_cast(dicomCache_.Access(instancePublicId)); + } } diff -r e5402c368b21 -r 727a6d766dde OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Mon Dec 10 17:49:14 2012 +0100 +++ b/OrthancServer/ServerContext.h Thu Dec 13 14:52:57 2012 +0100 @@ -32,10 +32,12 @@ #pragma once -#include "ServerIndex.h" +#include "../Core/Cache/MemoryCache.h" #include "../Core/FileStorage/CompressedFileStorageAccessor.h" #include "../Core/FileStorage/FileStorage.h" #include "../Core/RestApi/RestApiOutput.h" +#include "ServerIndex.h" +#include "FromDcmtkBridge.h" namespace Orthanc { @@ -47,10 +49,26 @@ class ServerContext { private: + class DicomCacheProvider : public ICachePageProvider + { + private: + ServerContext& context_; + + public: + DicomCacheProvider(ServerContext& context) : context_(context) + { + } + + virtual IDynamicObject* Provide(const std::string& id); + }; + FileStorage storage_; ServerIndex index_; CompressedFileStorageAccessor accessor_; bool compressionEnabled_; + + DicomCacheProvider provider_; + MemoryCache dicomCache_; public: ServerContext(const boost::filesystem::path& path); @@ -86,5 +104,8 @@ void ReadFile(std::string& result, const std::string& instancePublicId, FileContentType content); + + // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD + ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); }; } diff -r e5402c368b21 -r 727a6d766dde UnitTests/MemoryCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTests/MemoryCache.cpp Thu Dec 13 14:52:57 2012 +0100 @@ -0,0 +1,128 @@ +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include "../Core/IDynamicObject.h" +#include "../Core/Cache/MemoryCache.h" + + +TEST(CacheIndex, Basic) +{ + Orthanc::CacheIndex r; + + r.Add("d"); + r.Add("a"); + r.Add("c"); + r.Add("b"); + + r.TagAsMostRecent("a"); + r.TagAsMostRecent("d"); + r.TagAsMostRecent("b"); + r.TagAsMostRecent("c"); + r.TagAsMostRecent("d"); + r.TagAsMostRecent("c"); + + ASSERT_EQ("a", r.RemoveOldest()); + ASSERT_EQ("b", r.RemoveOldest()); + ASSERT_EQ("d", r.RemoveOldest()); + ASSERT_EQ("c", r.RemoveOldest()); + + ASSERT_TRUE(r.IsEmpty()); +} + + +TEST(CacheIndex, Payload) +{ + Orthanc::CacheIndex r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("c", 422); + r.Add("d", 423); + + r.TagAsMostRecent("a"); + r.TagAsMostRecent("d"); + r.TagAsMostRecent("b"); + r.TagAsMostRecent("c"); + r.TagAsMostRecent("d"); + r.TagAsMostRecent("c"); + + ASSERT_TRUE(r.Contains("b")); + ASSERT_EQ(421, r.Invalidate("b")); + ASSERT_FALSE(r.Contains("b")); + + int p; + ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p); + ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p); + ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p); + + ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); + ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); + ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); + + ASSERT_TRUE(r.IsEmpty()); +} + + + + +namespace +{ + class Integer : public Orthanc::IDynamicObject + { + private: + std::string& log_; + int value_; + + public: + Integer(std::string& log, int v) : log_(log), value_(v) + { + } + + virtual ~Integer() + { + LOG(INFO) << "Removing cache entry for " << value_; + log_ += boost::lexical_cast(value_) + " "; + } + + int GetValue() const + { + return value_; + } + }; + + class IntegerProvider : public Orthanc::ICachePageProvider + { + public: + std::string log_; + + Orthanc::IDynamicObject* Provide(const std::string& s) + { + LOG(INFO) << "Providing " << s; + return new Integer(log_, boost::lexical_cast(s)); + } + }; +} + + +TEST(MemoryCache, Basic) +{ + IntegerProvider provider; + + { + Orthanc::MemoryCache cache(provider, 3); + cache.Access("42"); // 42 -> exit + cache.Access("43"); // 43, 42 -> exit + cache.Access("45"); // 45, 43, 42 -> exit + cache.Access("42"); // 42, 45, 43 -> exit + cache.Access("43"); // 43, 42, 45 -> exit + cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit + cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit + cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit + // Closing the cache: 47, 44, 42 are successively removed + } + + ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); +}