# HG changeset patch # User Sebastien Jodogne # Date 1404304969 -7200 # Node ID b3d4f8a3032416726a5b120f4ca89bb0f9478f69 # Parent a91e7b4080d114bc24b38d26cc2f0b257260bf09# Parent 2f76b92addd4d02c4686db70fd48ce14d6019f20 integration mainline->lua-scripting diff -r a91e7b4080d1 -r b3d4f8a30324 CMakeLists.txt --- a/CMakeLists.txt Wed Jun 25 15:36:01 2014 +0200 +++ b/CMakeLists.txt Wed Jul 02 14:42:49 2014 +0200 @@ -84,6 +84,9 @@ Core/HttpServer/MongooseServer.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/FilesystemHttpSender.cpp + Core/RestApi/RestApiCall.cpp + Core/RestApi/RestApiGetCall.cpp + Core/RestApi/RestApiHierarchy.cpp Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp @@ -157,22 +160,22 @@ set(ORTHANC_UNIT_TESTS_SOURCES - UnitTestsSources/DicomMap.cpp - UnitTestsSources/FileStorage.cpp - UnitTestsSources/FromDcmtk.cpp - UnitTestsSources/MemoryCache.cpp - UnitTestsSources/Png.cpp - UnitTestsSources/RestApi.cpp - UnitTestsSources/SQLite.cpp - UnitTestsSources/SQLiteChromium.cpp + UnitTestsSources/DicomMapTests.cpp + UnitTestsSources/FileStorageTests.cpp + UnitTestsSources/FromDcmtkTests.cpp + UnitTestsSources/MemoryCacheTests.cpp + UnitTestsSources/PngTests.cpp + UnitTestsSources/RestApiTests.cpp + UnitTestsSources/SQLiteTests.cpp + UnitTestsSources/SQLiteChromiumTests.cpp UnitTestsSources/ServerIndexTests.cpp - UnitTestsSources/Versions.cpp - UnitTestsSources/Zip.cpp - UnitTestsSources/Lua.cpp - UnitTestsSources/MultiThreading.cpp + UnitTestsSources/VersionsTests.cpp + UnitTestsSources/ZipTests.cpp + UnitTestsSources/LuaTests.cpp + UnitTestsSources/MultiThreadingTests.cpp UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/ImageProcessingTests.cpp - UnitTestsSources/JpegLossless.cpp + UnitTestsSources/JpegLosslessTests.cpp ) diff -r a91e7b4080d1 -r b3d4f8a30324 Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -116,4 +116,132 @@ return ""; } + + + void DicomTag::GetTagsForModule(std::set& target, + ResourceType module) + { + // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions + target.clear(); + + switch (module) + { + case ResourceType_Patient: + // This is Table C.7-1 "Patient Module Attributes" (p. 373) + target.insert(DicomTag(0x0010, 0x0010)); // Patient's name + target.insert(DicomTag(0x0010, 0x0020)); // Patient ID + target.insert(DicomTag(0x0010, 0x0030)); // Patient's birth date + target.insert(DicomTag(0x0010, 0x0040)); // Patient's sex + target.insert(DicomTag(0x0008, 0x1120)); // Referenced patient sequence + target.insert(DicomTag(0x0010, 0x0032)); // Patient's birth time + target.insert(DicomTag(0x0010, 0x1000)); // Other patient IDs + target.insert(DicomTag(0x0010, 0x1002)); // Other patient IDs sequence + target.insert(DicomTag(0x0010, 0x1001)); // Other patient names + target.insert(DicomTag(0x0010, 0x2160)); // Ethnic group + target.insert(DicomTag(0x0010, 0x4000)); // Patient comments + target.insert(DicomTag(0x0010, 0x2201)); // Patient species description + target.insert(DicomTag(0x0010, 0x2202)); // Patient species code sequence + target.insert(DicomTag(0x0010, 0x2292)); // Patient breed description + target.insert(DicomTag(0x0010, 0x2293)); // Patient breed code sequence + target.insert(DicomTag(0x0010, 0x2294)); // Breed registration sequence + target.insert(DicomTag(0x0010, 0x2297)); // Responsible person + target.insert(DicomTag(0x0010, 0x2298)); // Responsible person role + target.insert(DicomTag(0x0010, 0x2299)); // Responsible organization + target.insert(DicomTag(0x0012, 0x0062)); // Patient identity removed + target.insert(DicomTag(0x0012, 0x0063)); // De-identification method + target.insert(DicomTag(0x0012, 0x0064)); // De-identification method code sequence + + // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112) + target.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + target.insert(DicomTag(0x0010, 0x0024)); // Issuer of Patient ID qualifiers sequence + break; + + case ResourceType_Study: + // This is Table C.7-3 "General Study Module Attributes" (p. 378) + target.insert(DicomTag(0x0020, 0x000d)); // Study instance UID + target.insert(DicomTag(0x0008, 0x0020)); // Study date + target.insert(DicomTag(0x0008, 0x0030)); // Study time + target.insert(DicomTag(0x0008, 0x0090)); // Referring physician's name + target.insert(DicomTag(0x0008, 0x0096)); // Referring physician identification sequence + target.insert(DicomTag(0x0020, 0x0010)); // Study ID + target.insert(DicomTag(0x0008, 0x0050)); // Accession number + target.insert(DicomTag(0x0008, 0x0051)); // Issuer of accession number sequence + target.insert(DicomTag(0x0008, 0x1030)); // Study description + target.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of record + target.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of record identification sequence + target.insert(DicomTag(0x0008, 0x1060)); // Name of physician(s) reading study + target.insert(DicomTag(0x0008, 0x1062)); // Physician(s) reading study identification sequence + target.insert(DicomTag(0x0032, 0x1034)); // Requesting service code sequence + target.insert(DicomTag(0x0008, 0x1110)); // Referenced study sequence + target.insert(DicomTag(0x0008, 0x1032)); // Procedure code sequence + target.insert(DicomTag(0x0040, 0x1012)); // Reason for performed procedure code sequence + break; + + case ResourceType_Series: + // This is Table C.7-5 "General Series Module Attributes" (p. 385) + target.insert(DicomTag(0x0008, 0x0060)); // Modality + target.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID + target.insert(DicomTag(0x0020, 0x0011)); // Series Number + target.insert(DicomTag(0x0020, 0x0060)); // Laterality + target.insert(DicomTag(0x0008, 0x0021)); // Series Date + target.insert(DicomTag(0x0008, 0x0031)); // Series Time + target.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians’ Name + target.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + target.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + target.insert(DicomTag(0x0008, 0x103e)); // Series Description + target.insert(DicomTag(0x0008, 0x103f)); // Series Description Code Sequence + target.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + target.insert(DicomTag(0x0008, 0x1072)); // Operator Identification Sequence + target.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + target.insert(DicomTag(0x0008, 0x1250)); // Related Series Sequence + target.insert(DicomTag(0x0018, 0x0015)); // Body Part Examined + target.insert(DicomTag(0x0018, 0x5100)); // Patient Position + target.insert(DicomTag(0x0028, 0x0108)); // Smallest Pixel Value in Series + target.insert(DicomTag(0x0029, 0x0109)); // Largest Pixel Value in Series + target.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + target.insert(DicomTag(0x0010, 0x2210)); // Anatomical Orientation Type + + // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES + target.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + target.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + target.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + target.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + target.insert(DicomTag(0x0040, 0x0260)); // Performed Protocol Code Sequence + target.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + break; + + case ResourceType_Instance: + // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207) + target.insert(DicomTag(0x0008, 0x0016)); // SOP Class UID + target.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID + target.insert(DicomTag(0x0008, 0x0005)); // Specific Character Set + target.insert(DicomTag(0x0008, 0x0012)); // Instance Creation Date + target.insert(DicomTag(0x0008, 0x0013)); // Instance Creation Time + target.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + target.insert(DicomTag(0x0008, 0x001a)); // Related General SOP Class UID + target.insert(DicomTag(0x0008, 0x001b)); // Original Specialized SOP Class UID + target.insert(DicomTag(0x0008, 0x0110)); // Coding Scheme Identification Sequence + target.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + target.insert(DicomTag(0x0018, 0xa001)); // Contributing Equipment Sequence + target.insert(DicomTag(0x0020, 0x0013)); // Instance Number + target.insert(DicomTag(0x0100, 0x0410)); // SOP Instance Status + target.insert(DicomTag(0x0100, 0x0420)); // SOP Authorization DateTime + target.insert(DicomTag(0x0100, 0x0424)); // SOP Authorization Comment + target.insert(DicomTag(0x0100, 0x0426)); // Authorization Equipment Certification Number + target.insert(DicomTag(0x0400, 0x0500)); // Encrypted Attributes Sequence + target.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + target.insert(DicomTag(0x0040, 0xa390)); // HL7 Structured Document Reference Sequence + target.insert(DicomTag(0x0028, 0x0303)); // Longitudinal Temporal Information Modified + + // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216) + target.insert(DicomTag(0x4ffe, 0x0001)); // MAC Parameters sequence + target.insert(DicomTag(0xfffa, 0xfffa)); // Digital signatures sequence + break; + + // TODO IMAGE MODULE? + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } diff -r a91e7b4080d1 -r b3d4f8a30324 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/DicomFormat/DicomTag.h Wed Jul 02 14:42:49 2014 +0200 @@ -33,8 +33,10 @@ #pragma once #include +#include #include +#include "../Enumerations.h" namespace Orthanc { @@ -81,6 +83,9 @@ std::string Format() const; friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); + + static void GetTagsForModule(std::set& target, + ResourceType module); }; // Aliases for the most useful tags diff -r a91e7b4080d1 -r b3d4f8a30324 Core/Enumerations.h --- a/Core/Enumerations.h Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/Enumerations.h Wed Jul 02 14:42:49 2014 +0200 @@ -230,6 +230,7 @@ enum Encoding { + Encoding_Ascii, Encoding_Utf8, Encoding_Latin1 }; diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApi.cpp --- a/Core/RestApi/RestApi.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/RestApi/RestApi.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -38,29 +38,6 @@ namespace Orthanc { - bool RestApi::Call::ParseJsonRequestInternal(Json::Value& result, - const char* request) - { - result.clear(); - Json::Reader reader; - return reader.parse(request, result); - } - - - bool RestApi::GetCall::ParseJsonRequest(Json::Value& result) const - { - result.clear(); - - for (HttpHandler::Arguments::const_iterator - it = getArguments_.begin(); it != getArguments_.end(); ++it) - { - result[it->first] = it->second; - } - - return true; - } - - bool RestApi::IsGetAccepted(const UriComponents& uri) { for (GetHandlers::const_iterator it = getHandlers_.begin(); @@ -189,7 +166,7 @@ { bool ok = false; RestApiOutput restOutput(output); - RestApiPath::Components components; + HttpHandler::Arguments components; UriComponents trailing; if (method == HttpMethod_Get) @@ -201,7 +178,7 @@ { //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri); ok = true; - GetCall call(restOutput, *this, headers, components, trailing, uri, getArguments); + RestApiGetCall call(restOutput, *this, headers, components, trailing, uri, getArguments); it->second(call); } } @@ -215,7 +192,7 @@ { //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri); ok = true; - PutCall call(restOutput, *this, headers, components, trailing, uri, postData); + RestApiPutCall call(restOutput, *this, headers, components, trailing, uri, postData); it->second(call); } } @@ -229,7 +206,7 @@ { //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri); ok = true; - PostCall call(restOutput, *this, headers, components, trailing, uri, postData); + RestApiPostCall call(restOutput, *this, headers, components, trailing, uri, postData); it->second(call); } } @@ -243,7 +220,7 @@ { //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri); ok = true; - DeleteCall call(restOutput, *this, headers, components, trailing, uri); + RestApiDeleteCall call(restOutput, *this, headers, components, trailing, uri); it->second(call); } } @@ -258,25 +235,25 @@ } void RestApi::Register(const std::string& path, - GetHandler handler) + RestApiGetCall::Handler handler) { getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); } void RestApi::Register(const std::string& path, - PutHandler handler) + RestApiPutCall::Handler handler) { putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); } void RestApi::Register(const std::string& path, - PostHandler handler) + RestApiPostCall::Handler handler) { postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); } void RestApi::Register(const std::string& path, - DeleteHandler handler) + RestApiDeleteCall::Handler handler) { deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler)); } diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/RestApi/RestApi.h Wed Jul 02 14:42:49 2014 +0200 @@ -35,6 +35,10 @@ #include "../HttpServer/HttpHandler.h" #include "RestApiPath.h" #include "RestApiOutput.h" +#include "RestApiGetCall.h" +#include "RestApiPutCall.h" +#include "RestApiPostCall.h" +#include "RestApiDeleteCall.h" #include @@ -42,215 +46,11 @@ { class RestApi : public HttpHandler { - public: - class Call - { - friend class RestApi; - - private: - RestApiOutput& output_; - RestApi& context_; - const HttpHandler::Arguments& httpHeaders_; - const RestApiPath::Components& uriComponents_; - const UriComponents& trailing_; - const UriComponents& fullUri_; - - Call(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - output_(output), - context_(context), - httpHeaders_(httpHeaders), - uriComponents_(uriComponents), - trailing_(trailing), - fullUri_(fullUri) - { - } - - protected: - static bool ParseJsonRequestInternal(Json::Value& result, - const char* request); - - public: - RestApiOutput& GetOutput() - { - return output_; - } - - RestApi& GetContext() - { - return context_; - } - - const UriComponents& GetFullUri() const - { - return fullUri_; - } - - const UriComponents& GetTrailingUri() const - { - return trailing_; - } - - std::string GetUriComponent(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(uriComponents_, name, defaultValue); - } - - std::string GetHttpHeader(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); - } - - const HttpHandler::Arguments& GetHttpHeaders() const - { - return httpHeaders_; - } - - void ParseCookies(HttpHandler::Arguments& result) const - { - HttpHandler::ParseCookies(result, httpHeaders_); - } - - virtual bool ParseJsonRequest(Json::Value& result) const = 0; - }; - - - class GetCall : public Call - { - friend class RestApi; - - private: - const HttpHandler::Arguments& getArguments_; - - public: - GetCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const HttpHandler::Arguments& getArguments) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - getArguments_(getArguments) - { - } - - std::string GetArgument(const std::string& name, - const std::string& defaultValue) const - { - return HttpHandler::GetArgument(getArguments_, name, defaultValue); - } - - bool HasArgument(const std::string& name) const - { - return getArguments_.find(name) != getArguments_.end(); - } - - virtual bool ParseJsonRequest(Json::Value& result) const; - }; - - - class PutCall : public Call - { - friend class RestApi; - - private: - const std::string& data_; - - public: - PutCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const std::string& data) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) - { - } - - const std::string& GetPutBody() const - { - return data_; - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, GetPutBody().c_str()); - } - }; - - class PostCall : public Call - { - friend class RestApi; - - private: - const std::string& data_; - - public: - PostCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const std::string& data) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) - { - } - - const std::string& GetPostBody() const - { - return data_; - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, GetPostBody().c_str()); - } - }; - - class DeleteCall : public Call - { - public: - DeleteCall(RestApiOutput& output, - RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const RestApiPath::Components& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - Call(output, context, httpHeaders, uriComponents, trailing, fullUri) - { - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - result.clear(); - return true; - } - }; - - typedef void (*GetHandler) (GetCall& call); - - typedef void (*DeleteHandler) (DeleteCall& call); - - typedef void (*PutHandler) (PutCall& call); - - typedef void (*PostHandler) (PostCall& call); - private: - typedef std::list< std::pair > GetHandlers; - typedef std::list< std::pair > PutHandlers; - typedef std::list< std::pair > PostHandlers; - typedef std::list< std::pair > DeleteHandlers; + typedef std::list< std::pair > GetHandlers; + typedef std::list< std::pair > PutHandlers; + typedef std::list< std::pair > PostHandlers; + typedef std::list< std::pair > DeleteHandlers; GetHandlers getHandlers_; PutHandlers putHandlers_; @@ -281,15 +81,15 @@ const std::string& postData); void Register(const std::string& path, - GetHandler handler); + RestApiGetCall::Handler handler); void Register(const std::string& path, - PutHandler handler); + RestApiPutCall::Handler handler); void Register(const std::string& path, - PostHandler handler); + RestApiPostCall::Handler handler); void Register(const std::string& path, - DeleteHandler handler); + RestApiDeleteCall::Handler handler); }; } diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiCall.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiCall.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiCall.h" + +namespace Orthanc +{ + bool RestApiCall::ParseJsonRequestInternal(Json::Value& result, + const char* request) + { + result.clear(); + Json::Reader reader; + return reader.parse(request, result); + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiCall.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,119 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "../HttpServer/HttpHandler.h" +#include "RestApiPath.h" +#include "RestApiOutput.h" + +#include + +namespace Orthanc +{ + class RestApi; + + class RestApiCall : public boost::noncopyable + { + private: + RestApiOutput& output_; + RestApi& context_; + const HttpHandler::Arguments& httpHeaders_; + const HttpHandler::Arguments& uriComponents_; + const UriComponents& trailing_; + const UriComponents& fullUri_; + + protected: + static bool ParseJsonRequestInternal(Json::Value& result, + const char* request); + + public: + RestApiCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri) : + output_(output), + context_(context), + httpHeaders_(httpHeaders), + uriComponents_(uriComponents), + trailing_(trailing), + fullUri_(fullUri) + { + } + + RestApiOutput& GetOutput() + { + return output_; + } + + RestApi& GetContext() + { + return context_; + } + + const UriComponents& GetFullUri() const + { + return fullUri_; + } + + const UriComponents& GetTrailingUri() const + { + return trailing_; + } + + std::string GetUriComponent(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(uriComponents_, name, defaultValue); + } + + std::string GetHttpHeader(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); + } + + const HttpHandler::Arguments& GetHttpHeaders() const + { + return httpHeaders_; + } + + void ParseCookies(HttpHandler::Arguments& result) const + { + HttpHandler::ParseCookies(result, httpHeaders_); + } + + virtual bool ParseJsonRequest(Json::Value& result) const = 0; + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiDeleteCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiDeleteCall.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiDeleteCall : public RestApiCall + { + public: + typedef void (*Handler) (RestApiDeleteCall& call); + + RestApiDeleteCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri) + { + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + result.clear(); + return true; + } + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiGetCall.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiGetCall.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiGetCall.h" + +namespace Orthanc +{ + bool RestApiGetCall::ParseJsonRequest(Json::Value& result) const + { + result.clear(); + + for (HttpHandler::Arguments::const_iterator + it = getArguments_.begin(); it != getArguments_.end(); ++it) + { + result[it->first] = it->second; + } + + return true; + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiGetCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiGetCall.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,72 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiGetCall : public RestApiCall + { + private: + const HttpHandler::Arguments& getArguments_; + + public: + typedef void (*Handler) (RestApiGetCall& call); + + RestApiGetCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const HttpHandler::Arguments& getArguments) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + getArguments_(getArguments) + { + } + + std::string GetArgument(const std::string& name, + const std::string& defaultValue) const + { + return HttpHandler::GetArgument(getArguments_, name, defaultValue); + } + + bool HasArgument(const std::string& name) const + { + return getArguments_.find(name) != getArguments_.end(); + } + + virtual bool ParseJsonRequest(Json::Value& result) const; + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiHierarchy.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,473 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiHierarchy.h" + +#include "../OrthancException.h" + +#include + +namespace Orthanc +{ + RestApiHierarchy::Resource::Resource() : + getHandler_(NULL), + postHandler_(NULL), + putHandler_(NULL), + deleteHandler_(NULL) + { + } + + + bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const + { + switch (method) + { + case HttpMethod_Get: + return getHandler_ != NULL; + + case HttpMethod_Post: + return postHandler_ != NULL; + + case HttpMethod_Put: + return putHandler_ != NULL; + + case HttpMethod_Delete: + return deleteHandler_ != NULL; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + bool RestApiHierarchy::Resource::IsEmpty() const + { + return (getHandler_ == NULL && + postHandler_ == NULL && + putHandler_ == NULL && + deleteHandler_ == NULL); + } + + + RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, + const std::string& name) + { + Children::iterator it = children.find(name); + + if (it == children.end()) + { + // Create new child + RestApiHierarchy *child = new RestApiHierarchy; + children[name] = child; + return *child; + } + else + { + return *it->second; + } + } + + + + bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const + { + if (getHandler_ != NULL) + { + getHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const + { + if (putHandler_ != NULL) + { + putHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const + { + if (postHandler_ != NULL) + { + postHandler_(call); + return true; + } + else + { + return false; + } + } + + + bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const + { + if (deleteHandler_ != NULL) + { + deleteHandler_(call); + return true; + } + else + { + return false; + } + } + + + + void RestApiHierarchy::DeleteChildren(Children& children) + { + for (Children::iterator it = children.begin(); + it != children.end(); it++) + { + delete it->second; + } + } + + + template + void RestApiHierarchy::RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level) + { + if (path.GetLevelCount() == level) + { + if (path.IsUniversalTrailing()) + { + universalHandlers_.Register(handler); + } + else + { + handlers_.Register(handler); + } + } + else + { + RestApiHierarchy* child; + if (path.IsWildcardLevel(level)) + { + child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); + } + else + { + child = &AddChild(children_, path.GetLevelName(level)); + } + + child->RegisterInternal(path, handler, level + 1); + } + } + + + bool RestApiHierarchy::LookupResource(HttpHandler::Arguments& components, + const UriComponents& uri, + IVisitor& visitor, + size_t level) + { + if (uri.size() != 0 && + level > uri.size()) + { + return false; + } + + UriComponents trailing; + + // Look for an exact match on the resource of interest + if (uri.size() == 0 || + level == uri.size()) + { + if (!handlers_.IsEmpty() && + visitor.Visit(handlers_, uri, components, trailing)) + { + return true; + } + } + + + if (level < uri.size()) // A recursive call is possible + { + // Try and go down in the hierarchy, using an exact match for the child + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->LookupResource(components, uri, visitor, level + 1)) + { + return true; + } + } + + // Try and go down in the hierarchy, using wildcard rules for children + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); child++) + { + HttpHandler::Arguments subComponents = components; + subComponents[child->first] = uri[level]; + + if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) + { + return true; + } + } + } + + + // As a last resort, call the universal handlers, if any + if (!universalHandlers_.IsEmpty()) + { + trailing.resize(uri.size() - level); + size_t pos = 0; + for (size_t i = level; i < uri.size(); i++, pos++) + { + trailing[pos] = uri[i]; + } + + assert(pos == trailing.size()); + + if (visitor.Visit(universalHandlers_, uri, components, trailing)) + { + return true; + } + } + + return false; + } + + + bool RestApiHierarchy::CanGenerateDirectory() const + { + return (!handlers_.HasHandler(HttpMethod_Get) && + universalHandlers_.IsEmpty() && + wildcardChildren_.size() == 0); + } + + + bool RestApiHierarchy::GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level) + { + if (uri.size() == level) + { + if (CanGenerateDirectory()) + { + result = Json::arrayValue; + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); it++) + { + result.append(it->first); + } + + return true; + } + else + { + return false; + } + } + + Children::const_iterator child = children_.find(uri[level]); + if (child != children_.end()) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + for (child = wildcardChildren_.begin(); + child != wildcardChildren_.end(); child++) + { + if (child->second->GetDirectory(result, uri, level + 1)) + { + return true; + } + } + + return false; + } + + + RestApiHierarchy::~RestApiHierarchy() + { + DeleteChildren(children_); + DeleteChildren(wildcardChildren_); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiGetCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiPutCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiPostCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::Register(const std::string& uri, + RestApiDeleteCall::Handler handler) + { + RestApiPath path(uri); + RegisterInternal(path, handler, 0); + } + + void RestApiHierarchy::CreateSiteMap(Json::Value& target) const + { + target = Json::objectValue; + + /*std::string s = " "; + if (handlers_.HasHandler(HttpMethod_Get)) + { + s += "GET "; + } + + if (handlers_.HasHandler(HttpMethod_Post)) + { + s += "POST "; + } + + if (handlers_.HasHandler(HttpMethod_Put)) + { + s += "PUT "; + } + + if (handlers_.HasHandler(HttpMethod_Delete)) + { + s += "DELETE "; + } + + target = s;*/ + + for (Children::const_iterator it = children_.begin(); + it != children_.end(); it++) + { + it->second->CreateSiteMap(target[it->first]); + } + + for (Children::const_iterator it = wildcardChildren_.begin(); + it != wildcardChildren_.end(); it++) + { + it->second->CreateSiteMap(target["<" + it->first + ">"]); + } + } + + + bool RestApiHierarchy::LookupResource(const UriComponents& uri, + IVisitor& visitor) + { + HttpHandler::Arguments components; + return LookupResource(components, uri, visitor, 0); + } + + + + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor + { + private: + std::set& methods_; + + public: + AcceptedMethodsVisitor(std::set& methods) : methods_(methods) + { + } + + virtual bool Visit(const RestApiHierarchy::Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) + { + if (trailing.size() == 0) // Ignore universal handlers + { + if (resource.HasHandler(HttpMethod_Get)) + { + methods_.insert(HttpMethod_Get); + } + + if (resource.HasHandler(HttpMethod_Post)) + { + methods_.insert(HttpMethod_Post); + } + + if (resource.HasHandler(HttpMethod_Put)) + { + methods_.insert(HttpMethod_Put); + } + + if (resource.HasHandler(HttpMethod_Delete)) + { + methods_.insert(HttpMethod_Delete); + } + } + + return false; // Continue to check all the possible ways to access this URI + } + }; + } + + void RestApiHierarchy::GetAcceptedMethods(std::set& methods, + const UriComponents& uri) + { + HttpHandler::Arguments components; + AcceptedMethodsVisitor visitor(methods); + LookupResource(components, uri, visitor, 0); + + Json::Value d; + if (GetDirectory(d, uri)) + { + methods.insert(HttpMethod_Get); + } + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiHierarchy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiHierarchy.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,164 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiGetCall.h" +#include "RestApiPostCall.h" +#include "RestApiPutCall.h" +#include "RestApiDeleteCall.h" + +#include + +namespace Orthanc +{ + class RestApiHierarchy : public boost::noncopyable + { + public: + class Resource : public boost::noncopyable + { + private: + RestApiGetCall::Handler getHandler_; + RestApiPostCall::Handler postHandler_; + RestApiPutCall::Handler putHandler_; + RestApiDeleteCall::Handler deleteHandler_; + + public: + Resource(); + + bool HasHandler(HttpMethod method) const; + + void Register(RestApiGetCall::Handler handler) + { + getHandler_ = handler; + } + + void Register(RestApiPutCall::Handler handler) + { + putHandler_ = handler; + } + + void Register(RestApiPostCall::Handler handler) + { + postHandler_ = handler; + } + + void Register(RestApiDeleteCall::Handler handler) + { + deleteHandler_ = handler; + } + + bool IsEmpty() const; + + bool Handle(RestApiGetCall& call) const; + + bool Handle(RestApiPutCall& call) const; + + bool Handle(RestApiPostCall& call) const; + + bool Handle(RestApiDeleteCall& call) const; + }; + + + class IVisitor : public boost::noncopyable + { + public: + virtual ~IVisitor() + { + } + + virtual bool Visit(const Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) = 0; + }; + + + private: + typedef std::map Children; + + Resource handlers_; + Children children_; + Children wildcardChildren_; + Resource universalHandlers_; + + static RestApiHierarchy& AddChild(Children& children, + const std::string& name); + + static void DeleteChildren(Children& children); + + template + void RegisterInternal(const RestApiPath& path, + Handler handler, + size_t level); + + bool CanGenerateDirectory() const; + + bool LookupResource(HttpHandler::Arguments& components, + const UriComponents& uri, + IVisitor& visitor, + size_t level); + + bool GetDirectory(Json::Value& result, + const UriComponents& uri, + size_t level); + + public: + ~RestApiHierarchy(); + + void Register(const std::string& uri, + RestApiGetCall::Handler handler); + + void Register(const std::string& uri, + RestApiPutCall::Handler handler); + + void Register(const std::string& uri, + RestApiPostCall::Handler handler); + + void Register(const std::string& uri, + RestApiDeleteCall::Handler handler); + + void CreateSiteMap(Json::Value& target) const; + + bool GetDirectory(Json::Value& result, + const UriComponents& uri) + { + return GetDirectory(result, uri, 0); + } + + bool LookupResource(const UriComponents& uri, + IVisitor& visitor); + + void GetAcceptedMethods(std::set& methods, + const UriComponents& uri); + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiPath.cpp --- a/Core/RestApi/RestApiPath.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/RestApi/RestApiPath.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -33,6 +33,8 @@ #include "../PrecompiledHeaders.h" #include "RestApiPath.h" +#include "../OrthancException.h" + #include namespace Orthanc @@ -76,7 +78,7 @@ } } - bool RestApiPath::Match(Components& components, + bool RestApiPath::Match(HttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const { @@ -85,10 +87,12 @@ return Match(components, trailing, uri); } - bool RestApiPath::Match(Components& components, + bool RestApiPath::Match(HttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const { + assert(uri_.size() == components_.size()); + if (uri.size() < uri_.size()) { return false; @@ -131,8 +135,46 @@ bool RestApiPath::Match(const UriComponents& uri) const { - Components components; + HttpHandler::Arguments components; UriComponents trailing; return Match(components, trailing, uri); } + + + bool RestApiPath::IsWildcardLevel(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (level >= uri_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + return uri_[level].length() == 0; + } + + const std::string& RestApiPath::GetWildcardName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (!IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return components_[level]; + } + + const std::string& RestApiPath::GetLevelName(size_t level) const + { + assert(uri_.size() == components_.size()); + + if (IsWildcardLevel(level)) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + return uri_[level]; + } } + diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiPath.h --- a/Core/RestApi/RestApiPath.h Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/RestApi/RestApiPath.h Wed Jul 02 14:42:49 2014 +0200 @@ -33,6 +33,8 @@ #pragma once #include "../Toolbox.h" +#include "../HttpServer/HttpHandler.h" + #include namespace Orthanc @@ -45,19 +47,34 @@ std::vector components_; public: - typedef std::map Components; - RestApiPath(const std::string& uri); // This version is slower - bool Match(Components& components, + bool Match(HttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const; - bool Match(Components& components, + bool Match(HttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const; bool Match(const UriComponents& uri) const; + + size_t GetLevelCount() const + { + return uri_.size(); + } + + bool IsWildcardLevel(size_t level) const; + + bool IsUniversalTrailing() const + { + return hasTrailing_; + } + + const std::string& GetWildcardName(size_t level) const; + + const std::string& GetLevelName(size_t level) const; + }; } diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiPostCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiPostCall.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiPostCall : public RestApiCall + { + private: + const std::string& data_; + + public: + typedef void (*Handler) (RestApiPostCall& call); + + RestApiPostCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const std::string& data) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + data_(data) + { + } + + const std::string& GetPostBody() const + { + return data_; + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPostBody().c_str()); + } + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/RestApi/RestApiPutCall.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/RestApi/RestApiPutCall.h Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "RestApiCall.h" + +namespace Orthanc +{ + class RestApiPutCall : public RestApiCall + { + private: + const std::string& data_; + + public: + typedef void (*Handler) (RestApiPutCall& call); + + RestApiPutCall(RestApiOutput& output, + RestApi& context, + const HttpHandler::Arguments& httpHeaders, + const HttpHandler::Arguments& uriComponents, + const UriComponents& trailing, + const UriComponents& fullUri, + const std::string& data) : + RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + data_(data) + { + } + + const std::string& GetPutBody() const + { + return data_; + } + + virtual bool ParseJsonRequest(Json::Value& result) const + { + return ParseJsonRequestInternal(result, GetPutBody().c_str()); + } + }; +} diff -r a91e7b4080d1 -r b3d4f8a30324 Core/Toolbox.cpp --- a/Core/Toolbox.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/Toolbox.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -284,6 +284,28 @@ } + void Toolbox::TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel) + { + target.clear(); + + if (source.size() > fromLevel) + { + target.resize(source.size() - fromLevel); + + size_t j = 0; + for (size_t i = fromLevel; i < source.size(); i++, j++) + { + target[j] = source[i]; + } + + assert(j == target.size()); + } + } + + + bool Toolbox::IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri) { @@ -505,12 +527,15 @@ // Already in UTF-8: No conversion is required return source; + case Encoding_Ascii: + return ConvertToAscii(source);; + case Encoding_Latin1: encoding = "ISO-8859-1"; break; default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NotImplemented); } try diff -r a91e7b4080d1 -r b3d4f8a30324 Core/Toolbox.h --- a/Core/Toolbox.h Wed Jun 25 15:36:01 2014 +0200 +++ b/Core/Toolbox.h Wed Jul 02 14:42:49 2014 +0200 @@ -73,6 +73,10 @@ void SplitUriComponents(UriComponents& components, const std::string& uri); + void TruncateUri(UriComponents& target, + const UriComponents& source, + size_t fromLevel); + bool IsChildUri(const UriComponents& baseUri, const UriComponents& testedUri); diff -r a91e7b4080d1 -r b3d4f8a30324 DarwinCompilation.txt --- a/DarwinCompilation.txt Wed Jun 25 15:36:01 2014 +0200 +++ b/DarwinCompilation.txt Wed Jul 02 14:42:49 2014 +0200 @@ -28,7 +28,7 @@ ---------------------------- # cd ~/OrthancBuild -# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON .. +# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON ~/Orthanc NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to your version of XCode. diff -r a91e7b4080d1 -r b3d4f8a30324 NEWS --- a/NEWS Wed Jun 25 15:36:01 2014 +0200 +++ b/NEWS Wed Jul 02 14:42:49 2014 +0200 @@ -1,9 +1,22 @@ Pending changes in the mainline =============================== + +Major changes +------------- + * Official support of OS X (Darwin) + + +Minor changes +------------- + +* Extraction of tags for the patient/study/series/instance DICOM modules +* Extraction of the tags shared by all the instances of a patient/study/series * Options to limit the number of results for an incoming C-FIND query * Support of kFreeBSD +* Several code refactorings +* Fix OrthancCppClient::GetVoxelSizeZ() Version 0.7.6 (2014/06/11) diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/Series.cpp --- a/OrthancCppClient/Series.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/Series.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -53,11 +53,12 @@ /** * Compute the slice normal from Image Orientation Patient. * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice + * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html * http://www.itk.org/pipermail/insight-users/2003-September/004762.html **/ std::vector cosines; - someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); + someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); // 0020-0037 if (cosines.size() != 6) { @@ -76,7 +77,7 @@ float ComputeSliceLocation(Instance& instance) const { std::vector ipp; - instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); + instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); // 0020-0032 if (ipp.size() != 3) { throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); @@ -129,7 +130,7 @@ if (instance_.GetPixelFormat() == format_) { - memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth()); + memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth()); } else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && format_ == PixelFormat_RGB24) @@ -212,32 +213,77 @@ { if (GetInstanceCount() == 0) { + // Empty image, use some default value (should never happen) + voxelSizeX_ = 1; + voxelSizeY_ = 1; + voxelSizeZ_ = 1; + sliceThickness_ = 1; + return true; } - Instance& i1 = GetInstance(0); + // Choose a reference slice + Instance& reference = GetInstance(0); + // Check that all the child instances share the same 3D parameters for (unsigned int i = 0; i < GetInstanceCount(); i++) { Instance& i2 = GetInstance(i); - if (std::string(i1.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) || - std::string(i1.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) || - std::string(i1.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) || - std::string(i1.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) || - std::string(i1.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing"))) + if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) || + std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) || + std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) || + std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) || + std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing"))) { return false; } } - SliceLocator locator(GetInstance(0)); + + // Extract X/Y voxel size and slice thickness + std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); // 0028-0030 + size_t pos = s.find('\\'); + assert(pos != std::string::npos); + std::string sy = s.substr(0, pos); + std::string sx = s.substr(pos + 1); + + try + { + voxelSizeX_ = boost::lexical_cast(sx); + voxelSizeY_ = boost::lexical_cast(sy); + } + catch (boost::bad_lexical_cast) + { + throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); + } + + sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness"); // 0018-0050 + + + // Compute the location of each slice to extract the voxel size along Z + voxelSizeZ_ = std::numeric_limits::infinity(); + + SliceLocator locator(reference); + float referenceSliceLocation = locator.ComputeSliceLocation(reference); + std::set l; for (unsigned int i = 0; i < GetInstanceCount(); i++) { - l.insert(locator.ComputeSliceLocation(GetInstance(i))); + float location = locator.ComputeSliceLocation(GetInstance(i)); + float distanceToReferenceSlice = fabs(location - referenceSliceLocation); + + l.insert(location); + + if (distanceToReferenceSlice > std::numeric_limits::epsilon() && + distanceToReferenceSlice < voxelSizeZ_) + { + voxelSizeZ_ = distanceToReferenceSlice; + } } + + // Make sure that 2 slices do not share the same Z location return l.size() == GetInstanceCount(); } catch (OrthancClientException) @@ -275,10 +321,10 @@ status_ = Status3DImage_NotTested; url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_; - isVoxelSizeRead_ = false; voxelSizeX_ = 0; voxelSizeY_ = 0; voxelSizeZ_ = 0; + sliceThickness_ = 0; instances_.SetThreadCount(connection.GetThreadCount()); } @@ -324,46 +370,6 @@ return GetInstance(0).GetTagAsInt("Rows"); } - void Series::LoadVoxelSize() - { - if (isVoxelSizeRead_) - { - return; - } - - Check3DImage(); - - if (GetInstanceCount() == 0) - { - // Empty image, use some default value - voxelSizeX_ = 1; - voxelSizeY_ = 1; - voxelSizeZ_ = 1; - } - else - { - try - { - std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); - size_t pos = s.find('\\'); - assert(pos != std::string::npos); - std::string sy = s.substr(0, pos); - std::string sx = s.substr(pos + 1); - - voxelSizeX_ = boost::lexical_cast(sx); - voxelSizeY_ = boost::lexical_cast(sy); - voxelSizeZ_ = GetInstance(0).GetTagAsFloat("SliceThickness"); - } - catch (boost::bad_lexical_cast) - { - throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); - } - } - - isVoxelSizeRead_ = true; - } - - const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const { if (series_["MainDicomTags"].isMember(tag)) @@ -486,22 +492,28 @@ float Series::GetVoxelSizeX() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeX_; } float Series::GetVoxelSizeY() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeY_; } float Series::GetVoxelSizeZ() { - LoadVoxelSize(); + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes return voxelSizeZ_; } + float Series::GetSliceThickness() + { + Check3DImage(); // Is3DImageInternal() will compute the voxel sizes + return sliceThickness_; + } + void Series::Load3DImage(void* target, Orthanc::PixelFormat format, int64_t lineStride, diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/Series.h --- a/OrthancCppClient/Series.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/Series.h Wed Jul 02 14:42:49 2014 +0200 @@ -62,11 +62,11 @@ Orthanc::ArrayFilledByThreads instances_; Status3DImage status_; - bool isVoxelSizeRead_; float voxelSizeX_; float voxelSizeY_; float voxelSizeZ_; - + float sliceThickness_; + void Check3DImage(); bool Is3DImageInternal(); @@ -86,8 +86,6 @@ size_t stackStride, Orthanc::ThreadedCommandProcessor::IListener* listener); - void LoadVoxelSize(); - public: /** * {summary}{Create a connection to some series.} @@ -189,6 +187,13 @@ **/ float GetVoxelSizeZ(); + /** + * {summary}{Get the slice thickness.} + * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.} + * {returns}{The slice thickness.} + **/ + float GetSliceThickness(); + LAAW_API_INTERNAL void Load3DImage(void* target, Orthanc::PixelFormat format, int64_t lineStride, diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -795,6 +795,29 @@ } } + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497(void* thisObject, float* result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Series* this_ = static_cast(thisObject); +*result = this_->GetSliceThickness(); + + return NULL; + } + catch (::Laaw::LaawException& e) + { + return LAAW_EXTERNC_CopyString(e.What()); + } + catch (...) + { + return LAAW_EXTERNC_CopyString("..."); + } + } + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3) { try diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Jul 02 14:42:49 2014 +0200 @@ -203,7 +203,7 @@ { private: LAAW_ORTHANC_CLIENT_HANDLE_TYPE handle_; - LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[62 + 1]; + LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[63 + 1]; @@ -239,7 +239,7 @@ void FreeString(char* str) { typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*); - Function function = (Function) GetFunction(62); + Function function = (Function) GetFunction(63); function(str); } @@ -302,7 +302,15 @@ { if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) { +#if 0 + /** + * Do not explicitly unload the shared library, as it might + * interfere with the destruction of static objects declared + * inside the library (e.g. this is the case of gflags that is + * internally used by googlelog). + **/ LAAW_ORTHANC_CLIENT_CLOSER(handle_); +#endif handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; } } @@ -391,7 +399,7 @@ throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version"); } - functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); + functionsIndex_[63] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); functionsIndex_[3] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7", "8"); functionsIndex_[4] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e", "8"); functionsIndex_[5] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f", "4"); @@ -423,40 +431,41 @@ functionsIndex_[30] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0", "8"); functionsIndex_[31] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab", "8"); functionsIndex_[32] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d", "8"); - functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28"); - functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32"); + functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497", "8"); + functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28"); + functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32"); functionsIndex_[19] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342", "12"); functionsIndex_[20] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0", "4"); - functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4"); - functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8"); - functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12"); - functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8"); - functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16"); - functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12"); - functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4"); - functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8"); - functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8"); - functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8"); - functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12"); - functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12"); - functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12"); - functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8"); - functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8"); - functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8"); - functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8"); - functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8"); - functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12"); - functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8"); - functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8"); - functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4"); - functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4"); - functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8"); - functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8"); - functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12"); - functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4"); + functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4"); + functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8"); + functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12"); + functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8"); + functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16"); + functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12"); + functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4"); + functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8"); + functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8"); + functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8"); + functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12"); + functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12"); + functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12"); + functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8"); + functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8"); + functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8"); + functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8"); + functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8"); + functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12"); + functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8"); + functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8"); + functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4"); + functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4"); + functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8"); + functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8"); + functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12"); + functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4"); /* Check whether the functions were properly loaded */ - for (unsigned int i = 0; i <= 62; i++) + for (unsigned int i = 0; i <= 63; i++) { if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL) { @@ -704,6 +713,7 @@ inline float GetVoxelSizeX(); inline float GetVoxelSizeY(); inline float GetVoxelSizeZ(); + inline float GetSliceThickness(); inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride); inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]); }; @@ -1323,6 +1333,22 @@ return result_; } /** + * @brief Get the slice thickness. + * + * Get the slice thickness. This call is only valid if this series corresponds to a 3D image. + * + * @return The slice thickness. + **/ + inline float Series::GetSliceThickness() + { + float result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return result_; + } + /** * @brief Load the 3D image into a memory buffer. * * Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. @@ -1335,7 +1361,7 @@ inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34); char* error = function(pimpl_, target, format, lineStride, stackStride); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1353,7 +1379,7 @@ inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); char* error = function(pimpl_, target, format, lineStride, stackStride, progress); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1373,7 +1399,7 @@ { isReference_ = false; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); char* error = function(&pimpl_, connection.pimpl_, id.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1387,7 +1413,7 @@ { if (isReference_) return; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); char* error = function(pimpl_); error = error; // Remove warning about unused variable } @@ -1400,7 +1426,7 @@ inline void Study::Reload() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1415,7 +1441,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1432,7 +1458,7 @@ { void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); char* error = function(pimpl_, &result_, index); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return ::OrthancClient::Series(result_); @@ -1448,7 +1474,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1466,7 +1492,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1487,7 +1513,7 @@ { isReference_ = false; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); char* error = function(&pimpl_, connection.pimpl_, id.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1501,7 +1527,7 @@ { if (isReference_) return; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); char* error = function(pimpl_); error = error; // Remove warning about unused variable } @@ -1516,7 +1542,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1531,7 +1557,7 @@ inline void Instance::SetImageExtractionMode(::Orthanc::ImageExtractionMode mode) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); char* error = function(pimpl_, mode); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1546,7 +1572,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return static_cast< ::Orthanc::ImageExtractionMode >(result_); @@ -1563,7 +1589,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); @@ -1580,7 +1606,7 @@ { float result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, float*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1597,7 +1623,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); char* error = function(pimpl_, &result_, tag.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1613,7 +1639,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1629,7 +1655,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1645,7 +1671,7 @@ { LAAW_UINT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1661,7 +1687,7 @@ { LAAW_INT32 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return static_cast< ::Orthanc::PixelFormat >(result_); @@ -1677,7 +1703,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1694,7 +1720,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); char* error = function(pimpl_, &result_, y); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1710,7 +1736,7 @@ { LAAW_UINT64 result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT64*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return result_; @@ -1726,7 +1752,7 @@ { const void* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return reinterpret_cast< const void* >(result_); @@ -1740,7 +1766,7 @@ inline void Instance::DiscardImage() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1753,7 +1779,7 @@ inline void Instance::DiscardDicom() { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60); char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1767,7 +1793,7 @@ inline void Instance::LoadTagContent(const ::std::string& path) { typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); char* error = function(pimpl_, path.c_str()); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } @@ -1782,7 +1808,7 @@ { const char* result_; typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(62); char* error = function(pimpl_, &result_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); return std::string(result_); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Wed Jul 02 14:42:49 2014 +0200 @@ -31,6 +31,7 @@ _LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 = LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 _LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 = LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 _LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 = LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 + _LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 = LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 _LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 = LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 _LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 = LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 _LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 = LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Wed Jul 02 14:42:49 2014 +0200 @@ -31,6 +31,7 @@ LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0 LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d + LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497 LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5 LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342 diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/DicomModification.cpp --- a/OrthancServer/DicomModification.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/DicomModification.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -96,12 +96,18 @@ { removals_.erase(tag); replacements_.erase(tag); + + if (FromDcmtkBridge::IsPrivateTag(tag)) + { + privateTagsToKeep_.insert(tag); + } } void DicomModification::Remove(const DicomTag& tag) { removals_.insert(tag); replacements_.erase(tag); + privateTagsToKeep_.erase(tag); } bool DicomModification::IsRemoved(const DicomTag& tag) const @@ -113,6 +119,7 @@ const std::string& value) { removals_.erase(tag); + privateTagsToKeep_.erase(tag); replacements_[tag] = value; } @@ -153,6 +160,7 @@ removePrivateTags_ = true; level_ = ResourceType_Patient; uidMap_.clear(); + privateTagsToKeep_.clear(); // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID @@ -261,11 +269,11 @@ // (1) Remove the private tags, if need be if (removePrivateTags_) { - toModify.RemovePrivateTags(); + toModify.RemovePrivateTags(privateTagsToKeep_); } // (2) Remove the tags specified by the user - for (Removals::const_iterator it = removals_.begin(); + for (SetOfTags::const_iterator it = removals_.begin(); it != removals_.end(); ++it) { toModify.Remove(*it); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/DicomModification.h --- a/OrthancServer/DicomModification.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/DicomModification.h Wed Jul 02 14:42:49 2014 +0200 @@ -46,15 +46,16 @@ **/ private: - typedef std::set Removals; + typedef std::set SetOfTags; typedef std::map Replacements; typedef std::map< std::pair, std::string> UidMap; - Removals removals_; + SetOfTags removals_; Replacements replacements_; bool removePrivateTags_; ResourceType level_; UidMap uidMap_; + SetOfTags privateTagsToKeep_; void MapDicomIdentifier(ParsedDicomFile& dicom, ResourceType level); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -61,6 +61,7 @@ #include #include #include +#include #include #include @@ -115,8 +116,44 @@ GetCharValue(c[3])); } + + Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset) + { + // By default, assume UTF-8 encoding (as in dcm2xml.cc) + Encoding encoding = Encoding_Utf8; + + OFString tmp; + if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) + { + std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); + + // TODO Add more encodings + + if (characterSet == "ISO_IR 6" || + characterSet == "ISO_IR 192") + { + encoding = Encoding_Utf8; + } + else if (characterSet == "ISO_IR 100") + { + encoding = Encoding_Latin1; + } + else if (!characterSet.empty()) + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet; + // Fallback to ASCII (remove all special characters) + encoding = Encoding_Ascii; + } + } + + return encoding; + } + + void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { + Encoding encoding = DetectEncoding(dataset); + target.Clear(); for (unsigned long i = 0; i < dataset.card(); i++) { @@ -125,19 +162,40 @@ { target.SetValue(element->getTag().getGTag(), element->getTag().getETag(), - ConvertLeafElement(*element)); + ConvertLeafElement(*element, encoding)); } } } + DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + + DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) { return DicomTag(element.getGTag(), element.getETag()); } - DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element) + bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag) + { + return (tag.getPrivateCreator() != NULL || + !strcmp("PrivateCreator", tag.getTagName())); // TODO - This may change with future versions of DCMTK + } + + + bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag) + { + DcmTag tmp(tag.GetGroup(), tag.GetElement()); + return IsPrivateTag(tmp); + } + + + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + Encoding encoding) { if (!element.isLeaf()) { @@ -151,7 +209,7 @@ c != NULL) { std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, Encoding_Latin1); // TODO Parameter? + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); return new DicomString(utf8); } else @@ -313,25 +371,28 @@ static void StoreElement(Json::Value& target, DcmElement& element, - unsigned int maxStringLength); + unsigned int maxStringLength, + Encoding encoding); static void StoreItem(Json::Value& target, DcmItem& item, - unsigned int maxStringLength) + unsigned int maxStringLength, + Encoding encoding) { target = Json::Value(Json::objectValue); for (unsigned long i = 0; i < item.card(); i++) { DcmElement* element = item.getElement(i); - StoreElement(target, *element, maxStringLength); + StoreElement(target, *element, maxStringLength, encoding); } } static void StoreElement(Json::Value& target, DcmElement& element, - unsigned int maxStringLength) + unsigned int maxStringLength, + Encoding encoding) { assert(target.type() == Json::objectValue); @@ -356,7 +417,7 @@ value["PrivateCreator"] = tagbis.getPrivateCreator(); } - std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(element)); + std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(element, encoding)); if (v->IsNull()) { value["Type"] = "Null"; @@ -393,7 +454,7 @@ { DcmItem* child = sequence.getItem(i); Json::Value& v = children.append(Json::objectValue); - StoreItem(v, *child, maxStringLength); + StoreItem(v, *child, maxStringLength, encoding); } target[formattedTag]["Name"] = tagName; @@ -407,7 +468,7 @@ DcmDataset& dataset, unsigned int maxStringLength) { - StoreItem(root, dataset, maxStringLength); + StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset)); } @@ -475,7 +536,7 @@ isxdigit(name[1]) && isxdigit(name[2]) && isxdigit(name[3]) && - name[4] == '-' && + (name[4] == '-' || name[4] == ',') && isxdigit(name[5]) && isxdigit(name[6]) && isxdigit(name[7]) && diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Wed Jul 02 14:42:49 2014 +0200 @@ -44,11 +44,20 @@ class FromDcmtkBridge { public: + static Encoding DetectEncoding(DcmDataset& dataset); + static void Convert(DicomMap& target, DcmDataset& dataset); + static DicomTag Convert(const DcmTag& tag); + static DicomTag GetTag(const DcmElement& element); - static DicomValue* ConvertLeafElement(DcmElement& element); + static bool IsPrivateTag(DcmTag& tag); + + static bool IsPrivateTag(const DicomTag& tag); + + static DicomValue* ConvertLeafElement(DcmElement& element, + Encoding encoding); static void ToJson(Json::Value& target, DcmDataset& dataset, diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -112,7 +112,7 @@ static bool ParseModifyRequest(DicomModification& target, - const RestApi::PostCall& call) + const RestApiPostCall& call) { // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}' @@ -144,7 +144,7 @@ static bool ParseAnonymizationRequest(DicomModification& target, - RestApi::PostCall& call) + RestApiPostCall& call) { // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm @@ -174,7 +174,8 @@ ParseListOfTags(target, request["Keep"], TagOperation_Keep); } - if (target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) + if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) && + target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName) { // Overwrite the random Patient's Name by one that is more // user-friendly (provided none was specified by the user) @@ -191,7 +192,7 @@ static void AnonymizeOrModifyInstance(DicomModification& modification, - RestApi::PostCall& call) + RestApiPostCall& call) { std::string id = call.GetUriComponent("id", ""); @@ -207,7 +208,7 @@ MetadataType metadataType, ChangeType changeType, ResourceType resourceType, - RestApi::PostCall& call) + RestApiPostCall& call) { bool isFirst = true; Json::Value result(Json::objectValue); @@ -333,7 +334,7 @@ - static void ModifyInstance(RestApi::PostCall& call) + static void ModifyInstance(RestApiPostCall& call) { DicomModification modification; @@ -361,7 +362,7 @@ } - static void AnonymizeInstance(RestApi::PostCall& call) + static void AnonymizeInstance(RestApiPostCall& call) { DicomModification modification; @@ -374,7 +375,7 @@ template - static void ModifyResource(RestApi::PostCall& call) + static void ModifyResource(RestApiPostCall& call) { DicomModification modification; @@ -389,7 +390,7 @@ template - static void AnonymizeResource(RestApi::PostCall& call) + static void AnonymizeResource(RestApiPostCall& call) { DicomModification modification; @@ -401,7 +402,7 @@ } - static void Create(RestApi::PostCall& call) + static void Create(RestApiPostCall& call) { // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}' diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -39,9 +39,9 @@ namespace Orthanc { - void OrthancRestApi::AnswerStoredInstance(RestApi::PostCall& call, + void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call, const std::string& publicId, - StoreStatus status) + StoreStatus status) const { Json::Value result = Json::objectValue; @@ -59,7 +59,7 @@ // Upload of DICOM files through HTTP --------------------------------------- - static void UploadDicomFile(RestApi::PostCall& call) + static void UploadDicomFile(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestApi.h --- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Jul 02 14:42:49 2014 +0200 @@ -62,23 +62,23 @@ public: OrthancRestApi(ServerContext& context); - static OrthancRestApi& GetApi(RestApi::Call& call) + static OrthancRestApi& GetApi(RestApiCall& call) { return dynamic_cast(call.GetContext()); } - static ServerContext& GetContext(RestApi::Call& call) + static ServerContext& GetContext(RestApiCall& call) { return GetApi(call).context_; } - static ServerIndex& GetIndex(RestApi::Call& call) + static ServerIndex& GetIndex(RestApiCall& call) { return GetContext(call).GetIndex(); } - void AnswerStoredInstance(RestApi::PostCall& call, + void AnswerStoredInstance(RestApiPostCall& call, const std::string& publicId, - StoreStatus status); + StoreStatus status) const; }; } diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -233,7 +233,7 @@ } template - static void GetArchive(RestApi::GetCall& call) + static void GetArchive(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestChanges.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -42,7 +42,7 @@ static void GetSinceAndLimit(int64_t& since, unsigned int& limit, bool& last, - const RestApi::GetCall& call) + const RestApiGetCall& call) { static const unsigned int MAX_RESULTS = 100; @@ -70,7 +70,7 @@ } } - static void GetChanges(RestApi::GetCall& call) + static void GetChanges(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -89,7 +89,7 @@ } - static void DeleteChanges(RestApi::DeleteCall& call) + static void DeleteChanges(RestApiDeleteCall& call) { OrthancRestApi::GetIndex(call).DeleteChanges(); call.GetOutput().AnswerBuffer("", "text/plain"); @@ -98,7 +98,7 @@ // Exports API -------------------------------------------------------------- - static void GetExports(RestApi::GetCall& call) + static void GetExports(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -116,7 +116,7 @@ } - static void DeleteExports(RestApi::DeleteCall& call) + static void DeleteExports(RestApiDeleteCall& call) { OrthancRestApi::GetIndex(call).DeleteExportedResources(); call.GetOutput().AnswerBuffer("", "text/plain"); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -65,7 +65,7 @@ return true; } - static void DicomFindPatient(RestApi::PostCall& call) + static void DicomFindPatient(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -87,7 +87,7 @@ call.GetOutput().AnswerJson(result); } - static void DicomFindStudy(RestApi::PostCall& call) + static void DicomFindStudy(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -115,7 +115,7 @@ call.GetOutput().AnswerJson(result); } - static void DicomFindSeries(RestApi::PostCall& call) + static void DicomFindSeries(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -144,7 +144,7 @@ call.GetOutput().AnswerJson(result); } - static void DicomFindInstance(RestApi::PostCall& call) + static void DicomFindInstance(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -174,7 +174,7 @@ call.GetOutput().AnswerJson(result); } - static void DicomFind(RestApi::PostCall& call) + static void DicomFind(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -248,7 +248,7 @@ static bool GetInstancesToExport(std::list& instances, const std::string& remote, - RestApi::PostCall& call) + RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -308,7 +308,7 @@ } - static void DicomStore(RestApi::PostCall& call) + static void DicomStore(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -345,7 +345,7 @@ return peers.find(id) != peers.end(); } - static void ListPeers(RestApi::GetCall& call) + static void ListPeers(RestApiGetCall& call) { OrthancRestApi::SetOfStrings peers; Configuration::GetListOfOrthancPeers(peers); @@ -360,7 +360,7 @@ call.GetOutput().AnswerJson(result); } - static void ListPeerOperations(RestApi::GetCall& call) + static void ListPeerOperations(RestApiGetCall& call) { OrthancRestApi::SetOfStrings peers; Configuration::GetListOfOrthancPeers(peers); @@ -374,7 +374,7 @@ } } - static void PeerStore(RestApi::PostCall& call) + static void PeerStore(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -429,7 +429,7 @@ return modalities.find(id) != modalities.end(); } - static void ListModalities(RestApi::GetCall& call) + static void ListModalities(RestApiGetCall& call) { OrthancRestApi::SetOfStrings modalities; Configuration::GetListOfDicomModalities(modalities); @@ -445,7 +445,7 @@ } - static void ListModalityOperations(RestApi::GetCall& call) + static void ListModalityOperations(RestApiGetCall& call) { OrthancRestApi::SetOfStrings modalities; Configuration::GetListOfDicomModalities(modalities); @@ -465,7 +465,7 @@ } - static void UpdateModality(RestApi::PutCall& call) + static void UpdateModality(RestApiPutCall& call) { Json::Value json; Json::Reader reader; @@ -479,14 +479,14 @@ } - static void DeleteModality(RestApi::DeleteCall& call) + static void DeleteModality(RestApiDeleteCall& call) { Configuration::RemoveModality(call.GetUriComponent("id", "")); call.GetOutput().AnswerBuffer("", "text/plain"); } - static void UpdatePeer(RestApi::PutCall& call) + static void UpdatePeer(RestApiPutCall& call) { Json::Value json; Json::Reader reader; @@ -500,7 +500,7 @@ } - static void DeletePeer(RestApi::DeleteCall& call) + static void DeletePeer(RestApiDeleteCall& call) { Configuration::RemovePeer(call.GetUriComponent("id", "")); call.GetOutput().AnswerBuffer("", "text/plain"); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -43,7 +43,7 @@ // List all the patients, studies, series or instances ---------------------- template - static void ListResources(RestApi::GetCall& call) + static void ListResources(RestApiGetCall& call) { Json::Value result; OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); @@ -51,7 +51,7 @@ } template - static void GetSingleResource(RestApi::GetCall& call) + static void GetSingleResource(RestApiGetCall& call) { Json::Value result; if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) @@ -61,7 +61,7 @@ } template - static void DeleteSingleResource(RestApi::DeleteCall& call) + static void DeleteSingleResource(RestApiDeleteCall& call) { Json::Value result; if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) @@ -73,7 +73,7 @@ // Get information about a single patient ----------------------------------- - static void IsProtectedPatient(RestApi::GetCall& call) + static void IsProtectedPatient(RestApiGetCall& call) { std::string publicId = call.GetUriComponent("id", ""); bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); @@ -81,7 +81,7 @@ } - static void SetPatientProtection(RestApi::PutCall& call) + static void SetPatientProtection(RestApiPutCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -107,7 +107,7 @@ // Get information about a single instance ---------------------------------- - static void GetInstanceFile(RestApi::GetCall& call) + static void GetInstanceFile(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -116,7 +116,7 @@ } - static void ExportInstanceFile(RestApi::PostCall& call) + static void ExportInstanceFile(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -132,7 +132,7 @@ template - static void GetInstanceTags(RestApi::GetCall& call) + static void GetInstanceTags(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -153,8 +153,23 @@ } } + + static void GetInstanceTagsBis(RestApiGetCall& call) + { + bool simplify = call.HasArgument("simplify"); + + if (simplify) + { + GetInstanceTags(call); + } + else + { + GetInstanceTags(call); + } + } + - static void ListFrames(RestApi::GetCall& call) + static void ListFrames(RestApiGetCall& call) { Json::Value instance; if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) @@ -182,7 +197,7 @@ template - static void GetImage(RestApi::GetCall& call) + static void GetImage(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -230,7 +245,7 @@ } - static void GetMatlabImage(RestApi::GetCall& call) + static void GetMatlabImage(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -264,7 +279,7 @@ - static void GetResourceStatistics(RestApi::GetCall& call) + static void GetResourceStatistics(RestApiGetCall& call) { std::string publicId = call.GetUriComponent("id", ""); Json::Value result; @@ -276,14 +291,14 @@ // Handling of metadata ----------------------------------------------------- - static void CheckValidResourceType(RestApi::Call& call) + static void CheckValidResourceType(RestApiCall& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); StringToResourceType(resourceType.c_str()); } - static void ListMetadata(RestApi::GetCall& call) + static void ListMetadata(RestApiGetCall& call) { CheckValidResourceType(call); @@ -303,7 +318,7 @@ } - static void GetMetadata(RestApi::GetCall& call) + static void GetMetadata(RestApiGetCall& call) { CheckValidResourceType(call); @@ -319,7 +334,7 @@ } - static void DeleteMetadata(RestApi::DeleteCall& call) + static void DeleteMetadata(RestApiDeleteCall& call) { CheckValidResourceType(call); @@ -337,7 +352,7 @@ } - static void SetMetadata(RestApi::PutCall& call) + static void SetMetadata(RestApiPutCall& call) { CheckValidResourceType(call); @@ -360,7 +375,7 @@ // Handling of attached files ----------------------------------------------- - static void ListAttachments(RestApi::GetCall& call) + static void ListAttachments(RestApiGetCall& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); std::string publicId = call.GetUriComponent("id", ""); @@ -379,7 +394,7 @@ } - static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call) + static bool GetAttachmentInfo(FileInfo& info, RestApiCall& call) { CheckValidResourceType(call); @@ -391,7 +406,7 @@ } - static void GetAttachmentOperations(RestApi::GetCall& call) + static void GetAttachmentOperations(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) @@ -427,7 +442,7 @@ template - static void GetAttachmentData(RestApi::GetCall& call) + static void GetAttachmentData(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -444,7 +459,7 @@ } - static void GetAttachmentSize(RestApi::GetCall& call) + static void GetAttachmentSize(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) @@ -454,7 +469,7 @@ } - static void GetAttachmentCompressedSize(RestApi::GetCall& call) + static void GetAttachmentCompressedSize(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) @@ -464,7 +479,7 @@ } - static void GetAttachmentMD5(RestApi::GetCall& call) + static void GetAttachmentMD5(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && @@ -475,7 +490,7 @@ } - static void GetAttachmentCompressedMD5(RestApi::GetCall& call) + static void GetAttachmentCompressedMD5(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && @@ -486,7 +501,7 @@ } - static void VerifyAttachment(RestApi::PostCall& call) + static void VerifyAttachment(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); @@ -540,7 +555,7 @@ } - static void UploadAttachment(RestApi::PutCall& call) + static void UploadAttachment(RestApiPutCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); @@ -560,7 +575,7 @@ } - static void DeleteAttachment(RestApi::DeleteCall& call) + static void DeleteAttachment(RestApiDeleteCall& call) { CheckValidResourceType(call); @@ -580,7 +595,7 @@ // Raw access to the DICOM tags of an instance ------------------------------ - static void GetRawContent(RestApi::GetCall& call) + static void GetRawContent(RestApiGetCall& call) { std::string id = call.GetUriComponent("id", ""); @@ -591,6 +606,160 @@ + static bool ExtractSharedTags(Json::Value& shared, + ServerContext& context, + const std::string& publicId) + { + // Retrieve all the instances of this patient/study/series + typedef std::list Instances; + Instances instances; + context.GetIndex().GetChildInstances(instances, publicId); // (*) + + // Loop over the instances + bool isFirst = true; + shared = Json::objectValue; + + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); it++) + { + // Get the tags of the current instance, in the simplified format + Json::Value tags; + + try + { + context.ReadJson(tags, *it); + } + catch (OrthancException&) + { + // Race condition: This instance has been removed since + // (*). Ignore this instance. + continue; + } + + if (tags.type() != Json::objectValue) + { + return false; // Error + } + + // Only keep the tags that are mapped to a string + Json::Value::Members members = tags.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tag = tags[members[i]]; + if (tag.type() != Json::objectValue || + tag["Type"].type() != Json::stringValue || + tag["Type"].asString() != "String") + { + tags.removeMember(members[i]); + } + } + + if (isFirst) + { + // This is the first instance, keep its tags as such + shared = tags; + isFirst = false; + } + else + { + // Loop over all the members of the shared tags extracted so + // far. If the value of one of these tags does not match its + // value in the current instance, remove it. + members = shared.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + if (!tags.isMember(members[i]) || + tags[members[i]]["Value"].asString() != shared[members[i]]["Value"].asString()) + { + shared.removeMember(members[i]); + } + } + } + } + + return true; + } + + + static void GetSharedTags(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string publicId = call.GetUriComponent("id", ""); + bool simplify = call.HasArgument("simplify"); + + Json::Value sharedTags; + if (ExtractSharedTags(sharedTags, context, publicId)) + { + // Success: Send the value of the shared tags + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, sharedTags); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(sharedTags); + } + } + } + + + template + static void GetModule(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string publicId = call.GetUriComponent("id", ""); + bool simplify = call.HasArgument("simplify"); + + typedef std::set Module; + Module module; + DicomTag::GetTagsForModule(module, resourceType); + + Json::Value tags; + + if (resourceType != ResourceType_Instance) + { + // Retrieve all the instances of this patient/study/series + typedef std::list Instances; + Instances instances; + context.GetIndex().GetChildInstances(instances, publicId); + + if (instances.empty()) + { + return; // Error: No instance (should never happen) + } + + // Select one child instance + publicId = instances.front(); + } + + context.ReadJson(tags, publicId); + + // Filter the tags of the instance according to the module + Json::Value result = Json::objectValue; + for (Module::const_iterator it = module.begin(); it != module.end(); it++) + { + std::string s = it->Format(); + if (tags.isMember(s)) + { + result[s] = tags[s]; + } + } + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, result); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(result); + } + } + + void OrthancRestApi::RegisterResources() { Register("/instances", ListResources); @@ -612,9 +781,18 @@ Register("/studies/{id}/statistics", GetResourceStatistics); Register("/series/{id}/statistics", GetResourceStatistics); + Register("/patients/{id}/shared-tags", GetSharedTags); + Register("/series/{id}/shared-tags", GetSharedTags); + Register("/studies/{id}/shared-tags", GetSharedTags); + + Register("/instances/{id}/module", GetModule); + Register("/patients/{id}/module", GetModule); + Register("/series/{id}/module", GetModule); + Register("/studies/{id}/module", GetModule); + Register("/instances/{id}/file", GetInstanceFile); Register("/instances/{id}/export", ExportInstanceFile); - Register("/instances/{id}/tags", GetInstanceTags); + Register("/instances/{id}/tags", GetInstanceTagsBis); Register("/instances/{id}/simplified-tags", GetInstanceTags); Register("/instances/{id}/frames", ListFrames); diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/OrthancRestApi/OrthancRestSystem.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -43,12 +43,12 @@ { // System information ------------------------------------------------------- - static void ServeRoot(RestApi::GetCall& call) + static void ServeRoot(RestApiGetCall& call) { call.GetOutput().Redirect("app/explorer.html"); } - static void GetSystemInformation(RestApi::GetCall& call) + static void GetSystemInformation(RestApiGetCall& call) { Json::Value result = Json::objectValue; @@ -58,14 +58,14 @@ call.GetOutput().AnswerJson(result); } - static void GetStatistics(RestApi::GetCall& call) + static void GetStatistics(RestApiGetCall& call) { Json::Value result = Json::objectValue; OrthancRestApi::GetIndex(call).ComputeStatistics(result); call.GetOutput().AnswerJson(result); } - static void GenerateUid(RestApi::GetCall& call) + static void GenerateUid(RestApiGetCall& call) { std::string level = call.GetArgument("level", ""); if (level == "patient") @@ -86,7 +86,7 @@ } } - static void ExecuteScript(RestApi::PostCall& call) + static void ExecuteScript(RestApiPostCall& call) { std::string result; ServerContext& context = OrthancRestApi::GetContext(call); @@ -94,7 +94,7 @@ call.GetOutput().AnswerBuffer(result, "text/plain"); } - static void GetNowIsoString(RestApi::GetCall& call) + static void GetNowIsoString(RestApiGetCall& call) { call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); } diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -146,6 +146,7 @@ struct ParsedDicomFile::PImpl { std::auto_ptr file_; + Encoding encoding_; }; @@ -170,6 +171,8 @@ } pimpl_->file_->loadAllDataIntoMemory(); pimpl_->file_->transferEnd(); + + pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset()); } @@ -753,24 +756,42 @@ - void ParsedDicomFile::RemovePrivateTags() + void ParsedDicomFile::RemovePrivateTagsInternal(const std::set* toKeep) { + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + // Loop over the dataset to detect its private tags typedef std::list Tags; - Tags privateTags; - DcmDataset& dataset = *pimpl_->file_->getDataset(); for (unsigned long i = 0; i < dataset.card(); i++) { DcmElement* element = dataset.getElement(i); DcmTag tag(element->getTag()); - if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK - tag.getPrivateCreator() != NULL) + + // Is this a private tag? + if (FromDcmtkBridge::IsPrivateTag(tag)) { - privateTags.push_back(element); + bool remove = true; + + // Check whether this private tag is to be kept + if (toKeep != NULL) + { + DicomTag tmp = FromDcmtkBridge::Convert(tag); + if (toKeep->find(tmp) != toKeep->end()) + { + remove = false; // Keep it + } + } + + if (remove) + { + privateTags.push_back(element); + } } } + // Loop over the detected private tags to remove them for (Tags::iterator it = privateTags.begin(); it != privateTags.end(); ++it) { @@ -872,7 +893,7 @@ return false; } - std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(*element)); + std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_)); if (v.get() == NULL) { @@ -887,7 +908,6 @@ } - DicomInstanceHasher ParsedDicomFile::GetHasher() { std::string patientId, studyUid, seriesUid, instanceUid; @@ -904,98 +924,6 @@ } - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength); - - static void StoreItem(Json::Value& target, - DcmItem& item, - unsigned int maxStringLength) - { - target = Json::Value(Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - StoreElement(target, *element, maxStringLength); - } - } - - - static void StoreElement(Json::Value& target, - DcmElement& element, - unsigned int maxStringLength) - { - assert(target.type() == Json::objectValue); - - DicomTag tag(FromDcmtkBridge::GetTag(element)); - const std::string formattedTag = tag.Format(); - -#if 0 - const std::string tagName = FromDcmtkBridge::GetName(tag); -#else - // This version of the code gives access to the name of the private tags - DcmTag tagbis(element.getTag()); - const std::string tagName(tagbis.getTagName()); -#endif - - if (element.isLeaf()) - { - Json::Value value(Json::objectValue); - value["Name"] = tagName; - - if (tagbis.getPrivateCreator() != NULL) - { - value["PrivateCreator"] = tagbis.getPrivateCreator(); - } - - std::auto_ptr v(FromDcmtkBridge::ConvertLeafElement(element)); - if (v->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - std::string s = v->AsString(); - if (maxStringLength == 0 || - s.size() <= maxStringLength) - { - value["Type"] = "String"; - value["Value"] = s; - } - else - { - value["Type"] = "TooLong"; - value["Value"] = Json::nullValue; - } - } - - target[formattedTag] = value; - } - else - { - Json::Value children(Json::arrayValue); - - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast(element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - DcmItem* child = sequence.getItem(i); - Json::Value& v = children.append(Json::objectValue); - StoreItem(v, *child, maxStringLength); - } - - target[formattedTag]["Name"] = tagName; - target[formattedTag]["Type"] = "Sequence"; - target[formattedTag]["Value"] = children; - } - } - - template static void ExtractPngImageTruncate(std::string& result, DicomIntegerPixelAccessor& accessor, @@ -1044,6 +972,7 @@ ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl) { pimpl_->file_.reset(new DcmFileFormat); + pimpl_->encoding_ = Encoding_Ascii; Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); @@ -1073,6 +1002,8 @@ pimpl_(new PImpl) { pimpl_->file_.reset(dynamic_cast(other.pimpl_->file_->clone())); + + pimpl_->encoding_ = other.pimpl_->encoding_; } @@ -1279,4 +1210,9 @@ writer.WriteToMemory(result, accessor); } + + Encoding ParsedDicomFile::GetEncoding() const + { + return pimpl_->encoding_; + } } diff -r a91e7b4080d1 -r b3d4f8a30324 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Wed Jun 25 15:36:01 2014 +0200 +++ b/OrthancServer/ParsedDicomFile.h Wed Jul 02 14:42:49 2014 +0200 @@ -51,6 +51,8 @@ void Setup(const char* content, size_t size); + void RemovePrivateTagsInternal(const std::set* toKeep); + public: ParsedDicomFile(); // Create a minimal DICOM instance @@ -79,7 +81,15 @@ const std::string& value, DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); - void RemovePrivateTags(); + void RemovePrivateTags() + { + RemovePrivateTagsInternal(NULL); + } + + void RemovePrivateTags(const std::set& toKeep) + { + RemovePrivateTagsInternal(&toKeep); + } bool GetTagValue(std::string& value, const DicomTag& tag); @@ -104,6 +114,8 @@ void ExtractPngImage(std::string& result, unsigned int frame, ImageExtractionMode mode); + + Encoding GetEncoding() const; }; } diff -r a91e7b4080d1 -r b3d4f8a30324 Resources/Samples/OrthancClient/Basic/main.cpp --- a/Resources/Samples/OrthancClient/Basic/main.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/Resources/Samples/OrthancClient/Basic/main.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -54,6 +54,15 @@ OrthancClient::Series series(study.GetSeries(k)); std::cout << " Series: " << series.GetId() << std::endl; + if (series.Is3DImage()) + { + std::cout << " This is a 3D image whose voxel size is " + << series.GetVoxelSizeX() << " x " + << series.GetVoxelSizeY() << " x " + << series.GetVoxelSizeZ() << ", and slice thickness is " + << series.GetSliceThickness() << std::endl; + } + for (unsigned int l = 0; l < series.GetInstanceCount(); l++) { std::cout << " Instance: " << series.GetInstance(l).GetId() << std::endl; diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/DicomMap.cpp --- a/UnitTestsSources/DicomMap.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Uuid.h" -#include "../Core/OrthancException.h" -#include "../Core/DicomFormat/DicomMap.h" -#include "../Core/DicomFormat/DicomNullValue.h" - -#include - -using namespace Orthanc; - -TEST(DicomMap, MainTags) -{ - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); - ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); - - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); - - std::set s; - DicomMap::GetMainDicomTags(s); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Patient); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Study); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Series); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); - - DicomMap::GetMainDicomTags(s, ResourceType_Instance); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); -} - - -TEST(DicomMap, Tags) -{ - DicomMap m; - ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); - ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); - m.SetValue(0x0010, 0x0010, "PatientName"); - ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); - ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); - - ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); - ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); - m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); - ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); - - m.Remove(DICOM_TAG_PATIENT_ID); - ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); - - std::auto_ptr mm(m.Clone()); - ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); - - m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); - ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); - mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); - ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); - - DicomNullValue v; - ASSERT_TRUE(v.IsNull()); -} - - -TEST(DicomMap, FindTemplates) -{ - DicomMap m; - - DicomMap::SetupFindPatientTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID)); - - DicomMap::SetupFindStudyTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER)); - - DicomMap::SetupFindSeriesTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)); - - DicomMap::SetupFindInstanceTemplate(m); - ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID)); -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/DicomMapTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/DicomMapTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,189 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomNullValue.h" +#include "../OrthancServer/FromDcmtkBridge.h" + +#include + +using namespace Orthanc; + +TEST(DicomMap, MainTags) +{ + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); + ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); + + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + + std::set s; + DicomMap::GetMainDicomTags(s); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Patient); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Study); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Series); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + + DicomMap::GetMainDicomTags(s, ResourceType_Instance); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); +} + + +TEST(DicomMap, Tags) +{ + DicomMap m; + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); + m.SetValue(0x0010, 0x0010, "PatientName"); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); + + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); + ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); + m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); + ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); + + m.Remove(DICOM_TAG_PATIENT_ID); + ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); + + std::auto_ptr mm(m.Clone()); + ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); + + m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); + ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); + mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); + ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); + + DicomNullValue v; + ASSERT_TRUE(v.IsNull()); +} + + +TEST(DicomMap, FindTemplates) +{ + DicomMap m; + + DicomMap::SetupFindPatientTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID)); + + DicomMap::SetupFindStudyTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER)); + + DicomMap::SetupFindSeriesTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)); + + DicomMap::SetupFindInstanceTemplate(m); + ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID)); +} + + + + +static void TestModule(ResourceType level) +{ + std::set module, main; + DicomTag::GetTagsForModule(module, level); + DicomMap::GetMainDicomTags(main, level); + + // The main dicom tags are a subset of the module + for (std::set::const_iterator it = main.begin(); it != main.end(); it++) + { + bool ok = module.find(*it) != module.end(); + + // Exceptions for the Series level + /*if ((// + *it == DicomTag(0x, 0x) && + level == ResourceType_Series)) + { + ok = true; + }*/ + + // Exceptions for the Instance level + if ((/* Accession number, from Image module */ + *it == DicomTag(0x0020, 0x0012) && + level == ResourceType_Instance) || + (/* Image Index, from PET Image module */ + *it == DicomTag(0x0054, 0x1330) && + level == ResourceType_Instance) || + (/* Temporal Position Identifier, from MR Image module */ + *it == DicomTag(0x0020, 0x0100) && + level == ResourceType_Instance) || + (/* Number of Frames, from Multi-frame module attributes, related to Image IOD */ + *it == DicomTag(0x0028, 0x0008) && + level == ResourceType_Instance )) + { + ok = true; + } + + if (!ok) + { + std::cout << it->Format() << ": " << FromDcmtkBridge::GetName(*it) + << " not expected at level " << EnumerationToString(level) << std::endl; + } + + EXPECT_TRUE(ok); + } +} + + +TEST(DicomMap, Modules) +{ + TestModule(ResourceType_Patient); + TestModule(ResourceType_Study); + //TestModule(ResourceType_Series); // TODO + TestModule(ResourceType_Instance); +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/FileStorage.cpp --- a/UnitTestsSources/FileStorage.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,228 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include -#include - -#include "../Core/FileStorage/FileStorage.h" -#include "../OrthancServer/ServerIndex.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" -#include "../Core/Uuid.h" -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/HttpServer/BufferHttpSender.h" -#include "../Core/FileStorage/FileStorageAccessor.h" -#include "../Core/FileStorage/CompressedFileStorageAccessor.h" - -using namespace Orthanc; - - -static void StringToVector(std::vector& v, - const std::string& s) -{ - v.resize(s.size()); - for (size_t i = 0; i < s.size(); i++) - v[i] = s[i]; -} - - -TEST(FileStorage, Basic) -{ - FileStorage s("UnitTestsStorage"); - - std::string data = Toolbox::GenerateUuid(); - std::string uid = s.Create(data); - std::string d; - s.ReadFile(d, uid); - ASSERT_EQ(d.size(), data.size()); - ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); - ASSERT_EQ(s.GetCompressedSize(uid), data.size()); -} - -TEST(FileStorage, Basic2) -{ - FileStorage s("UnitTestsStorage"); - - std::vector data; - StringToVector(data, Toolbox::GenerateUuid()); - std::string uid = s.Create(data); - std::string d; - s.ReadFile(d, uid); - ASSERT_EQ(d.size(), data.size()); - ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); - ASSERT_EQ(s.GetCompressedSize(uid), data.size()); -} - -TEST(FileStorage, EndToEnd) -{ - FileStorage s("UnitTestsStorage"); - s.Clear(); - - std::list u; - for (unsigned int i = 0; i < 10; i++) - { - u.push_back(s.Create(Toolbox::GenerateUuid())); - } - - std::set ss; - s.ListAllFiles(ss); - ASSERT_EQ(10u, ss.size()); - - unsigned int c = 0; - for (std::list::iterator - i = u.begin(); i != u.end(); i++, c++) - { - ASSERT_TRUE(ss.find(*i) != ss.end()); - if (c < 5) - s.Remove(*i); - } - - s.ListAllFiles(ss); - ASSERT_EQ(5u, ss.size()); - - s.Clear(); - s.ListAllFiles(ss); - ASSERT_EQ(0u, ss.size()); -} - - -TEST(FileStorageAccessor, Simple) -{ - FileStorage s("UnitTestsStorage"); - FileStorageAccessor accessor(s); - - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression2) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::vector data; - StringToVector(data, "Hello world"); - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size())); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Compression) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid()); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, Mix) -{ - FileStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - std::string r; - std::string compressedData = "Hello"; - std::string uncompressedData = "HelloWorld"; - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_None); - FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_EQ(compressedData, r); - - accessor.SetCompressionForNextOperations(CompressionType_None); - accessor.Read(r, compressedInfo.GetUuid()); - ASSERT_NE(compressedData, r); - - /* - // This test is too slow on Windows - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); - */ -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/FileStorageTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FileStorageTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include +#include + +#include "../Core/FileStorage/FileStorage.h" +#include "../OrthancServer/ServerIndex.h" +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/Uuid.h" +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/HttpServer/BufferHttpSender.h" +#include "../Core/FileStorage/FileStorageAccessor.h" +#include "../Core/FileStorage/CompressedFileStorageAccessor.h" + +using namespace Orthanc; + + +static void StringToVector(std::vector& v, + const std::string& s) +{ + v.resize(s.size()); + for (size_t i = 0; i < s.size(); i++) + v[i] = s[i]; +} + + +TEST(FileStorage, Basic) +{ + FileStorage s("UnitTestsStorage"); + + std::string data = Toolbox::GenerateUuid(); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, Basic2) +{ + FileStorage s("UnitTestsStorage"); + + std::vector data; + StringToVector(data, Toolbox::GenerateUuid()); + std::string uid = s.Create(data); + std::string d; + s.ReadFile(d, uid); + ASSERT_EQ(d.size(), data.size()); + ASSERT_FALSE(memcmp(&d[0], &data[0], data.size())); + ASSERT_EQ(s.GetCompressedSize(uid), data.size()); +} + +TEST(FileStorage, EndToEnd) +{ + FileStorage s("UnitTestsStorage"); + s.Clear(); + + std::list u; + for (unsigned int i = 0; i < 10; i++) + { + u.push_back(s.Create(Toolbox::GenerateUuid())); + } + + std::set ss; + s.ListAllFiles(ss); + ASSERT_EQ(10u, ss.size()); + + unsigned int c = 0; + for (std::list::iterator + i = u.begin(); i != u.end(); i++, c++) + { + ASSERT_TRUE(ss.find(*i) != ss.end()); + if (c < 5) + s.Remove(*i); + } + + s.ListAllFiles(ss); + ASSERT_EQ(5u, ss.size()); + + s.Clear(); + s.ListAllFiles(ss); + ASSERT_EQ(0u, ss.size()); +} + + +TEST(FileStorageAccessor, Simple) +{ + FileStorage s("UnitTestsStorage"); + FileStorageAccessor accessor(s); + + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, NoCompression2) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::vector data; + StringToVector(data, "Hello world"); + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size())); + ASSERT_EQ(CompressionType_None, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(11u, info.GetCompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Compression) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + std::string data = "Hello world"; + FileInfo info = accessor.Write(data, FileContentType_Dicom); + + std::string r; + accessor.Read(r, info.GetUuid()); + + ASSERT_EQ(data, r); + ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); + ASSERT_EQ(11u, info.GetUncompressedSize()); + ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); +} + + +TEST(FileStorageAccessor, Mix) +{ + FileStorage s("UnitTestsStorage"); + CompressedFileStorageAccessor accessor(s); + + std::string r; + std::string compressedData = "Hello"; + std::string uncompressedData = "HelloWorld"; + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_None); + FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); + + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_EQ(compressedData, r); + + accessor.SetCompressionForNextOperations(CompressionType_None); + accessor.Read(r, compressedInfo.GetUuid()); + ASSERT_NE(compressedData, r); + + /* + // This test is too slow on Windows + accessor.SetCompressionForNextOperations(CompressionType_Zlib); + ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException); + */ +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/FromDcmtk.cpp --- a/UnitTestsSources/FromDcmtk.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/OrthancInitialization.h" -#include "../OrthancServer/DicomModification.h" -#include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" - -using namespace Orthanc; - -TEST(DicomFormat, Tag) -{ - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); - - DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); - ASSERT_EQ(0x0008, t.GetGroup()); - ASSERT_EQ(0x103E, t.GetElement()); - - t = FromDcmtkBridge::ParseTag("0020-e040"); - ASSERT_EQ(0x0020, t.GetGroup()); - ASSERT_EQ(0xe040, t.GetElement()); - - // Test ==() and !=() operators - ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); - ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); -} - - -TEST(DicomModification, Basic) -{ - DicomModification m; - m.SetupAnonymization(); - //m.SetLevel(DicomRootLevel_Study); - //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); - //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); - - ParsedDicomFile o; - o.SaveToFile("UnitTestsResults/anon.dcm"); - - for (int i = 0; i < 10; i++) - { - char b[1024]; - sprintf(b, "UnitTestsResults/anon%06d.dcm", i); - std::auto_ptr f(o.Clone()); - if (i > 4) - o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); - m.Apply(*f); - f->SaveToFile(b); - } -} - - -#include - -TEST(DicomModification, Png) -{ - // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) - std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; - - std::string m, c; - Toolbox::DecodeDataUriScheme(m, c, s); - - ASSERT_EQ("image/png", m); - ASSERT_EQ(116, c.size()); - - std::string cc; - Toolbox::DecodeBase64(cc, c); - PngReader reader; - reader.ReadFromMemory(cc); - - ASSERT_EQ(5, reader.GetHeight()); - ASSERT_EQ(5, reader.GetWidth()); - ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); - - ParsedDicomFile o; - o.EmbedImage(s); - o.SaveToFile("UnitTestsResults/png1.dcm"); - - // Red dot, without alpha channel - s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; - o.EmbedImage(s); - o.SaveToFile("UnitTestsResults/png2.dcm"); - - // Check box in Graylevel8 - s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; - o.EmbedImage(s); - //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); - o.SaveToFile("UnitTestsResults/png3.dcm"); - - - { - // Gradient in Graylevel16 - - ImageBuffer img; - img.SetWidth(256); - img.SetHeight(256); - img.SetFormat(PixelFormat_Grayscale16); - - int v = 0; - for (unsigned int y = 0; y < img.GetHeight(); y++) - { - uint16_t *p = reinterpret_cast(img.GetAccessor().GetRow(y)); - for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) - { - *p = v; - } - } - - o.EmbedImage(img.GetAccessor()); - o.SaveToFile("UnitTestsResults/png4.dcm"); - } -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/FromDcmtkTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/FromDcmtkTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,178 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/FromDcmtkBridge.h" +#include "../OrthancServer/OrthancInitialization.h" +#include "../OrthancServer/DicomModification.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Uuid.h" + +using namespace Orthanc; + +TEST(DicomFormat, Tag) +{ + ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + + DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); + ASSERT_EQ(0x0008, t.GetGroup()); + ASSERT_EQ(0x103E, t.GetElement()); + + t = FromDcmtkBridge::ParseTag("0020-e040"); + ASSERT_EQ(0x0020, t.GetGroup()); + ASSERT_EQ(0xe040, t.GetElement()); + + // Test ==() and !=() operators + ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); + ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); +} + + +TEST(DicomModification, Basic) +{ + DicomModification m; + m.SetupAnonymization(); + //m.SetLevel(DicomRootLevel_Study); + //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); + //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + + ParsedDicomFile o; + o.SaveToFile("UnitTestsResults/anon.dcm"); + + for (int i = 0; i < 10; i++) + { + char b[1024]; + sprintf(b, "UnitTestsResults/anon%06d.dcm", i); + std::auto_ptr f(o.Clone()); + if (i > 4) + o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); + m.Apply(*f); + f->SaveToFile(b); + } +} + + +TEST(DicomModification, Anonymization) +{ + const DicomTag privateTag(0x0045, 0x0010); + ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag)); + + ParsedDicomFile o; + o.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); + o.Replace(privateTag, "private tag"); + + std::string s; + ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_FALSE(Toolbox::IsUuid(s)); + + DicomModification m; + m.SetupAnonymization(); + m.Keep(privateTag); + + m.Apply(o); + + ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(Toolbox::IsUuid(s)); + ASSERT_TRUE(o.GetTagValue(s, privateTag)); + ASSERT_EQ("private tag", s); + + m.SetupAnonymization(); + m.Apply(o); + ASSERT_FALSE(o.GetTagValue(s, privateTag)); +} + + +#include + +TEST(DicomModification, Png) +{ + // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) + std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + + std::string m, c; + Toolbox::DecodeDataUriScheme(m, c, s); + + ASSERT_EQ("image/png", m); + ASSERT_EQ(116, c.size()); + + std::string cc; + Toolbox::DecodeBase64(cc, c); + PngReader reader; + reader.ReadFromMemory(cc); + + ASSERT_EQ(5, reader.GetHeight()); + ASSERT_EQ(5, reader.GetWidth()); + ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); + + ParsedDicomFile o; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png1.dcm"); + + // Red dot, without alpha channel + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; + o.EmbedImage(s); + o.SaveToFile("UnitTestsResults/png2.dcm"); + + // Check box in Graylevel8 + s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; + o.EmbedImage(s); + //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); + o.SaveToFile("UnitTestsResults/png3.dcm"); + + + { + // Gradient in Graylevel16 + + ImageBuffer img; + img.SetWidth(256); + img.SetHeight(256); + img.SetFormat(PixelFormat_Grayscale16); + + int v = 0; + for (unsigned int y = 0; y < img.GetHeight(); y++) + { + uint16_t *p = reinterpret_cast(img.GetAccessor().GetRow(y)); + for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++) + { + *p = v; + } + } + + o.EmbedImage(img.GetAccessor()); + o.SaveToFile("UnitTestsResults/png4.dcm"); + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/JpegLossless.cpp --- a/UnitTestsSources/JpegLossless.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../OrthancServer/Internals/DicomImageDecoder.h" - -#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 - -#include - -#include "../OrthancServer/ParsedDicomFile.h" -#include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngWriter.h" - -using namespace Orthanc; - - - -// TODO Write a test - - -#endif diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/JpegLosslessTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/JpegLosslessTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../OrthancServer/Internals/DicomImageDecoder.h" + +#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 + +#include + +#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/OrthancException.h" +#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/ImageFormats/PngWriter.h" + +using namespace Orthanc; + + + +// TODO Write a test + + +#endif diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/Lua.cpp --- a/UnitTestsSources/Lua.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Lua/LuaFunctionCall.h" - - -TEST(Lua, Json) -{ - Orthanc::LuaContext lua; - lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - lua.Execute("a={}"); - lua.Execute("a['x'] = 10"); - lua.Execute("a['y'] = {}"); - lua.Execute("a['y'][1] = 20"); - lua.Execute("a['y'][2] = 20"); - lua.Execute("PrintRecursive(a)"); - - lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); - - Json::Value v, vv, o; - //v["a"] = "b"; - v.append("hello"); - v.append("world"); - v.append("42"); - vv.append("sub"); - vv.append("set"); - v.append(vv); - o = Json::objectValue; - o["x"] = 10; - o["y"] = 20; - o["z"] = 20.5f; - v.append(o); - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushJSON(v); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); - } - - o["bool"] = false; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_FALSE(f.ExecutePredicate()); - } - - o["bool"] = true; - - { - Orthanc::LuaFunctionCall f(lua, "f"); - f.PushJSON(o); - ASSERT_TRUE(f.ExecutePredicate()); - } -} - - -TEST(Lua, Existing) -{ - Orthanc::LuaContext lua; - lua.Execute("a={}"); - lua.Execute("function f() end"); - - ASSERT_TRUE(lua.IsExistingFunction("f")); - ASSERT_FALSE(lua.IsExistingFunction("a")); - ASSERT_FALSE(lua.IsExistingFunction("Dummy")); -} - - -TEST(Lua, Simple) -{ - Orthanc::LuaContext lua; - lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushString("hello"); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushBoolean(true); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushInteger(42); - f.Execute(); - } - - { - Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); - f.PushDouble(3.1415); - f.Execute(); - } -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/LuaTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/LuaTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,136 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Lua/LuaFunctionCall.h" + + +TEST(Lua, Json) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + lua.Execute("a={}"); + lua.Execute("a['x'] = 10"); + lua.Execute("a['y'] = {}"); + lua.Execute("a['y'][1] = 20"); + lua.Execute("a['y'][2] = 20"); + lua.Execute("PrintRecursive(a)"); + + lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end"); + + Json::Value v, vv, o; + //v["a"] = "b"; + v.append("hello"); + v.append("world"); + v.append("42"); + vv.append("sub"); + vv.append("set"); + v.append(vv); + o = Json::objectValue; + o["x"] = 10; + o["y"] = 20; + o["z"] = 20.5f; + v.append(o); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushJSON(v); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); + } + + o["bool"] = false; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_FALSE(f.ExecutePredicate()); + } + + o["bool"] = true; + + { + Orthanc::LuaFunctionCall f(lua, "f"); + f.PushJSON(o); + ASSERT_TRUE(f.ExecutePredicate()); + } +} + + +TEST(Lua, Existing) +{ + Orthanc::LuaContext lua; + lua.Execute("a={}"); + lua.Execute("function f() end"); + + ASSERT_TRUE(lua.IsExistingFunction("f")); + ASSERT_FALSE(lua.IsExistingFunction("a")); + ASSERT_FALSE(lua.IsExistingFunction("Dummy")); +} + + +TEST(Lua, Simple) +{ + Orthanc::LuaContext lua; + lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushString("hello"); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushBoolean(true); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushInteger(42); + f.Execute(); + } + + { + Orthanc::LuaFunctionCall f(lua, "PrintRecursive"); + f.PushDouble(3.1415); + f.Execute(); + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/MemoryCache.cpp --- a/UnitTestsSources/MemoryCache.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include "../Core/IDynamicObject.h" -#include "../Core/Cache/MemoryCache.h" - - -TEST(LRU, Basic) -{ - Orthanc::LeastRecentlyUsedIndex r; - - r.Add("d"); - r.Add("a"); - r.Add("c"); - r.Add("b"); - - r.MakeMostRecent("a"); - r.MakeMostRecent("d"); - r.MakeMostRecent("b"); - r.MakeMostRecent("c"); - r.MakeMostRecent("d"); - r.MakeMostRecent("c"); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ("a", r.RemoveOldest()); - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ("b", r.RemoveOldest()); - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ("d", r.RemoveOldest()); - ASSERT_EQ("c", r.GetOldest()); - ASSERT_EQ("c", r.RemoveOldest()); - - ASSERT_TRUE(r.IsEmpty()); - - ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); - ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); -} - - -TEST(LRU, Payload) -{ - Orthanc::LeastRecentlyUsedIndex r; - - r.Add("a", 420); - r.Add("b", 421); - r.Add("c", 422); - r.Add("d", 423); - - r.MakeMostRecent("a"); - r.MakeMostRecent("d"); - r.MakeMostRecent("b"); - r.MakeMostRecent("c"); - r.MakeMostRecent("d"); - r.MakeMostRecent("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.GetOldest()); - ASSERT_EQ(420, r.GetOldestPayload()); - ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(423, r.GetOldestPayload()); - ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); - - ASSERT_EQ("c", r.GetOldest()); - ASSERT_EQ(422, r.GetOldestPayload()); - ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); - - ASSERT_TRUE(r.IsEmpty()); -} - - -TEST(LRU, PayloadUpdate) -{ - Orthanc::LeastRecentlyUsedIndex r; - - r.Add("a", 420); - r.Add("b", 421); - r.Add("d", 423); - - r.MakeMostRecent("a", 424); - r.MakeMostRecent("d", 421); - - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ(424, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_TRUE(r.IsEmpty()); -} - - - -TEST(LRU, PayloadUpdateBis) -{ - Orthanc::LeastRecentlyUsedIndex r; - - r.AddOrMakeMostRecent("a", 420); - r.AddOrMakeMostRecent("b", 421); - r.AddOrMakeMostRecent("d", 423); - r.AddOrMakeMostRecent("a", 424); - r.AddOrMakeMostRecent("d", 421); - - ASSERT_EQ("b", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("a", r.GetOldest()); - ASSERT_EQ(424, r.GetOldestPayload()); - r.RemoveOldest(); - - ASSERT_EQ("d", r.GetOldest()); - ASSERT_EQ(421, r.GetOldestPayload()); - r.RemoveOldest(); - - 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_); -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/MemoryCacheTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MemoryCacheTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,230 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include "../Core/IDynamicObject.h" +#include "../Core/Cache/MemoryCache.h" + + +TEST(LRU, Basic) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.Add("d"); + r.Add("a"); + r.Add("c"); + r.Add("b"); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("c"); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ("a", r.RemoveOldest()); + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ("b", r.RemoveOldest()); + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ("d", r.RemoveOldest()); + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ("c", r.RemoveOldest()); + + ASSERT_TRUE(r.IsEmpty()); + + ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException); + ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException); +} + + +TEST(LRU, Payload) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("c", 422); + r.Add("d", 423); + + r.MakeMostRecent("a"); + r.MakeMostRecent("d"); + r.MakeMostRecent("b"); + r.MakeMostRecent("c"); + r.MakeMostRecent("d"); + r.MakeMostRecent("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.GetOldest()); + ASSERT_EQ(420, r.GetOldestPayload()); + ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(423, r.GetOldestPayload()); + ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p); + + ASSERT_EQ("c", r.GetOldest()); + ASSERT_EQ(422, r.GetOldestPayload()); + ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p); + + ASSERT_TRUE(r.IsEmpty()); +} + + +TEST(LRU, PayloadUpdate) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.Add("a", 420); + r.Add("b", 421); + r.Add("d", 423); + + r.MakeMostRecent("a", 424); + r.MakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_TRUE(r.IsEmpty()); +} + + + +TEST(LRU, PayloadUpdateBis) +{ + Orthanc::LeastRecentlyUsedIndex r; + + r.AddOrMakeMostRecent("a", 420); + r.AddOrMakeMostRecent("b", 421); + r.AddOrMakeMostRecent("d", 423); + r.AddOrMakeMostRecent("a", 424); + r.AddOrMakeMostRecent("d", 421); + + ASSERT_EQ("b", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("a", r.GetOldest()); + ASSERT_EQ(424, r.GetOldestPayload()); + r.RemoveOldest(); + + ASSERT_EQ("d", r.GetOldest()); + ASSERT_EQ(421, r.GetOldestPayload()); + r.RemoveOldest(); + + 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_); +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/MultiThreading.cpp --- a/UnitTestsSources/MultiThreading.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,383 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" -#include - -#include "../OrthancServer/Scheduler/ServerScheduler.h" - -#include "../Core/OrthancException.h" -#include "../Core/Toolbox.h" -#include "../Core/MultiThreading/ArrayFilledByThreads.h" -#include "../Core/MultiThreading/Locker.h" -#include "../Core/MultiThreading/Mutex.h" -#include "../Core/MultiThreading/ReaderWriterLock.h" -#include "../Core/MultiThreading/ThreadedCommandProcessor.h" - -using namespace Orthanc; - -namespace -{ - class DynamicInteger : public ICommand - { - private: - int value_; - std::set& target_; - - public: - DynamicInteger(int value, std::set& target) : - value_(value), target_(target) - { - } - - int GetValue() const - { - return value_; - } - - virtual bool Execute() - { - static boost::mutex mutex; - boost::mutex::scoped_lock lock(mutex); - target_.insert(value_); - return true; - } - }; - - class MyFiller : public ArrayFilledByThreads::IFiller - { - private: - int size_; - unsigned int created_; - std::set set_; - - public: - MyFiller(int size) : size_(size), created_(0) - { - } - - virtual size_t GetFillerSize() - { - return size_; - } - - virtual IDynamicObject* GetFillerItem(size_t index) - { - static boost::mutex mutex; - boost::mutex::scoped_lock lock(mutex); - created_++; - return new DynamicInteger(index * 2, set_); - } - - unsigned int GetCreatedCount() const - { - return created_; - } - - std::set GetSet() - { - return set_; - } - }; -} - - - - -TEST(MultiThreading, SharedMessageQueueBasic) -{ - std::set s; - - SharedMessageQueue q; - ASSERT_TRUE(q.WaitEmpty(0)); - q.Enqueue(new DynamicInteger(10, s)); - ASSERT_FALSE(q.WaitEmpty(1)); - q.Enqueue(new DynamicInteger(20, s)); - q.Enqueue(new DynamicInteger(30, s)); - q.Enqueue(new DynamicInteger(40, s)); - - std::auto_ptr i; - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); - ASSERT_FALSE(q.WaitEmpty(1)); - i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); - ASSERT_TRUE(q.WaitEmpty(0)); - ASSERT_EQ(NULL, q.Dequeue(1)); -} - - -TEST(MultiThreading, SharedMessageQueueClean) -{ - std::set s; - - try - { - SharedMessageQueue q; - q.Enqueue(new DynamicInteger(10, s)); - q.Enqueue(new DynamicInteger(20, s)); - throw OrthancException("Nope"); - } - catch (OrthancException&) - { - } -} - - -TEST(MultiThreading, ArrayFilledByThreadEmpty) -{ - MyFiller f(0); - ArrayFilledByThreads a(f); - a.SetThreadCount(1); - ASSERT_EQ(0, a.GetSize()); -} - - -TEST(MultiThreading, ArrayFilledByThread1) -{ - MyFiller f(100); - ArrayFilledByThreads a(f); - a.SetThreadCount(1); - ASSERT_EQ(100, a.GetSize()); - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); - } -} - - -TEST(MultiThreading, ArrayFilledByThread4) -{ - MyFiller f(100); - ArrayFilledByThreads a(f); - a.SetThreadCount(4); - ASSERT_EQ(100, a.GetSize()); - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); - } - - ASSERT_EQ(100u, f.GetCreatedCount()); - - a.Invalidate(); - - ASSERT_EQ(100, a.GetSize()); - ASSERT_EQ(200u, f.GetCreatedCount()); - ASSERT_EQ(4u, a.GetThreadCount()); - ASSERT_TRUE(f.GetSet().empty()); - - for (size_t i = 0; i < a.GetSize(); i++) - { - ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); - } -} - - -TEST(MultiThreading, CommandProcessor) -{ - ThreadedCommandProcessor p(4); - - std::set s; - - for (size_t i = 0; i < 100; i++) - { - p.Post(new DynamicInteger(i * 2, s)); - } - - p.Join(); - - for (size_t i = 0; i < 200; i++) - { - if (i % 2) - ASSERT_TRUE(s.find(i) == s.end()); - else - ASSERT_TRUE(s.find(i) != s.end()); - } -} - - -TEST(MultiThreading, Mutex) -{ - Mutex mutex; - Locker locker(mutex); -} - - -TEST(MultiThreading, ReaderWriterLock) -{ - ReaderWriterLock lock; - - { - Locker locker1(lock.ForReader()); - Locker locker2(lock.ForReader()); - } - - { - Locker locker3(lock.ForWriter()); - } -} - - - -#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" - -TEST(ReusableDicomUserConnection, DISABLED_Basic) -{ - ReusableDicomUserConnection c; - c.SetMillisecondsBeforeClose(200); - printf("START\n"); fflush(stdout); - - { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); - } - - printf("**\n"); fflush(stdout); - Toolbox::USleep(1000000); - printf("**\n"); fflush(stdout); - - { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); - lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); - } - - Toolbox::ServerBarrier(); - printf("DONE\n"); fflush(stdout); -} - - - -class Tutu : public IServerFilter -{ -private: - int factor_; - -public: - Tutu(int f) : factor_(f) - { - } - - virtual bool Apply(ListOfStrings& outputs, - const ListOfStrings& inputs) - { - for (ListOfStrings::const_iterator - it = inputs.begin(); it != inputs.end(); it++) - { - int a = boost::lexical_cast(*it); - int b = factor_ * a; - - printf("%d * %d = %d\n", a, factor_, b); - - //if (a == 84) { printf("BREAK\n"); return false; } - - outputs.push_back(boost::lexical_cast(b)); - } - - Toolbox::USleep(1000000); - - return true; - } - - virtual bool SendOutputsToSink() const - { - return true; - } -}; - - -static void Tata(ServerScheduler* s, ServerJob* j, bool* done) -{ - typedef IServerFilter::ListOfStrings ListOfStrings; - -#if 1 - while (!(*done)) - { - ListOfStrings l; - s->GetListOfJobs(l); - for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) - printf(">> %s: %0.1f\n", i->c_str(), 100.0f * s->GetProgress(*i)); - Toolbox::USleep(100000); - } -#else - ListOfStrings l; - s->GetListOfJobs(l); - for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) - printf(">> %s\n", i->c_str()); - Toolbox::USleep(1500000); - s->Cancel(*j); - Toolbox::USleep(1000000); - s->GetListOfJobs(l); - for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) - printf(">> %s\n", i->c_str()); -#endif -} - - -TEST(Toto, Toto) -{ - ServerScheduler scheduler; - - ServerJob job; - ServerFilterInstance& f2 = job.AddFilter(new Tutu(2)); - ServerFilterInstance& f3 = job.AddFilter(new Tutu(3)); - ServerFilterInstance& f4 = job.AddFilter(new Tutu(4)); - ServerFilterInstance& f5 = job.AddFilter(new Tutu(5)); - f2.AddInput(boost::lexical_cast(42)); - //f3.AddInput(boost::lexical_cast(42)); - //f4.AddInput(boost::lexical_cast(42)); - f2.ConnectNext(f3); - f3.ConnectNext(f4); - f4.ConnectNext(f5); - - job.SetDescription("tutu"); - - bool done = false; - boost::thread t(Tata, &scheduler, &job, &done); - - - //scheduler.Submit(job); - - IServerFilter::ListOfStrings l; - scheduler.SubmitAndWait(l, job); - - for (IServerFilter::ListOfStrings::iterator i = l.begin(); i != l.end(); i++) - { - printf("** %s\n", i->c_str()); - } - - //Toolbox::ServerBarrier(); - //Toolbox::USleep(3000000); - - done = true; - t.join(); -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/MultiThreadingTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MultiThreadingTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,383 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" +#include + +#include "../OrthancServer/Scheduler/ServerScheduler.h" + +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/Locker.h" +#include "../Core/MultiThreading/Mutex.h" +#include "../Core/MultiThreading/ReaderWriterLock.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +using namespace Orthanc; + +namespace +{ + class DynamicInteger : public ICommand + { + private: + int value_; + std::set& target_; + + public: + DynamicInteger(int value, std::set& target) : + value_(value), target_(target) + { + } + + int GetValue() const + { + return value_; + } + + virtual bool Execute() + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + target_.insert(value_); + return true; + } + }; + + class MyFiller : public ArrayFilledByThreads::IFiller + { + private: + int size_; + unsigned int created_; + std::set set_; + + public: + MyFiller(int size) : size_(size), created_(0) + { + } + + virtual size_t GetFillerSize() + { + return size_; + } + + virtual IDynamicObject* GetFillerItem(size_t index) + { + static boost::mutex mutex; + boost::mutex::scoped_lock lock(mutex); + created_++; + return new DynamicInteger(index * 2, set_); + } + + unsigned int GetCreatedCount() const + { + return created_; + } + + std::set GetSet() + { + return set_; + } + }; +} + + + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set s; + + SharedMessageQueue q; + ASSERT_TRUE(q.WaitEmpty(0)); + q.Enqueue(new DynamicInteger(10, s)); + ASSERT_FALSE(q.WaitEmpty(1)); + q.Enqueue(new DynamicInteger(20, s)); + q.Enqueue(new DynamicInteger(30, s)); + q.Enqueue(new DynamicInteger(40, s)); + + std::auto_ptr i; + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set s; + + try + { + SharedMessageQueue q; + q.Enqueue(new DynamicInteger(10, s)); + q.Enqueue(new DynamicInteger(20, s)); + throw OrthancException("Nope"); + } + catch (OrthancException&) + { + } +} + + +TEST(MultiThreading, ArrayFilledByThreadEmpty) +{ + MyFiller f(0); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(0, a.GetSize()); +} + + +TEST(MultiThreading, ArrayFilledByThread1) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(1); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, ArrayFilledByThread4) +{ + MyFiller f(100); + ArrayFilledByThreads a(f); + a.SetThreadCount(4); + ASSERT_EQ(100, a.GetSize()); + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); + } + + ASSERT_EQ(100u, f.GetCreatedCount()); + + a.Invalidate(); + + ASSERT_EQ(100, a.GetSize()); + ASSERT_EQ(200u, f.GetCreatedCount()); + ASSERT_EQ(4u, a.GetThreadCount()); + ASSERT_TRUE(f.GetSet().empty()); + + for (size_t i = 0; i < a.GetSize(); i++) + { + ASSERT_EQ(2 * i, dynamic_cast(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, CommandProcessor) +{ + ThreadedCommandProcessor p(4); + + std::set s; + + for (size_t i = 0; i < 100; i++) + { + p.Post(new DynamicInteger(i * 2, s)); + } + + p.Join(); + + for (size_t i = 0; i < 200; i++) + { + if (i % 2) + ASSERT_TRUE(s.find(i) == s.end()); + else + ASSERT_TRUE(s.find(i) != s.end()); + } +} + + +TEST(MultiThreading, Mutex) +{ + Mutex mutex; + Locker locker(mutex); +} + + +TEST(MultiThreading, ReaderWriterLock) +{ + ReaderWriterLock lock; + + { + Locker locker1(lock.ForReader()); + Locker locker2(lock.ForReader()); + } + + { + Locker locker3(lock.ForWriter()); + } +} + + + +#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" + +TEST(ReusableDicomUserConnection, DISABLED_Basic) +{ + ReusableDicomUserConnection c; + c.SetMillisecondsBeforeClose(200); + printf("START\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); + } + + printf("**\n"); fflush(stdout); + Toolbox::USleep(1000000); + printf("**\n"); fflush(stdout); + + { + ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); + } + + Toolbox::ServerBarrier(); + printf("DONE\n"); fflush(stdout); +} + + + +class Tutu : public IServerFilter +{ +private: + int factor_; + +public: + Tutu(int f) : factor_(f) + { + } + + virtual bool Apply(ListOfStrings& outputs, + const ListOfStrings& inputs) + { + for (ListOfStrings::const_iterator + it = inputs.begin(); it != inputs.end(); it++) + { + int a = boost::lexical_cast(*it); + int b = factor_ * a; + + printf("%d * %d = %d\n", a, factor_, b); + + //if (a == 84) { printf("BREAK\n"); return false; } + + outputs.push_back(boost::lexical_cast(b)); + } + + Toolbox::USleep(1000000); + + return true; + } + + virtual bool SendOutputsToSink() const + { + return true; + } +}; + + +static void Tata(ServerScheduler* s, ServerJob* j, bool* done) +{ + typedef IServerFilter::ListOfStrings ListOfStrings; + +#if 1 + while (!(*done)) + { + ListOfStrings l; + s->GetListOfJobs(l); + for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + printf(">> %s: %0.1f\n", i->c_str(), 100.0f * s->GetProgress(*i)); + Toolbox::USleep(100000); + } +#else + ListOfStrings l; + s->GetListOfJobs(l); + for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + printf(">> %s\n", i->c_str()); + Toolbox::USleep(1500000); + s->Cancel(*j); + Toolbox::USleep(1000000); + s->GetListOfJobs(l); + for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + printf(">> %s\n", i->c_str()); +#endif +} + + +TEST(Toto, Toto) +{ + ServerScheduler scheduler; + + ServerJob job; + ServerFilterInstance& f2 = job.AddFilter(new Tutu(2)); + ServerFilterInstance& f3 = job.AddFilter(new Tutu(3)); + ServerFilterInstance& f4 = job.AddFilter(new Tutu(4)); + ServerFilterInstance& f5 = job.AddFilter(new Tutu(5)); + f2.AddInput(boost::lexical_cast(42)); + //f3.AddInput(boost::lexical_cast(42)); + //f4.AddInput(boost::lexical_cast(42)); + f2.ConnectNext(f3); + f3.ConnectNext(f4); + f4.ConnectNext(f5); + + job.SetDescription("tutu"); + + bool done = false; + boost::thread t(Tata, &scheduler, &job, &done); + + + //scheduler.Submit(job); + + IServerFilter::ListOfStrings l; + scheduler.SubmitAndWait(l, job); + + for (IServerFilter::ListOfStrings::iterator i = l.begin(); i != l.end(); i++) + { + printf("** %s\n", i->c_str()); + } + + //Toolbox::ServerBarrier(); + //Toolbox::USleep(3000000); + + done = true; + t.join(); +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/Png.cpp --- a/UnitTestsSources/Png.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" -#include "../Core/Toolbox.h" -#include "../Core/Uuid.h" - - -TEST(PngWriter, ColorPattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 61; - int pitch = width * 3; - - std::vector image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p += 3) - { - p[0] = (y % 3 == 0) ? 255 : 0; - p[1] = (y % 3 == 1) ? 255 : 0; - p[2] = (y % 3 == 2) ? 255 : 0; - } - } - - w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); -} - -TEST(PngWriter, Gray8Pattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 256; - int pitch = width; - - std::vector image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p++) - { - *p = y; - } - } - - w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); -} - -TEST(PngWriter, Gray16Pattern) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); -} - -TEST(PngWriter, EndToEnd) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - std::string s; - w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - { - Orthanc::PngReader r; - r.ReadFromMemory(s); - - ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r.GetWidth(), width); - ASSERT_EQ(r.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); - ASSERT_EQ(p, r.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } - - { - Orthanc::Toolbox::TemporaryFile tmp; - Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); - - Orthanc::PngReader r2; - r2.ReadFromFile(tmp.GetPath()); - - ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r2.GetWidth(), width); - ASSERT_EQ(r2.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); - ASSERT_EQ(p, r2.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/PngTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/PngTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,186 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include +#include "../Core/ImageFormats/PngReader.h" +#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" + + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 61; + int pitch = width * 3; + + std::vector image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); +} + +TEST(PngWriter, EndToEnd) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + std::string s; + w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + { + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetConstRow(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } + + { + Orthanc::Toolbox::TemporaryFile tmp; + Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PngReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetConstRow(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/RestApi.cpp --- a/UnitTestsSources/RestApi.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include -#include - -#include "../Core/ChunkedBuffer.h" -#include "../Core/HttpClient.h" -#include "../Core/RestApi/RestApi.h" -#include "../Core/Uuid.h" -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZlibCompressor.h" - -using namespace Orthanc; - -#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) -#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" -#endif - -TEST(HttpClient, Basic) -{ - HttpClient c; - ASSERT_FALSE(c.IsVerbose()); - c.SetVerbose(true); - ASSERT_TRUE(c.IsVerbose()); - c.SetVerbose(false); - ASSERT_FALSE(c.IsVerbose()); - -#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 - Json::Value v; - c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); - c.Apply(v); - ASSERT_TRUE(v.isMember("StorageDirectory")); - //ASSERT_EQ(GetLastStatusText()); - - v = Json::nullValue; - - HttpClient cc(c); - cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json"); - cc.Apply(v); - ASSERT_TRUE(v.isMember("LuaScripts")); -#endif -} - -TEST(RestApi, ChunkedBuffer) -{ - ChunkedBuffer b; - ASSERT_EQ(0, b.GetNumBytes()); - - b.AddChunk("hello", 5); - ASSERT_EQ(5, b.GetNumBytes()); - - b.AddChunk("world", 5); - ASSERT_EQ(10, b.GetNumBytes()); - - std::string s; - b.Flatten(s); - ASSERT_EQ("helloworld", s); -} - -TEST(RestApi, ParseCookies) -{ - HttpHandler::Arguments headers; - HttpHandler::Arguments cookies; - - headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(4u, cookies.size()); - ASSERT_EQ("b", cookies["a"]); - ASSERT_EQ("d", cookies["c"]); - ASSERT_EQ("f", cookies["e"]); - ASSERT_EQ("h", cookies["g"]); - - headers["cookie"] = " name = value ; name2=value2"; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(2u, cookies.size()); - ASSERT_EQ("value", cookies["name"]); - ASSERT_EQ("value2", cookies["name2"]); - - headers["cookie"] = " ;;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(0u, cookies.size()); - - headers["cookie"] = " ; n=v ;; "; - HttpHandler::ParseCookies(cookies, headers); - ASSERT_EQ(1u, cookies.size()); - ASSERT_EQ("v", cookies["n"]); -} - -TEST(RestApi, RestApiPath) -{ - RestApiPath::Components args; - UriComponents trail; - - { - RestApiPath uri("/coucou/{abc}/d/*"); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - ASSERT_EQ("e", trail[0]); - ASSERT_EQ("f", trail[1]); - ASSERT_EQ("g", trail[2]); - - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); - ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); - } - - { - RestApiPath uri("/coucou/{abc}/d"); - ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); - ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); - ASSERT_EQ(1u, args.size()); - ASSERT_EQ(0u, trail.size()); - ASSERT_EQ("moi", args["abc"]); - } - - { - RestApiPath uri("/*"); - ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); - ASSERT_EQ(0u, args.size()); - ASSERT_EQ(3u, trail.size()); - ASSERT_EQ("a", trail[0]); - ASSERT_EQ("b", trail[1]); - ASSERT_EQ("c", trail[2]); - } -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/RestApiTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/RestApiTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,283 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include +#include + +#include "../Core/ChunkedBuffer.h" +#include "../Core/HttpClient.h" +#include "../Core/RestApi/RestApi.h" +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZlibCompressor.h" +#include "../Core/RestApi/RestApiHierarchy.h" + +using namespace Orthanc; + +#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) +#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" +#endif + +TEST(HttpClient, Basic) +{ + HttpClient c; + ASSERT_FALSE(c.IsVerbose()); + c.SetVerbose(true); + ASSERT_TRUE(c.IsVerbose()); + c.SetVerbose(false); + ASSERT_FALSE(c.IsVerbose()); + +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 + Json::Value v; + c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); + c.Apply(v); + ASSERT_TRUE(v.isMember("StorageDirectory")); + //ASSERT_EQ(GetLastStatusText()); + + v = Json::nullValue; + + HttpClient cc(c); + cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json"); + cc.Apply(v); + ASSERT_TRUE(v.isMember("LuaScripts")); +#endif +} + +TEST(RestApi, ChunkedBuffer) +{ + ChunkedBuffer b; + ASSERT_EQ(0, b.GetNumBytes()); + + b.AddChunk("hello", 5); + ASSERT_EQ(5, b.GetNumBytes()); + + b.AddChunk("world", 5); + ASSERT_EQ(10, b.GetNumBytes()); + + std::string s; + b.Flatten(s); + ASSERT_EQ("helloworld", s); +} + +TEST(RestApi, ParseCookies) +{ + HttpHandler::Arguments headers; + HttpHandler::Arguments cookies; + + headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(4u, cookies.size()); + ASSERT_EQ("b", cookies["a"]); + ASSERT_EQ("d", cookies["c"]); + ASSERT_EQ("f", cookies["e"]); + ASSERT_EQ("h", cookies["g"]); + + headers["cookie"] = " name = value ; name2=value2"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(2u, cookies.size()); + ASSERT_EQ("value", cookies["name"]); + ASSERT_EQ("value2", cookies["name2"]); + + headers["cookie"] = " ;;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(0u, cookies.size()); + + headers["cookie"] = " ; n=v ;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(1u, cookies.size()); + ASSERT_EQ("v", cookies["n"]); +} + +TEST(RestApi, RestApiPath) +{ + HttpHandler::Arguments args; + UriComponents trail; + + { + RestApiPath uri("/coucou/{abc}/d/*"); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + ASSERT_EQ("e", trail[0]); + ASSERT_EQ("f", trail[1]); + ASSERT_EQ("g", trail[2]); + + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); + ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); + + ASSERT_EQ(3u, uri.GetLevelCount()); + ASSERT_TRUE(uri.IsUniversalTrailing()); + + ASSERT_EQ("coucou", uri.GetLevelName(0)); + ASSERT_THROW(uri.GetWildcardName(0), OrthancException); + + ASSERT_EQ("abc", uri.GetWildcardName(1)); + ASSERT_THROW(uri.GetLevelName(1), OrthancException); + + ASSERT_EQ("d", uri.GetLevelName(2)); + ASSERT_THROW(uri.GetWildcardName(2), OrthancException); + } + + { + RestApiPath uri("/coucou/{abc}/d"); + ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); + ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); + ASSERT_EQ(1u, args.size()); + ASSERT_EQ(0u, trail.size()); + ASSERT_EQ("moi", args["abc"]); + + ASSERT_EQ(3u, uri.GetLevelCount()); + ASSERT_FALSE(uri.IsUniversalTrailing()); + + ASSERT_EQ("coucou", uri.GetLevelName(0)); + ASSERT_THROW(uri.GetWildcardName(0), OrthancException); + + ASSERT_EQ("abc", uri.GetWildcardName(1)); + ASSERT_THROW(uri.GetLevelName(1), OrthancException); + + ASSERT_EQ("d", uri.GetLevelName(2)); + ASSERT_THROW(uri.GetWildcardName(2), OrthancException); + } + + { + RestApiPath uri("/*"); + ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); + ASSERT_EQ(0u, args.size()); + ASSERT_EQ(3u, trail.size()); + ASSERT_EQ("a", trail[0]); + ASSERT_EQ("b", trail[1]); + ASSERT_EQ("c", trail[2]); + + ASSERT_EQ(0u, uri.GetLevelCount()); + ASSERT_TRUE(uri.IsUniversalTrailing()); + } +} + + + + + + +static int testValue; + +template +static void SetValue(RestApiGetCall& get) +{ + testValue = value; +} + + +static bool GetDirectory(Json::Value& target, + RestApiHierarchy& hierarchy, + const std::string& uri) +{ + UriComponents p; + Toolbox::SplitUriComponents(p, uri); + return hierarchy.GetDirectory(target, p); +} + + + +namespace +{ + class MyVisitor : public RestApiHierarchy::IVisitor + { + public: + virtual bool Visit(const RestApiHierarchy::Resource& resource, + const UriComponents& uri, + const HttpHandler::Arguments& components, + const UriComponents& trailing) + { + return resource.Handle(*reinterpret_cast(NULL)); + } + }; +} + + +static bool HandleGet(RestApiHierarchy& hierarchy, + const std::string& uri) +{ + UriComponents p; + Toolbox::SplitUriComponents(p, uri); + MyVisitor visitor; + return hierarchy.LookupResource(p, visitor); +} + + +TEST(RestApi, RestApiHierarchy) +{ + RestApiHierarchy root; + root.Register("/hello/world/test", SetValue<1>); + root.Register("/hello/world/test2", SetValue<2>); + root.Register("/hello/{world}/test3/test4", SetValue<3>); + root.Register("/hello2/*", SetValue<4>); + + Json::Value m; + root.CreateSiteMap(m); + std::cout << m; + + Json::Value d; + ASSERT_FALSE(GetDirectory(d, root, "/hello")); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/a")); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("test3", d[0].asString()); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/world")); + ASSERT_EQ(2u, d.size()); + + ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3")); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("test4", d[0].asString()); + + ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test")); + ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test2")); + ASSERT_FALSE(GetDirectory(d, root, "/hello2")); + + testValue = 0; + ASSERT_TRUE(HandleGet(root, "/hello/world/test")); + ASSERT_EQ(testValue, 1); + ASSERT_TRUE(HandleGet(root, "/hello/world/test2")); + ASSERT_EQ(testValue, 2); + ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4")); + ASSERT_EQ(testValue, 3); + ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test")); + ASSERT_EQ(testValue, 3); + ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); + ASSERT_EQ(testValue, 4); +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/SQLite.cpp --- a/UnitTestsSources/SQLite.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,334 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include - -using namespace Orthanc; - - -TEST(SQLite, Configuration) -{ - ASSERT_EQ(1, sqlite3_threadsafe()); -} - - -TEST(SQLite, Connection) -{ - Toolbox::RemoveFile("UnitTestsResults/coucou"); - SQLite::Connection c; - c.Open("UnitTestsResults/coucou"); - c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); - c.Execute("INSERT INTO c VALUES(NULL, 42);"); -} - - -TEST(SQLite, StatementReferenceBasic) -{ - sqlite3* db; - sqlite3_open(":memory:", &db); - - { - SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); - ASSERT_EQ(0u, r.GetReferenceCount()); - - { - SQLite::StatementReference r1(r); - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - - SQLite::StatementReference r3(r2); - ASSERT_EQ(3u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - ASSERT_EQ(0u, r3.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - - { - SQLite::StatementReference r2(r); - ASSERT_EQ(2u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - ASSERT_EQ(0u, r2.GetReferenceCount()); - } - - ASSERT_EQ(1u, r.GetReferenceCount()); - ASSERT_EQ(0u, r1.GetReferenceCount()); - } - - ASSERT_EQ(0u, r.GetReferenceCount()); - } - - sqlite3_close(db); -} - -TEST(SQLite, StatementBasic) -{ - SQLite::Connection c; - c.OpenInMemory(); - - SQLite::Statement s(c, "SELECT * from sqlite_master"); - s.Run(); - - for (unsigned int i = 0; i < 5; i++) - { - SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); - cs.Step(); - } -} - - -namespace -{ - static bool destroyed; - - class MyFunc : public SQLite::IScalarFunction - { - public: - MyFunc() - { - destroyed = false; - } - - virtual ~MyFunc() - { - destroyed = true; - } - - virtual const char* GetName() const - { - return "MYFUNC"; - } - - virtual unsigned int GetCardinality() const - { - return 2; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); - } - }; - - class MyDelete : public SQLite::IScalarFunction - { - public: - std::set deleted_; - - virtual const char* GetName() const - { - return "MYDELETE"; - } - - virtual unsigned int GetCardinality() const - { - return 1; - } - - virtual void Compute(SQLite::FunctionContext& context) - { - deleted_.insert(context.GetIntValue(0)); - context.SetNullResult(); - } - }; -} - -TEST(SQLite, ScalarFunction) -{ - { - SQLite::Connection c; - c.OpenInMemory(); - c.Register(new MyFunc()); - c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); - c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); - c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); - c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); - SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); - int i = 0; - while (t.Step()) - { - ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); - i++; - } - ASSERT_EQ(3, i); - ASSERT_FALSE(destroyed); - } - ASSERT_TRUE(destroyed); -} - -TEST(SQLite, CascadedDeleteCallback) -{ - SQLite::Connection c; - c.OpenInMemory(); - MyDelete *func = new MyDelete(); - c.Register(func); - c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); - c.Execute("CREATE TABLE child(" - " id INTEGER PRIMARY KEY, " - " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " - " value INTEGER);"); - c.Execute("CREATE TRIGGER childRemoved " - "AFTER DELETE ON child " - "FOR EACH ROW BEGIN " - " SELECT MYDELETE(old.value); " - "END;"); - - c.Execute("INSERT INTO parent VALUES(42, 100);"); - c.Execute("INSERT INTO parent VALUES(43, 101);"); - - c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); - c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); - - c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); - c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); - - // The following command deletes "parent(43, 101)", then in turns - // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and - // 4301 - c.Execute("DELETE FROM parent WHERE dummy=101"); - - ASSERT_EQ(2u, func->deleted_.size()); - ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); - ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); -} - - -TEST(SQLite, EmptyTransactions) -{ - try - { - SQLite::Connection c; - c.OpenInMemory(); - - c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); - c.Execute("INSERT INTO a VALUES(NULL)"); - - { - SQLite::Transaction t(c); - t.Begin(); - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - //t.Commit(); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); - s.Step(); - } - } - catch (OrthancException& e) - { - fprintf(stderr, "Exception: [%s]\n", e.What()); - throw e; - } -} - - -TEST(SQLite, Types) -{ - SQLite::Connection c; - c.OpenInMemory(); - c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)"); - - { - SQLite::Statement s(c, std::string("SELECT * FROM a")); - ASSERT_EQ(2, s.ColumnCount()); - ASSERT_FALSE(s.Step()); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); - ASSERT_FALSE(s.Step()); - ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement()); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);"); - s.BindNull(0); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindBool(0, true); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindInt(0, 42); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindInt64(0, 42ll); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindDouble(0, 42.5); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset(); - s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset(); - } - - { - SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1)); - ASSERT_TRUE(s.ColumnIsNull(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_TRUE(s.ColumnBool(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_EQ(42, s.ColumnInt(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); - ASSERT_EQ(42ll, s.ColumnInt64(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); - ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); - ASSERT_EQ("Hello", s.ColumnString(1)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1)); - ASSERT_EQ(5, s.ColumnByteLength(1)); - ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5)); - - std::string t; - ASSERT_TRUE(s.ColumnBlobAsString(1, &t)); - ASSERT_EQ("Hello", t); - - ASSERT_FALSE(s.Step()); - } -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/SQLiteChromium.cpp --- a/UnitTestsSources/SQLiteChromium.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,377 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/Toolbox.h" -#include "../Core/SQLite/Connection.h" -#include "../Core/SQLite/Statement.h" -#include "../Core/SQLite/Transaction.h" - -#include - - -using namespace Orthanc; -using namespace Orthanc::SQLite; - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc - ********************************************************************/ - -class SQLConnectionTest : public testing::Test -{ -public: - SQLConnectionTest() - { - } - - virtual ~SQLConnectionTest() - { - } - - virtual void SetUp() - { - db_.OpenInMemory(); - } - - virtual void TearDown() - { - db_.Close(); - } - - Connection& db() - { - return db_; - } - -private: - Connection db_; -}; - - - -TEST_F(SQLConnectionTest, Execute) -{ - // Valid statement should return true. - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); - - // Invalid statement should fail. - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); - EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); -} - -TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { - ASSERT_EQ(SQLITE_OK, - db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); - ASSERT_EQ(SQLITE_ERROR, - db().ExecuteAndReturnErrorCode( - "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); -} - -TEST_F(SQLConnectionTest, CachedStatement) { - StatementId id1("foo", 12); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); - - // Create a new cached statement. - { - Statement s(db(), id1, "SELECT a FROM foo"); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // The statement should be cached still. - EXPECT_TRUE(db().HasCachedStatement(id1)); - - { - // Get the same statement using different SQL. This should ignore our - // SQL and use the cached one (so it will be valid). - Statement s(db(), id1, "something invalid("); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - } - - // Make sure other statements aren't marked as cached. - EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); -} - -TEST_F(SQLConnectionTest, IsSQLValidTest) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); - ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); -} - - - -TEST_F(SQLConnectionTest, DoesStuffExist) { - // Test DoesTableExist. - EXPECT_FALSE(db().DoesTableExist("foo")); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - EXPECT_TRUE(db().DoesTableExist("foo")); - - // Should be case sensitive. - EXPECT_FALSE(db().DoesTableExist("FOO")); - - // Test DoesColumnExist. - EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); - EXPECT_TRUE(db().DoesColumnExist("foo", "a")); - - // Testing for a column on a nonexistent table. - EXPECT_FALSE(db().DoesColumnExist("bar", "b")); -} - -TEST_F(SQLConnectionTest, GetLastInsertRowId) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); - - ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); - - // Last insert row ID should be valid. - int64_t row = db().GetLastInsertRowId(); - EXPECT_LT(0, row); - - // It should be the primary key of the row we just inserted. - Statement s(db(), "SELECT value FROM foo WHERE id=?"); - s.BindInt64(0, row); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); -} - -TEST_F(SQLConnectionTest, Rollback) { - ASSERT_TRUE(db().BeginTransaction()); - ASSERT_TRUE(db().BeginTransaction()); - EXPECT_EQ(2, db().GetTransactionNesting()); - db().RollbackTransaction(); - EXPECT_FALSE(db().CommitTransaction()); - EXPECT_TRUE(db().BeginTransaction()); -} - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc - ********************************************************************/ - -namespace Orthanc -{ - namespace SQLite - { - class SQLStatementTest : public SQLConnectionTest - { - }; - - TEST_F(SQLStatementTest, Run) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - - Statement s(db(), "SELECT b FROM foo WHERE a=?"); - // Stepping it won't work since we haven't bound the value. - EXPECT_FALSE(s.Step()); - - // Run should fail since this produces output, and we should use Step(). This - // gets a bit wonky since sqlite says this is OK so succeeded is set. - s.Reset(true); - s.BindInt(0, 3); - EXPECT_FALSE(s.Run()); - EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); - - // Resetting it should put it back to the previous state (not runnable). - s.Reset(true); - - // Binding and stepping should produce one row. - s.BindInt(0, 3); - EXPECT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - } - - TEST_F(SQLStatementTest, BasicErrorCallback) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); - // Insert in the foo table the primary key. It is an error to insert - // something other than an number. This error causes the error callback - // handler to be called with SQLITE_MISMATCH as error code. - Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); - s.BindCString(0, "bad bad"); - EXPECT_THROW(s.Run(), OrthancException); - } - - TEST_F(SQLStatementTest, Reset) { - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); - - Statement s(db(), "SELECT b FROM foo WHERE a = ? "); - s.BindInt(0, 3); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - ASSERT_FALSE(s.Step()); - - s.Reset(false); - // Verify that we can get all rows again. - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - - s.Reset(true); - ASSERT_FALSE(s.Step()); - } - } -} - - - - - - -/******************************************************************** - ** Tests from - ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc - ********************************************************************/ - -class SQLTransactionTest : public SQLConnectionTest -{ -public: - virtual void SetUp() - { - SQLConnectionTest::SetUp(); - ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); - } - - // Returns the number of rows in table "foo". - int CountFoo() - { - Statement count(db(), "SELECT count(*) FROM foo"); - count.Step(); - return count.ColumnInt(0); - } -}; - - -TEST_F(SQLTransactionTest, Commit) { - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - - t.Commit(); - EXPECT_FALSE(t.IsOpen()); - } - - EXPECT_EQ(1, CountFoo()); -} - -TEST_F(SQLTransactionTest, Rollback) { - // Test some basic initialization, and that rollback runs when you exit the - // scope. - { - Transaction t(db()); - EXPECT_FALSE(t.IsOpen()); - t.Begin(); - EXPECT_TRUE(t.IsOpen()); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - } - - // Nothing should have been committed since it was implicitly rolled back. - EXPECT_EQ(0, CountFoo()); - - // Test explicit rollback. - Transaction t2(db()); - EXPECT_FALSE(t2.IsOpen()); - t2.Begin(); - - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - t2.Rollback(); - EXPECT_FALSE(t2.IsOpen()); - - // Nothing should have been committed since it was explicitly rolled back. - EXPECT_EQ(0, CountFoo()); -} - -// Rolling back any part of a transaction should roll back all of them. -TEST_F(SQLTransactionTest, NestedRollback) { - EXPECT_EQ(0, db().GetTransactionNesting()); - - // Outermost transaction. - { - Transaction outer(db()); - outer.Begin(); - EXPECT_EQ(1, db().GetTransactionNesting()); - - // The first inner one gets committed. - { - Transaction inner1(db()); - inner1.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner1.Commit(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // One row should have gotten inserted. - EXPECT_EQ(1, CountFoo()); - - // The second inner one gets rolled back. - { - Transaction inner2(db()); - inner2.Begin(); - EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); - EXPECT_EQ(2, db().GetTransactionNesting()); - - inner2.Rollback(); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - - // A third inner one will fail in Begin since one has already been rolled - // back. - EXPECT_EQ(1, db().GetTransactionNesting()); - { - Transaction inner3(db()); - EXPECT_THROW(inner3.Begin(), OrthancException); - EXPECT_EQ(1, db().GetTransactionNesting()); - } - } - EXPECT_EQ(0, db().GetTransactionNesting()); - EXPECT_EQ(0, CountFoo()); -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/SQLiteChromiumTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLiteChromiumTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,377 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include + + +using namespace Orthanc; +using namespace Orthanc::SQLite; + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc + ********************************************************************/ + +class SQLConnectionTest : public testing::Test +{ +public: + SQLConnectionTest() + { + } + + virtual ~SQLConnectionTest() + { + } + + virtual void SetUp() + { + db_.OpenInMemory(); + } + + virtual void TearDown() + { + db_.Close(); + } + + Connection& db() + { + return db_; + } + +private: + Connection db_; +}; + + + +TEST_F(SQLConnectionTest, Execute) +{ + // Valid statement should return true. + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_EQ(SQLITE_OK, db().GetErrorCode()); + + // Invalid statement should fail. + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b")); + EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode()); +} + +TEST_F(SQLConnectionTest, ExecuteWithErrorCode) { + ASSERT_EQ(SQLITE_OK, + db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE")); + ASSERT_EQ(SQLITE_ERROR, + db().ExecuteAndReturnErrorCode( + "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)")); +} + +TEST_F(SQLConnectionTest, CachedStatement) { + StatementId id1("foo", 12); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)")); + + // Create a new cached statement. + { + Statement s(db(), id1, "SELECT a FROM foo"); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // The statement should be cached still. + EXPECT_TRUE(db().HasCachedStatement(id1)); + + { + // Get the same statement using different SQL. This should ignore our + // SQL and use the cached one (so it will be valid). + Statement s(db(), id1, "something invalid("); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + } + + // Make sure other statements aren't marked as cached. + EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE)); +} + +TEST_F(SQLConnectionTest, IsSQLValidTest) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo")); + ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo")); +} + + + +TEST_F(SQLConnectionTest, DoesStuffExist) { + // Test DoesTableExist. + EXPECT_FALSE(db().DoesTableExist("foo")); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + EXPECT_TRUE(db().DoesTableExist("foo")); + + // Should be case sensitive. + EXPECT_FALSE(db().DoesTableExist("FOO")); + + // Test DoesColumnExist. + EXPECT_FALSE(db().DoesColumnExist("foo", "bar")); + EXPECT_TRUE(db().DoesColumnExist("foo", "a")); + + // Testing for a column on a nonexistent table. + EXPECT_FALSE(db().DoesColumnExist("bar", "b")); +} + +TEST_F(SQLConnectionTest, GetLastInsertRowId) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)")); + + ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)")); + + // Last insert row ID should be valid. + int64_t row = db().GetLastInsertRowId(); + EXPECT_LT(0, row); + + // It should be the primary key of the row we just inserted. + Statement s(db(), "SELECT value FROM foo WHERE id=?"); + s.BindInt64(0, row); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); +} + +TEST_F(SQLConnectionTest, Rollback) { + ASSERT_TRUE(db().BeginTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_EQ(2, db().GetTransactionNesting()); + db().RollbackTransaction(); + EXPECT_FALSE(db().CommitTransaction()); + EXPECT_TRUE(db().BeginTransaction()); +} + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc + ********************************************************************/ + +namespace Orthanc +{ + namespace SQLite + { + class SQLStatementTest : public SQLConnectionTest + { + }; + + TEST_F(SQLStatementTest, Run) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + + Statement s(db(), "SELECT b FROM foo WHERE a=?"); + // Stepping it won't work since we haven't bound the value. + EXPECT_FALSE(s.Step()); + + // Run should fail since this produces output, and we should use Step(). This + // gets a bit wonky since sqlite says this is OK so succeeded is set. + s.Reset(true); + s.BindInt(0, 3); + EXPECT_FALSE(s.Run()); + EXPECT_EQ(SQLITE_ROW, db().GetErrorCode()); + + // Resetting it should put it back to the previous state (not runnable). + s.Reset(true); + + // Binding and stepping should produce one row. + s.BindInt(0, 3); + EXPECT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + } + + TEST_F(SQLStatementTest, BasicErrorCallback) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + // Insert in the foo table the primary key. It is an error to insert + // something other than an number. This error causes the error callback + // handler to be called with SQLITE_MISMATCH as error code. + Statement s(db(), "INSERT INTO foo (a) VALUES (?)"); + s.BindCString(0, "bad bad"); + EXPECT_THROW(s.Run(), OrthancException); + } + + TEST_F(SQLStatementTest, Reset) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); + ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); + + Statement s(db(), "SELECT b FROM foo WHERE a = ? "); + s.BindInt(0, 3); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + ASSERT_FALSE(s.Step()); + + s.Reset(false); + // Verify that we can get all rows again. + ASSERT_TRUE(s.Step()); + EXPECT_EQ(12, s.ColumnInt(0)); + EXPECT_FALSE(s.Step()); + + s.Reset(true); + ASSERT_FALSE(s.Step()); + } + } +} + + + + + + +/******************************************************************** + ** Tests from + ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc + ********************************************************************/ + +class SQLTransactionTest : public SQLConnectionTest +{ +public: + virtual void SetUp() + { + SQLConnectionTest::SetUp(); + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); + } + + // Returns the number of rows in table "foo". + int CountFoo() + { + Statement count(db(), "SELECT count(*) FROM foo"); + count.Step(); + return count.ColumnInt(0); + } +}; + + +TEST_F(SQLTransactionTest, Commit) { + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + + t.Commit(); + EXPECT_FALSE(t.IsOpen()); + } + + EXPECT_EQ(1, CountFoo()); +} + +TEST_F(SQLTransactionTest, Rollback) { + // Test some basic initialization, and that rollback runs when you exit the + // scope. + { + Transaction t(db()); + EXPECT_FALSE(t.IsOpen()); + t.Begin(); + EXPECT_TRUE(t.IsOpen()); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + } + + // Nothing should have been committed since it was implicitly rolled back. + EXPECT_EQ(0, CountFoo()); + + // Test explicit rollback. + Transaction t2(db()); + EXPECT_FALSE(t2.IsOpen()); + t2.Begin(); + + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + t2.Rollback(); + EXPECT_FALSE(t2.IsOpen()); + + // Nothing should have been committed since it was explicitly rolled back. + EXPECT_EQ(0, CountFoo()); +} + +// Rolling back any part of a transaction should roll back all of them. +TEST_F(SQLTransactionTest, NestedRollback) { + EXPECT_EQ(0, db().GetTransactionNesting()); + + // Outermost transaction. + { + Transaction outer(db()); + outer.Begin(); + EXPECT_EQ(1, db().GetTransactionNesting()); + + // The first inner one gets committed. + { + Transaction inner1(db()); + inner1.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner1.Commit(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // One row should have gotten inserted. + EXPECT_EQ(1, CountFoo()); + + // The second inner one gets rolled back. + { + Transaction inner2(db()); + inner2.Begin(); + EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)")); + EXPECT_EQ(2, db().GetTransactionNesting()); + + inner2.Rollback(); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + + // A third inner one will fail in Begin since one has already been rolled + // back. + EXPECT_EQ(1, db().GetTransactionNesting()); + { + Transaction inner3(db()); + EXPECT_THROW(inner3.Begin(), OrthancException); + EXPECT_EQ(1, db().GetTransactionNesting()); + } + } + EXPECT_EQ(0, db().GetTransactionNesting()); + EXPECT_EQ(0, CountFoo()); +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/SQLiteTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/SQLiteTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,334 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/SQLite/Connection.h" +#include "../Core/SQLite/Statement.h" +#include "../Core/SQLite/Transaction.h" + +#include + +using namespace Orthanc; + + +TEST(SQLite, Configuration) +{ + ASSERT_EQ(1, sqlite3_threadsafe()); +} + + +TEST(SQLite, Connection) +{ + Toolbox::RemoveFile("UnitTestsResults/coucou"); + SQLite::Connection c; + c.Open("UnitTestsResults/coucou"); + c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)"); + c.Execute("INSERT INTO c VALUES(NULL, 42);"); +} + + +TEST(SQLite, StatementReferenceBasic) +{ + sqlite3* db; + sqlite3_open(":memory:", &db); + + { + SQLite::StatementReference r(db, "SELECT * FROM sqlite_master"); + ASSERT_EQ(0u, r.GetReferenceCount()); + + { + SQLite::StatementReference r1(r); + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + + SQLite::StatementReference r3(r2); + ASSERT_EQ(3u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + ASSERT_EQ(0u, r3.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + + { + SQLite::StatementReference r2(r); + ASSERT_EQ(2u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + ASSERT_EQ(0u, r2.GetReferenceCount()); + } + + ASSERT_EQ(1u, r.GetReferenceCount()); + ASSERT_EQ(0u, r1.GetReferenceCount()); + } + + ASSERT_EQ(0u, r.GetReferenceCount()); + } + + sqlite3_close(db); +} + +TEST(SQLite, StatementBasic) +{ + SQLite::Connection c; + c.OpenInMemory(); + + SQLite::Statement s(c, "SELECT * from sqlite_master"); + s.Run(); + + for (unsigned int i = 0; i < 5; i++) + { + SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master"); + cs.Step(); + } +} + + +namespace +{ + static bool destroyed; + + class MyFunc : public SQLite::IScalarFunction + { + public: + MyFunc() + { + destroyed = false; + } + + virtual ~MyFunc() + { + destroyed = true; + } + + virtual const char* GetName() const + { + return "MYFUNC"; + } + + virtual unsigned int GetCardinality() const + { + return 2; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1)); + } + }; + + class MyDelete : public SQLite::IScalarFunction + { + public: + std::set deleted_; + + virtual const char* GetName() const + { + return "MYDELETE"; + } + + virtual unsigned int GetCardinality() const + { + return 1; + } + + virtual void Compute(SQLite::FunctionContext& context) + { + deleted_.insert(context.GetIntValue(0)); + context.SetNullResult(); + } + }; +} + +TEST(SQLite, ScalarFunction) +{ + { + SQLite::Connection c; + c.OpenInMemory(); + c.Register(new MyFunc()); + c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);"); + c.Execute("INSERT INTO t VALUES(NULL, 2, 3);"); + c.Execute("INSERT INTO t VALUES(NULL, 4, 4);"); + c.Execute("INSERT INTO t VALUES(NULL, 6, 5);"); + SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t"); + int i = 0; + while (t.Step()) + { + ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2)); + i++; + } + ASSERT_EQ(3, i); + ASSERT_FALSE(destroyed); + } + ASSERT_TRUE(destroyed); +} + +TEST(SQLite, CascadedDeleteCallback) +{ + SQLite::Connection c; + c.OpenInMemory(); + MyDelete *func = new MyDelete(); + c.Register(func); + c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);"); + c.Execute("CREATE TABLE child(" + " id INTEGER PRIMARY KEY, " + " parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, " + " value INTEGER);"); + c.Execute("CREATE TRIGGER childRemoved " + "AFTER DELETE ON child " + "FOR EACH ROW BEGIN " + " SELECT MYDELETE(old.value); " + "END;"); + + c.Execute("INSERT INTO parent VALUES(42, 100);"); + c.Execute("INSERT INTO parent VALUES(43, 101);"); + + c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);"); + c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);"); + + c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);"); + c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);"); + + // The following command deletes "parent(43, 101)", then in turns + // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and + // 4301 + c.Execute("DELETE FROM parent WHERE dummy=101"); + + ASSERT_EQ(2u, func->deleted_.size()); + ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end()); + ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end()); +} + + +TEST(SQLite, EmptyTransactions) +{ + try + { + SQLite::Connection c; + c.OpenInMemory(); + + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);"); + c.Execute("INSERT INTO a VALUES(NULL)"); + + { + SQLite::Transaction t(c); + t.Begin(); + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + //t.Commit(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a"); + s.Step(); + } + } + catch (OrthancException& e) + { + fprintf(stderr, "Exception: [%s]\n", e.What()); + throw e; + } +} + + +TEST(SQLite, Types) +{ + SQLite::Connection c; + c.OpenInMemory(); + c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)"); + + { + SQLite::Statement s(c, std::string("SELECT * FROM a")); + ASSERT_EQ(2, s.ColumnCount()); + ASSERT_FALSE(s.Step()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_FALSE(s.Step()); + ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement()); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);"); + s.BindNull(0); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBool(0, true); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt(0, 42); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindInt64(0, 42ll); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindDouble(0, 42.5); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset(); + s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset(); + } + + { + SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a")); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnIsNull(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_TRUE(s.ColumnBool(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42, s.ColumnInt(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1)); + ASSERT_EQ(42ll, s.ColumnInt64(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1)); + ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1)); + ASSERT_EQ("Hello", s.ColumnString(1)); + ASSERT_TRUE(s.Step()); + ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1)); + ASSERT_EQ(5, s.ColumnByteLength(1)); + ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5)); + + std::string t; + ASSERT_TRUE(s.ColumnBlobAsString(1, &t)); + ASSERT_EQ("Hello", t); + + ASSERT_FALSE(s.Step()); + } +} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Wed Jun 25 15:36:01 2014 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -212,7 +212,7 @@ TEST(Uri, SplitUriComponents) { - UriComponents c; + UriComponents c, d; Toolbox::SplitUriComponents(c, "/cou/hello/world"); ASSERT_EQ(3u, c.size()); ASSERT_EQ("cou", c[0]); @@ -253,6 +253,37 @@ } +TEST(Uri, Truncate) +{ + UriComponents c, d; + Toolbox::SplitUriComponents(c, "/cou/hello/world"); + + Toolbox::TruncateUri(d, c, 0); + ASSERT_EQ(3u, d.size()); + ASSERT_EQ("cou", d[0]); + ASSERT_EQ("hello", d[1]); + ASSERT_EQ("world", d[2]); + + Toolbox::TruncateUri(d, c, 1); + ASSERT_EQ(2u, d.size()); + ASSERT_EQ("hello", d[0]); + ASSERT_EQ("world", d[1]); + + Toolbox::TruncateUri(d, c, 2); + ASSERT_EQ(1u, d.size()); + ASSERT_EQ("world", d[0]); + + Toolbox::TruncateUri(d, c, 3); + ASSERT_EQ(0u, d.size()); + + Toolbox::TruncateUri(d, c, 4); + ASSERT_EQ(0u, d.size()); + + Toolbox::TruncateUri(d, c, 5); + ASSERT_EQ(0u, d.size()); +} + + TEST(Uri, Child) { UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world"); diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/Versions.cpp --- a/UnitTestsSources/Versions.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -TEST(Versions, Zlib) -{ - ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); -} - -TEST(Versions, Curl) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ(LIBCURL_VERSION, v->version); -} - -TEST(Versions, Png) -{ - ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, - png_access_version_number()); -} - -TEST(Versions, SQLite) -{ - // http://www.sqlite.org/capi3ref.html#sqlite3_libversion - assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); - assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); - assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); - - // Ensure that the SQLite version is above 3.7.0. - // "sqlite3_create_function_v2" is not defined in previous versions. - ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); -} - - -TEST(Versions, Lua) -{ - // Ensure that the Lua version is above 5.1.0. This version has - // introduced some API changes. - ASSERT_GE(LUA_VERSION_NUM, 501); -} - - -#if ORTHANC_STATIC == 1 -TEST(Versions, ZlibStatic) -{ - ASSERT_STREQ("1.2.7", zlibVersion()); -} - -TEST(Versions, BoostStatic) -{ - ASSERT_STREQ("1_55", BOOST_LIB_VERSION); -} - -TEST(Versions, CurlStatic) -{ - curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("7.26.0", v->version); -} - -TEST(Versions, PngStatic) -{ - ASSERT_EQ(10512, png_access_version_number()); - ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); -} - -TEST(Versions, CurlSslStatic) -{ - curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); - - // Check that SSL support is enabled when required - bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; - -#if ORTHANC_SSL_ENABLED == 0 - ASSERT_FALSE(curlSupportsSsl); -#else - ASSERT_TRUE(curlSupportsSsl); -#endif -} - -TEST(Version, LuaStatic) -{ - ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); -} - -TEST(Version, OpenSslStatic) -{ - ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); -} - -#endif diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/VersionsTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/VersionsTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,133 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +TEST(Versions, Zlib) +{ + ASSERT_STREQ(zlibVersion(), ZLIB_VERSION); +} + +TEST(Versions, Curl) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ(LIBCURL_VERSION, v->version); +} + +TEST(Versions, Png) +{ + ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, + png_access_version_number()); +} + +TEST(Versions, SQLite) +{ + // http://www.sqlite.org/capi3ref.html#sqlite3_libversion + assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); + assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); + assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); + + // Ensure that the SQLite version is above 3.7.0. + // "sqlite3_create_function_v2" is not defined in previous versions. + ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000); +} + + +TEST(Versions, Lua) +{ + // Ensure that the Lua version is above 5.1.0. This version has + // introduced some API changes. + ASSERT_GE(LUA_VERSION_NUM, 501); +} + + +#if ORTHANC_STATIC == 1 +TEST(Versions, ZlibStatic) +{ + ASSERT_STREQ("1.2.7", zlibVersion()); +} + +TEST(Versions, BoostStatic) +{ + ASSERT_STREQ("1_55", BOOST_LIB_VERSION); +} + +TEST(Versions, CurlStatic) +{ + curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + ASSERT_STREQ("7.26.0", v->version); +} + +TEST(Versions, PngStatic) +{ + ASSERT_EQ(10512, png_access_version_number()); + ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); +} + +TEST(Versions, CurlSslStatic) +{ + curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); + + // Check that SSL support is enabled when required + bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; + +#if ORTHANC_SSL_ENABLED == 0 + ASSERT_FALSE(curlSupportsSsl); +#else + ASSERT_TRUE(curlSupportsSsl); +#endif +} + +TEST(Version, LuaStatic) +{ + ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); +} + +TEST(Version, OpenSslStatic) +{ + ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); +} + +#endif diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/Zip.cpp --- a/UnitTestsSources/Zip.cpp Wed Jun 25 15:36:01 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include "../Core/OrthancException.h" -#include "../Core/Compression/ZipWriter.h" -#include "../Core/Compression/HierarchicalZipWriter.h" -#include "../Core/Toolbox.h" - - -using namespace Orthanc; - -TEST(ZipWriter, Basic) -{ - Orthanc::ZipWriter w; - w.SetOutputPath("UnitTestsResults/hello.zip"); - w.Open(); - w.OpenFile("world/hello"); - w.Write("Hello world"); -} - - -TEST(ZipWriter, Basic64) -{ - Orthanc::ZipWriter w; - w.SetOutputPath("UnitTestsResults/hello64.zip"); - w.SetZip64(true); - w.Open(); - w.OpenFile("world/hello"); - w.Write("Hello world"); -} - - -TEST(ZipWriter, Exceptions) -{ - Orthanc::ZipWriter w; - ASSERT_THROW(w.Open(), Orthanc::OrthancException); - w.SetOutputPath("UnitTestsResults/hello3.zip"); - w.Open(); - ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException); -} - - - - - -namespace Orthanc -{ - // The namespace is necessary - // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members - - TEST(HierarchicalZipWriter, Index) - { - HierarchicalZipWriter::Index i; - ASSERT_EQ("hello", i.OpenFile("hello")); - ASSERT_EQ("hello-2", i.OpenFile("hello")); - ASSERT_EQ("coucou", i.OpenFile("coucou")); - ASSERT_EQ("hello-3", i.OpenFile("hello")); - - i.OpenDirectory("coucou"); - - ASSERT_EQ("coucou-2/world", i.OpenFile("world")); - ASSERT_EQ("coucou-2/world-2", i.OpenFile("world")); - - i.OpenDirectory("world"); - - ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello")); - ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello")); - - i.CloseDirectory(); - - ASSERT_EQ("coucou-2/world-4", i.OpenFile("world")); - - i.CloseDirectory(); - - ASSERT_EQ("coucou-3", i.OpenFile("coucou")); - - ASSERT_THROW(i.CloseDirectory(), OrthancException); - } - - - TEST(HierarchicalZipWriter, Filenames) - { - ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric(" ÊtrE hellô ")); - - // The "^" character is considered as a space in DICOM - ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric(" Hel^^ ^\r\n\t^^lo \t ")); - } -} - - -TEST(HierarchicalZipWriter, Basic) -{ - static const std::string SPACES = " "; - - HierarchicalZipWriter w("UnitTestsResults/hello2.zip"); - - w.SetCompressionLevel(0); - - // Inside "/" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenFile("hello"); - w.Write(SPACES + "hello-2\n"); - w.OpenDirectory("hello"); - - // Inside "/hello-3" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenDirectory("hello"); - - w.SetCompressionLevel(9); - - // Inside "/hello-3/hello-2" - w.OpenFile("hello"); - w.Write(SPACES + "hello\n"); - w.OpenFile("hello"); - w.Write(SPACES + "hello-2\n"); - w.CloseDirectory(); - - // Inside "/hello-3" - w.OpenFile("hello"); - w.Write(SPACES + "hello-3\n"); - - /** - - TO CHECK THE CONTENT OF THE "hello2.zip" FILE: - - # unzip -v hello2.zip - - => There must be 6 files. The first 3 files must have a negative - compression ratio. - - **/ -} diff -r a91e7b4080d1 -r b3d4f8a30324 UnitTestsSources/ZipTests.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ZipTests.cpp Wed Jul 02 14:42:49 2014 +0200 @@ -0,0 +1,166 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2014 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/OrthancException.h" +#include "../Core/Compression/ZipWriter.h" +#include "../Core/Compression/HierarchicalZipWriter.h" +#include "../Core/Toolbox.h" + + +using namespace Orthanc; + +TEST(ZipWriter, Basic) +{ + Orthanc::ZipWriter w; + w.SetOutputPath("UnitTestsResults/hello.zip"); + w.Open(); + w.OpenFile("world/hello"); + w.Write("Hello world"); +} + + +TEST(ZipWriter, Basic64) +{ + Orthanc::ZipWriter w; + w.SetOutputPath("UnitTestsResults/hello64.zip"); + w.SetZip64(true); + w.Open(); + w.OpenFile("world/hello"); + w.Write("Hello world"); +} + + +TEST(ZipWriter, Exceptions) +{ + Orthanc::ZipWriter w; + ASSERT_THROW(w.Open(), Orthanc::OrthancException); + w.SetOutputPath("UnitTestsResults/hello3.zip"); + w.Open(); + ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException); +} + + + + + +namespace Orthanc +{ + // The namespace is necessary + // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members + + TEST(HierarchicalZipWriter, Index) + { + HierarchicalZipWriter::Index i; + ASSERT_EQ("hello", i.OpenFile("hello")); + ASSERT_EQ("hello-2", i.OpenFile("hello")); + ASSERT_EQ("coucou", i.OpenFile("coucou")); + ASSERT_EQ("hello-3", i.OpenFile("hello")); + + i.OpenDirectory("coucou"); + + ASSERT_EQ("coucou-2/world", i.OpenFile("world")); + ASSERT_EQ("coucou-2/world-2", i.OpenFile("world")); + + i.OpenDirectory("world"); + + ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello")); + ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello")); + + i.CloseDirectory(); + + ASSERT_EQ("coucou-2/world-4", i.OpenFile("world")); + + i.CloseDirectory(); + + ASSERT_EQ("coucou-3", i.OpenFile("coucou")); + + ASSERT_THROW(i.CloseDirectory(), OrthancException); + } + + + TEST(HierarchicalZipWriter, Filenames) + { + ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric(" ÊtrE hellô ")); + + // The "^" character is considered as a space in DICOM + ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric(" Hel^^ ^\r\n\t^^lo \t ")); + } +} + + +TEST(HierarchicalZipWriter, Basic) +{ + static const std::string SPACES = " "; + + HierarchicalZipWriter w("UnitTestsResults/hello2.zip"); + + w.SetCompressionLevel(0); + + // Inside "/" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenFile("hello"); + w.Write(SPACES + "hello-2\n"); + w.OpenDirectory("hello"); + + // Inside "/hello-3" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenDirectory("hello"); + + w.SetCompressionLevel(9); + + // Inside "/hello-3/hello-2" + w.OpenFile("hello"); + w.Write(SPACES + "hello\n"); + w.OpenFile("hello"); + w.Write(SPACES + "hello-2\n"); + w.CloseDirectory(); + + // Inside "/hello-3" + w.OpenFile("hello"); + w.Write(SPACES + "hello-3\n"); + + /** + + TO CHECK THE CONTENT OF THE "hello2.zip" FILE: + + # unzip -v hello2.zip + + => There must be 6 files. The first 3 files must have a negative + compression ratio. + + **/ +}