Mercurial > hg > orthanc
changeset 758:67e6400fca03 query-retrieve
integration mainline -> query-retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Apr 2014 16:34:09 +0200 |
parents | 3bdb5db8e839 (current diff) 40d09221077a (diff) |
children | c2c28dd17e87 |
files | Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomMap.h Core/DicomFormat/DicomTag.h OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h OrthancCppClient/SharedLibrary/Laaw/laaw.h OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h OrthancServer/main.cpp UnitTestsSources/ServerIndex.cpp UnitTestsSources/main.cpp |
diffstat | 221 files changed, 7287 insertions(+), 5100 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Jan 24 17:40:45 2014 +0100 +++ b/CMakeLists.txt Wed Apr 16 16:34:09 2014 +0200 @@ -15,8 +15,9 @@ SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)") SET(ENABLE_SSL ON CACHE BOOL "Include support for SSL") SET(BUILD_CLIENT_LIBRARY ON CACHE BOOL "Build the client library") -SET(DCMTK_DICTIONARY_DIR "/usr/share/dcmtk" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") +SET(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)") SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") # Advanced parameters to fine-tune linking against system libraries SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") @@ -93,6 +94,7 @@ # Prepare the embedded files set(EMBEDDED_FILES PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql + UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua ) @@ -205,7 +207,13 @@ OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/StoreScp.cpp OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancRestApi.cpp + OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp + OrthancServer/OrthancRestApi/OrthancRestApi.cpp + OrthancServer/OrthancRestApi/OrthancRestArchive.cpp + OrthancServer/OrthancRestApi/OrthancRestChanges.cpp + OrthancServer/OrthancRestApi/OrthancRestModalities.cpp + OrthancServer/OrthancRestApi/OrthancRestResources.cpp + OrthancServer/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/ServerIndex.cpp OrthancServer/ToDcmtkBridge.cpp OrthancServer/DatabaseWrapper.cpp @@ -240,21 +248,29 @@ ## Build the unit tests ##################################################################### +if (UNIT_TESTS_WITH_HTTP_CONNEXIONS) + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1) +else() + add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) +endif() + add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) add_executable(UnitTests ${GTEST_SOURCES} + UnitTestsSources/DicomMap.cpp UnitTestsSources/FileStorage.cpp UnitTestsSources/MemoryCache.cpp UnitTestsSources/Png.cpp UnitTestsSources/RestApi.cpp UnitTestsSources/SQLite.cpp UnitTestsSources/SQLiteChromium.cpp - UnitTestsSources/ServerIndex.cpp + UnitTestsSources/ServerIndexTests.cpp UnitTestsSources/Versions.cpp UnitTestsSources/Zip.cpp UnitTestsSources/Lua.cpp - UnitTestsSources/main.cpp + UnitTestsSources/MultiThreading.cpp + UnitTestsSources/UnitTestsMain.cpp ) target_link_libraries(UnitTests ServerLibrary CoreLibrary)
--- a/Core/Cache/ICachePageProvider.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Cache/ICachePageProvider.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Cache/LeastRecentlyUsedIndex.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Cache/LeastRecentlyUsedIndex.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Cache/MemoryCache.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Cache/MemoryCache.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Cache/MemoryCache.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Cache/MemoryCache.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/ChunkedBuffer.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/ChunkedBuffer.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/ChunkedBuffer.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/ChunkedBuffer.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/BufferCompressor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/BufferCompressor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/BufferCompressor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/HierarchicalZipWriter.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/HierarchicalZipWriter.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/HierarchicalZipWriter.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/ZipWriter.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZipWriter.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/ZipWriter.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/ZlibCompressor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Compression/ZlibCompressor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Compression/ZlibCompressor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomArray.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomArray.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomArray.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomElement.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomElement.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomInstanceHasher.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomInstanceHasher.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomMap.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -199,7 +199,7 @@ } else { - throw OrthancException("Inexistent tag"); + throw OrthancException(ErrorCode_InexistentTag); } }
--- a/Core/DicomFormat/DicomMap.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomMap.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomNullValue.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomNullValue.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomString.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomString.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomTag.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomTag.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomTag.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/DicomFormat/DicomValue.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/DicomFormat/DicomValue.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/EnumerationDictionary.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/EnumerationDictionary.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Enumerations.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Enumerations.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -252,11 +252,11 @@ std::string s(type); Toolbox::ToUpperCase(s); - if (s == "PATIENT") + if (s == "PATIENT" || s == "PATIENTS") { return ResourceType_Patient; } - else if (s == "STUDY") + else if (s == "STUDY" || s == "STUDIES") { return ResourceType_Study; } @@ -264,7 +264,8 @@ { return ResourceType_Series; } - else if (s == "INSTANCE" || s == "IMAGE") + else if (s == "INSTANCE" || s == "IMAGE" || + s == "INSTANCES" || s == "IMAGES") { return ResourceType_Instance; }
--- a/Core/Enumerations.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Enumerations.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,7 +32,7 @@ #pragma once -#include "../OrthancCppClient/SharedLibrary/Laaw/laaw.h" +#include <laaw/laaw.h> namespace Orthanc { @@ -66,7 +66,9 @@ ErrorCode_Timeout, ErrorCode_UnknownResource, ErrorCode_IncompatibleDatabaseVersion, - ErrorCode_FullStorage + ErrorCode_FullStorage, + ErrorCode_CorruptedFile, + ErrorCode_InexistentTag }; /** @@ -225,7 +227,11 @@ enum FileContentType { FileContentType_Dicom = 1, - FileContentType_Json = 2 + FileContentType_DicomAsJson = 2, + + // Make sure that the value "65535" can be stored into this enumeration + FileContentType_StartUser = 1024, + FileContentType_EndUser = 65535 }; enum ResourceType
--- a/Core/FileFormats/PngReader.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileFormats/PngReader.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileFormats/PngReader.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileFormats/PngReader.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -96,6 +96,11 @@ void ReadFromFile(const char* filename); + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + void ReadFromMemory(const void* buffer, size_t size);
--- a/Core/FileFormats/PngWriter.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileFormats/PngWriter.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileFormats/PngWriter.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileFormats/PngWriter.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -42,21 +42,36 @@ size_t size, FileContentType type) { + std::string md5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(md5, data, size); + } + switch (compressionType_) { case CompressionType_None: { std::string uuid = storage_.Create(data, size); - return FileInfo(uuid, type, size); + return FileInfo(uuid, type, size, md5); } case CompressionType_Zlib: { std::string compressed; zlib_.Compress(compressed, data, size); + + std::string compressedMD5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(compressedMD5, compressed); + } + std::string uuid = storage_.Create(compressed); - return FileInfo(uuid, type, size, - CompressionType_Zlib, compressed.size()); + return FileInfo(uuid, type, size, md5, + CompressionType_Zlib, compressed.size(), compressedMD5); } default:
--- a/Core/FileStorage/CompressedFileStorageAccessor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/CompressedFileStorageAccessor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileInfo.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/FileInfo.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -43,9 +43,13 @@ private: std::string uuid_; FileContentType contentType_; + uint64_t uncompressedSize_; + std::string uncompressedMD5_; + CompressionType compressionType_; uint64_t compressedSize_; + std::string compressedMD5_; public: FileInfo() @@ -57,12 +61,15 @@ **/ FileInfo(const std::string& uuid, FileContentType contentType, - uint64_t size) : + uint64_t size, + const std::string& md5) : uuid_(uuid), contentType_(contentType), uncompressedSize_(size), + uncompressedMD5_(md5), compressionType_(CompressionType_None), - compressedSize_(size) + compressedSize_(size), + compressedMD5_(md5) { } @@ -72,13 +79,17 @@ FileInfo(const std::string& uuid, FileContentType contentType, uint64_t uncompressedSize, + const std::string& uncompressedMD5, CompressionType compressionType, - uint64_t compressedSize) : + uint64_t compressedSize, + const std::string& compressedMD5) : uuid_(uuid), contentType_(contentType), uncompressedSize_(uncompressedSize), + uncompressedMD5_(uncompressedMD5), compressionType_(compressionType), - compressedSize_(compressedSize) + compressedSize_(compressedSize), + compressedMD5_(compressedMD5) { } @@ -106,5 +117,15 @@ { return compressedSize_; } + + const std::string& GetCompressedMD5() const + { + return compressedMD5_; + } + + const std::string& GetUncompressedMD5() const + { + return uncompressedMD5_; + } }; }
--- a/Core/FileStorage/FileStorage.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/FileStorage.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorage.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/FileStorage.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/FileStorageAccessor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/FileStorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -38,6 +38,13 @@ size_t size, FileContentType type) { - return FileInfo(storage_.Create(data, size), type, size); + std::string md5; + + if (storeMD5_) + { + Toolbox::ComputeMD5(md5, data, size); + } + + return FileInfo(storage_.Create(data, size), type, size, md5); } }
--- a/Core/FileStorage/FileStorageAccessor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/FileStorageAccessor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/StorageAccessor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/FileStorage/StorageAccessor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/FileStorage/StorageAccessor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -45,15 +45,32 @@ class StorageAccessor : boost::noncopyable { protected: + bool storeMD5_; + virtual FileInfo WriteInternal(const void* data, size_t size, FileContentType type) = 0; public: + StorageAccessor() + { + storeMD5_ = true; + } + virtual ~StorageAccessor() { } + void SetStoreMD5(bool storeMD5) + { + storeMD5_ = storeMD5; + } + + bool IsStoreMD5() const + { + return storeMD5_; + } + FileInfo Write(const void* data, size_t size, FileContentType type)
--- a/Core/HttpClient.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpClient.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -254,9 +254,4 @@ { curl_global_cleanup(); } - - const char* HttpClient::GetLastStatusText() const - { - return EnumerationToString(lastStatus_); - } }
--- a/Core/HttpClient.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpClient.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -115,8 +115,6 @@ return lastStatus_; } - const char* GetLastStatusText() const; - void SetCredentials(const char* username, const char* password);
--- a/Core/HttpServer/BufferHttpSender.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/BufferHttpSender.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/FilesystemHttpSender.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpFileSender.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpFileSender.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpFileSender.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpHandler.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpOutput.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpOutput.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/HttpOutput.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/HttpOutput.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/HttpServer/MongooseServer.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/MongooseServer.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -49,6 +49,9 @@ #include "HttpOutput.h" #include "mongoose.h" +#if ORTHANC_SSL_ENABLED == 1 +#include <openssl/opensslv.h> +#endif #define ORTHANC_REALM "Orthanc Secure Area" @@ -751,6 +754,16 @@ ssl_ = false; port_ = 8000; filter_ = NULL; + +#if ORTHANC_SSL_ENABLED == 1 + // Check for the Heartbleed exploit + // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug + if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && + OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) + { + LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; + } +#endif }
--- a/Core/HttpServer/MongooseServer.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/HttpServer/MongooseServer.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/ICommand.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/ICommand.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/IDynamicObject.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/IDynamicObject.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaContext.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Lua/LuaContext.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaContext.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Lua/LuaContext.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaException.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Lua/LuaException.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaFunctionCall.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Lua/LuaFunctionCall.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Lua/LuaFunctionCall.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Lua/LuaFunctionCall.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/ArrayFilledByThreads.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/ArrayFilledByThreads.h Wed Apr 16 16:34:09 2014 +0200 @@ -2,7 +2,7 @@ #include <boost/thread.hpp> -#include "../ICommand.h" +#include "../IDynamicObject.h" namespace Orthanc {
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/IRunnableBySteps.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/IRunnableBySteps.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/SharedMessageQueue.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -89,7 +89,11 @@ std::auto_ptr<IDynamicObject> message(queue_.front()); queue_.pop_front(); - emptied_.notify_all(); + + if (queue_.empty()) + { + emptied_.notify_all(); + } return message.release(); } @@ -101,7 +105,7 @@ boost::mutex::scoped_lock lock(mutex_); // Wait for the queue to become empty - if (!queue_.empty()) + while (!queue_.empty()) { if (millisecondsTimeout == 0) {
--- a/Core/MultiThreading/SharedMessageQueue.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/SharedMessageQueue.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/MultiThreading/ThreadedCommandProcessor.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -158,6 +158,11 @@ void ThreadedCommandProcessor::Post(ICommand* command) { + if (command == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + boost::mutex::scoped_lock lock(mutex_); queue_.Enqueue(command); remainingCommands_++;
--- a/Core/MultiThreading/ThreadedCommandProcessor.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/MultiThreading/ThreadedCommandProcessor.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/OrthancException.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/OrthancException.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -105,6 +105,12 @@ case ErrorCode_NetworkProtocol: return "Error in the network protocol"; + case ErrorCode_CorruptedFile: + return "Corrupted file (inconsistent MD5 hash)"; + + case ErrorCode_InexistentTag: + return "Inexistent tag"; + case ErrorCode_Custom: default: return "???";
--- a/Core/OrthancException.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/OrthancException.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApi.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApi.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApi.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApi.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiOutput.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApiOutput.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiOutput.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApiOutput.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApiPath.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/RestApi/RestApiPath.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/RestApi/RestApiPath.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/SQLite/Connection.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Connection.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Connection.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Connection.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/FunctionContext.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/FunctionContext.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,12 @@ CheckIndex(index); return std::string(reinterpret_cast<const char*>(sqlite3_value_text(argv_[index]))); } + + bool FunctionContext::IsNullValue(unsigned int index) const + { + CheckIndex(index); + return sqlite3_value_type(argv_[index]) == SQLITE_NULL; + } void FunctionContext::SetNullResult() {
--- a/Core/SQLite/FunctionContext.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/FunctionContext.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without @@ -74,6 +74,8 @@ double GetDoubleValue(unsigned int index) const; std::string GetStringValue(unsigned int index) const; + + bool IsNullValue(unsigned int index) const; void SetNullResult();
--- a/Core/SQLite/IScalarFunction.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/IScalarFunction.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Redistribution and use in source and binary forms, with or without
--- a/Core/SQLite/Statement.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Statement.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -295,7 +295,7 @@ return true; }*/ - bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const + /*bool Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const { val->clear(); @@ -306,14 +306,14 @@ memcpy(&(*val)[0], data, len); } return true; - } + }*/ - bool Statement::ColumnBlobAsVector( + /*bool Statement::ColumnBlobAsVector( int col, std::vector<unsigned char>* val) const { return ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val)); - } + }*/ } }
--- a/Core/SQLite/Statement.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Statement.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved. @@ -89,10 +89,6 @@ return reference_.GetWrappedObject(); } - // Resets the statement to its initial condition. This includes any current - // result row, and also the bound variables if the |clear_bound_vars| is true. - void Reset(bool clear_bound_vars = true); - public: Statement(Connection& database, const std::string& sql); @@ -166,9 +162,12 @@ const void* ColumnBlob(int col) const; bool ColumnBlobAsString(int col, std::string* blob); //bool ColumnBlobAsString16(int col, string16* val) const; - bool ColumnBlobAsVector(int col, std::vector<char>* val) const; - bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + //bool ColumnBlobAsVector(int col, std::vector<char>* val) const; + //bool ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + // Resets the statement to its initial condition. This includes any current + // result row, and also the bound variables if the |clear_bound_vars| is true. + void Reset(bool clear_bound_vars = true); }; } }
--- a/Core/SQLite/StatementId.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/StatementId.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementId.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/StatementId.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/StatementReference.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/StatementReference.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/StatementReference.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Transaction.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/SQLite/Transaction.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/SQLite/Transaction.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Copyright (c) 2012 The Chromium Authors. All rights reserved.
--- a/Core/Toolbox.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Toolbox.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -157,17 +157,6 @@ } #endif - void Toolbox::Sleep(uint32_t seconds) - { -#if defined(_WIN32) - ::Sleep(static_cast<DWORD>(seconds) * static_cast<DWORD>(1000)); -#elif defined(__linux) - usleep(static_cast<uint64_t>(seconds) * static_cast<uint64_t>(1000000)); -#else -#error Support your platform here -#endif - } - void Toolbox::USleep(uint64_t microSeconds) { #if defined(_WIN32) @@ -187,6 +176,7 @@ #else signal(SIGINT, SignalHandler); signal(SIGQUIT, SignalHandler); + signal(SIGTERM, SignalHandler); #endif finish = false; @@ -200,6 +190,7 @@ #else signal(SIGINT, NULL); signal(SIGQUIT, NULL); + signal(SIGTERM, NULL); #endif } @@ -217,6 +208,20 @@ } + void Toolbox::ToUpperCase(std::string& result, + const std::string& source) + { + result = source; + ToUpperCase(result); + } + + void Toolbox::ToLowerCase(std::string& result, + const std::string& source) + { + result = source; + ToLowerCase(result); + } + void Toolbox::ReadFile(std::string& content, const std::string& path) @@ -449,13 +454,29 @@ void Toolbox::ComputeMD5(std::string& result, const std::string& data) { + if (data.size() > 0) + { + ComputeMD5(result, &data[0], data.size()); + } + else + { + ComputeMD5(result, NULL, 0); + } + } + + + void Toolbox::ComputeMD5(std::string& result, + const void* data, + size_t length) + { md5_state_s state; md5_init(&state); - if (data.size() > 0) + if (length > 0) { - md5_append(&state, reinterpret_cast<const md5_byte_t*>(&data[0]), - static_cast<int>(data.size())); + md5_append(&state, + reinterpret_cast<const md5_byte_t*>(data), + static_cast<int>(length)); } md5_byte_t actualHash[16];
--- a/Core/Toolbox.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Toolbox.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -50,9 +50,15 @@ { void ServerBarrier(); - void ToUpperCase(std::string& s); + void ToUpperCase(std::string& s); // Inplace version + + void ToLowerCase(std::string& s); // Inplace version - void ToLowerCase(std::string& s); + void ToUpperCase(std::string& result, + const std::string& source); + + void ToLowerCase(std::string& result, + const std::string& source); void ReadFile(std::string& content, const std::string& path); @@ -60,8 +66,6 @@ void WriteFile(const std::string& content, const std::string& path); - void Sleep(uint32_t seconds); - void USleep(uint64_t microSeconds); void RemoveFile(const std::string& path); @@ -82,6 +86,10 @@ void ComputeMD5(std::string& result, const std::string& data); + void ComputeMD5(std::string& result, + const void* data, + size_t length); + void ComputeSHA1(std::string& result, const std::string& data);
--- a/Core/Uuid.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Uuid.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/Core/Uuid.h Fri Jan 24 17:40:45 2014 +0100 +++ b/Core/Uuid.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/LinuxCompilation.txt Fri Jan 24 17:40:45 2014 +0100 +++ b/LinuxCompilation.txt Wed Apr 16 16:34:09 2014 +0200 @@ -89,7 +89,6 @@ -DUSE_SYSTEM_GOOGLE_LOG=OFF \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DDCMTK_DICTIONARY_DIR:PATH=/usr/share/dcmtk \ ~/Orthanc @@ -105,7 +104,6 @@ # cmake -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DDCMTK_DICTIONARY_DIR:PATH=/usr/share/libdcmtk2 \ ~/Orthanc Note: Have also a look at the official package: @@ -146,9 +144,25 @@ ~/Orthanc +SUPPORTED - Ubuntu 13.10 +------------------------ -SUPPORTED - Fedora 18 ---------------------- +# sudo apt-get install build-essential unzip cmake mercurial \ + uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ + libgoogle-glog-dev libgtest-dev libpng-dev \ + libsqlite3-dev libssl-dev zlib1g-dev \ + libdcmtk2-dev libboost-all-dev libwrap0-dev libjsoncpp-dev + +# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + ~/Orthanc + + + +SUPPORTED - Fedora 18/19/20 +--------------------------- # sudo yum install make automake gcc gcc-c++ python cmake \ boost-devel curl-devel dcmtk-devel glog-devel \
--- a/NEWS Fri Jan 24 17:40:45 2014 +0100 +++ b/NEWS Wed Apr 16 16:34:09 2014 +0200 @@ -3,6 +3,39 @@ +Version 0.7.4 (2014/04/16) +========================== + +* Switch to openssl-1.0.1g in static builds (cf. Heartbleed exploit) +* Switch to boost 1.55.0 in static builds (to solve compiling errors) +* Better logging about nonexistent tags +* Dcm4Chee manufacturer +* Automatic discovering of the path to the DICOM dictionaries +* In the "DicomModalities" config, the port number can be a string + + +Version 0.7.3 (2014/02/14) +========================== + +Major changes +------------- + +* Fixes in the implementation of the C-FIND handler for Query/Retrieve +* Custom attachment of files to patients, studies, series or instances +* Access to lowlevel info about the attached files through the REST API +* Recover pixel data for more transfer syntaxes (notably JPEG) + +Minor changes +------------- + +* AET comparison is now case-insensitive by default +* Possibility to disable the HTTP server or the DICOM server +* Automatic computation of MD5 hashes for the stored DICOM files +* Maintenance tool to recover DICOM files compressed by Orthanc +* The newline characters in the configuration file are fixed for Linux +* Capture of the SIGTERM signal in Linux + + Version 0.7.2 (2013/11/08) ==========================
--- a/OrthancCppClient/Instance.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Instance.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -265,4 +265,21 @@ } } + + void Instance::LoadTagContent(const char* path) + { + Orthanc::HttpClient client(connection_.GetHttpClient()); + client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/content/" + path); + + if (!client.Apply(content_)) + { + throw OrthancClientException(Orthanc::ErrorCode_UnknownResource); + } + } + + + const char* Instance::GetLoadedTagContent() const + { + return content_.c_str(); + } }
--- a/OrthancCppClient/Instance.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Instance.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -57,6 +57,7 @@ std::auto_ptr<Orthanc::PngReader> reader_; Orthanc::ImageExtractionMode mode_; std::auto_ptr<std::string> dicom_; + std::string content_; void DownloadImage(); @@ -185,5 +186,17 @@ LAAW_API_INTERNAL void SplitVectorOfFloats(std::vector<float>& target, const char* tag); + + /** + * {summary}{Load a raw tag from the DICOM file.} + * {param}{path The path to the tag of interest (e.g. "0020-000d").} + **/ + void LoadTagContent(const char* path); + + /** + * {summary}{Return the value of the raw tag that was loaded by LoadContent.} + * {returns}{The tag value.} + **/ + const char* GetLoadedTagContent() const; }; }
--- a/OrthancCppClient/OrthancClientException.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/OrthancClientException.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,7 +33,7 @@ #pragma once #include "../Core/OrthancException.h" -#include "SharedLibrary/Laaw/laaw.h" +#include <laaw/laaw.h> namespace OrthancClient {
--- a/OrthancCppClient/OrthancConnection.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/OrthancConnection.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/OrthancConnection.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/OrthancConnection.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Patient.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Patient.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Patient.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Patient.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Series.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Series.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Series.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Series.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -30,7 +30,7 @@ **/ -#include <laaw.h> +#include <laaw/laaw.h> #include <string.h> // For strcpy() and strlen() #include <stdlib.h> // For free() @@ -1412,6 +1412,52 @@ } } + LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd(void* thisObject, const char* arg0) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); +this_->LoadTagContent(reinterpret_cast< const char* >(arg0)); + + 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_bb55aaf772ddceaadee36f4e54136bcb(const void* thisObject, const char** result) + { + try + { + #ifdef LAAW_EXTERNC_START_FUNCTION + LAAW_EXTERNC_START_FUNCTION; + #endif + + const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); +*result = this_->GetLoadedTagContent(); + + return NULL; + } + catch (::Laaw::LaawException& e) + { + return LAAW_EXTERNC_CopyString(e.What()); + } + catch (...) + { + return LAAW_EXTERNC_CopyString("..."); + } + } + LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetDescription() { @@ -1430,7 +1476,7 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright() { - return "(c) 2012-2013, Sebastien Jodogne, CHU of Liege"; + return "(c) 2012-2014, Sebastien Jodogne, CHU of Liege"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion() @@ -1440,12 +1486,12 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() { - return "0.7.0.2"; + return "0.7.0.4"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() { - return "0.7.2"; + return "0.7.4"; } LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str)
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Apr 16 16:34:09 2014 +0200 @@ -203,7 +203,7 @@ { private: LAAW_ORTHANC_CLIENT_HANDLE_TYPE handle_; - LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[60 + 1]; + LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[62 + 1]; @@ -239,7 +239,7 @@ void FreeString(char* str) { typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*); - Function function = (Function) GetFunction(60); + Function function = (Function) GetFunction(62); function(str); } @@ -391,7 +391,7 @@ throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version"); } - functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); + functionsIndex_[62] = 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"); @@ -450,11 +450,13 @@ 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"); /* Check whether the functions were properly loaded */ - for (unsigned int i = 0; i <= 60; i++) + for (unsigned int i = 0; i <= 62; i++) { if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL) { @@ -784,6 +786,8 @@ inline const void* GetDicom(); inline void DiscardImage(); inline void DiscardDicom(); + inline void LoadTagContent(const ::std::string& path); + inline ::std::string GetLoadedTagContent() const; }; } @@ -1746,5 +1750,35 @@ char* error = function(pimpl_); ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); } + /** + * @brief Load a raw tag from the DICOM file. + * + * Load a raw tag from the DICOM file. + * + * @param path The path to the tag of interest (e.g. "0020-000d"). + **/ + 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); + char* error = function(pimpl_, path.c_str()); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + } + /** + * @brief Return the value of the raw tag that was loaded by LoadContent. + * + * Return the value of the raw tag that was loaded by LoadContent. + * + * @return The tag value. + **/ + inline ::std::string Instance::GetLoadedTagContent() const + { + const char* result_; + typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); + Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61); + char* error = function(pimpl_, &result_); + ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); + return std::string(result_); + } }
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Wed Apr 16 16:34:09 2014 +0200 @@ -58,6 +58,8 @@ _LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e@8 = LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e@8 _LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a@4 = LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a@4 _LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c@4 = LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c@4 + _LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd@8 = LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd@8 + _LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb@8 = LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb@8 _LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d@12 = LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d@12 _LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207@4 = LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207@4 _LAAW_EXTERNC_GetDescription@0 = LAAW_EXTERNC_GetDescription@0
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Apr 16 16:34:09 2014 +0200 @@ -1,7 +1,7 @@ #include <winver.h> VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,2 + FILEVERSION 0,7,0,4 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,12 +10,12 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.2" + VALUE "Comments", "Release 0.7.4" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.2" + VALUE "FileVersion", "0.7.0.4" VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2013, Sebastien Jodogne, CHU of Liege" + VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" VALUE "OriginalFilename", "OrthancClient_Windows32.dll" VALUE "ProductName", "OrthancClient"
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Wed Apr 16 16:34:09 2014 +0200 @@ -58,6 +58,8 @@ LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c + LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd + LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207 LAAW_EXTERNC_GetDescription
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Apr 16 16:34:09 2014 +0200 @@ -1,7 +1,7 @@ #include <winver.h> VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,0,2 + FILEVERSION 0,7,0,4 PRODUCTVERSION 0,7,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,12 +10,12 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.7.2" + VALUE "Comments", "Release 0.7.4" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.7.0.2" + VALUE "FileVersion", "0.7.0.4" VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2013, Sebastien Jodogne, CHU of Liege" + VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, CHU of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" VALUE "OriginalFilename", "OrthancClient_Windows64.dll" VALUE "ProductName", "OrthancClient"
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw-exports.h Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Laaw - Lightweight, Automated API Wrapper - * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, - * Sebastien Jodogne - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -/******************************************************************** - ** Windows target - ********************************************************************/ - -#if defined _WIN32 - -#include <windows.h> - -#if defined(__GNUC__) -// This is Mingw -#define LAAW_EXPORT_DLL_API // The exports are handled by the .DEF file -#else -// This is MSVC -#define LAAW_EXPORT_DLL_API __declspec(dllexport) -#endif - -#ifdef _M_X64 -// 64 bits target -#define LAAW_CALL_CONVENTION -#else -// 32 bits target -#define LAAW_CALL_CONVENTION __stdcall // Use the StdCall in Windows32 (for VB6) -#endif - - -/******************************************************************** - ** Linux target - ********************************************************************/ - -#elif defined(__linux) - -// Try the gcc visibility support -// http://gcc.gnu.org/wiki/Visibility -#if ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) -#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) -#define LAAW_CALL_CONVENTION -#else -#error No support for visibility in your version of GCC -#endif - - -/******************************************************************** - ** Max OS X target - ********************************************************************/ - -#else - -#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) -#define LAAW_CALL_CONVENTION - -#endif
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw.h Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Laaw - Lightweight, Automated API Wrapper - * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, - * Sebastien Jodogne - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "laaw-exports.h" -#include <stddef.h> -#include <string> - -#if (LAAW_PARSING == 1) - -#define LAAW_API __attribute__((deprecated(""))) -#define LAAW_API_INTERNAL __attribute__((deprecated(""))) -#define LAAW_API_OVERLOAD(name) __attribute__((deprecated(""))) -#define LAAW_API_PROPERTY __attribute__((deprecated(""))) -#define LAAW_API_STATIC_CLASS __attribute__((deprecated(""))) -#define LAAW_API_CUSTOM(name, value) __attribute__((deprecated(""))) - -#else - -#define LAAW_API -#define LAAW_API_INTERNAL -#define LAAW_API_OVERLOAD(name) -#define LAAW_API_PROPERTY -#define LAAW_API_STATIC_CLASS -#define LAAW_API_CUSTOM(name, value) - -#endif - - -namespace Laaw -{ - /** - * This is the base class from which all the public exceptions in - * the SDK should derive. - **/ - class LaawException - { - private: - std::string what_; - - public: - LaawException() - { - } - - LaawException(const std::string& what) : what_(what) - { - } - - LaawException(const char* what) : what_(what) - { - } - - virtual const char* What() const - { - return what_.c_str(); - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Laaw/laaw/laaw-exports.h Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,85 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +/******************************************************************** + ** Windows target + ********************************************************************/ + +#if defined _WIN32 + +#include <windows.h> + +#if defined(__GNUC__) +// This is Mingw +#define LAAW_EXPORT_DLL_API // The exports are handled by the .DEF file +#else +// This is MSVC +#define LAAW_EXPORT_DLL_API __declspec(dllexport) +#endif + +#ifdef _M_X64 +// 64 bits target +#define LAAW_CALL_CONVENTION +#else +// 32 bits target +#define LAAW_CALL_CONVENTION __stdcall // Use the StdCall in Windows32 (for VB6) +#endif + + +/******************************************************************** + ** Linux target + ********************************************************************/ + +#elif defined(__linux) + +// Try the gcc visibility support +// http://gcc.gnu.org/wiki/Visibility +#if ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) +#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) +#define LAAW_CALL_CONVENTION +#else +#error No support for visibility in your version of GCC +#endif + + +/******************************************************************** + ** Max OS X target + ********************************************************************/ + +#else + +#define LAAW_EXPORT_DLL_API __attribute__ ((visibility("default"))) +#define LAAW_CALL_CONVENTION + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancCppClient/SharedLibrary/Laaw/laaw/laaw.h Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,89 @@ +/** + * Laaw - Lightweight, Automated API Wrapper + * Copyright (C) 2010-2013 Jomago - Alain Mazy, Benjamin Golinvaux, + * Sebastien Jodogne + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "laaw-exports.h" +#include <stddef.h> +#include <string> + +#if (LAAW_PARSING == 1) + +#define LAAW_API __attribute__((deprecated(""))) +#define LAAW_API_INTERNAL __attribute__((deprecated(""))) +#define LAAW_API_OVERLOAD(name) __attribute__((deprecated(""))) +#define LAAW_API_PROPERTY __attribute__((deprecated(""))) +#define LAAW_API_STATIC_CLASS __attribute__((deprecated(""))) +#define LAAW_API_CUSTOM(name, value) __attribute__((deprecated(""))) + +#else + +#define LAAW_API +#define LAAW_API_INTERNAL +#define LAAW_API_OVERLOAD(name) +#define LAAW_API_PROPERTY +#define LAAW_API_STATIC_CLASS +#define LAAW_API_CUSTOM(name, value) + +#endif + + +namespace Laaw +{ + /** + * This is the base class from which all the public exceptions in + * the SDK should derive. + **/ + class LaawException + { + private: + std::string what_; + + public: + LaawException() + { + } + + LaawException(const std::string& what) : what_(what) + { + } + + LaawException(const char* what) : what_(what) + { + } + + virtual const char* What() const + { + return what_.c_str(); + } + }; +}
--- a/OrthancCppClient/SharedLibrary/Product.json Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/Product.json Wed Apr 16 16:34:09 2014 +0200 @@ -2,7 +2,7 @@ "Product" : "OrthancClient", "Description" : "Native client to the REST API of Orthanc", "Company" : "CHU of Liege", - "Copyright" : "(c) 2012-2013, Sebastien Jodogne, CHU of Liege", + "Copyright" : "(c) 2012-2014, Sebastien Jodogne, CHU of Liege", "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/", - "Version" : "0.7.2" + "Version" : "0.7.4" }
--- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Study.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Study.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancCppClient/Study.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancCppClient/Study.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancExplorer/libs/jquery.mobile.simpledialog2.js Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancExplorer/libs/jquery.mobile.simpledialog2.js Wed Apr 16 16:34:09 2014 +0200 @@ -3,8 +3,6 @@ * Copyright (c) JTSage * CC 3.0 Attribution. May be relicensed without permission/notifcation. * https://github.com/jtsage/jquery-mobile-simpledialog - * - * Modifications by Sebastien Jodogne */ (function($, undefined ) {
--- a/OrthancServer/DatabaseWrapper.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -62,16 +62,30 @@ virtual unsigned int GetCardinality() const { - return 5; + return 7; } virtual void Compute(SQLite::FunctionContext& context) { + std::string uncompressedMD5, compressedMD5; + + if (!context.IsNullValue(5)) + { + uncompressedMD5 = context.GetStringValue(5); + } + + if (!context.IsNullValue(6)) + { + compressedMD5 = context.GetStringValue(6); + } + FileInfo info(context.GetStringValue(0), static_cast<FileContentType>(context.GetIntValue(1)), static_cast<uint64_t>(context.GetInt64Value(2)), + uncompressedMD5, static_cast<CompressionType>(context.GetIntValue(3)), - static_cast<uint64_t>(context.GetInt64Value(4))); + static_cast<uint64_t>(context.GetInt64Value(4)), + compressedMD5); listener_.SignalFileDeleted(info); } @@ -374,7 +388,7 @@ } } - bool DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target, + void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target, int64_t id) { target.clear(); @@ -386,8 +400,6 @@ { target.push_back(static_cast<MetadataType>(s.ColumnInt(0))); } - - return true; } @@ -433,16 +445,30 @@ void DatabaseWrapper::AddAttachment(int64_t id, const FileInfo& attachment) { - SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?)"); + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); s.BindInt64(0, id); s.BindInt(1, attachment.GetContentType()); s.BindString(2, attachment.GetUuid()); s.BindInt64(3, attachment.GetCompressedSize()); s.BindInt64(4, attachment.GetUncompressedSize()); s.BindInt(5, attachment.GetCompressionType()); + s.BindString(6, attachment.GetUncompressedMD5()); + s.BindString(7, attachment.GetCompressedMD5()); s.Run(); } + + void DatabaseWrapper::DeleteAttachment(int64_t id, + FileContentType attachment) + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?"); + s.BindInt64(0, id); + s.BindInt(1, attachment); + s.Run(); + } + + + void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& result, int64_t id) { @@ -463,7 +489,7 @@ FileContentType contentType) { SQLite::Statement s(db_, SQLITE_FROM_HERE, - "SELECT uuid, uncompressedSize, compressionType, compressedSize FROM AttachedFiles WHERE id=? AND fileType=?"); + "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?"); s.BindInt64(0, id); s.BindInt(1, contentType); @@ -475,9 +501,11 @@ { attachment = FileInfo(s.ColumnString(0), contentType, - s.ColumnInt(1), + s.ColumnInt64(1), + s.ColumnString(4), static_cast<CompressionType>(s.ColumnInt(2)), - s.ColumnInt(3)); + s.ColumnInt64(3), + s.ColumnString(5)); return true; } } @@ -559,7 +587,7 @@ while (s.Step()) { - result.push_back(s.ColumnInt(0)); + result.push_back(s.ColumnInt64(0)); } } @@ -588,9 +616,9 @@ while (changes.size() < maxResults && s.Step()) { - int64_t seq = s.ColumnInt(0); + int64_t seq = s.ColumnInt64(0); ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1)); - int64_t internalId = s.ColumnInt(2); + int64_t internalId = s.ColumnInt64(2); ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3)); const std::string& date = s.ColumnString(4); std::string publicId = GetPublicId(internalId); @@ -656,17 +684,17 @@ } - void DatabaseWrapper::GetExportedResources(Json::Value& target, - SQLite::Statement& s, - int64_t since, - unsigned int maxResults) + void DatabaseWrapper::GetExportedResourcesInternal(Json::Value& target, + SQLite::Statement& s, + int64_t since, + unsigned int maxResults) { Json::Value changes = Json::arrayValue; int64_t last = since; while (changes.size() < maxResults && s.Step()) { - int64_t seq = s.ColumnInt(0); + int64_t seq = s.ColumnInt64(0); ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1)); std::string publicId = s.ColumnString(2); @@ -718,7 +746,7 @@ "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?"); s.BindInt64(0, since); s.BindInt(1, maxResults + 1); - GetExportedResources(target, s, since, maxResults); + GetExportedResourcesInternal(target, s, since, maxResults); } @@ -726,7 +754,7 @@ { SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1"); - GetExportedResources(target, s, 0, 1); + GetExportedResourcesInternal(target, s, 0, 1); } @@ -820,9 +848,24 @@ LOG(INFO) << "Version of the Orthanc database: " << version; unsigned int v = boost::lexical_cast<unsigned int>(version); - // This version of Orthanc is only compatible with version 3 of - // the DB schema (since Orthanc 0.3.2) - ok = (v == 3); + /** + * History of the database versions: + * - Version 3: from Orthanc 0.3.2 to Orthanc 0.7.2 (inclusive) + * - Version 4: from Orthanc 0.7.3 (inclusive) + **/ + + // This version of Orthanc is only compatible with versions 3 of 4 of the DB schema + ok = (v == 3 || v == 4); + + if (v == 3) + { + LOG(WARNING) << "Upgrading database version from 3 to 4"; + std::string upgrade; + EmbeddedResources::GetFileResource(upgrade, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); + db_.BeginTransaction(); + db_.Execute(upgrade); + db_.CommitTransaction(); + } } catch (boost::bad_lexical_cast&) { @@ -830,6 +873,7 @@ if (!ok) { + LOG(ERROR) << "Incompatible version of the Orthanc database: " << version; throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); }
--- a/OrthancServer/DatabaseWrapper.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DatabaseWrapper.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -67,10 +67,10 @@ int64_t since, unsigned int maxResults); - void GetExportedResources(Json::Value& target, - SQLite::Statement& s, - int64_t since, - unsigned int maxResults); + void GetExportedResourcesInternal(Json::Value& target, + SQLite::Statement& s, + int64_t since, + unsigned int maxResults); public: void SetGlobalProperty(GlobalProperty property, @@ -115,7 +115,7 @@ int64_t id, MetadataType type); - bool ListAvailableMetadata(std::list<MetadataType>& target, + void ListAvailableMetadata(std::list<MetadataType>& target, int64_t id); std::string GetMetadata(int64_t id, @@ -129,6 +129,9 @@ void AddAttachment(int64_t id, const FileInfo& attachment); + void DeleteAttachment(int64_t id, + FileContentType attachment); + void ListAvailableAttachments(std::list<FileContentType>& result, int64_t id);
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -36,6 +36,7 @@ #include "../../Core/Toolbox.h" #include "../../Core/Uuid.h" #include "../Internals/CommandDispatcher.h" +#include "../OrthancInitialization.h" #include "EmbeddedResources.h" #include <boost/thread.hpp> @@ -94,7 +95,7 @@ #endif - void DicomServer::ServerThread(DicomServer* server) + void DicomServer::InitializeDictionary() { /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ dcmDisableGethostbyaddr.set(OFTrue); @@ -148,7 +149,11 @@ throw OrthancException(ErrorCode_InternalError); } } + } + + void DicomServer::ServerThread(DicomServer* server) + { /* initialize network, i.e. create an instance of T_ASC_Network*. */ T_ASC_Network *net; OFCondition cond = ASC_initializeNetwork @@ -400,4 +405,16 @@ bagOfDispatchers_.StopAll(); } + + bool DicomServer::IsMyAETitle(const std::string& aet) const + { + if (!HasCalledApplicationEntityTitleCheck()) + { + // OK, no check on the AET. + return true; + } + + return Orthanc::IsSameAETitle(aet, GetApplicationEntityTitle()); + } + }
--- a/OrthancServer/DicomProtocol/DicomServer.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomServer.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -66,6 +66,8 @@ static void ServerThread(DicomServer* server); public: + static void InitializeDictionary(); + DicomServer(); ~DicomServer(); @@ -104,6 +106,8 @@ void Start(); void Stop(); + + bool IsMyAETitle(const std::string& aet) const; }; }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -335,7 +335,8 @@ break; case FindRootModel_Instance: - if (manufacturer_ == ModalityManufacturer_ClearCanvas) + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) { // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J @@ -419,6 +420,7 @@ s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_MODALITIES_IN_STUDY); Find(result, FindRootModel_Study, s); }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/FromDcmtkBridge.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -130,6 +130,11 @@ #include <glog/logging.h> #include <dcmtk/dcmdata/dcostrmb.h> + +static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; + + + namespace Orthanc { void ParsedDicomFile::Setup(const char* buffer, size_t size) @@ -214,36 +219,18 @@ } - static void AnswerPixelData(RestApiOutput& output, - DcmPixelData& pixelData, - E_TransferSyntax transferSyntax) + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) { DcmPixelSequence* pixelSequence = NULL; if (pixelData.getEncapsulatedRepresentation (transferSyntax, NULL, pixelSequence).good() && pixelSequence) { - for (unsigned long i = 0; i < pixelSequence->card(); i++) - { - DcmPixelItem* pixelItem = NULL; - if (pixelSequence->getItem(pixelItem, i).good() && pixelItem) - { - Uint8* b = NULL; - if (pixelItem->getUint8Array(b).good() && b) - { - // JPEG-LS (lossless) - // http://gdcm.sourceforge.net/wiki/index.php/Tools/ffmpeg#JPEG_LS - // http://www.stat.columbia.edu/~jakulin/jpeg-ls/ - // http://itohws03.ee.noda.sut.ac.jp/~matsuda/mrp/ - - printf("ITEM: %d %d\n", transferSyntax, pixelItem->getLength()); - char buf[64]; - sprintf(buf, "/tmp/toto-%06ld.jpg", i); - FILE* fp = fopen(buf, "wb"); - fwrite(b, pixelItem->getLength(), 1, fp); - fclose(fp); - } - } - } + return pixelSequence->card(); + } + else + { + return 1; } } @@ -252,29 +239,13 @@ DcmElement& element, E_TransferSyntax transferSyntax) { - // This element is not a sequence. Test if it is pixel data. - if (element.getTag().getGTag() == DICOM_TAG_PIXEL_DATA.GetGroup() && - element.getTag().getETag() == DICOM_TAG_PIXEL_DATA.GetElement()) - { - try - { - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(element); - AnswerPixelData(output, pixelData, transferSyntax); - //return; - } - catch (std::bad_cast&) - { - } - } - - // This element is nor a sequence, neither a pixel-data std::string buffer; buffer.resize(65536); Uint32 length = element.getLength(transferSyntax); Uint32 offset = 0; - output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); while (offset < length) { @@ -305,6 +276,92 @@ output.MarkLowLevelOutputDone(); } + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are presents in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + AnswerDicomField(output, *element, transferSyntax); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + static void SendPathValueForLeaf(RestApiOutput& output, const std::string& tag, DcmItem& dicom, @@ -336,6 +393,22 @@ const UriComponents& uri) { DcmItem* dicom = file_->getDataset(); + E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } // Go down in the tag hierarchy according to the URI for (size_t pos = 0; pos < uri.size() / 2; pos++) @@ -369,7 +442,7 @@ } else { - SendPathValueForLeaf(output, uri.back(), *dicom, file_->getDataset()->getOriginalXfer()); + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); } } @@ -760,7 +833,7 @@ std::string serialized; if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) { - output.AnswerBuffer(serialized, "application/octet-stream"); + output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); } }
--- a/OrthancServer/FromDcmtkBridge.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/IServerIndexListener.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/IServerIndexListener.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/CommandDispatcher.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -385,12 +385,8 @@ callingIP = std::string(/*OFSTRING_GUARD*/(callingIP_C)); callingTitle = std::string(/*OFSTRING_GUARD*/(callingTitle_C)); std::string calledTitle(/*OFSTRING_GUARD*/(calledTitle_C)); - Toolbox::ToUpperCase(callingIP); - Toolbox::ToUpperCase(callingTitle); - Toolbox::ToUpperCase(calledTitle); - if (server.HasCalledApplicationEntityTitleCheck() && - calledTitle != server.GetApplicationEntityTitle()) + if (!server.IsMyAETitle(calledTitle)) { T_ASC_RejectParameters rej = {
--- a/OrthancServer/Internals/CommandDispatcher.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/CommandDispatcher.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/FindScp.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/FindScp.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -95,7 +95,7 @@ *responseIdentifiers = NULL; return; } - + if (responseCount <= static_cast<int>(data.answers_.GetSize())) { response->DimseStatus = STATUS_Pending;
--- a/OrthancServer/Internals/FindScp.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/FindScp.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/MoveScp.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/MoveScp.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -82,6 +82,7 @@ try { data.iterator_.reset(data.handler_->Handle(data.target_, data.input_)); + if (data.iterator_.get() == NULL) { // Internal error!
--- a/OrthancServer/Internals/MoveScp.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/MoveScp.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/Internals/StoreScp.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/StoreScp.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -33,6 +33,7 @@ #include "StoreScp.h" #include "../FromDcmtkBridge.h" +#include "../ServerToolbox.h" #include "../ToDcmtkBridge.h" #include "../../Core/OrthancException.h" @@ -57,7 +58,7 @@ uint32_t messageID; }; - + static void storeScpCallback( void *callbackData, @@ -155,7 +156,15 @@ catch (OrthancException& e) { rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + LogMissingRequiredTag(summary); + } + else + { + LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + } } } }
--- a/OrthancServer/Internals/StoreScp.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/Internals/StoreScp.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/OrthancFindRequestHandler.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -48,20 +48,14 @@ constraint.find('?') != std::string::npos); } - static std::string ToLowerCase(const std::string& s) - { - std::string result = s; - Toolbox::ToLowerCase(result); - return result; - } - static bool ApplyRangeConstraint(const std::string& value, const std::string& constraint) { size_t separator = constraint.find('-'); - std::string lower = ToLowerCase(constraint.substr(0, separator)); - std::string upper = ToLowerCase(constraint.substr(separator + 1)); - std::string v = ToLowerCase(value); + std::string lower, upper, v; + Toolbox::ToLowerCase(lower, constraint.substr(0, separator)); + Toolbox::ToLowerCase(upper, constraint.substr(separator + 1)); + Toolbox::ToLowerCase(v, value); if (lower.size() == 0 && upper.size() == 0) { @@ -85,15 +79,17 @@ static bool ApplyListConstraint(const std::string& value, const std::string& constraint) { - std::string v1 = ToLowerCase(value); + std::string v1; + Toolbox::ToLowerCase(v1, value); std::vector<std::string> items; Toolbox::TokenizeString(items, constraint, '\\'); for (size_t i = 0; i < items.size(); i++) { - Toolbox::ToLowerCase(items[i]); - if (items[i] == v1) + std::string lower; + Toolbox::ToLowerCase(lower, items[i]); + if (lower == v1) { return true; } @@ -129,7 +125,10 @@ } else { - return ToLowerCase(value) == ToLowerCase(constraint); + std::string v, c; + Toolbox::ToLowerCase(v, value); + Toolbox::ToLowerCase(c, constraint); + return v == c; } } @@ -211,6 +210,10 @@ value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); result.SetValue(query.GetElement(i).GetTag(), value); } + else + { + result.SetValue(query.GetElement(i).GetTag(), ""); + } } } @@ -287,221 +290,152 @@ } - static bool LookupCandidateResourcesInternal(/* out */ std::list<std::string>& resources, - /* in */ ServerIndex& index, - /* in */ ResourceType level, - /* in */ const DicomMap& query, - /* in */ DicomTag tag) - { - if (query.HasTag(tag)) - { - const DicomValue& value = query.GetValue(tag); - if (!value.IsNull()) - { - std::string str = query.GetValue(tag).AsString(); - if (!IsWildcard(str)) - { - index.LookupTagValue(resources, tag, str/*, level*/); - return true; - } - } - } - - return false; - } - - - static bool LookupCandidateResourcesInternal(/* inout */ std::set<std::string>& resources, - /* in */ bool alreadyFiltered, - /* in */ ServerIndex& index, - /* in */ ResourceType level, - /* in */ const DicomMap& query, - /* in */ DicomTag tag) + namespace { - assert(alreadyFiltered || resources.size() == 0); - - if (!query.HasTag(tag)) - { - return alreadyFiltered; - } - - const DicomValue& value = query.GetValue(tag); - if (value.IsNull()) - { - return alreadyFiltered; - } - - std::string str = query.GetValue(tag).AsString(); - if (IsWildcard(str)) + class CandidateResources { - return alreadyFiltered; - } - - std::list<std::string> matches; - index.LookupTagValue(matches, tag, str/*, level*/); + private: + ServerIndex& index_; + ModalityManufacturer manufacturer_; + ResourceType level_; + bool isFilterApplied_; + std::set<std::string> filtered_; - if (alreadyFiltered) - { - std::set<std::string> previous = resources; - - for (std::list<std::string>::const_iterator - it = matches.begin(); it != matches.end(); it++) + static void ListToSet(std::set<std::string>& target, + const std::list<std::string>& source) { - if (previous.find(*it) != previous.end()) + for (std::list<std::string>::const_iterator + it = source.begin(); it != source.end(); ++it) { - resources.insert(*it); + target.insert(*it); } } - } - else - { - for (std::list<std::string>::const_iterator - it = matches.begin(); it != matches.end(); it++) - { - resources.insert(*it); - } - } - - return true; - } - - - static bool LookupCandidateResourcesAtOneLevel(/* out */ std::set<std::string>& resources, - /* in */ ServerIndex& index, - /* in */ ResourceType level, - /* in */ const DicomMap& fullQuery, - /* in */ ModalityManufacturer manufacturer) - { - DicomMap tmp; - fullQuery.ExtractMainDicomTagsForLevel(tmp, level); - DicomArray query(tmp); - - if (query.GetSize() == 0) - { - return false; - } - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag tag = query.GetElement(i).GetTag(); - const DicomValue& value = query.GetElement(i).GetValue(); - if (!value.IsNull()) + void ApplyExactFilter(const DicomTag& tag, const std::string& value) { - // TODO TODO TODO - } - } - - printf(">>>>>>>>>>\n"); - query.Print(stdout); - printf("<<<<<<<<<<\n\n"); - return true; - } + LOG(INFO) << "Applying exact filter on tag " + << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; - - static void LookupCandidateResources(/* out */ std::list<std::string>& resources, - /* in */ ServerIndex& index, - /* in */ ResourceType level, - /* in */ const DicomMap& query, - /* in */ ModalityManufacturer manufacturer) - { -#if 1 - { - std::set<std::string> s; - LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Patient, query, manufacturer); - LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Study, query, manufacturer); - LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Series, query, manufacturer); - LookupCandidateResourcesAtOneLevel(s, index, ResourceType_Instance, query, manufacturer); - } + std::list<std::string> resources; + index_.LookupTagValue(resources, tag, value, level_); - std::set<std::string> filtered; - bool isFiltered = false; - - // Filter by indexed tags, from most specific to least specific - //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_SOP_INSTANCE_UID); - isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID); - //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID); - //isFiltered = LookupCandidateResourcesInternal(filtered, isFiltered, index, level, query, DICOM_TAG_PATIENT_ID); - - resources.clear(); + if (isFilterApplied_) + { + std::set<std::string> s; + ListToSet(s, resources); - if (isFiltered) - { - for (std::set<std::string>::const_iterator - it = filtered.begin(); it != filtered.end(); it++) - { - resources.push_back(*it); - } - } - else - { - // No indexed tag matches the query. Return all the resources at this query level. - Json::Value allResources; - index.GetAllUuids(allResources, level); - assert(allResources.type() == Json::arrayValue); - - for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++) - { - resources.push_back(allResources[i].asString()); - } - } + std::set<std::string> tmp = filtered_; + filtered_.clear(); -#else - - // TODO : Speed up using full querying against the MainDicomTags. - - resources.clear(); - - bool done = false; - - switch (level) - { - case ResourceType_Patient: - done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - if (manufacturer == ModalityManufacturer_MedInria) - { - std::list<std::string> series; - - if (LookupCandidateResourcesInternal(series, index, ResourceType_Series, query, DICOM_TAG_SERIES_INSTANCE_UID) && - series.size() == 1) + for (std::set<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) { - index.GetChildInstances(resources, series.front()); - done = true; - } + if (s.find(*it) != s.end()) + { + filtered_.insert(*it); + } + } } else { - done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SOP_INSTANCE_UID); + assert(filtered_.empty()); + isFilterApplied_ = true; + ListToSet(filtered_, resources); + } + } + + public: + CandidateResources(ServerIndex& index, + ModalityManufacturer manufacturer) : + index_(index), + manufacturer_(manufacturer), + level_(ResourceType_Patient), + isFilterApplied_(false) + { + } + + ResourceType GetLevel() const + { + return level_; + } + + void GoDown() + { + assert(level_ != ResourceType_Instance); + + if (isFilterApplied_) + { + std::set<std::string> tmp = filtered_; + + filtered_.clear(); + + for (std::set<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + std::list<std::string> children; + index_.GetChildren(children, *it); + ListToSet(filtered_, children); + } } - break; + switch (level_) + { + case ResourceType_Patient: + level_ = ResourceType_Study; + break; + + case ResourceType_Study: + level_ = ResourceType_Series; + break; - default: - break; - } + case ResourceType_Series: + level_ = ResourceType_Instance; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + void Flatten(std::list<std::string>& resources) const + { + resources.clear(); - if (!done) - { - Json::Value allResources; - index.GetAllUuids(allResources, level); - assert(allResources.type() == Json::arrayValue); + if (isFilterApplied_) + { + for (std::set<std::string>::const_iterator + it = filtered_.begin(); it != filtered_.end(); ++it) + { + resources.push_back(*it); + } + } + else + { + Json::Value tmp; + index_.GetAllUuids(tmp, level_); + for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) + { + resources.push_back(tmp[i].asString()); + } + } + } - for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++) + void ApplyFilter(const DicomTag& tag, const DicomMap& query) { - resources.push_back(allResources[i].asString()); + if (query.HasTag(tag)) + { + const DicomValue& value = query.GetValue(tag); + if (!value.IsNull()) + { + std::string value = query.GetValue(tag).AsString(); + if (!IsWildcard(value)) + { + ApplyExactFilter(tag, value); + } + } + } } - } -#endif + }; } @@ -538,20 +472,26 @@ ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); - switch (manufacturer) + if (level != ResourceType_Patient && + level != ResourceType_Study && + level != ResourceType_Series && + level != ResourceType_Instance) { - case ModalityManufacturer_MedInria: - // MedInria makes FIND requests at the instance level before starting MOVE - break; + throw OrthancException(ErrorCode_NotImplemented); + } + - default: - if (level != ResourceType_Patient && - level != ResourceType_Study && - level != ResourceType_Series && - level != ResourceType_Instance) - { - throw OrthancException(ErrorCode_NotImplemented); - } + DicomArray query(input); + LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); + + for (size_t i = 0; i < query.GetSize(); i++) + { + if (!query.GetElement(i).GetValue().IsNull()) + { + LOG(INFO) << " " << query.GetElement(i).GetTag() + << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " = " << query.GetElement(i).GetValue().AsString(); + } } @@ -562,9 +502,45 @@ * for each of them. **/ + CandidateResources candidates(context_.GetIndex(), manufacturer); + + for (;;) + { + switch (candidates.GetLevel()) + { + case ResourceType_Patient: + candidates.ApplyFilter(DICOM_TAG_PATIENT_ID, input); + break; + + case ResourceType_Study: + candidates.ApplyFilter(DICOM_TAG_STUDY_INSTANCE_UID, input); + candidates.ApplyFilter(DICOM_TAG_ACCESSION_NUMBER, input); + break; + + case ResourceType_Series: + candidates.ApplyFilter(DICOM_TAG_SERIES_INSTANCE_UID, input); + break; + + case ResourceType_Instance: + candidates.ApplyFilter(DICOM_TAG_SOP_INSTANCE_UID, input); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (candidates.GetLevel() == level) + { + break; + } + + candidates.GoDown(); + } + std::list<std::string> resources; - LookupCandidateResources(resources, context_.GetIndex(), level, input, manufacturer); + candidates.Flatten(resources); + LOG(INFO) << "Number of candidate resources after exact filtering: " << resources.size(); /** * Apply filtering on modalities for studies, if asked (this is an @@ -587,9 +563,6 @@ * Loop over all the resources for this query level. **/ - DicomArray query(input); - query.Print(stdout); - for (std::list<std::string>::const_iterator resource = resources.begin(); resource != resources.end(); ++resource) { @@ -614,3 +587,15 @@ } } } + + + +/** + * TODO : Case-insensitive match for PN value representation (Patient + * Name). Case-senstive match for all the other value representations. + * + * Reference: DICOM PS 3.4 + * - C.2.2.2.1 ("Single Value Matching") + * - C.2.2.2.4 ("Wild Card Matching") + * http://medical.nema.org/Dicom/2011/11_04pu.pdf ( + **/
--- a/OrthancServer/OrthancFindRequestHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/OrthancInitialization.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancInitialization.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -35,6 +35,7 @@ #include "../Core/HttpClient.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" +#include "DicomProtocol/DicomServer.h" #include "ServerEnumerations.h" #include <boost/lexical_cast.hpp> @@ -107,7 +108,7 @@ for (size_t i = 0; i < members.size(); i++) { std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); - LOG(WARNING) << "Registering user-defined metadata: " << info; + LOG(INFO) << "Registering user-defined metadata: " << info; if (!parameter[members[i]].asBool()) { @@ -131,6 +132,40 @@ } + static void RegisterUserContentType() + { + if (configuration_->isMember("UserContentType")) + { + const Json::Value& parameter = (*configuration_) ["UserContentType"]; + + Json::Value::Members members = parameter.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString(); + LOG(INFO) << "Registering user-defined attachment type: " << info; + + if (!parameter[members[i]].asBool()) + { + LOG(ERROR) << "Not a number in this user-defined attachment type: " << info; + throw OrthancException(ErrorCode_BadParameterType); + } + + int contentType = parameter[members[i]].asInt(); + + try + { + RegisterUserContentType(contentType, members[i]); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot register this user-defined attachment type: " << info; + throw; + } + } + } + } + + void OrthancInitialize(const char* configurationFile) { boost::mutex::scoped_lock lock(globalMutex_); @@ -142,6 +177,9 @@ HttpClient::GlobalInitialize(); RegisterUserMetadata(); + RegisterUserContentType(); + + DicomServer::InitializeDictionary(); } @@ -214,7 +252,7 @@ if (!configuration_->isMember("DicomModalities")) { - throw OrthancException(""); + throw OrthancException(ErrorCode_BadFileFormat); } const Json::Value& modalities = (*configuration_) ["DicomModalities"]; @@ -222,14 +260,30 @@ !modalities.isMember(name) || (modalities[name].size() != 3 && modalities[name].size() != 4)) { - throw OrthancException(""); + throw OrthancException(ErrorCode_BadFileFormat); } try { aet = modalities[name].get(0u, "").asString(); address = modalities[name].get(1u, "").asString(); - port = modalities[name].get(2u, "").asInt(); + + const Json::Value& portValue = modalities[name].get(2u, ""); + try + { + port = portValue.asInt(); + } + catch (std::runtime_error /* error inside JsonCpp */) + { + try + { + port = boost::lexical_cast<int>(portValue.asString()); + } + catch (boost::bad_lexical_cast) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } if (modalities[name].size() == 4) { @@ -240,9 +294,11 @@ manufacturer = ModalityManufacturer_Generic; } } - catch (...) + catch (OrthancException& e) { - throw OrthancException("Badly formatted DICOM modality"); + LOG(ERROR) << "Syntax error in the definition of modality \"" << name + << "\". Please check your configuration file."; + throw e; } } @@ -257,43 +313,52 @@ if (!configuration_->isMember("OrthancPeers")) { - throw OrthancException(""); - } - - const Json::Value& modalities = (*configuration_) ["OrthancPeers"]; - if (modalities.type() != Json::objectValue || - !modalities.isMember(name)) - { - throw OrthancException(""); + throw OrthancException(ErrorCode_BadFileFormat); } try { - url = modalities[name].get(0u, "").asString(); - - if (modalities[name].size() == 1) - { - username = ""; - password = ""; - } - else if (modalities[name].size() == 3) - { - username = modalities[name].get(1u, "").asString(); - password = modalities[name].get(2u, "").asString(); - } - else + const Json::Value& modalities = (*configuration_) ["OrthancPeers"]; + if (modalities.type() != Json::objectValue || + !modalities.isMember(name)) { throw OrthancException(ErrorCode_BadFileFormat); } - } - catch (...) - { - throw OrthancException(ErrorCode_BadFileFormat); + + try + { + url = modalities[name].get(0u, "").asString(); + + if (modalities[name].size() == 1) + { + username = ""; + password = ""; + } + else if (modalities[name].size() == 3) + { + username = modalities[name].get(1u, "").asString(); + password = modalities[name].get(2u, "").asString(); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + catch (...) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (url.size() != 0 && url[url.size() - 1] != '/') + { + url += '/'; + } } - - if (url.size() != 0 && url[url.size() - 1] != '/') + catch (OrthancException& e) { - url += '/'; + LOG(ERROR) << "Syntax error in the definition of peer \"" << name + << "\". Please check your configuration file."; + throw e; } } @@ -398,7 +463,7 @@ However, for some unknown reason, some versions of Boost do not make the proper path resolution when "baseDirectory" is an absolute path. So, a hack is used below. - **/ + **/ if (relative.is_absolute()) { @@ -462,6 +527,25 @@ } + bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) + { + if (GetGlobalBoolParameter("StrictAetComparison", false)) + { + // Case-sensitive matching + return aet1 == aet2; + } + else + { + // Case-insensitive matching (default) + std::string tmp1, tmp2; + Toolbox::ToLowerCase(tmp1, aet1); + Toolbox::ToLowerCase(tmp2, aet2); + return tmp1 == tmp2; + } + } + + bool LookupDicomModalityUsingAETitle(const std::string& aet, std::string& symbolicName, std::string& address, @@ -478,8 +562,8 @@ { std::string thisAet; GetDicomModalityUsingSymbolicName(*it, thisAet, address, port, manufacturer); - - if (aet == thisAet) + + if (IsSameAETitle(aet, thisAet)) { return true; }
--- a/OrthancServer/OrthancInitialization.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancInitialization.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -93,4 +93,7 @@ const std::string& aet); bool IsKnownAETitle(const std::string& aet); + + bool IsSameAETitle(const std::string& aet1, + const std::string& aet2); }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -139,7 +139,6 @@ ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); - /** * Lookup for the resource to be sent. **/
--- a/OrthancServer/OrthancMoveRequestHandler.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/OrthancRestApi.cpp Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1927 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "OrthancRestApi.h" - -#include "../Core/Compression/HierarchicalZipWriter.h" -#include "../Core/HttpClient.h" -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/Uuid.h" -#include "DicomProtocol/DicomUserConnection.h" -#include "FromDcmtkBridge.h" -#include "OrthancInitialization.h" -#include "ServerToolbox.h" - -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <boost/lexical_cast.hpp> -#include <glog/logging.h> - -#if defined(_MSC_VER) -#define snprintf _snprintf -#endif - -static const uint64_t MEGA_BYTES = 1024 * 1024; -static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; - - -#define RETRIEVE_CONTEXT(call) \ - OrthancRestApi& contextApi = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()); \ - ServerContext& context = contextApi.GetContext() - -#define RETRIEVE_MODALITIES(call) \ - const OrthancRestApi::SetOfStrings& modalities = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()).GetModalities(); - -#define RETRIEVE_PEERS(call) \ - const OrthancRestApi::SetOfStrings& peers = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()).GetPeers(); - - - -namespace Orthanc -{ - // TODO IMPROVE MULTITHREADING - // Every call to "ParsedDicomFile" must lock this mutex!!! - static boost::mutex cacheMutex_; - - - // DICOM SCU ---------------------------------------------------------------- - - static bool MergeQueryAndTemplate(DicomMap& result, - const std::string& postData) - { - Json::Value query; - Json::Reader reader; - - if (!reader.parse(postData, query) || - query.type() != Json::objectValue) - { - return false; - } - - Json::Value::Members members = query.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - DicomTag t = FromDcmtkBridge::ParseTag(members[i]); - result.SetValue(t, query[members[i]].asString()); - } - - return true; - } - - static void DicomFindPatient(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindPatient(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindStudy(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindStudy(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindSeries(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindSeries(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindInstance(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindInstanceTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || - m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindInstance(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFind(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers patients; - connection.FindPatient(patients, m); - - // Loop over the found patients - Json::Value result = Json::arrayValue; - for (size_t i = 0; i < patients.GetSize(); i++) - { - Json::Value patient(Json::objectValue); - FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); - - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); - - DicomFindAnswers studies; - connection.FindStudy(studies, m); - - patient["Studies"] = Json::arrayValue; - - // Loop over the found studies - for (size_t j = 0; j < studies.GetSize(); j++) - { - Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); - - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); - - DicomFindAnswers series; - connection.FindSeries(series, m); - - // Loop over the found series - study["Series"] = Json::arrayValue; - for (size_t k = 0; k < series.GetSize(); k++) - { - Json::Value series2(Json::objectValue); - FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); - study["Series"].append(series2); - } - - patient["Studies"].append(study); - } - - result.append(patient); - } - - call.GetOutput().AnswerJson(result); - } - - - static bool GetInstancesToExport(std::list<std::string>& instances, - const std::string& remote, - RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); - - Json::Value request; - if (Toolbox::IsSHA1(stripped)) - { - // This is for compatibility with Orthanc <= 0.5.1. - request = stripped; - } - else if (!call.ParseJsonRequest(request)) - { - // Bad JSON request - return false; - } - - if (request.isString()) - { - context.GetIndex().LogExportedResource(request.asString(), remote); - context.GetIndex().GetChildInstances(instances, request.asString()); - } - else if (request.isArray()) - { - for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) - { - if (!request[i].isString()) - { - return false; - } - - std::string stripped = Toolbox::StripSpaces(request[i].asString()); - if (!Toolbox::IsSHA1(stripped)) - { - return false; - } - - context.GetIndex().LogExportedResource(stripped, remote); - - std::list<std::string> tmp; - context.GetIndex().GetChildInstances(tmp, stripped); - - for (std::list<std::string>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - instances.push_back(*it); - } - } - } - else - { - // Neither a string, nor a list of strings. Bad request. - return false; - } - - return true; - } - - - static void DicomStore(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string remote = call.GetUriComponent("id", ""); - - std::list<std::string> instances; - if (!GetInstancesToExport(instances, remote, call)) - { - return; - } - - DicomUserConnection connection; - ConnectToModalityUsingSymbolicName(connection, remote); - - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\""; - - std::string dicom; - context.ReadFile(dicom, *it, FileContentType_Dicom); - connection.Store(dicom); - } - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - - - - // System information ------------------------------------------------------- - - static void ServeRoot(RestApi::GetCall& call) - { - call.GetOutput().Redirect("app/explorer.html"); - } - - static void GetSystemInformation(RestApi::GetCall& call) - { - Json::Value result = Json::objectValue; - - result["Version"] = ORTHANC_VERSION; - result["Name"] = GetGlobalStringParameter("Name", ""); - - call.GetOutput().AnswerJson(result); - } - - static void GetStatistics(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - Json::Value result = Json::objectValue; - context.GetIndex().ComputeStatistics(result); - call.GetOutput().AnswerJson(result); - } - - static void GenerateUid(RestApi::GetCall& call) - { - std::string level = call.GetArgument("level", ""); - if (level == "patient") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); - } - else if (level == "study") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); - } - else if (level == "series") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); - } - else if (level == "instance") - { - call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); - } - } - - static void ExecuteScript(RestApi::PostCall& call) - { - std::string result; - RETRIEVE_CONTEXT(call); - context.GetLuaContext().Execute(result, call.GetPostBody()); - call.GetOutput().AnswerBuffer(result, "text/plain"); - } - - static void GetNowIsoString(RestApi::GetCall& call) - { - call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); - } - - - - - - - // List all the patients, studies, series or instances ---------------------- - - template <enum ResourceType resourceType> - static void ListResources(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - context.GetIndex().GetAllUuids(result, resourceType); - call.GetOutput().AnswerJson(result); - } - - template <enum ResourceType resourceType> - static void GetSingleResource(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().LookupResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - template <enum ResourceType resourceType> - static void DeleteSingleResource(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - - // Download of ZIP files ---------------------------------------------------- - - - static std::string GetDirectoryNameInArchive(const Json::Value& resource, - ResourceType resourceType) - { - switch (resourceType) - { - case ResourceType_Patient: - { - std::string p = resource["MainDicomTags"]["PatientID"].asString(); - std::string n = resource["MainDicomTags"]["PatientName"].asString(); - return p + " " + n; - } - - case ResourceType_Study: - { - return resource["MainDicomTags"]["StudyDescription"].asString(); - } - - case ResourceType_Series: - { - std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); - std::string m = resource["MainDicomTags"]["Modality"].asString(); - return m + " " + d; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, - ServerContext& context, - const Json::Value& resource, - ResourceType resourceType) - { - if (resourceType == ResourceType_Patient) - { - return true; - } - - ResourceType parentType = GetParentResourceType(resourceType); - Json::Value parent; - - switch (resourceType) - { - case ResourceType_Study: - { - if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) - { - return false; - } - - break; - } - - case ResourceType_Series: - if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || - !CreateRootDirectoryInArchive(writer, context, parent, parentType)) - { - return false; - } - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); - return true; - } - - static bool ArchiveInstance(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& instancePublicId, - const char* filename) - { - Json::Value instance; - if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) - { - return false; - } - - writer.OpenFile(filename); - - std::string dicom; - context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); - writer.Write(dicom); - - return true; - } - - static bool ArchiveInternal(HierarchicalZipWriter& writer, - ServerContext& context, - const std::string& publicId, - ResourceType resourceType, - bool isFirstLevel) - { - Json::Value resource; - if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) - { - return false; - } - - if (isFirstLevel && - !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) - { - return false; - } - - writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); - - switch (resourceType) - { - case ResourceType_Patient: - for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) - { - std::string studyId = resource["Studies"][i].asString(); - if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) - { - return false; - } - } - break; - - case ResourceType_Study: - for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) - { - std::string seriesId = resource["Series"][i].asString(); - if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) - { - return false; - } - } - break; - - case ResourceType_Series: - { - // Create a filename prefix, depending on the modality - char format[16] = "%08d"; - - if (resource["MainDicomTags"].isMember("Modality")) - { - std::string modality = resource["MainDicomTags"]["Modality"].asString(); - - if (modality.size() == 1) - { - snprintf(format, sizeof(format) - 1, "%c%%07d", toupper(modality[0])); - } - else if (modality.size() >= 2) - { - snprintf(format, sizeof(format) - 1, "%c%c%%06d", toupper(modality[0]), toupper(modality[1])); - } - } - - char filename[16]; - - for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) - { - snprintf(filename, sizeof(filename) - 1, format, i); - - std::string publicId = resource["Instances"][i].asString(); - - // This was the implementation up to Orthanc 0.7.0: - // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; - - if (!ArchiveInstance(writer, context, publicId, filename)) - { - return false; - } - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - writer.CloseDirectory(); - return true; - } - - template <enum ResourceType resourceType> - static void GetArchive(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - std::string id = call.GetUriComponent("id", ""); - - /** - * Determine whether ZIP64 is required. Original ZIP format can - * store up to 2GB of data (some implementation supporting up to - * 4GB of data), and up to 65535 files. - * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 - **/ - - uint64_t uncompressedSize; - uint64_t compressedSize; - unsigned int countStudies; - unsigned int countSeries; - unsigned int countInstances; - context.GetIndex().GetStatistics(compressedSize, uncompressedSize, - countStudies, countSeries, countInstances, id); - const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || - countInstances >= 65535); - - LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " - << (uncompressedSize / MEGA_BYTES) << "MB using the " - << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; - - // Create a RAII for the temporary file to manage the ZIP file - Toolbox::TemporaryFile tmp; - - { - // Create a ZIP writer - HierarchicalZipWriter writer(tmp.GetPath().c_str()); - writer.SetZip64(isZip64); - - // Store the requested resource into the ZIP - if (!ArchiveInternal(writer, context, id, resourceType, true)) - { - return; - } - } - - // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath().c_str()); - sender.SetContentType("application/zip"); - sender.SetDownloadFilename(id + ".zip"); - - // Send the ZIP - call.GetOutput().AnswerFile(sender); - - // The temporary file is automatically removed thanks to the RAII - } - - - // Changes API -------------------------------------------------------------- - - static void GetSinceAndLimit(int64_t& since, - unsigned int& limit, - bool& last, - const RestApi::GetCall& call) - { - static const unsigned int MAX_RESULTS = 100; - - if (call.HasArgument("last")) - { - last = true; - return; - } - - last = false; - - try - { - since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); - limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); - } - catch (boost::bad_lexical_cast) - { - return; - } - - if (limit == 0 || limit > MAX_RESULTS) - { - limit = MAX_RESULTS; - } - } - - static void GetChanges(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - //std::string filter = GetArgument(getArguments, "filter", ""); - int64_t since; - unsigned int limit; - bool last; - GetSinceAndLimit(since, limit, last, call); - - Json::Value result; - if ((!last && context.GetIndex().GetChanges(result, since, limit)) || - ( last && context.GetIndex().GetLastChange(result))) - { - call.GetOutput().AnswerJson(result); - } - } - - - static void DeleteChanges(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - context.GetIndex().DeleteChanges(); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - - - static void GetExports(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - int64_t since; - unsigned int limit; - bool last; - GetSinceAndLimit(since, limit, last, call); - - Json::Value result; - if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) || - ( last && context.GetIndex().GetLastExportedResource(result))) - { - call.GetOutput().AnswerJson(result); - } - } - - - static void DeleteExports(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - context.GetIndex().DeleteExportedResources(); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - - - // Get information about a single patient ----------------------------------- - - static void IsProtectedPatient(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - std::string publicId = call.GetUriComponent("id", ""); - bool isProtected = context.GetIndex().IsProtectedPatient(publicId); - call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); - } - - - static void SetPatientProtection(RestApi::PutCall& call) - { - RETRIEVE_CONTEXT(call); - std::string publicId = call.GetUriComponent("id", ""); - std::string s = Toolbox::StripSpaces(call.GetPutBody()); - - if (s == "0") - { - context.GetIndex().SetProtectedPatient(publicId, false); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - else if (s == "1") - { - context.GetIndex().SetProtectedPatient(publicId, true); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - else - { - // Bad request - } - } - - - // Get information about a single instance ---------------------------------- - - static void GetInstanceFile(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - context.AnswerFile(call.GetOutput(), publicId, FileContentType_Dicom); - } - - - static void ExportInstanceFile(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - - std::string dicom; - context.ReadFile(dicom, publicId, FileContentType_Dicom); - - Toolbox::WriteFile(dicom, call.GetPostBody()); - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - - - template <bool simplify> - static void GetInstanceTags(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - - Json::Value full; - context.ReadJson(full, publicId); - - if (simplify) - { - Json::Value simplified; - SimplifyTags(simplified, full); - call.GetOutput().AnswerJson(simplified); - } - else - { - call.GetOutput().AnswerJson(full); - } - } - - - static void ListFrames(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value instance; - if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) - { - unsigned int numberOfFrames = 1; - - try - { - Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; - numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); - } - catch (...) - { - } - - Json::Value result = Json::arrayValue; - for (unsigned int i = 0; i < numberOfFrames; i++) - { - result.append(i); - } - - call.GetOutput().AnswerJson(result); - } - } - - - template <enum ImageExtractionMode mode> - static void GetImage(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string frameId = call.GetUriComponent("frame", "0"); - - unsigned int frame; - try - { - frame = boost::lexical_cast<unsigned int>(frameId); - } - catch (boost::bad_lexical_cast) - { - return; - } - - std::string publicId = call.GetUriComponent("id", ""); - std::string dicomContent, png; - context.ReadFile(dicomContent, publicId, FileContentType_Dicom); - - try - { - FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); - call.GetOutput().AnswerBuffer(png, "image/png"); - } - catch (OrthancException& e) - { - if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) - { - // The frame number is out of the range for this DICOM - // instance, the resource is not existent - } - else - { - std::string root = ""; - for (size_t i = 1; i < call.GetFullUri().size(); i++) - { - root += "../"; - } - - call.GetOutput().Redirect(root + "app/images/unsupported.png"); - } - } - } - - - // Upload of DICOM files through HTTP --------------------------------------- - - static void UploadDicomFile(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - const std::string& postData = call.GetPostBody(); - if (postData.size() == 0) - { - return; - } - - LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; - - std::string publicId; - StoreStatus status = context.Store(publicId, postData); - Json::Value result = Json::objectValue; - - if (status != StoreStatus_Failure) - { - result["ID"] = publicId; - result["Path"] = GetBasePath(ResourceType_Instance, publicId); - } - - result["Status"] = EnumerationToString(status); - call.GetOutput().AnswerJson(result); - } - - - - // DICOM bridge ------------------------------------------------------------- - - static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities, - const std::string& id) - { - return modalities.find(id) != modalities.end(); - } - - static void ListModalities(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - Json::Value result = Json::arrayValue; - for (OrthancRestApi::SetOfStrings::const_iterator - it = modalities.begin(); it != modalities.end(); ++it) - { - result.append(*it); - } - - call.GetOutput().AnswerJson(result); - } - - - static void ListModalityOperations(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - std::string id = call.GetUriComponent("id", ""); - if (IsExistingModality(modalities, id)) - { - Json::Value result = Json::arrayValue; - result.append("find-patient"); - result.append("find-study"); - result.append("find-series"); - result.append("find-instance"); - result.append("find"); - result.append("store"); - call.GetOutput().AnswerJson(result); - } - } - - - - // Raw access to the DICOM tags of an instance ------------------------------ - - static void GetRawContent(RestApi::GetCall& call) - { - boost::mutex::scoped_lock lock(cacheMutex_); - - RETRIEVE_CONTEXT(call); - std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); - } - - - - // Modification of DICOM instances ------------------------------------------ - - namespace - { - typedef std::set<DicomTag> Removals; - typedef std::map<DicomTag, std::string> Replacements; - typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; - } - - static void ReplaceInstanceInternal(ParsedDicomFile& toModify, - const Removals& removals, - const Replacements& replacements, - DicomReplaceMode mode, - bool removePrivateTags) - { - if (removePrivateTags) - { - toModify.RemovePrivateTags(); - } - - for (Removals::const_iterator it = removals.begin(); - it != removals.end(); ++it) - { - toModify.Remove(*it); - } - - for (Replacements::const_iterator it = replacements.begin(); - it != replacements.end(); ++it) - { - toModify.Replace(it->first, it->second, mode); - } - - // A new SOP instance UID is automatically generated - std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); - toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); - } - - - static void ParseRemovals(Removals& target, - const Json::Value& removals) - { - if (!removals.isArray()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) - { - std::string name = removals[i].asString(); - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target.insert(tag); - - VLOG(1) << "Removal: " << name << " " << tag << std::endl; - } - } - - - static void ParseReplacements(Replacements& target, - const Json::Value& replacements) - { - if (!replacements.isObject()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const std::string& name = members[i]; - std::string value = replacements[name].asString(); - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - target[tag] = value; - - VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; - } - } - - - static std::string GeneratePatientName(ServerContext& context) - { - uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); - return "Anonymized" + boost::lexical_cast<std::string>(seq); - } - - - static void SetupAnonymization(Removals& removals, - Replacements& replacements) - { - // 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 - //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal() - removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID - removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name - //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) - removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*) - //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) - removals.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals.insert(DicomTag(0x0040, 0xa124)); // UID - removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID - removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID - - /** - * (*) Patient ID, Study Instance UID and Series Instance UID - * are modified by "AnonymizeInstance()" if anonymizing a single - * instance, or by "RetrieveMappedUid()" if anonymizing a - * patient/study/series. - **/ - - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - - // Set the DeidentificationMethod tag - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); - - // Set the PatientIdentityRemoved tag - replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); - } - - - static bool ParseModifyRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, - const RestApi::PostCall& call) - { - removePrivateTags = false; - Json::Value request; - if (call.ParseJsonRequest(request) && - request.isObject()) - { - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; - - if (request.isMember("Remove")) - { - removalsPart = request["Remove"]; - } - - if (request.isMember("Replace")) - { - replacementsPart = request["Replace"]; - } - - if (request.isMember("RemovePrivateTags")) - { - removePrivateTags = true; - } - - ParseRemovals(removals, removalsPart); - ParseReplacements(replacements, replacementsPart); - - return true; - } - else - { - return false; - } - } - - - static bool ParseAnonymizationRequest(Removals& removals, - Replacements& replacements, - bool& removePrivateTags, - bool& keepPatientId, - RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - removePrivateTags = true; - keepPatientId = false; - - Json::Value request; - if (call.ParseJsonRequest(request) && - request.isObject()) - { - Json::Value keepPart = Json::arrayValue; - Json::Value removalsPart = Json::arrayValue; - Json::Value replacementsPart = Json::objectValue; - - if (request.isMember("Keep")) - { - keepPart = request["Keep"]; - } - - if (request.isMember("KeepPrivateTags")) - { - removePrivateTags = false; - } - - if (request.isMember("Replace")) - { - replacementsPart = request["Replace"]; - } - - Removals toKeep; - ParseRemovals(toKeep, keepPart); - - SetupAnonymization(removals, replacements); - - for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it) - { - if (*it == DICOM_TAG_PATIENT_ID) - { - keepPatientId = true; - } - - removals.erase(*it); - } - - Removals additionalRemovals; - ParseRemovals(additionalRemovals, removalsPart); - - for (Removals::iterator it = additionalRemovals.begin(); - it != additionalRemovals.end(); ++it) - { - removals.insert(*it); - } - - ParseReplacements(replacements, replacementsPart); - - // Generate random Patient's Name if none is specified - if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() && - replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context))); - } - - return true; - } - else - { - return false; - } - } - - - static void AnonymizeOrModifyInstance(Removals& removals, - Replacements& replacements, - bool removePrivateTags, - RestApi::PostCall& call) - { - boost::mutex::scoped_lock lock(cacheMutex_); - RETRIEVE_CONTEXT(call); - - std::string id = call.GetUriComponent("id", ""); - ParsedDicomFile& dicom = context.GetDicomFile(id); - - std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); - modified->Answer(call.GetOutput()); - } - - - static bool RetrieveMappedUid(ParsedDicomFile& dicom, - DicomRootLevel level, - Replacements& replacements, - UidMap& uidMap) - { - std::auto_ptr<DicomTag> tag; - - switch (level) - { - case DicomRootLevel_Series: - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - break; - - case DicomRootLevel_Study: - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - break; - - case DicomRootLevel_Patient: - tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - throw OrthancException(ErrorCode_InternalError); - } - - std::string mapped; - bool isNew; - - UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original)); - if (previous == uidMap.end()) - { - mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); - uidMap.insert(std::make_pair(std::make_pair(level, original), mapped)); - isNew = true; - } - else - { - mapped = previous->second; - isNew = false; - } - - replacements[*tag] = mapped; - return isNew; - } - - - static void AnonymizeOrModifyResource(Removals& removals, - Replacements& replacements, - bool removePrivateTags, - bool keepPatientId, - MetadataType metadataType, - ChangeType changeType, - ResourceType resourceType, - RestApi::PostCall& call) - { - typedef std::list<std::string> Instances; - - bool isFirst = true; - Json::Value result(Json::objectValue); - - boost::mutex::scoped_lock lock(cacheMutex_); - RETRIEVE_CONTEXT(call); - - Instances instances; - std::string id = call.GetUriComponent("id", ""); - context.GetIndex().GetChildInstances(instances, id); - - if (instances.empty()) - { - return; - } - - /** - * Loop over all the instances of the resource. - **/ - - UidMap uidMap; - for (Instances::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - LOG(INFO) << "Modifying instance " << *it; - ParsedDicomFile& original = context.GetDicomFile(*it); - - DicomInstanceHasher originalHasher = original.GetHasher(); - - if (isFirst && keepPatientId) - { - std::string patientId = originalHasher.GetPatientId(); - uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; - } - - bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); - bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); - bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); - - - /** - * Compute the resulting DICOM instance and store it into the Orthanc store. - **/ - - std::auto_ptr<ParsedDicomFile> modified(original.Clone()); - ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); - - std::string modifiedInstance; - if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) - { - LOG(ERROR) << "Error while storing a modified instance " << *it; - return; - } - - - /** - * Record metadata information (AnonymizedFrom/ModifiedFrom). - **/ - - DicomInstanceHasher modifiedHasher = modified->GetHasher(); - - if (isNewSeries) - { - context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), - metadataType, originalHasher.HashSeries()); - } - - if (isNewStudy) - { - context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), - metadataType, originalHasher.HashStudy()); - } - - if (isNewPatient) - { - context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), - metadataType, originalHasher.HashPatient()); - } - - assert(*it == originalHasher.HashInstance()); - assert(modifiedInstance == modifiedHasher.HashInstance()); - context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); - - - /** - * Compute the JSON object that is returned by the REST call. - **/ - - if (isFirst) - { - std::string newId; - - switch (resourceType) - { - case ResourceType_Series: - newId = modifiedHasher.HashSeries(); - break; - - case ResourceType_Study: - newId = modifiedHasher.HashStudy(); - break; - - case ResourceType_Patient: - newId = modifiedHasher.HashPatient(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - result["Type"] = EnumerationToString(resourceType); - result["ID"] = newId; - result["Path"] = GetBasePath(resourceType, newId); - result["PatientID"] = modifiedHasher.HashPatient(); - isFirst = false; - } - } - - call.GetOutput().AnswerJson(result); - } - - - - static void ModifyInstance(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); - } - } - - - static void AnonymizeInstance(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - // TODO Handle "keepPatientId" - - // Generate random patient ID if not specified - if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); - } - - // Generate random study UID if not specified - if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); - } - - // Generate random series UID if not specified - if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) - { - replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, - FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); - } - - AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); - } - } - - - static void ModifySeriesInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, - MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, - ResourceType_Series, call); - } - } - - - static void AnonymizeSeriesInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, - ResourceType_Series, call); - } - } - - - static void ModifyStudyInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, - MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, - ResourceType_Study, call); - } - } - - - static void AnonymizeStudyInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, - ResourceType_Study, call); - } - } - - - /*static void ModifyPatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags; - - if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) - { - AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, - MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, - ResourceType_Patient, call); - } - }*/ - - - static void AnonymizePatientInplace(RestApi::PostCall& call) - { - Removals removals; - Replacements replacements; - bool removePrivateTags, keepPatientId; - - if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) - { - AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, - MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, - ResourceType_Patient, call); - } - } - - - // Handling of metadata ----------------------------------------------------- - - static void ListMetadata(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - std::list<MetadataType> metadata; - if (context.GetIndex().ListAvailableMetadata(metadata, publicId)) - { - Json::Value result = Json::arrayValue; - - for (std::list<MetadataType>::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) - { - result.append(EnumerationToString(*it)); - } - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetMetadata(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - std::string name = call.GetUriComponent("name", ""); - MetadataType metadata = StringToMetadata(name); - - std::string value; - if (context.GetIndex().LookupMetadata(value, publicId, metadata)) - { - call.GetOutput().AnswerBuffer(value, "text/plain"); - } - } - - - static void DeleteMetadata(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - std::string name = call.GetUriComponent("name", ""); - MetadataType metadata = StringToMetadata(name); - - if (metadata >= MetadataType_StartUser && - metadata <= MetadataType_EndUser) - { - // It is forbidden to modify internal metadata - context.GetIndex().DeleteMetadata(publicId, metadata); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - } - - - static void SetMetadata(RestApi::PutCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - std::string name = call.GetUriComponent("name", ""); - MetadataType metadata = StringToMetadata(name); - std::string value = call.GetPutBody(); - - if (metadata >= MetadataType_StartUser && - metadata <= MetadataType_EndUser) - { - // It is forbidden to modify internal metadata - context.GetIndex().SetMetadata(publicId, metadata, value); - call.GetOutput().AnswerBuffer("", "text/plain"); - } - } - - - static void GetResourceStatistics(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - std::string publicId = call.GetUriComponent("id", ""); - Json::Value result; - context.GetIndex().GetStatistics(result, publicId); - call.GetOutput().AnswerJson(result); - } - - - - // Orthanc Peers ------------------------------------------------------------ - - static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, - const std::string& id) - { - return peers.find(id) != peers.end(); - } - - static void ListPeers(RestApi::GetCall& call) - { - RETRIEVE_PEERS(call); - - Json::Value result = Json::arrayValue; - for (OrthancRestApi::SetOfStrings::const_iterator - it = peers.begin(); it != peers.end(); ++it) - { - result.append(*it); - } - - call.GetOutput().AnswerJson(result); - } - - static void ListPeerOperations(RestApi::GetCall& call) - { - RETRIEVE_PEERS(call); - - std::string id = call.GetUriComponent("id", ""); - if (IsExistingPeer(peers, id)) - { - Json::Value result = Json::arrayValue; - result.append("store"); - call.GetOutput().AnswerJson(result); - } - } - - static void PeerStore(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string remote = call.GetUriComponent("id", ""); - - std::list<std::string> instances; - if (!GetInstancesToExport(instances, remote, call)) - { - return; - } - - std::string url, username, password; - GetOrthancPeer(remote, url, username, password); - - // Configure the HTTP client - HttpClient client; - if (username.size() != 0 && password.size() != 0) - { - client.SetCredentials(username.c_str(), password.c_str()); - } - - client.SetUrl(url + "instances"); - client.SetMethod(HttpMethod_Post); - - // Loop over the instances that are to be sent - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) - { - LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\""; - - context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); - - std::string answer; - if (!client.Apply(answer)) - { - LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\""; - return; - } - } - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - - - - - - // Registration of the various REST handlers -------------------------------- - - OrthancRestApi::OrthancRestApi(ServerContext& context) : - context_(context) - { - GetListOfDicomModalities(modalities_); - GetListOfOrthancPeers(peers_); - - Register("/", ServeRoot); - Register("/system", GetSystemInformation); - Register("/statistics", GetStatistics); - Register("/changes", GetChanges); - Register("/changes", DeleteChanges); - Register("/exports", GetExports); - Register("/exports", DeleteExports); - - Register("/instances", UploadDicomFile); - Register("/instances", ListResources<ResourceType_Instance>); - Register("/patients", ListResources<ResourceType_Patient>); - Register("/series", ListResources<ResourceType_Series>); - Register("/studies", ListResources<ResourceType_Study>); - - Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); - Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); - Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); - Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); - Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); - Register("/series/{id}", GetSingleResource<ResourceType_Series>); - Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); - Register("/studies/{id}", GetSingleResource<ResourceType_Study>); - - Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); - Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); - Register("/series/{id}/archive", GetArchive<ResourceType_Series>); - - Register("/instances/{id}/statistics", GetResourceStatistics); - Register("/patients/{id}/statistics", GetResourceStatistics); - Register("/studies/{id}/statistics", GetResourceStatistics); - Register("/series/{id}/statistics", GetResourceStatistics); - - Register("/instances/{id}/metadata", ListMetadata); - Register("/instances/{id}/metadata/{name}", DeleteMetadata); - Register("/instances/{id}/metadata/{name}", GetMetadata); - Register("/instances/{id}/metadata/{name}", SetMetadata); - Register("/patients/{id}/metadata", ListMetadata); - Register("/patients/{id}/metadata/{name}", DeleteMetadata); - Register("/patients/{id}/metadata/{name}", GetMetadata); - Register("/patients/{id}/metadata/{name}", SetMetadata); - Register("/series/{id}/metadata", ListMetadata); - Register("/series/{id}/metadata/{name}", DeleteMetadata); - Register("/series/{id}/metadata/{name}", GetMetadata); - Register("/series/{id}/metadata/{name}", SetMetadata); - Register("/studies/{id}/metadata", ListMetadata); - Register("/studies/{id}/metadata/{name}", DeleteMetadata); - Register("/studies/{id}/metadata/{name}", GetMetadata); - Register("/studies/{id}/metadata/{name}", SetMetadata); - - Register("/patients/{id}/protected", IsProtectedPatient); - Register("/patients/{id}/protected", SetPatientProtection); - Register("/instances/{id}/file", GetInstanceFile); - Register("/instances/{id}/export", ExportInstanceFile); - Register("/instances/{id}/tags", GetInstanceTags<false>); - Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); - Register("/instances/{id}/frames", ListFrames); - Register("/instances/{id}/content/*", GetRawContent); - - Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); - Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); - - Register("/modalities", ListModalities); - Register("/modalities/{id}", ListModalityOperations); - Register("/modalities/{id}/find-patient", DicomFindPatient); - Register("/modalities/{id}/find-study", DicomFindStudy); - Register("/modalities/{id}/find-series", DicomFindSeries); - Register("/modalities/{id}/find-instance", DicomFindInstance); - Register("/modalities/{id}/find", DicomFind); - Register("/modalities/{id}/store", DicomStore); - - Register("/peers", ListPeers); - Register("/peers/{id}", ListPeerOperations); - Register("/peers/{id}/store", PeerStore); - - Register("/instances/{id}/modify", ModifyInstance); - Register("/series/{id}/modify", ModifySeriesInplace); - Register("/studies/{id}/modify", ModifyStudyInplace); - //Register("/patients/{id}/modify", ModifyPatientInplace); - - Register("/instances/{id}/anonymize", AnonymizeInstance); - Register("/series/{id}/anonymize", AnonymizeSeriesInplace); - Register("/studies/{id}/anonymize", AnonymizeStudyInplace); - Register("/patients/{id}/anonymize", AnonymizePatientInplace); - - Register("/tools/generate-uid", GenerateUid); - Register("/tools/execute-script", ExecuteScript); - Register("/tools/now", GetNowIsoString); - } -}
--- a/OrthancServer/OrthancRestApi.h Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerContext.h" -#include "../Core/RestApi/RestApi.h" - -#include <set> - -namespace Orthanc -{ - class OrthancRestApi : public RestApi - { - public: - typedef std::set<std::string> SetOfStrings; - - private: - ServerContext& context_; - SetOfStrings modalities_; - SetOfStrings peers_; - - public: - OrthancRestApi(ServerContext& context); - - ServerContext& GetContext() - { - return context_; - } - - SetOfStrings& GetModalities() - { - return modalities_; - } - - SetOfStrings& GetPeers() - { - return peers_; - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,692 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // TODO IMPROVE MULTITHREADING + // Every call to "ParsedDicomFile" must lock this mutex!!! + static boost::mutex cacheMutex_; + + + // Raw access to the DICOM tags of an instance ------------------------------ + + static void GetRawContent(RestApi::GetCall& call) + { + boost::mutex::scoped_lock lock(cacheMutex_); + + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); + } + + + + // Modification of DICOM instances ------------------------------------------ + + namespace + { + typedef std::set<DicomTag> Removals; + typedef std::map<DicomTag, std::string> Replacements; + typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; + } + + static void ReplaceInstanceInternal(ParsedDicomFile& toModify, + const Removals& removals, + const Replacements& replacements, + DicomReplaceMode mode, + bool removePrivateTags) + { + if (removePrivateTags) + { + toModify.RemovePrivateTags(); + } + + for (Removals::const_iterator it = removals.begin(); + it != removals.end(); ++it) + { + toModify.Remove(*it); + } + + for (Replacements::const_iterator it = replacements.begin(); + it != replacements.end(); ++it) + { + toModify.Replace(it->first, it->second, mode); + } + + // A new SOP instance UID is automatically generated + std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance); + toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent); + } + + + static void ParseRemovals(Removals& target, + const Json::Value& removals) + { + if (!removals.isArray()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) + { + std::string name = removals[i].asString(); + DicomTag tag = FromDcmtkBridge::ParseTag(name); + target.insert(tag); + + VLOG(1) << "Removal: " << name << " " << tag << std::endl; + } + } + + + static void ParseReplacements(Replacements& target, + const Json::Value& replacements) + { + if (!replacements.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + Json::Value::Members members = replacements.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + std::string value = replacements[name].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + target[tag] = value; + + VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; + } + } + + + static std::string GeneratePatientName(ServerContext& context) + { + uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); + return "Anonymized" + boost::lexical_cast<std::string>(seq); + } + + + static void SetupAnonymization(Removals& removals, + Replacements& replacements) + { + // 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 + //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal() + removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID + removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name + //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*) + //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*) + removals.insert(DicomTag(0x0020, 0x0010)); // Study ID + removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID + removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals.insert(DicomTag(0x0040, 0xa124)); // UID + removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID + removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + + /** + * (*) Patient ID, Study Instance UID and Series Instance UID + * are modified by "AnonymizeInstance()" if anonymizing a single + * instance, or by "RetrieveMappedUid()" if anonymizing a + * patient/study/series. + **/ + + + // Some more removals (from the experience of DICOM files at the CHU of Liege) + removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + + // Set the DeidentificationMethod tag + replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1")); + + // Set the PatientIdentityRemoved tag + replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES")); + } + + + static bool ParseModifyRequest(Removals& removals, + Replacements& replacements, + bool& removePrivateTags, + const RestApi::PostCall& call) + { + removePrivateTags = false; + Json::Value request; + if (call.ParseJsonRequest(request) && + request.isObject()) + { + Json::Value removalsPart = Json::arrayValue; + Json::Value replacementsPart = Json::objectValue; + + if (request.isMember("Remove")) + { + removalsPart = request["Remove"]; + } + + if (request.isMember("Replace")) + { + replacementsPart = request["Replace"]; + } + + if (request.isMember("RemovePrivateTags")) + { + removePrivateTags = true; + } + + ParseRemovals(removals, removalsPart); + ParseReplacements(replacements, replacementsPart); + + return true; + } + else + { + return false; + } + } + + + static bool ParseAnonymizationRequest(Removals& removals, + Replacements& replacements, + bool& removePrivateTags, + bool& keepPatientId, + RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + removePrivateTags = true; + keepPatientId = false; + + Json::Value request; + if (call.ParseJsonRequest(request) && + request.isObject()) + { + Json::Value keepPart = Json::arrayValue; + Json::Value removalsPart = Json::arrayValue; + Json::Value replacementsPart = Json::objectValue; + + if (request.isMember("Keep")) + { + keepPart = request["Keep"]; + } + + if (request.isMember("KeepPrivateTags")) + { + removePrivateTags = false; + } + + if (request.isMember("Replace")) + { + replacementsPart = request["Replace"]; + } + + Removals toKeep; + ParseRemovals(toKeep, keepPart); + + SetupAnonymization(removals, replacements); + + for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it) + { + if (*it == DICOM_TAG_PATIENT_ID) + { + keepPatientId = true; + } + + removals.erase(*it); + } + + Removals additionalRemovals; + ParseRemovals(additionalRemovals, removalsPart); + + for (Removals::iterator it = additionalRemovals.begin(); + it != additionalRemovals.end(); ++it) + { + removals.insert(*it); + } + + ParseReplacements(replacements, replacementsPart); + + // Generate random Patient's Name if none is specified + if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() && + replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context))); + } + + return true; + } + else + { + return false; + } + } + + + static void AnonymizeOrModifyInstance(Removals& removals, + Replacements& replacements, + bool removePrivateTags, + RestApi::PostCall& call) + { + boost::mutex::scoped_lock lock(cacheMutex_); + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + + std::auto_ptr<ParsedDicomFile> modified(dicom.Clone()); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + modified->Answer(call.GetOutput()); + } + + + static bool RetrieveMappedUid(ParsedDicomFile& dicom, + DicomRootLevel level, + Replacements& replacements, + UidMap& uidMap) + { + std::auto_ptr<DicomTag> tag; + + switch (level) + { + case DicomRootLevel_Series: + tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + break; + + case DicomRootLevel_Study: + tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + break; + + case DicomRootLevel_Patient: + tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string original; + if (!dicom.GetTagValue(original, *tag)) + { + throw OrthancException(ErrorCode_InternalError); + } + + std::string mapped; + bool isNew; + + UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original)); + if (previous == uidMap.end()) + { + mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); + uidMap.insert(std::make_pair(std::make_pair(level, original), mapped)); + isNew = true; + } + else + { + mapped = previous->second; + isNew = false; + } + + replacements[*tag] = mapped; + return isNew; + } + + + static void AnonymizeOrModifyResource(Removals& removals, + Replacements& replacements, + bool removePrivateTags, + bool keepPatientId, + MetadataType metadataType, + ChangeType changeType, + ResourceType resourceType, + RestApi::PostCall& call) + { + typedef std::list<std::string> Instances; + + bool isFirst = true; + Json::Value result(Json::objectValue); + + boost::mutex::scoped_lock lock(cacheMutex_); + ServerContext& context = OrthancRestApi::GetContext(call); + + Instances instances; + std::string id = call.GetUriComponent("id", ""); + context.GetIndex().GetChildInstances(instances, id); + + if (instances.empty()) + { + return; + } + + /** + * Loop over all the instances of the resource. + **/ + + UidMap uidMap; + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + LOG(INFO) << "Modifying instance " << *it; + ParsedDicomFile& original = context.GetDicomFile(*it); + + DicomInstanceHasher originalHasher = original.GetHasher(); + + if (isFirst && keepPatientId) + { + std::string patientId = originalHasher.GetPatientId(); + uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId; + } + + bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap); + bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap); + bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap); + + + /** + * Compute the resulting DICOM instance and store it into the Orthanc store. + **/ + + std::auto_ptr<ParsedDicomFile> modified(original.Clone()); + ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); + + std::string modifiedInstance; + if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) + { + LOG(ERROR) << "Error while storing a modified instance " << *it; + return; + } + + + /** + * Record metadata information (AnonymizedFrom/ModifiedFrom). + **/ + + DicomInstanceHasher modifiedHasher = modified->GetHasher(); + + if (isNewSeries) + { + context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), + metadataType, originalHasher.HashSeries()); + } + + if (isNewStudy) + { + context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), + metadataType, originalHasher.HashStudy()); + } + + if (isNewPatient) + { + context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), + metadataType, originalHasher.HashPatient()); + } + + assert(*it == originalHasher.HashInstance()); + assert(modifiedInstance == modifiedHasher.HashInstance()); + context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); + + + /** + * Compute the JSON object that is returned by the REST call. + **/ + + if (isFirst) + { + std::string newId; + + switch (resourceType) + { + case ResourceType_Series: + newId = modifiedHasher.HashSeries(); + break; + + case ResourceType_Study: + newId = modifiedHasher.HashStudy(); + break; + + case ResourceType_Patient: + newId = modifiedHasher.HashPatient(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + result["Type"] = EnumerationToString(resourceType); + result["ID"] = newId; + result["Path"] = GetBasePath(resourceType, newId); + result["PatientID"] = modifiedHasher.HashPatient(); + isFirst = false; + } + } + + call.GetOutput().AnswerJson(result); + } + + + + static void ModifyInstance(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + } + } + + + static void AnonymizeInstance(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + // TODO Handle "keepPatientId" + + // Generate random patient ID if not specified + if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); + } + + // Generate random study UID if not specified + if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); + } + + // Generate random series UID if not specified + if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) + { + replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, + FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); + } + + AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); + } + } + + + static void ModifySeriesInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, + MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, + ResourceType_Series, call); + } + } + + + static void AnonymizeSeriesInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, + ResourceType_Series, call); + } + } + + + static void ModifyStudyInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, + MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, + ResourceType_Study, call); + } + } + + + static void AnonymizeStudyInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, + ResourceType_Study, call); + } + } + + + /*static void ModifyPatientInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags; + + if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) + { + AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags, + MetadataType_ModifiedFrom, ChangeType_ModifiedPatient, + ResourceType_Patient, call); + } + }*/ + + + static void AnonymizePatientInplace(RestApi::PostCall& call) + { + Removals removals; + Replacements replacements; + bool removePrivateTags, keepPatientId; + + if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) + { + AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId, + MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient, + ResourceType_Patient, call); + } + } + + + + void OrthancRestApi::RegisterAnonymizeModify() + { + Register("/instances/{id}/content/*", GetRawContent); + + Register("/instances/{id}/modify", ModifyInstance); + Register("/series/{id}/modify", ModifySeriesInplace); + Register("/studies/{id}/modify", ModifyStudyInplace); + //Register("/patients/{id}/modify", ModifyPatientInplace); + + Register("/instances/{id}/anonymize", AnonymizeInstance); + Register("/series/{id}/anonymize", AnonymizeSeriesInplace); + Register("/studies/{id}/anonymize", AnonymizeStudyInplace); + Register("/patients/{id}/anonymize", AnonymizePatientInplace); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,84 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // Upload of DICOM files through HTTP --------------------------------------- + + static void UploadDicomFile(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + const std::string& postData = call.GetPostBody(); + if (postData.size() == 0) + { + return; + } + + LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; + + std::string publicId; + StoreStatus status = context.Store(publicId, postData); + Json::Value result = Json::objectValue; + + if (status != StoreStatus_Failure) + { + result["ID"] = publicId; + result["Path"] = GetBasePath(ResourceType_Instance, publicId); + } + + result["Status"] = EnumerationToString(status); + call.GetOutput().AnswerJson(result); + } + + + + // Registration of the various REST handlers -------------------------------- + + OrthancRestApi::OrthancRestApi(ServerContext& context) : + context_(context) + { + RegisterSystem(); + + RegisterChanges(); + RegisterResources(); + RegisterModalities(); + RegisterAnonymizeModify(); + RegisterArchive(); + + Register("/instances", UploadDicomFile); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,76 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ServerContext.h" +#include "../../Core/RestApi/RestApi.h" + +#include <set> + +namespace Orthanc +{ + class OrthancRestApi : public RestApi + { + public: + typedef std::set<std::string> SetOfStrings; + + private: + ServerContext& context_; + + void RegisterSystem(); + + void RegisterChanges(); + + void RegisterResources(); + + void RegisterModalities(); + + void RegisterAnonymizeModify(); + + void RegisterArchive(); + + public: + OrthancRestApi(ServerContext& context); + + static ServerContext& GetContext(RestApi::Call& call) + { + OrthancRestApi& that = dynamic_cast<OrthancRestApi&>(call.GetContext()); + return that.context_; + } + + static ServerIndex& GetIndex(RestApi::Call& call) + { + return GetContext(call).GetIndex(); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,295 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../../Core/Compression/HierarchicalZipWriter.h" +#include "../../Core/HttpServer/FilesystemHttpSender.h" +#include "../../Core/Uuid.h" + +#include <glog/logging.h> + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + +static const uint64_t MEGA_BYTES = 1024 * 1024; +static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; + +namespace Orthanc +{ + // Download of ZIP files ---------------------------------------------------- + + static std::string GetDirectoryNameInArchive(const Json::Value& resource, + ResourceType resourceType) + { + switch (resourceType) + { + case ResourceType_Patient: + { + std::string p = resource["MainDicomTags"]["PatientID"].asString(); + std::string n = resource["MainDicomTags"]["PatientName"].asString(); + return p + " " + n; + } + + case ResourceType_Study: + { + return resource["MainDicomTags"]["StudyDescription"].asString(); + } + + case ResourceType_Series: + { + std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); + std::string m = resource["MainDicomTags"]["Modality"].asString(); + return m + " " + d; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer, + ServerContext& context, + const Json::Value& resource, + ResourceType resourceType) + { + if (resourceType == ResourceType_Patient) + { + return true; + } + + ResourceType parentType = GetParentResourceType(resourceType); + Json::Value parent; + + switch (resourceType) + { + case ResourceType_Study: + { + if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType)) + { + return false; + } + + break; + } + + case ResourceType_Series: + if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) || + !CreateRootDirectoryInArchive(writer, context, parent, parentType)) + { + return false; + } + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str()); + return true; + } + + static bool ArchiveInstance(HierarchicalZipWriter& writer, + ServerContext& context, + const std::string& instancePublicId, + const char* filename) + { + Json::Value instance; + + if (!context.GetIndex().LookupResource(instance, instancePublicId, ResourceType_Instance)) + { + return false; + } + + writer.OpenFile(filename); + + std::string dicom; + context.ReadFile(dicom, instancePublicId, FileContentType_Dicom); + writer.Write(dicom); + + return true; + } + + static bool ArchiveInternal(HierarchicalZipWriter& writer, + ServerContext& context, + const std::string& publicId, + ResourceType resourceType, + bool isFirstLevel) + { + Json::Value resource; + if (!context.GetIndex().LookupResource(resource, publicId, resourceType)) + { + return false; + } + + if (isFirstLevel && + !CreateRootDirectoryInArchive(writer, context, resource, resourceType)) + { + return false; + } + + writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str()); + + switch (resourceType) + { + case ResourceType_Patient: + for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++) + { + std::string studyId = resource["Studies"][i].asString(); + if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false)) + { + return false; + } + } + break; + + case ResourceType_Study: + for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++) + { + std::string seriesId = resource["Series"][i].asString(); + if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false)) + { + return false; + } + } + break; + + case ResourceType_Series: + { + // Create a filename prefix, depending on the modality + char format[16] = "%08d"; + + if (resource["MainDicomTags"].isMember("Modality")) + { + std::string modality = resource["MainDicomTags"]["Modality"].asString(); + + if (modality.size() == 1) + { + snprintf(format, sizeof(format) - 1, "%c%%07d", toupper(modality[0])); + } + else if (modality.size() >= 2) + { + snprintf(format, sizeof(format) - 1, "%c%c%%06d", toupper(modality[0]), toupper(modality[1])); + } + } + + char filename[16]; + + for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) + { + snprintf(filename, sizeof(filename) - 1, format, i); + + std::string publicId = resource["Instances"][i].asString(); + + // This was the implementation up to Orthanc 0.7.0: + // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm"; + + if (!ArchiveInstance(writer, context, publicId, filename)) + { + return false; + } + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + writer.CloseDirectory(); + return true; + } + + template <enum ResourceType resourceType> + static void GetArchive(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string id = call.GetUriComponent("id", ""); + + /** + * Determine whether ZIP64 is required. Original ZIP format can + * store up to 2GB of data (some implementation supporting up to + * 4GB of data), and up to 65535 files. + * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 + **/ + + uint64_t uncompressedSize; + uint64_t compressedSize; + unsigned int countStudies; + unsigned int countSeries; + unsigned int countInstances; + context.GetIndex().GetStatistics(compressedSize, uncompressedSize, + countStudies, countSeries, countInstances, id); + const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || + countInstances >= 65535); + + LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " + << (uncompressedSize / MEGA_BYTES) << "MB using the " + << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; + + // Create a RAII for the temporary file to manage the ZIP file + Toolbox::TemporaryFile tmp; + + { + // Create a ZIP writer + HierarchicalZipWriter writer(tmp.GetPath().c_str()); + writer.SetZip64(isZip64); + + // Store the requested resource into the ZIP + if (!ArchiveInternal(writer, context, id, resourceType, true)) + { + return; + } + } + + // Prepare the sending of the ZIP file + FilesystemHttpSender sender(tmp.GetPath().c_str()); + sender.SetContentType("application/zip"); + sender.SetDownloadFilename(id + ".zip"); + + // Send the ZIP + call.GetOutput().AnswerFile(sender); + + // The temporary file is automatically removed thanks to the RAII + } + + + void OrthancRestApi::RegisterArchive() + { + Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); + Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); + Register("/series/{id}/archive", GetArchive<ResourceType_Series>); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,132 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // Changes API -------------------------------------------------------------- + + static void GetSinceAndLimit(int64_t& since, + unsigned int& limit, + bool& last, + const RestApi::GetCall& call) + { + static const unsigned int MAX_RESULTS = 100; + + if (call.HasArgument("last")) + { + last = true; + return; + } + + last = false; + + try + { + since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); + limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); + } + catch (boost::bad_lexical_cast) + { + return; + } + + if (limit == 0 || limit > MAX_RESULTS) + { + limit = MAX_RESULTS; + } + } + + static void GetChanges(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + //std::string filter = GetArgument(getArguments, "filter", ""); + int64_t since; + unsigned int limit; + bool last; + GetSinceAndLimit(since, limit, last, call); + + Json::Value result; + if ((!last && context.GetIndex().GetChanges(result, since, limit)) || + ( last && context.GetIndex().GetLastChange(result))) + { + call.GetOutput().AnswerJson(result); + } + } + + + static void DeleteChanges(RestApi::DeleteCall& call) + { + OrthancRestApi::GetIndex(call).DeleteChanges(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + // Exports API -------------------------------------------------------------- + + static void GetExports(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + int64_t since; + unsigned int limit; + bool last; + GetSinceAndLimit(since, limit, last, call); + + Json::Value result; + if ((!last && context.GetIndex().GetExportedResources(result, since, limit)) || + ( last && context.GetIndex().GetLastExportedResource(result))) + { + call.GetOutput().AnswerJson(result); + } + } + + + static void DeleteExports(RestApi::DeleteCall& call) + { + OrthancRestApi::GetIndex(call).DeleteExportedResources(); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + void OrthancRestApi::RegisterChanges() + { + Register("/changes", GetChanges); + Register("/changes", DeleteChanges); + Register("/exports", GetExports); + Register("/exports", DeleteExports); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,470 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../DicomProtocol/DicomUserConnection.h" +#include "../OrthancInitialization.h" +#include "../../Core/HttpClient.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // DICOM SCU ---------------------------------------------------------------- + + static bool MergeQueryAndTemplate(DicomMap& result, + const std::string& postData) + { + Json::Value query; + Json::Reader reader; + + if (!reader.parse(postData, query) || + query.type() != Json::objectValue) + { + return false; + } + + Json::Value::Members members = query.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + DicomTag t = FromDcmtkBridge::ParseTag(members[i]); + result.SetValue(t, query[members[i]].asString()); + } + + return true; + } + + static void DicomFindPatient(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindPatient(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindStudy(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindStudy(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindSeries(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindSeries(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindInstance(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindInstanceTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || + m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindInstance(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFind(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers patients; + connection.FindPatient(patients, m); + + // Loop over the found patients + Json::Value result = Json::arrayValue; + for (size_t i = 0; i < patients.GetSize(); i++) + { + Json::Value patient(Json::objectValue); + FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); + + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); + + DicomFindAnswers studies; + connection.FindStudy(studies, m); + + patient["Studies"] = Json::arrayValue; + + // Loop over the found studies + for (size_t j = 0; j < studies.GetSize(); j++) + { + Json::Value study(Json::objectValue); + FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); + + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); + + DicomFindAnswers series; + connection.FindSeries(series, m); + + // Loop over the found series + study["Series"] = Json::arrayValue; + for (size_t k = 0; k < series.GetSize(); k++) + { + Json::Value series2(Json::objectValue); + FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); + study["Series"].append(series2); + } + + patient["Studies"].append(study); + } + + result.append(patient); + } + + call.GetOutput().AnswerJson(result); + } + + + static bool GetInstancesToExport(std::list<std::string>& instances, + const std::string& remote, + RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); + + Json::Value request; + if (Toolbox::IsSHA1(stripped)) + { + // This is for compatibility with Orthanc <= 0.5.1. + request = stripped; + } + else if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + return false; + } + + if (request.isString()) + { + context.GetIndex().LogExportedResource(request.asString(), remote); + context.GetIndex().GetChildInstances(instances, request.asString()); + } + else if (request.isArray()) + { + for (Json::Value::ArrayIndex i = 0; i < request.size(); i++) + { + if (!request[i].isString()) + { + return false; + } + + std::string stripped = Toolbox::StripSpaces(request[i].asString()); + if (!Toolbox::IsSHA1(stripped)) + { + return false; + } + + context.GetIndex().LogExportedResource(stripped, remote); + + std::list<std::string> tmp; + context.GetIndex().GetChildInstances(tmp, stripped); + + for (std::list<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + instances.push_back(*it); + } + } + } + else + { + // Neither a string, nor a list of strings. Bad request. + return false; + } + + return true; + } + + + static void DicomStore(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + + DicomUserConnection connection; + ConnectToModalityUsingSymbolicName(connection, remote); + + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\""; + + std::string dicom; + context.ReadFile(dicom, *it, FileContentType_Dicom); + connection.Store(dicom); + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + // Orthanc Peers ------------------------------------------------------------ + + static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, + const std::string& id) + { + return peers.find(id) != peers.end(); + } + + static void ListPeers(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings peers; + GetListOfOrthancPeers(peers); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + static void ListPeerOperations(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings peers; + GetListOfOrthancPeers(peers); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingPeer(peers, id)) + { + Json::Value result = Json::arrayValue; + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + static void PeerStore(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string remote = call.GetUriComponent("id", ""); + + std::list<std::string> instances; + if (!GetInstancesToExport(instances, remote, call)) + { + return; + } + + std::string url, username, password; + GetOrthancPeer(remote, url, username, password); + + // Configure the HTTP client + HttpClient client; + if (username.size() != 0 && password.size() != 0) + { + client.SetCredentials(username.c_str(), password.c_str()); + } + + client.SetUrl(url + "instances"); + client.SetMethod(HttpMethod_Post); + + // Loop over the instances that are to be sent + for (std::list<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\""; + + context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); + + std::string answer; + if (!client.Apply(answer)) + { + LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\""; + return; + } + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + // DICOM bridge ------------------------------------------------------------- + + static bool IsExistingModality(const OrthancRestApi::SetOfStrings& modalities, + const std::string& id) + { + return modalities.find(id) != modalities.end(); + } + + static void ListModalities(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings modalities; + GetListOfDicomModalities(modalities); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi::SetOfStrings::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + + static void ListModalityOperations(RestApi::GetCall& call) + { + OrthancRestApi::SetOfStrings modalities; + GetListOfDicomModalities(modalities); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingModality(modalities, id)) + { + Json::Value result = Json::arrayValue; + result.append("find-patient"); + result.append("find-study"); + result.append("find-series"); + result.append("find-instance"); + result.append("find"); + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + + void OrthancRestApi::RegisterModalities() + { + Register("/modalities", ListModalities); + Register("/modalities/{id}", ListModalityOperations); + Register("/modalities/{id}/find-patient", DicomFindPatient); + Register("/modalities/{id}/find-study", DicomFindStudy); + Register("/modalities/{id}/find-series", DicomFindSeries); + Register("/modalities/{id}/find-instance", DicomFindInstance); + Register("/modalities/{id}/find", DicomFind); + Register("/modalities/{id}/store", DicomStore); + + Register("/peers", ListPeers); + Register("/peers/{id}", ListPeerOperations); + Register("/peers/{id}/store", PeerStore); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,602 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../ServerToolbox.h" + +#include <glog/logging.h> + +namespace Orthanc +{ + // List all the patients, studies, series or instances ---------------------- + + template <enum ResourceType resourceType> + static void ListResources(RestApi::GetCall& call) + { + Json::Value result; + OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); + call.GetOutput().AnswerJson(result); + } + + template <enum ResourceType resourceType> + static void GetSingleResource(RestApi::GetCall& call) + { + Json::Value result; + if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + template <enum ResourceType resourceType> + static void DeleteSingleResource(RestApi::DeleteCall& call) + { + Json::Value result; + if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + + // Get information about a single patient ----------------------------------- + + static void IsProtectedPatient(RestApi::GetCall& call) + { + std::string publicId = call.GetUriComponent("id", ""); + bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); + call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); + } + + + static void SetPatientProtection(RestApi::PutCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string s = Toolbox::StripSpaces(call.GetPutBody()); + + if (s == "0") + { + context.GetIndex().SetProtectedPatient(publicId, false); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + else if (s == "1") + { + context.GetIndex().SetProtectedPatient(publicId, true); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + else + { + // Bad request + } + } + + + // Get information about a single instance ---------------------------------- + + static void GetInstanceFile(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + context.AnswerDicomFile(call.GetOutput(), publicId, FileContentType_Dicom); + } + + + static void ExportInstanceFile(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + + std::string dicom; + context.ReadFile(dicom, publicId, FileContentType_Dicom); + + Toolbox::WriteFile(dicom, call.GetPostBody()); + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + template <bool simplify> + static void GetInstanceTags(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string publicId = call.GetUriComponent("id", ""); + + Json::Value full; + context.ReadJson(full, publicId); + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, full); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(full); + } + } + + + static void ListFrames(RestApi::GetCall& call) + { + Json::Value instance; + if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) + { + unsigned int numberOfFrames = 1; + + try + { + Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; + numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); + } + catch (...) + { + } + + Json::Value result = Json::arrayValue; + for (unsigned int i = 0; i < numberOfFrames; i++) + { + result.append(i); + } + + call.GetOutput().AnswerJson(result); + } + } + + + template <enum ImageExtractionMode mode> + static void GetImage(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::string frameId = call.GetUriComponent("frame", "0"); + + unsigned int frame; + try + { + frame = boost::lexical_cast<unsigned int>(frameId); + } + catch (boost::bad_lexical_cast) + { + return; + } + + std::string publicId = call.GetUriComponent("id", ""); + std::string dicomContent, png; + context.ReadFile(dicomContent, publicId, FileContentType_Dicom); + + try + { + FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); + call.GetOutput().AnswerBuffer(png, "image/png"); + } + catch (OrthancException& e) + { + if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) + { + // The frame number is out of the range for this DICOM + // instance, the resource is not existent + } + else + { + std::string root = ""; + for (size_t i = 1; i < call.GetFullUri().size(); i++) + { + root += "../"; + } + + call.GetOutput().Redirect(root + "app/images/unsupported.png"); + } + } + } + + + + static void GetResourceStatistics(RestApi::GetCall& call) + { + std::string publicId = call.GetUriComponent("id", ""); + Json::Value result; + OrthancRestApi::GetIndex(call).GetStatistics(result, publicId); + call.GetOutput().AnswerJson(result); + } + + + + // Handling of metadata ----------------------------------------------------- + + static void CheckValidResourceType(RestApi::Call& call) + { + std::string resourceType = call.GetUriComponent("resourceType", ""); + StringToResourceType(resourceType.c_str()); + } + + + static void ListMetadata(RestApi::GetCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::list<MetadataType> metadata; + + OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); + Json::Value result = Json::arrayValue; + + for (std::list<MetadataType>::const_iterator + it = metadata.begin(); it != metadata.end(); ++it) + { + result.append(EnumerationToString(*it)); + } + + call.GetOutput().AnswerJson(result); + } + + + static void GetMetadata(RestApi::GetCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + std::string value; + if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) + { + call.GetOutput().AnswerBuffer(value, "text/plain"); + } + } + + + static void DeleteMetadata(RestApi::DeleteCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + static void SetMetadata(RestApi::PutCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + MetadataType metadata = StringToMetadata(name); + std::string value = call.GetPutBody(); + + if (metadata >= MetadataType_StartUser && + metadata <= MetadataType_EndUser) + { + // It is forbidden to modify internal metadata + OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + } + + + + + // Handling of attached files ----------------------------------------------- + + static void ListAttachments(RestApi::GetCall& call) + { + std::string resourceType = call.GetUriComponent("resourceType", ""); + std::string publicId = call.GetUriComponent("id", ""); + std::list<FileContentType> attachments; + OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); + + Json::Value result = Json::arrayValue; + + for (std::list<FileContentType>::const_iterator + it = attachments.begin(); it != attachments.end(); ++it) + { + result.append(EnumerationToString(*it)); + } + + call.GetOutput().AnswerJson(result); + } + + + static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + FileContentType contentType = StringToContentType(name); + + return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); + } + + + static void GetAttachmentOperations(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + Json::Value operations = Json::arrayValue; + + operations.append("compressed-data"); + + if (info.GetCompressedMD5() != "") + { + operations.append("compressed-md5"); + } + + operations.append("compressed-size"); + operations.append("data"); + + if (info.GetUncompressedMD5() != "") + { + operations.append("md5"); + } + + operations.append("size"); + + if (info.GetCompressedMD5() != "" && + info.GetUncompressedMD5() != "") + { + operations.append("verify-md5"); + } + + call.GetOutput().AnswerJson(operations); + } + } + + + template <int uncompress> + static void GetAttachmentData(RestApi::GetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + std::string content; + context.ReadFile(content, publicId, StringToContentType(name), + (uncompress == 1)); + + call.GetOutput().AnswerBuffer(content, "application/octet-stream"); + } + + + static void GetAttachmentSize(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), "text/plain"); + } + } + + + static void GetAttachmentCompressedSize(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), "text/plain"); + } + } + + + static void GetAttachmentMD5(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call) && + info.GetUncompressedMD5() != "") + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), "text/plain"); + } + } + + + static void GetAttachmentCompressedMD5(RestApi::GetCall& call) + { + FileInfo info; + if (GetAttachmentInfo(info, call) && + info.GetCompressedMD5() != "") + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), "text/plain"); + } + } + + + static void VerifyAttachment(RestApi::PostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + FileInfo info; + if (!GetAttachmentInfo(info, call) || + info.GetCompressedMD5() == "" || + info.GetUncompressedMD5() == "") + { + // Inexistent resource, or no MD5 available + return; + } + + bool ok = false; + + // First check whether the compressed data is correctly stored in the disk + std::string data; + context.ReadFile(data, publicId, StringToContentType(name), false); + + std::string actualMD5; + Toolbox::ComputeMD5(actualMD5, data); + + if (actualMD5 == info.GetCompressedMD5()) + { + // The compressed data is OK. If a compression algorithm was + // applied to it, now check the MD5 of the uncompressed data. + if (info.GetCompressionType() == CompressionType_None) + { + ok = true; + } + else + { + context.ReadFile(data, publicId, StringToContentType(name), true); + Toolbox::ComputeMD5(actualMD5, data); + ok = (actualMD5 == info.GetUncompressedMD5()); + } + } + + if (ok) + { + LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; + } + } + + + static void UploadAttachment(RestApi::PutCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + + const void* data = call.GetPutBody().size() ? &call.GetPutBody()[0] : NULL; + + FileContentType contentType = StringToContentType(name); + if (contentType >= FileContentType_StartUser && // It is forbidden to modify internal attachments + contentType <= FileContentType_EndUser && + context.AddAttachment(publicId, StringToContentType(name), data, call.GetPutBody().size())) + { + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + + static void DeleteAttachment(RestApi::DeleteCall& call) + { + CheckValidResourceType(call); + + std::string publicId = call.GetUriComponent("id", ""); + std::string name = call.GetUriComponent("name", ""); + FileContentType contentType = StringToContentType(name); + + if (contentType >= FileContentType_StartUser && + contentType <= FileContentType_EndUser) + { + // It is forbidden to delete internal attachments + OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + + + + void OrthancRestApi::RegisterResources() + { + Register("/instances", ListResources<ResourceType_Instance>); + Register("/patients", ListResources<ResourceType_Patient>); + Register("/series", ListResources<ResourceType_Series>); + Register("/studies", ListResources<ResourceType_Study>); + + Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); + Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); + Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); + Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); + Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); + Register("/series/{id}", GetSingleResource<ResourceType_Series>); + Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); + Register("/studies/{id}", GetSingleResource<ResourceType_Study>); + + Register("/instances/{id}/statistics", GetResourceStatistics); + Register("/patients/{id}/statistics", GetResourceStatistics); + Register("/studies/{id}/statistics", GetResourceStatistics); + Register("/series/{id}/statistics", GetResourceStatistics); + + Register("/instances/{id}/file", GetInstanceFile); + Register("/instances/{id}/export", ExportInstanceFile); + Register("/instances/{id}/tags", GetInstanceTags<false>); + Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); + Register("/instances/{id}/frames", ListFrames); + + Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); + Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); + + Register("/patients/{id}/protected", IsProtectedPatient); + Register("/patients/{id}/protected", SetPatientProtection); + + Register("/{resourceType}/{id}/metadata", ListMetadata); + Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); + Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); + Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); + + Register("/{resourceType}/{id}/attachments", ListAttachments); + Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); + Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); + Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); + Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); + Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); + Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); + Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); + Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); + Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); + Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,109 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancRestApi.h" + +#include "../OrthancInitialization.h" + +#include <glog/logging.h> + + +namespace Orthanc +{ + // System information ------------------------------------------------------- + + static void ServeRoot(RestApi::GetCall& call) + { + call.GetOutput().Redirect("app/explorer.html"); + } + + static void GetSystemInformation(RestApi::GetCall& call) + { + Json::Value result = Json::objectValue; + + result["Version"] = ORTHANC_VERSION; + result["Name"] = GetGlobalStringParameter("Name", ""); + + call.GetOutput().AnswerJson(result); + } + + static void GetStatistics(RestApi::GetCall& call) + { + Json::Value result = Json::objectValue; + OrthancRestApi::GetIndex(call).ComputeStatistics(result); + call.GetOutput().AnswerJson(result); + } + + static void GenerateUid(RestApi::GetCall& call) + { + std::string level = call.GetArgument("level", ""); + if (level == "patient") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient), "text/plain"); + } + else if (level == "study") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study), "text/plain"); + } + else if (level == "series") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series), "text/plain"); + } + else if (level == "instance") + { + call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance), "text/plain"); + } + } + + static void ExecuteScript(RestApi::PostCall& call) + { + std::string result; + ServerContext& context = OrthancRestApi::GetContext(call); + context.GetLuaContext().Execute(result, call.GetPostBody()); + call.GetOutput().AnswerBuffer(result, "text/plain"); + } + + static void GetNowIsoString(RestApi::GetCall& call) + { + call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain"); + } + + void OrthancRestApi::RegisterSystem() + { + Register("/", ServeRoot); + Register("/system", GetSystemInformation); + Register("/statistics", GetStatistics); + Register("/tools/generate-uid", GenerateUid); + Register("/tools/execute-script", ExecuteScript); + Register("/tools/now", GetNowIsoString); + } +}
--- a/OrthancServer/PrepareDatabase.sql Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/PrepareDatabase.sql Wed Apr 16 16:34:09 2014 +0200 @@ -32,6 +32,8 @@ compressedSize INTEGER, uncompressedSize INTEGER, compressionType INTEGER, + uncompressedMD5 TEXT, -- New in Orthanc 0.7.3 (database v4) + compressedMD5 TEXT, -- New in Orthanc 0.7.3 (database v4) PRIMARY KEY(id, fileType) ); @@ -75,7 +77,9 @@ AFTER DELETE ON AttachedFiles BEGIN SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, - old.compressionType, old.compressedSize); + old.compressionType, old.compressedSize, + -- These 2 arguments are new in Orthanc 0.7.3 (database v4) + old.uncompressedMD5, old.compressedMD5); END; CREATE TRIGGER ResourceDeleted @@ -103,4 +107,4 @@ -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "3"); +INSERT INTO GlobalProperties VALUES (1, "4");
--- a/OrthancServer/ServerContext.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerContext.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -116,7 +116,7 @@ } FileInfo dicomInfo = accessor_.Write(dicomInstance, dicomSize, FileContentType_Dicom); - FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_Json); + FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_DicomAsJson); ServerIndex::Attachments attachments; attachments.push_back(dicomInfo); @@ -153,9 +153,9 @@ } - void ServerContext::AnswerFile(RestApiOutput& output, - const std::string& instancePublicId, - FileContentType content) + void ServerContext::AnswerDicomFile(RestApiOutput& output, + const std::string& instancePublicId, + FileContentType content) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) @@ -176,7 +176,7 @@ const std::string& instancePublicId) { std::string s; - ReadFile(s, instancePublicId, FileContentType_Json); + ReadFile(s, instancePublicId, FileContentType_DicomAsJson); Json::Reader reader; if (!reader.parse(s, result)) @@ -188,7 +188,8 @@ void ServerContext::ReadFile(std::string& result, const std::string& instancePublicId, - FileContentType content) + FileContentType content, + bool uncompressIfNeeded) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) @@ -196,7 +197,15 @@ throw OrthancException(ErrorCode_InternalError); } - accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); + if (uncompressIfNeeded) + { + accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); + } + else + { + accessor_.SetCompressionForNextOperations(CompressionType_None); + } + accessor_.Read(result, attachment.GetUuid()); } @@ -229,19 +238,31 @@ DicomMap dicomSummary; FromDcmtkBridge::Convert(dicomSummary, *dicomInstance.getDataset()); - DicomInstanceHasher hasher(dicomSummary); - resultPublicId = hasher.HashInstance(); + try + { + DicomInstanceHasher hasher(dicomSummary); + resultPublicId = hasher.HashInstance(); - Json::Value dicomJson; - FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); + Json::Value dicomJson; + FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset()); - StoreStatus status = StoreStatus_Failure; - if (dicomSize > 0) + StoreStatus status = StoreStatus_Failure; + if (dicomSize > 0) + { + status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, ""); + } + + return status; + } + catch (OrthancException& e) { - status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, ""); - } + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + LogMissingRequiredTag(dicomSummary); + } - return status; + throw e; + } } @@ -269,4 +290,40 @@ return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize); } + void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) + { + LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); + accessor_.SetStoreMD5(storeMD5); + } + + + bool ServerContext::AddAttachment(const std::string& resourceId, + FileContentType attachmentType, + const void* data, + size_t size) + { + LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; + + if (compressionEnabled_) + { + accessor_.SetCompressionForNextOperations(CompressionType_Zlib); + } + else + { + accessor_.SetCompressionForNextOperations(CompressionType_None); + } + + FileInfo info = accessor_.Write(data, size, attachmentType); + StoreStatus status = index_.AddAttachment(info, resourceId); + + if (status != StoreStatus_Success) + { + storage_.Remove(info.GetUuid()); + return false; + } + else + { + return true; + } + } }
--- a/OrthancServer/ServerContext.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerContext.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -91,6 +91,11 @@ void RemoveFile(const std::string& fileUuid); + bool AddAttachment(const std::string& resourceId, + FileContentType attachmentType, + const void* data, + size_t size); + StoreStatus Store(const char* dicomInstance, size_t dicomSize, const DicomMap& dicomSummary, @@ -118,9 +123,9 @@ return Store(resultPublicId, &dicomContent[0], dicomContent.size()); } - void AnswerFile(RestApiOutput& output, - const std::string& instancePublicId, - FileContentType content); + void AnswerDicomFile(RestApiOutput& output, + const std::string& instancePublicId, + FileContentType content); void ReadJson(Json::Value& result, const std::string& instancePublicId); @@ -128,7 +133,8 @@ // TODO CACHING MECHANISM AT THIS POINT void ReadFile(std::string& result, const std::string& instancePublicId, - FileContentType content); + FileContentType content, + bool uncompressIfNeeded = true); // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); @@ -137,5 +143,12 @@ { return lua_; } + + void SetStoreMD5ForAttachments(bool storeMD5); + + bool IsStoreMD5ForAttachments() const + { + return accessor_.IsStoreMD5(); + } }; }
--- a/OrthancServer/ServerEnumerations.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerEnumerations.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -41,6 +41,7 @@ { static boost::mutex enumerationsMutex_; static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_; + static Toolbox::EnumerationDictionary<FileContentType> dictContentType_; void InitializeServerEnumerations() { @@ -53,6 +54,9 @@ dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom"); dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate"); + + dictContentType_.Add(FileContentType_Dicom, "dicom"); + dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json"); } void RegisterUserMetadata(int metadata, @@ -83,6 +87,34 @@ return dictMetadataType_.Translate(str); } + void RegisterUserContentType(int contentType, + const std::string& name) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + + if (contentType < static_cast<int>(FileContentType_StartUser) || + contentType > static_cast<int>(FileContentType_EndUser)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + dictContentType_.Add(static_cast<FileContentType>(contentType), name); + } + + std::string EnumerationToString(FileContentType type) + { + // This function MUST return a "std::string" and not "const + // char*", as the result is not a static string + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictContentType_.Translate(type); + } + + FileContentType StringToContentType(const std::string& str) + { + boost::mutex::scoped_lock lock(enumerationsMutex_); + return dictContentType_.Translate(str); + } + std::string GetBasePath(ResourceType type, const std::string& publicId) { @@ -250,6 +282,9 @@ case ModalityManufacturer_MedInria: return "MedInria"; + + case ModalityManufacturer_Dcm4Chee: + return "Dcm4Chee"; default: throw OrthancException(ErrorCode_ParameterOutOfRange); @@ -303,6 +338,10 @@ { return ModalityManufacturer_MedInria; } + else if (manufacturer == "Dcm4Chee") + { + return ModalityManufacturer_Dcm4Chee; + } else { throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancServer/ServerEnumerations.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerEnumerations.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -57,7 +57,8 @@ { ModalityManufacturer_Generic, ModalityManufacturer_ClearCanvas, - ModalityManufacturer_MedInria + ModalityManufacturer_MedInria, + ModalityManufacturer_Dcm4Chee }; enum DicomRequestType @@ -121,13 +122,20 @@ void RegisterUserMetadata(int metadata, const std::string& name); - std::string GetBasePath(ResourceType type, - const std::string& publicId); - MetadataType StringToMetadata(const std::string& str); std::string EnumerationToString(MetadataType type); + void RegisterUserContentType(int contentType, + const std::string& name); + + FileContentType StringToContentType(const std::string& str); + + std::string EnumerationToString(FileContentType type); + + std::string GetBasePath(ResourceType type, + const std::string& publicId); + const char* EnumerationToString(SeriesStatus status); const char* EnumerationToString(StoreStatus status);
--- a/OrthancServer/ServerIndex.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerIndex.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -255,6 +255,7 @@ try { + boost::mutex::scoped_lock lock(that->mutex_); std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep); sleep = boost::lexical_cast<unsigned int>(sleepString); } @@ -819,8 +820,7 @@ int64_t id; ResourceType type; - if (!db_->LookupResource(instanceUuid, id, type) || - type != ResourceType_Instance) + if (!db_->LookupResource(instanceUuid, id, type)) { throw OrthancException(ErrorCode_InternalError); } @@ -1108,6 +1108,37 @@ } + void ServerIndex::GetChildren(std::list<std::string>& result, + const std::string& publicId) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t resource; + if (!db_->LookupResource(publicId, resource, type)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + if (type == ResourceType_Instance) + { + // An instance cannot have a child + throw OrthancException(ErrorCode_BadParameterType); + } + + std::list<int64_t> tmp; + db_->GetChildrenInternalId(tmp, resource); + + for (std::list<int64_t>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + result.push_back(db_->GetPublicId(*it)); + } + } + + void ServerIndex::GetChildInstances(std::list<std::string>& result, const std::string& publicId) { @@ -1208,7 +1239,7 @@ } - bool ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target, + void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target, const std::string& publicId) { boost::mutex::scoped_lock lock(mutex_); @@ -1220,7 +1251,25 @@ throw OrthancException(ErrorCode_UnknownResource); } - return db_->ListAvailableMetadata(target, id); + db_->ListAvailableMetadata(target, id); + } + + + void ServerIndex::ListAvailableAttachments(std::list<FileContentType>& target, + const std::string& publicId, + ResourceType expectedType) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t id; + if (!db_->LookupResource(publicId, id, type) || + expectedType != type) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + db_->ListAvailableAttachments(target, id); } @@ -1322,22 +1371,22 @@ ResourceType thisType = db_->GetResourceType(resource); + std::list<FileContentType> f; + db_->ListAvailableAttachments(f, resource); + + for (std::list<FileContentType>::const_iterator + it = f.begin(); it != f.end(); ++it) + { + FileInfo attachment; + if (db_->LookupAttachment(attachment, resource, *it)) + { + compressedSize += attachment.GetCompressedSize(); + uncompressedSize += attachment.GetUncompressedSize(); + } + } + if (thisType == ResourceType_Instance) { - std::list<FileContentType> f; - db_->ListAvailableAttachments(f, resource); - - for (std::list<FileContentType>::const_iterator - it = f.begin(); it != f.end(); ++it) - { - FileInfo attachment; - if (db_->LookupAttachment(attachment, resource, *it)) - { - compressedSize += attachment.GetCompressedSize(); - uncompressedSize += attachment.GetUncompressedSize(); - } - } - countInstances++; } else @@ -1577,53 +1626,71 @@ } - // TODO IS IT USEFUL??? - void ServerIndex::LookupTagValue(std::set<std::string>& result, - DicomTag tag, - const std::string& value, - ResourceType type) + StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment, + const std::string& publicId) { - std::list<std::string> lst; - LookupTagValue(lst, tag, value, type); + boost::mutex::scoped_lock lock(mutex_); + + Transaction t(*this); + + ResourceType resourceType; + int64_t resourceId; + if (!db_->LookupResource(publicId, resourceId, resourceType)) + { + return StoreStatus_Failure; // Inexistent resource + } + + // Remove possible previous attachment + db_->DeleteAttachment(resourceId, attachment.GetContentType()); - result.clear(); - for (std::list<std::string>::const_iterator - it = lst.begin(); it != lst.end(); it++) + // Locate the patient of the target resource + int64_t patientId = resourceId; + for (;;) { - result.insert(*it); + int64_t parent; + if (db_->LookupParent(parent, patientId)) + { + // We have not reached the patient level yet + patientId = parent; + } + else + { + // We have reached the patient level + break; + } } + + // Possibly apply the recycling mechanism while preserving this patient + assert(db_->GetResourceType(patientId) == ResourceType_Patient); + Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId)); + + db_->AddAttachment(resourceId, attachment); + + t.Commit(attachment.GetCompressedSize()); + + return StoreStatus_Success; } - // TODO IS IT USEFUL??? - void ServerIndex::LookupTagValue(std::set<std::string>& result, - DicomTag tag, - const std::string& value) + void ServerIndex::DeleteAttachment(const std::string& publicId, + FileContentType type) { - std::list<std::string> lst; - LookupTagValue(lst, tag, value); + boost::mutex::scoped_lock lock(mutex_); + listener_->Reset(); + + Transaction t(*this); - result.clear(); - for (std::list<std::string>::const_iterator - it = lst.begin(); it != lst.end(); it++) + ResourceType rtype; + int64_t id; + if (!db_->LookupResource(publicId, id, rtype)) { - result.insert(*it); + throw OrthancException(ErrorCode_UnknownResource); } + + db_->DeleteAttachment(id, type); + + t.Commit(0); } - // TODO IS IT USEFUL??? - void ServerIndex::LookupTagValue(std::set<std::string>& result, - const std::string& value) - { - std::list<std::string> lst; - LookupTagValue(lst, value); - - result.clear(); - for (std::list<std::string>::const_iterator - it = lst.begin(); it != lst.end(); it++) - { - result.insert(*it); - } - } }
--- a/OrthancServer/ServerIndex.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerIndex.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -163,6 +163,9 @@ void SetProtectedPatient(const std::string& publicId, bool isProtected); + void GetChildren(std::list<std::string>& result, + const std::string& publicId); + void GetChildInstances(std::list<std::string>& result, const std::string& publicId); @@ -177,9 +180,13 @@ const std::string& publicId, MetadataType type); - bool ListAvailableMetadata(std::list<MetadataType>& target, + void ListAvailableMetadata(std::list<MetadataType>& target, const std::string& publicId); + void ListAvailableAttachments(std::list<FileContentType>& target, + const std::string& publicId, + ResourceType expectedType); + bool LookupParent(std::string& target, const std::string& publicId); @@ -214,20 +221,10 @@ void LookupTagValue(std::list<std::string>& result, const std::string& value); - - // TODO IS IT USEFUL??? - void LookupTagValue(std::set<std::string>& result, - DicomTag tag, - const std::string& value, - ResourceType type); + StoreStatus AddAttachment(const FileInfo& attachment, + const std::string& publicId); - // TODO IS IT USEFUL??? - void LookupTagValue(std::set<std::string>& result, - DicomTag tag, - const std::string& value); - - // TODO IS IT USEFUL??? - void LookupTagValue(std::set<std::string>& result, - const std::string& value); + void DeleteAttachment(const std::string& publicId, + FileContentType type); }; }
--- a/OrthancServer/ServerToolbox.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerToolbox.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -35,6 +35,7 @@ #include "../Core/OrthancException.h" #include <cassert> +#include <glog/logging.h> namespace Orthanc { @@ -82,4 +83,71 @@ } } } + + + void LogMissingRequiredTag(const DicomMap& summary) + { + std::string s, t; + + if (summary.HasTag(DICOM_TAG_PATIENT_ID)) + { + if (t.size() > 0) + t += ", "; + t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "PatientID"; + } + + if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "StudyInstanceUID"; + } + + if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SeriesInstanceUID"; + } + + if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) + { + if (t.size() > 0) + t += ", "; + t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); + } + else + { + if (s.size() > 0) + s += ", "; + s += "SOPInstanceUID"; + } + + if (t.size() == 0) + { + LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; + } + else + { + LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; + } + } }
--- a/OrthancServer/ServerToolbox.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ServerToolbox.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -32,10 +32,14 @@ #pragma once +#include "../Core/DicomFormat/DicomMap.h" + #include <json/json.h> namespace Orthanc { void SimplifyTags(Json::Value& target, const Json::Value& source); + + void LogMissingRequiredTag(const DicomMap& summary); }
--- a/OrthancServer/ToDcmtkBridge.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ToDcmtkBridge.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- a/OrthancServer/ToDcmtkBridge.h Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/ToDcmtkBridge.h Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Upgrade3To4.sql Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,24 @@ +-- This SQLite script updates the version of the Orthanc database from 3 to 4. + +-- Add 2 new columns at "AttachedFiles" + +ALTER TABLE AttachedFiles ADD COLUMN uncompressedMD5 TEXT; +ALTER TABLE AttachedFiles ADD COLUMN compressedMD5 TEXT; + +-- Update the "AttachedFileDeleted" trigger + +DROP TRIGGER AttachedFileDeleted; + +CREATE TRIGGER AttachedFileDeleted +AFTER DELETE ON AttachedFiles +BEGIN + SELECT SignalFileDeleted(old.uuid, old.fileType, old.uncompressedSize, + old.compressionType, old.compressedSize, + -- These 2 arguments are new in Orthanc 0.7.3 (database v4) + old.uncompressedMD5, old.compressedMD5); +END; + +-- Change the database version +-- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration + +UPDATE GlobalProperties SET value="4" WHERE property=1;
--- a/OrthancServer/main.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/OrthancServer/main.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or @@ -30,7 +30,7 @@ **/ -#include "OrthancRestApi.h" +#include "OrthancRestApi/OrthancRestApi.h" #include <fstream> #include <glog/logging.h> @@ -46,6 +46,7 @@ #include "ServerContext.h" #include "OrthancFindRequestHandler.h" #include "OrthancMoveRequestHandler.h" +#include "ServerToolbox.h" using namespace Orthanc; @@ -131,7 +132,7 @@ if (!IsKnownAETitle(callingAet)) { - LOG(ERROR) << "Unkwnown remote DICOM modality AET: \"" << callingAet << "\""; + LOG(ERROR) << "Unknown remote DICOM modality AET: \"" << callingAet << "\""; return false; } else @@ -233,7 +234,7 @@ { std::cout << path << " " << ORTHANC_VERSION << std::endl - << "Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege (Belgium) " << std::endl + << "Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege (Belgium) " << std::endl << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl << "This is free software: you are free to change and redistribute it." << std::endl << "There is NO WARRANTY, to the extent permitted by law." << std::endl @@ -285,6 +286,11 @@ std::string configurationSample; GetFileResource(configurationSample, EmbeddedResources::CONFIGURATION_SAMPLE); +#if defined(_WIN32) + // Replace UNIX newlines with DOS newlines + boost::replace_all(configurationSample, "\n", "\r\n"); +#endif + std::string target = std::string(argv[i]).substr(9); std::ofstream f(target.c_str()); f << configurationSample; @@ -328,6 +334,7 @@ LOG(WARNING) << "Index directory: " << indexDirectory; context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false)); + context.SetStoreMD5ForAttachments(GetGlobalBoolParameter("StoreMD5ForAttachments", true)); std::list<std::string> luaScripts; GetGlobalListOfStringsParameter(luaScripts, "LuaScripts"); @@ -397,9 +404,6 @@ httpServer.SetSslEnabled(false); } - LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); - LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); - #if ORTHANC_STANDALONE == 1 httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER)); #else @@ -408,9 +412,26 @@ httpServer.RegisterHandler(new OrthancRestApi(context)); - // GO !!! - httpServer.Start(); - dicomServer.Start(); + // GO !!! Start the requested servers + if (GetGlobalBoolParameter("HttpServerEnabled", true)) + { + httpServer.Start(); + LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); + } + else + { + LOG(WARNING) << "The HTTP server is disabled"; + } + + if (GetGlobalBoolParameter("DicomServerEnabled", true)) + { + dicomServer.Start(); + LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); + } + else + { + LOG(WARNING) << "The DICOM server is disabled"; + } { @@ -438,7 +459,7 @@ LOG(WARNING) << "Orthanc has started"; Toolbox::ServerBarrier(); - // Stop + // We're done LOG(WARNING) << "Orthanc is stopping"; } @@ -457,5 +478,7 @@ OrthancFinalize(); + LOG(WARNING) << "Orthanc has stopped"; + return status; }
--- a/README Fri Jan 24 17:40:45 2014 +0100 +++ b/README Wed Apr 16 16:34:09 2014 +0200 @@ -5,9 +5,9 @@ General Information ------------------- -General information about this software can be found on our Google -Code hosting page: -http://code.google.com/p/orthanc/ +General information about this software can be found on its official +Website: +http://www.orthanc-server.com/ The instructions for building Orthanc can be found in the "INSTALL" file.
--- a/Resources/CMake/BoostConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,125 +1,125 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) - set(BOOST_STATIC 1) -else() - include(FindBoost) - - set(BOOST_STATIC 0) - #set(Boost_DEBUG 1) - #set(Boost_USE_STATIC_LIBS ON) - - find_package(Boost - COMPONENTS filesystem thread system date_time regex) - - if (NOT Boost_FOUND) - message(FATAL_ERROR "Unable to locate Boost on this system") - endif() - - # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem - # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm - if (${Boost_VERSION} LESS 104400) - add_definitions( - -DBOOST_HAS_FILESYSTEM_V3=0 - ) - else() - add_definitions( - -DBOOST_HAS_FILESYSTEM_V3=1 - -DBOOST_FILESYSTEM_VERSION=3 - ) - endif() - - #if (${Boost_VERSION} LESS 104800) - # boost::locale is only available from 1.48.00 - #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") - # set(BOOST_STATIC 1) - #endif() - - include_directories(${Boost_INCLUDE_DIRS}) - link_libraries(${Boost_LIBRARIES}) -endif() - - -if (BOOST_STATIC) - # Parameters for Boost 1.54.0 - set(BOOST_NAME boost_1_54_0) - set(BOOST_BCP_SUFFIX bcpdigest-0.6.2) - set(BOOST_MD5 "a464288a976ba133f9b325f454cb503d") - set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") - - set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) - DownloadPackage( - "${BOOST_MD5}" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz" - "${BOOST_SOURCES_DIR}" - ) - - set(BOOST_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp - ) - add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - ) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - add_definitions(-DBOOST_HAS_SCHED_YIELD=1) - endif() - - elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(APPEND BOOST_SOURCES - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp - ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp - ) - add_definitions( - -DBOOST_LOCALE_WITH_WCONV=1 - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - - aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) - - list(APPEND BOOST_SOURCES - ${BOOST_REGEX_SOURCES} - ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp - ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp - ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp - ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp - ) - - list(APPEND THIRD_PARTY_SOURCES ${BOOST_SOURCES}) - - add_definitions( - # Static build of Boost - -DBOOST_ALL_NO_LIB - -DBOOST_ALL_NOLIB - -DBOOST_DATE_TIME_NO_LIB - -DBOOST_THREAD_BUILD_LIB - -DBOOST_PROGRAM_OPTIONS_NO_LIB - -DBOOST_REGEX_NO_LIB - -DBOOST_SYSTEM_NO_LIB - -DBOOST_LOCALE_NO_LIB - -DBOOST_HAS_LOCALE=1 - -DBOOST_HAS_FILESYSTEM_V3=1 - ) - - if (${CMAKE_COMPILER_IS_GNUCXX}) - add_definitions(-isystem ${BOOST_SOURCES_DIR}) - endif() - - include_directories( - ${BOOST_SOURCES_DIR} - ) - - source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) -else() - add_definitions( - -DBOOST_HAS_LOCALE=0 - ) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) + set(BOOST_STATIC 1) +else() + include(FindBoost) + + set(BOOST_STATIC 0) + #set(Boost_DEBUG 1) + #set(Boost_USE_STATIC_LIBS ON) + + find_package(Boost + COMPONENTS filesystem thread system date_time regex) + + if (NOT Boost_FOUND) + message(FATAL_ERROR "Unable to locate Boost on this system") + endif() + + # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem + # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm + if (${Boost_VERSION} LESS 104400) + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=0 + ) + else() + add_definitions( + -DBOOST_HAS_FILESYSTEM_V3=1 + -DBOOST_FILESYSTEM_VERSION=3 + ) + endif() + + #if (${Boost_VERSION} LESS 104800) + # boost::locale is only available from 1.48.00 + #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") + # set(BOOST_STATIC 1) + #endif() + + include_directories(${Boost_INCLUDE_DIRS}) + link_libraries(${Boost_LIBRARIES}) +endif() + + +if (BOOST_STATIC) + # Parameters for Boost 1.55.0 + set(BOOST_NAME boost_1_55_0) + set(BOOST_BCP_SUFFIX bcpdigest-0.7.4) + set(BOOST_MD5 "409f7a0e4fb1f5659d07114f3133b67b") + set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + + set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) + DownloadPackage( + "${BOOST_MD5}" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz" + "${BOOST_SOURCES_DIR}" + ) + + set(BOOST_SOURCES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp + ) + add_definitions( + -DBOOST_LOCALE_WITH_ICONV=1 + ) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + add_definitions(-DBOOST_HAS_SCHED_YIELD=1) + endif() + + elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(APPEND BOOST_SOURCES + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp + ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp + ) + add_definitions( + -DBOOST_LOCALE_WITH_WCONV=1 + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) + + list(APPEND BOOST_SOURCES + ${BOOST_REGEX_SOURCES} + ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp + ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp + ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp + ) + + list(APPEND THIRD_PARTY_SOURCES ${BOOST_SOURCES}) + + add_definitions( + # Static build of Boost + -DBOOST_ALL_NO_LIB + -DBOOST_ALL_NOLIB + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_THREAD_BUILD_LIB + -DBOOST_PROGRAM_OPTIONS_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_SYSTEM_NO_LIB + -DBOOST_LOCALE_NO_LIB + -DBOOST_HAS_LOCALE=1 + -DBOOST_HAS_FILESYSTEM_V3=1 + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + add_definitions(-isystem ${BOOST_SOURCES_DIR}) + endif() + + include_directories( + ${BOOST_SOURCES_DIR} + ) + + source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) +else() + add_definitions( + -DBOOST_HAS_LOCALE=0 + ) +endif()
--- a/Resources/CMake/BoostConfiguration.sh Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/BoostConfiguration.sh Wed Apr 16 16:34:09 2014 +0200 @@ -9,21 +9,25 @@ ## http://www.boost.org/doc/libs/1_54_0/tools/bcp/doc/html/index.html ## ## This script generates this subset. +## +## History: +## - Orthanc between 0.6.2 and 0.7.3: Boost 1.54.0 +## - Orthanc above 0.7.4: Boost 1.55.0 -rm -rf /tmp/boost_1_54_0 -rm -rf /tmp/bcp/boost_1_54_0 +rm -rf /tmp/boost_1_55_0 +rm -rf /tmp/bcp/boost_1_55_0 cd /tmp -echo "Uncompressing the source of Boost 1.54.0..." -tar xfz boost_1_54_0.tar.gz +echo "Uncompressing the source of Boost 1.55.0..." +tar xfz boost_1_55_0.tar.gz echo "Generating the subset..." -mkdir -p /tmp/bcp/boost_1_54_0 -bcp --boost=/tmp/boost_1_54_0 thread system locale date_time filesystem math/special_functions algorithm uuid /tmp/bcp/boost_1_54_0 +mkdir -p /tmp/bcp/boost_1_55_0 +bcp --boost=/tmp/boost_1_55_0 thread system locale date_time filesystem math/special_functions algorithm uuid /tmp/bcp/boost_1_55_0 cd /tmp/bcp echo "Compressing the subset..." -tar cfz boost_1_54_0_bcpdigest-0.6.2.tar.gz boost_1_54_0 -ls -l boost_1_54_0_bcpdigest-0.6.2.tar.gz -md5sum boost_1_54_0_bcpdigest-0.6.2.tar.gz -readlink -f boost_1_54_0_bcpdigest-0.6.2.tar.gz +tar cfz boost_1_55_0_bcpdigest-0.7.4.tar.gz boost_1_55_0 +ls -l boost_1_55_0_bcpdigest-0.7.4.tar.gz +md5sum boost_1_55_0_bcpdigest-0.7.4.tar.gz +readlink -f boost_1_55_0_bcpdigest-0.7.4.tar.gz
--- a/Resources/CMake/DcmtkConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,138 +1,151 @@ -add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") - -if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) - SET(DCMTK_VERSION_NUMBER 360) - SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - DownloadPackage( - "219ad631b82031806147e4abbfba4fa4" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" - "${DCMTK_SOURCES_DIR}") - - IF(CMAKE_CROSSCOMPILING) - SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") - ENDIF() - SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) - include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) - include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) - - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - set(HAVE_SSTREAM 1) - set(HAVE_PROTOTYPE_BZERO 1) - set(HAVE_PROTOTYPE_GETHOSTNAME 1) - set(HAVE_PROTOTYPE_GETSOCKOPT 1) - set(HAVE_PROTOTYPE_SETSOCKOPT 1) - set(HAVE_PROTOTYPE_CONNECT 1) - set(HAVE_PROTOTYPE_BIND 1) - set(HAVE_PROTOTYPE_ACCEPT 1) - set(HAVE_PROTOTYPE_SETSOCKNAME 1) - set(HAVE_PROTOTYPE_GETSOCKNAME 1) - endif() - - CONFIGURE_FILE( - ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in - ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) - - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) - - # Source for the logging facility of DCMTK - AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc - ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc - ) - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc - ) - - if (${CMAKE_COMPILER_IS_GNUCXX}) - # This is a patch for MinGW64 - execute_process( - COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - endif() - - endif() - - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc - ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc - ) - - # This fixes crashes related to the destruction of the DCMTK OFLogger - # http://support.dcmtk.org/docs-snapshot/file_macros.html - add_definitions( - -DLOG4CPLUS_DISABLE_FATAL=1 - -DDCMTK_VERSION_NUMBER=360 - ) - - include_directories( - #${DCMTK_SOURCES_DIR} - ${DCMTK_SOURCES_DIR}/config/include - ${DCMTK_SOURCES_DIR}/dcmnet/include - ${DCMTK_SOURCES_DIR}/ofstd/include - ${DCMTK_SOURCES_DIR}/oflog/include - ${DCMTK_SOURCES_DIR}/dcmdata/include - ) - - source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) - - set(DCMTK_BUNDLES_LOG4CPLUS 1) - - if (STANDALONE_BUILD) - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) - else() - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - endif() - - set(DCMTK_DICTIONARIES - DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic - DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic - DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic - ) - -else() - # The following line allows to manually add libraries at the - # command-line, which is necessary for Ubuntu/Debian packages - set(tmp "${DCMTK_LIBRARIES}") - include(FindDCMTK) - list(APPEND DCMTK_LIBRARIES "${tmp}") - - include_directories(${DCMTK_INCLUDE_DIR}) - link_libraries(${DCMTK_LIBRARIES}) - - add_definitions( - -DHAVE_CONFIG_H=1 - ) - - if (EXISTS "${DCMTK_DIR}/config/cfunix.h") - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") - elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux - set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") - else() - message(FATAL_ERROR "Please install libdcmtk1-dev") - endif() - - # Autodetection of the version of DCMTK - file(STRINGS - "${DCMTK_CONFIGURATION_FILE}" - DCMTK_VERSION_NUMBER1 REGEX - ".*PACKAGE_VERSION .*") - - string(REGEX REPLACE - ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" - "\\1\\2\\3" - DCMTK_VERSION_NUMBER - ${DCMTK_VERSION_NUMBER1}) - - add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) - -endif() - -add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) -message("DCMTK version: ${DCMTK_VERSION_NUMBER}") +# Lookup for DICOM dictionaries, if none is specified by the user +if (DCMTK_DICTIONARY_DIR STREQUAL "") + find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic + /usr/share/dcmtk + /usr/share/libdcmtk2 + ) + + message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}") +else() + add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}") +endif() + + + +if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) + SET(DCMTK_VERSION_NUMBER 360) + SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + DownloadPackage( + "219ad631b82031806147e4abbfba4fa4" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip" + "${DCMTK_SOURCES_DIR}") + + IF(CMAKE_CROSSCOMPILING) + SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.") + ENDIF() + SET(DCMTK_SOURCE_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.0) + include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) + include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) + + if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + set(HAVE_SSTREAM 1) + set(HAVE_PROTOTYPE_BZERO 1) + set(HAVE_PROTOTYPE_GETHOSTNAME 1) + set(HAVE_PROTOTYPE_GETSOCKOPT 1) + set(HAVE_PROTOTYPE_SETSOCKOPT 1) + set(HAVE_PROTOTYPE_CONNECT 1) + set(HAVE_PROTOTYPE_BIND 1) + set(HAVE_PROTOTYPE_ACCEPT 1) + set(HAVE_PROTOTYPE_SETSOCKNAME 1) + set(HAVE_PROTOTYPE_GETSOCKNAME 1) + endif() + + CONFIGURE_FILE( + ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in + ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) + + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmnet/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmdata/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/ofstd/libsrc DCMTK_SOURCES) + + # Source for the logging facility of DCMTK + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc + ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc + ) + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc + ) + + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + execute_process( + COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/Resources/Patches/dcmtk-mingw64.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + endif() + + endif() + + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc + ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdictbi.cc + ) + + # This fixes crashes related to the destruction of the DCMTK OFLogger + # http://support.dcmtk.org/docs-snapshot/file_macros.html + add_definitions( + -DLOG4CPLUS_DISABLE_FATAL=1 + -DDCMTK_VERSION_NUMBER=360 + ) + + include_directories( + #${DCMTK_SOURCES_DIR} + ${DCMTK_SOURCES_DIR}/config/include + ${DCMTK_SOURCES_DIR}/dcmnet/include + ${DCMTK_SOURCES_DIR}/ofstd/include + ${DCMTK_SOURCES_DIR}/oflog/include + ${DCMTK_SOURCES_DIR}/dcmdata/include + ) + + source_group(ThirdParty\\Dcmtk REGULAR_EXPRESSION ${DCMTK_SOURCES_DIR}/.*) + + set(DCMTK_BUNDLES_LOG4CPLUS 1) + + if (STANDALONE_BUILD) + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1) + else() + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + endif() + + set(DCMTK_DICTIONARIES + DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic + DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic + ) + +else() + # The following line allows to manually add libraries at the + # command-line, which is necessary for Ubuntu/Debian packages + set(tmp "${DCMTK_LIBRARIES}") + include(FindDCMTK) + list(APPEND DCMTK_LIBRARIES "${tmp}") + + include_directories(${DCMTK_INCLUDE_DIR}) + link_libraries(${DCMTK_LIBRARIES}) + + add_definitions( + -DHAVE_CONFIG_H=1 + ) + + if (EXISTS "${DCMTK_DIR}/config/cfunix.h") + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/cfunix.h") + elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h") # This is for Arch Linux + set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h") + else() + message(FATAL_ERROR "Please install libdcmtk1-dev") + endif() + + # Autodetection of the version of DCMTK + file(STRINGS + "${DCMTK_CONFIGURATION_FILE}" + DCMTK_VERSION_NUMBER1 REGEX + ".*PACKAGE_VERSION .*") + + string(REGEX REPLACE + ".*PACKAGE_VERSION.*\"([0-9]*)\\.([0-9]*)\\.([0-9]*)\"$" + "\\1\\2\\3" + DCMTK_VERSION_NUMBER + ${DCMTK_VERSION_NUMBER1}) + + add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0) + +endif() + +add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER}) +message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
--- a/Resources/CMake/DownloadPackage.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/DownloadPackage.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,133 +1,133 @@ -macro(GetUrlFilename TargetVariable Url) - string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}") -endmacro() - - -macro(GetUrlExtension TargetVariable Url) - #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}") - string(REGEX REPLACE "^.*\\." "" TMP "${Url}") - string(TOLOWER "${TMP}" "${TargetVariable}") -endmacro() - - -## -## Check the existence of the required decompression tools -## - -if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - find_program(ZIP_EXECUTABLE 7z PATHS "$ENV{ProgramFiles}/7-Zip") - if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND") - message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)") - endif() - -else() - find_program(UNZIP_EXECUTABLE unzip) - if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND") - message(FATAL_ERROR "Please install the 'unzip' package") - endif() - - find_program(TAR_EXECUTABLE tar) - if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND") - message(FATAL_ERROR "Please install the 'tar' package") - endif() -endif() - - -macro(DownloadPackage MD5 Url TargetDirectory) - if (NOT IS_DIRECTORY "${TargetDirectory}") - GetUrlFilename(TMP_FILENAME "${Url}") - - set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") - if (NOT EXISTS "${TMP_PATH}") - message("Downloading ${Url}") - - # This fixes issue 6: "I think cmake shouldn't download the - # packages which are not in the system, it should stop and let - # user know." - # https://code.google.com/p/orthanc/issues/detail?id=6 - if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) - message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") - endif() - - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") - else() - message("Using local copy of ${Url}") - endif() - - GetUrlExtension(TMP_EXTENSION "${Url}") - #message(${TMP_EXTENSION}) - message("Uncompressing ${TMP_FILENAME}") - - if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - # How to silently extract files using 7-zip - # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly - - if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) - execute_process( - COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - - if ("${TMP_EXTENSION}" STREQUAL "tgz") - string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") - else() - string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") - endif() - - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - elseif ("${TMP_EXTENSION}" STREQUAL "zip") - execute_process( - COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - OUTPUT_QUIET - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - - else() - if ("${TMP_EXTENSION}" STREQUAL "zip") - execute_process( - COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) - #message("tar xvfz ${TMP_PATH}") - execute_process( - COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - elseif ("${TMP_EXTENSION}" STREQUAL "bz2") - execute_process( - COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - RESULT_VARIABLE Failure - ) - else() - message(FATAL_ERROR "Unknown package format.") - endif() - endif() - - if (Failure) - message(FATAL_ERROR "Error while running the uncompression tool") - endif() - - if (NOT IS_DIRECTORY "${TargetDirectory}") - message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.") - endif() - endif() -endmacro() +macro(GetUrlFilename TargetVariable Url) + string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}") +endmacro() + + +macro(GetUrlExtension TargetVariable Url) + #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}") + string(REGEX REPLACE "^.*\\." "" TMP "${Url}") + string(TOLOWER "${TMP}" "${TargetVariable}") +endmacro() + + +## +## Check the existence of the required decompression tools +## + +if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + find_program(ZIP_EXECUTABLE 7z PATHS "$ENV{ProgramFiles}/7-Zip") + if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)") + endif() + +else() + find_program(UNZIP_EXECUTABLE unzip) + if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'unzip' package") + endif() + + find_program(TAR_EXECUTABLE tar) + if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'tar' package") + endif() +endif() + + +macro(DownloadPackage MD5 Url TargetDirectory) + if (NOT IS_DIRECTORY "${TargetDirectory}") + GetUrlFilename(TMP_FILENAME "${Url}") + + set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") + if (NOT EXISTS "${TMP_PATH}") + message("Downloading ${Url}") + + # This fixes issue 6: "I think cmake shouldn't download the + # packages which are not in the system, it should stop and let + # user know." + # https://code.google.com/p/orthanc/issues/detail?id=6 + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS EXPECTED_MD5 "${MD5}") + else() + message("Using local copy of ${Url}") + endif() + + GetUrlExtension(TMP_EXTENSION "${Url}") + #message(${TMP_EXTENSION}) + message("Uncompressing ${TMP_FILENAME}") + + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + # How to silently extract files using 7-zip + # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly + + if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + execute_process( + COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if ("${TMP_EXTENSION}" STREQUAL "tgz") + string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") + else() + string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + endif() + + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + elseif ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + + else() + if ("${TMP_EXTENSION}" STREQUAL "zip") + execute_process( + COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + #message("tar xvfz ${TMP_PATH}") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + elseif ("${TMP_EXTENSION}" STREQUAL "bz2") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + else() + message(FATAL_ERROR "Unknown package format.") + endif() + endif() + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if (NOT IS_DIRECTORY "${TargetDirectory}") + message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.") + endif() + endif() +endmacro()
--- a/Resources/CMake/JsonCppConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/JsonCppConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,29 +1,29 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) - set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2) - DownloadPackage( - "363e2f4cbd3aeb63bf4e571f377400fb" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz" - "${JSONCPP_SOURCES_DIR}") - - list(APPEND THIRD_PARTY_SOURCES - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp - ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp - ) - - include_directories( - ${JSONCPP_SOURCES_DIR}/include - ) - - source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) - -else() - CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) - if (NOT HAVE_JSONCPP_H) - message(FATAL_ERROR "Please install the libjsoncpp-dev package") - endif() - - include_directories(/usr/include/jsoncpp) - link_libraries(jsoncpp) - -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP) + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-src-0.6.0-rc2) + DownloadPackage( + "363e2f4cbd3aeb63bf4e571f377400fb" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-src-0.6.0-rc2.tar.gz" + "${JSONCPP_SOURCES_DIR}") + + list(APPEND THIRD_PARTY_SOURCES + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp + ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp + ) + + include_directories( + ${JSONCPP_SOURCES_DIR}/include + ) + + source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) + if (NOT HAVE_JSONCPP_H) + message(FATAL_ERROR "Please install the libjsoncpp-dev package") + endif() + + include_directories(/usr/include/jsoncpp) + link_libraries(jsoncpp) + +endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/LibCurlConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,101 +1,101 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_CURL) - SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0) - DownloadPackage( - "3fa4d5236f2a36ca5c3af6715e837691" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz" - "${CURL_SOURCES_DIR}") - - include_directories(${CURL_SOURCES_DIR}/include) - AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) - source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) - - #add_library(Curl STATIC ${CURL_SOURCES}) - #link_libraries(Curl) - - add_definitions( - -DCURL_STATICLIB=1 - -DBUILDING_LIBCURL=1 - -DCURL_DISABLE_LDAPS=1 - -DCURL_DISABLE_LDAP=1 - -D_WIN32_WINNT=0x0501 - - -DCURL_DISABLE_DICT=1 - -DCURL_DISABLE_FILE=1 - -DCURL_DISABLE_FTP=1 - -DCURL_DISABLE_GOPHER=1 - -DCURL_DISABLE_LDAP=1 - -DCURL_DISABLE_LDAPS=1 - -DCURL_DISABLE_POP3=1 - -DCURL_DISABLE_PROXY=1 - -DCURL_DISABLE_RTSP=1 - -DCURL_DISABLE_TELNET=1 - -DCURL_DISABLE_TFTP=1 - ) - - if (${ENABLE_SSL}) - add_definitions( - #-DHAVE_LIBSSL=1 - -DUSE_OPENSSL=1 - -DUSE_SSLEAY=1 - ) - endif() - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - SET(TMP_OS "x86_64") - else() - SET(TMP_OS "x86") - endif() - - set_property( - SOURCE ${CURL_SOURCES} - PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;OS=\"${TMP_OS}\"") - - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=ssize_t - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=ssize_t - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=8 - ) - elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") - add_definitions( - -DRECV_TYPE_ARG1=int - -DRECV_TYPE_ARG2=void* - -DRECV_TYPE_ARG3=size_t - -DRECV_TYPE_ARG4=int - -DRECV_TYPE_RETV=int - -DSEND_TYPE_ARG1=int - -DSEND_TYPE_ARG2=void* - -DSEND_QUAL_ARG2=const - -DSEND_TYPE_ARG3=size_t - -DSEND_TYPE_ARG4=int - -DSEND_TYPE_RETV=int - -DSIZEOF_SHORT=2 - -DSIZEOF_INT=4 - -DSIZEOF_SIZE_T=4 - ) - else() - message(FATAL_ERROR "Support your platform here") - endif() - endif() - -else() - include(FindCURL) - include_directories(${CURL_INCLUDE_DIRS}) - link_libraries(${CURL_LIBRARIES}) - - if (NOT ${CURL_FOUND}) - message(FATAL_ERROR "Unable to find LibCurl") - endif() -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_CURL) + SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.26.0) + DownloadPackage( + "3fa4d5236f2a36ca5c3af6715e837691" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.26.0.tar.gz" + "${CURL_SOURCES_DIR}") + + include_directories(${CURL_SOURCES_DIR}/include) + AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) + source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) + + #add_library(Curl STATIC ${CURL_SOURCES}) + #link_libraries(Curl) + + add_definitions( + -DCURL_STATICLIB=1 + -DBUILDING_LIBCURL=1 + -DCURL_DISABLE_LDAPS=1 + -DCURL_DISABLE_LDAP=1 + -D_WIN32_WINNT=0x0501 + + -DCURL_DISABLE_DICT=1 + -DCURL_DISABLE_FILE=1 + -DCURL_DISABLE_FTP=1 + -DCURL_DISABLE_GOPHER=1 + -DCURL_DISABLE_LDAP=1 + -DCURL_DISABLE_LDAPS=1 + -DCURL_DISABLE_POP3=1 + -DCURL_DISABLE_PROXY=1 + -DCURL_DISABLE_RTSP=1 + -DCURL_DISABLE_TELNET=1 + -DCURL_DISABLE_TFTP=1 + ) + + if (${ENABLE_SSL}) + add_definitions( + #-DHAVE_LIBSSL=1 + -DUSE_OPENSSL=1 + -DUSE_SSLEAY=1 + ) + endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + SET(TMP_OS "x86_64") + else() + SET(TMP_OS "x86") + endif() + + set_property( + SOURCE ${CURL_SOURCES} + PROPERTY COMPILE_DEFINITIONS "HAVE_TIME_H;HAVE_STRUCT_TIMEVAL;HAVE_SYS_STAT_H;HAVE_SOCKET;HAVE_STRUCT_SOCKADDR_STORAGE;HAVE_SYS_SOCKET_H;HAVE_SOCKET;HAVE_SYS_SOCKET_H;HAVE_NETINET_IN_H;HAVE_NETDB_H;HAVE_FCNTL_O_NONBLOCK;HAVE_FCNTL_H;HAVE_SELECT;HAVE_ERRNO_H;HAVE_SEND;HAVE_RECV;OS=\"${TMP_OS}\"") + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + add_definitions( + -DRECV_TYPE_ARG1=int + -DRECV_TYPE_ARG2=void* + -DRECV_TYPE_ARG3=size_t + -DRECV_TYPE_ARG4=int + -DRECV_TYPE_RETV=ssize_t + -DSEND_TYPE_ARG1=int + -DSEND_TYPE_ARG2=void* + -DSEND_QUAL_ARG2=const + -DSEND_TYPE_ARG3=size_t + -DSEND_TYPE_ARG4=int + -DSEND_TYPE_RETV=ssize_t + -DSIZEOF_SHORT=2 + -DSIZEOF_INT=4 + -DSIZEOF_SIZE_T=8 + ) + elseif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") + add_definitions( + -DRECV_TYPE_ARG1=int + -DRECV_TYPE_ARG2=void* + -DRECV_TYPE_ARG3=size_t + -DRECV_TYPE_ARG4=int + -DRECV_TYPE_RETV=int + -DSEND_TYPE_ARG1=int + -DSEND_TYPE_ARG2=void* + -DSEND_QUAL_ARG2=const + -DSEND_TYPE_ARG3=size_t + -DSEND_TYPE_ARG4=int + -DSEND_TYPE_RETV=int + -DSIZEOF_SHORT=2 + -DSIZEOF_INT=4 + -DSIZEOF_SIZE_T=4 + ) + else() + message(FATAL_ERROR "Support your platform here") + endif() + endif() + +else() + include(FindCURL) + include_directories(${CURL_INCLUDE_DIRS}) + link_libraries(${CURL_LIBRARIES}) + + if (NOT ${CURL_FOUND}) + message(FATAL_ERROR "Unable to find LibCurl") + endif() +endif()
--- a/Resources/CMake/LibPngConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/LibPngConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,60 +1,60 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) - SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) - DownloadPackage( - "8ea7f60347a306c5faf70b977fa80e28" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" - "${LIBPNG_SOURCES_DIR}") - - include_directories( - ${LIBPNG_SOURCES_DIR} - ) - - configure_file( - ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt - ${LIBPNG_SOURCES_DIR}/pnglibconf.h - COPY_ONLY) - - set(LIBPNG_SOURCES - #${LIBPNG_SOURCES_DIR}/example.c - ${LIBPNG_SOURCES_DIR}/png.c - ${LIBPNG_SOURCES_DIR}/pngerror.c - ${LIBPNG_SOURCES_DIR}/pngget.c - ${LIBPNG_SOURCES_DIR}/pngmem.c - ${LIBPNG_SOURCES_DIR}/pngpread.c - ${LIBPNG_SOURCES_DIR}/pngread.c - ${LIBPNG_SOURCES_DIR}/pngrio.c - ${LIBPNG_SOURCES_DIR}/pngrtran.c - ${LIBPNG_SOURCES_DIR}/pngrutil.c - ${LIBPNG_SOURCES_DIR}/pngset.c - #${LIBPNG_SOURCES_DIR}/pngtest.c - ${LIBPNG_SOURCES_DIR}/pngtrans.c - ${LIBPNG_SOURCES_DIR}/pngwio.c - ${LIBPNG_SOURCES_DIR}/pngwrite.c - ${LIBPNG_SOURCES_DIR}/pngwtran.c - ${LIBPNG_SOURCES_DIR}/pngwutil.c - ) - - #set_property( - # SOURCE ${LIBPNG_SOURCES} - # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) - - list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES}) - - add_definitions( - -DPNG_NO_CONSOLE_IO=1 - -DPNG_NO_STDIO=1 - ) - - source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) - -else() - include(FindPNG) - - if (NOT ${PNG_FOUND}) - message(FATAL_ERROR "Unable to find LibPNG") - endif() - - include_directories(${PNG_INCLUDE_DIRS}) - link_libraries(${PNG_LIBRARIES}) - add_definitions(${PNG_DEFINITIONS}) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG) + SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12) + DownloadPackage( + "8ea7f60347a306c5faf70b977fa80e28" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz" + "${LIBPNG_SOURCES_DIR}") + + include_directories( + ${LIBPNG_SOURCES_DIR} + ) + + configure_file( + ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt + ${LIBPNG_SOURCES_DIR}/pnglibconf.h + COPY_ONLY) + + set(LIBPNG_SOURCES + #${LIBPNG_SOURCES_DIR}/example.c + ${LIBPNG_SOURCES_DIR}/png.c + ${LIBPNG_SOURCES_DIR}/pngerror.c + ${LIBPNG_SOURCES_DIR}/pngget.c + ${LIBPNG_SOURCES_DIR}/pngmem.c + ${LIBPNG_SOURCES_DIR}/pngpread.c + ${LIBPNG_SOURCES_DIR}/pngread.c + ${LIBPNG_SOURCES_DIR}/pngrio.c + ${LIBPNG_SOURCES_DIR}/pngrtran.c + ${LIBPNG_SOURCES_DIR}/pngrutil.c + ${LIBPNG_SOURCES_DIR}/pngset.c + #${LIBPNG_SOURCES_DIR}/pngtest.c + ${LIBPNG_SOURCES_DIR}/pngtrans.c + ${LIBPNG_SOURCES_DIR}/pngwio.c + ${LIBPNG_SOURCES_DIR}/pngwrite.c + ${LIBPNG_SOURCES_DIR}/pngwtran.c + ${LIBPNG_SOURCES_DIR}/pngwutil.c + ) + + #set_property( + # SOURCE ${LIBPNG_SOURCES} + # PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H) + + list(APPEND THIRD_PARTY_SOURCES ${LIBPNG_SOURCES}) + + add_definitions( + -DPNG_NO_CONSOLE_IO=1 + -DPNG_NO_STDIO=1 + ) + + source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + +else() + include(FindPNG) + + if (NOT ${PNG_FOUND}) + message(FATAL_ERROR "Unable to find LibPNG") + endif() + + include_directories(${PNG_INCLUDE_DIRS}) + link_libraries(${PNG_LIBRARIES}) + add_definitions(${PNG_DEFINITIONS}) +endif()
--- a/Resources/CMake/LuaConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/LuaConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,67 +1,67 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_LUA) - SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) - DownloadPackage( - "2e115fe26e435e33b0d5c022e4490567" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" - "${LUA_SOURCES_DIR}") - - add_definitions( - #-DLUA_LIB=1 - #-Dluaall_c=1 - #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua - ) - - include_directories( - ${LUA_SOURCES_DIR}/src - ) - - set(LUA_SOURCES - # Core Lua - ${LUA_SOURCES_DIR}/src/lapi.c - ${LUA_SOURCES_DIR}/src/lcode.c - ${LUA_SOURCES_DIR}/src/ldebug.c - ${LUA_SOURCES_DIR}/src/ldo.c - ${LUA_SOURCES_DIR}/src/ldump.c - ${LUA_SOURCES_DIR}/src/lfunc.c - ${LUA_SOURCES_DIR}/src/lgc.c - ${LUA_SOURCES_DIR}/src/llex.c - ${LUA_SOURCES_DIR}/src/lmem.c - ${LUA_SOURCES_DIR}/src/lobject.c - ${LUA_SOURCES_DIR}/src/lopcodes.c - ${LUA_SOURCES_DIR}/src/lparser.c - ${LUA_SOURCES_DIR}/src/lstate.c - ${LUA_SOURCES_DIR}/src/lstring.c - ${LUA_SOURCES_DIR}/src/ltable.c - ${LUA_SOURCES_DIR}/src/ltm.c - ${LUA_SOURCES_DIR}/src/lundump.c - ${LUA_SOURCES_DIR}/src/lvm.c - ${LUA_SOURCES_DIR}/src/lzio.c - - # Base Lua modules - ${LUA_SOURCES_DIR}/src/lauxlib.c - ${LUA_SOURCES_DIR}/src/lbaselib.c - ${LUA_SOURCES_DIR}/src/ldblib.c - ${LUA_SOURCES_DIR}/src/liolib.c - ${LUA_SOURCES_DIR}/src/lmathlib.c - ${LUA_SOURCES_DIR}/src/loslib.c - ${LUA_SOURCES_DIR}/src/ltablib.c - ${LUA_SOURCES_DIR}/src/lstrlib.c - ${LUA_SOURCES_DIR}/src/loadlib.c - ${LUA_SOURCES_DIR}/src/linit.c - ) - - add_library(Lua STATIC ${LUA_SOURCES}) - link_libraries(Lua) - - source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) - -else() - include(FindLua51) - - if (NOT LUA51_FOUND) - message(FATAL_ERROR "Please install the liblua-dev package") - endif() - - include_directories(${LUA_INCLUDE_DIR}) - link_libraries(${LUA_LIBRARIES}) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_LUA) + SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5) + DownloadPackage( + "2e115fe26e435e33b0d5c022e4490567" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" + "${LUA_SOURCES_DIR}") + + add_definitions( + #-DLUA_LIB=1 + #-Dluaall_c=1 + #-DLUA_COMPAT_ALL=1 # Compile a generic version of Lua + ) + + include_directories( + ${LUA_SOURCES_DIR}/src + ) + + set(LUA_SOURCES + # Core Lua + ${LUA_SOURCES_DIR}/src/lapi.c + ${LUA_SOURCES_DIR}/src/lcode.c + ${LUA_SOURCES_DIR}/src/ldebug.c + ${LUA_SOURCES_DIR}/src/ldo.c + ${LUA_SOURCES_DIR}/src/ldump.c + ${LUA_SOURCES_DIR}/src/lfunc.c + ${LUA_SOURCES_DIR}/src/lgc.c + ${LUA_SOURCES_DIR}/src/llex.c + ${LUA_SOURCES_DIR}/src/lmem.c + ${LUA_SOURCES_DIR}/src/lobject.c + ${LUA_SOURCES_DIR}/src/lopcodes.c + ${LUA_SOURCES_DIR}/src/lparser.c + ${LUA_SOURCES_DIR}/src/lstate.c + ${LUA_SOURCES_DIR}/src/lstring.c + ${LUA_SOURCES_DIR}/src/ltable.c + ${LUA_SOURCES_DIR}/src/ltm.c + ${LUA_SOURCES_DIR}/src/lundump.c + ${LUA_SOURCES_DIR}/src/lvm.c + ${LUA_SOURCES_DIR}/src/lzio.c + + # Base Lua modules + ${LUA_SOURCES_DIR}/src/lauxlib.c + ${LUA_SOURCES_DIR}/src/lbaselib.c + ${LUA_SOURCES_DIR}/src/ldblib.c + ${LUA_SOURCES_DIR}/src/liolib.c + ${LUA_SOURCES_DIR}/src/lmathlib.c + ${LUA_SOURCES_DIR}/src/loslib.c + ${LUA_SOURCES_DIR}/src/ltablib.c + ${LUA_SOURCES_DIR}/src/lstrlib.c + ${LUA_SOURCES_DIR}/src/loadlib.c + ${LUA_SOURCES_DIR}/src/linit.c + ) + + add_library(Lua STATIC ${LUA_SOURCES}) + link_libraries(Lua) + + source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) + +else() + include(FindLua51) + + if (NOT LUA51_FOUND) + message(FATAL_ERROR "Please install the liblua-dev package") + endif() + + include_directories(${LUA_INCLUDE_DIR}) + link_libraries(${LUA_LIBRARIES}) +endif()
--- a/Resources/CMake/MongooseConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/MongooseConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,59 +1,59 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_MONGOOSE) - SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) - DownloadPackage( - "e718fc287b4eb1bd523be3fa00942bb0" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" - "${MONGOOSE_SOURCES_DIR}") - - # Patch mongoose - execute_process( - COMMAND patch mongoose.c ${CMAKE_SOURCE_DIR}/Resources/Patches/mongoose-patch.diff - WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR} - ) - - include_directories( - ${MONGOOSE_SOURCES_DIR} - ) - - list(APPEND THIRD_PARTY_SOURCES - ${MONGOOSE_SOURCES_DIR}/mongoose.c - ) - - - if (${ENABLE_SSL}) - add_definitions( - -DNO_SSL_DL=1 - ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - link_libraries(dl) - endif() - - else() - add_definitions( - -DNO_SSL=1 # Remove SSL support from mongoose - ) - endif() - - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - if (${CMAKE_COMPILER_IS_GNUCXX}) - # This is a patch for MinGW64 - add_definitions(-D_TIMESPEC_DEFINED=1) - endif() - endif() - - source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*) - -else() - CHECK_INCLUDE_FILE_CXX(mongoose.h HAVE_MONGOOSE_H) - if (NOT HAVE_MONGOOSE_H) - message(FATAL_ERROR "Please install the mongoose-devel package") - endif() - - CHECK_LIBRARY_EXISTS(mongoose mg_start "" HAVE_MONGOOSE_LIB) - if (NOT HAVE_MONGOOSE_LIB) - message(FATAL_ERROR "Please install the mongoose-devel package") - endif() - - link_libraries(mongoose) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_MONGOOSE) + SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) + DownloadPackage( + "e718fc287b4eb1bd523be3fa00942bb0" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/mongoose-3.1.tgz" + "${MONGOOSE_SOURCES_DIR}") + + # Patch mongoose + execute_process( + COMMAND patch mongoose.c ${CMAKE_SOURCE_DIR}/Resources/Patches/mongoose-patch.diff + WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR} + ) + + include_directories( + ${MONGOOSE_SOURCES_DIR} + ) + + list(APPEND THIRD_PARTY_SOURCES + ${MONGOOSE_SOURCES_DIR}/mongoose.c + ) + + + if (${ENABLE_SSL}) + add_definitions( + -DNO_SSL_DL=1 + ) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + link_libraries(dl) + endif() + + else() + add_definitions( + -DNO_SSL=1 # Remove SSL support from mongoose + ) + endif() + + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (${CMAKE_COMPILER_IS_GNUCXX}) + # This is a patch for MinGW64 + add_definitions(-D_TIMESPEC_DEFINED=1) + endif() + endif() + + source_group(ThirdParty\\Mongoose REGULAR_EXPRESSION ${MONGOOSE_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(mongoose.h HAVE_MONGOOSE_H) + if (NOT HAVE_MONGOOSE_H) + message(FATAL_ERROR "Please install the mongoose-devel package") + endif() + + CHECK_LIBRARY_EXISTS(mongoose mg_start "" HAVE_MONGOOSE_LIB) + if (NOT HAVE_MONGOOSE_LIB) + message(FATAL_ERROR "Please install the mongoose-devel package") + endif() + + link_libraries(mongoose) +endif()
--- a/Resources/CMake/OpenSslConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/OpenSslConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,209 +1,209 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) - SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1c) - DownloadPackage( - "ae412727c8c15b67880aef7bd2999b2e" - "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1c.tar.gz" - "${OPENSSL_SOURCES_DIR}") - - if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED") - if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - message("Patching the symbolic links") - # Patch the symbolic links by copying the files - file(GLOB headers "${OPENSSL_SOURCES_DIR}/include/openssl/*.h") - foreach(header ${headers}) - message(${header}) - file(READ "${header}" symbolicLink) - message(${symbolicLink}) - configure_file("${OPENSSL_SOURCES_DIR}/include/openssl/${symbolicLink}" "${header}" COPYONLY) - endforeach() - file(WRITE "${OPENSSL_SOURCES_DIR}/include/PATCHED") - endif() - endif() - - add_definitions( - -DOPENSSL_THREADS - -DOPENSSL_IA32_SSE2 - -DOPENSSL_NO_ASM - -DOPENSSL_NO_DYNAMIC_ENGINE - -DNO_WINDOWS_BRAINDEATH - - -DOPENSSL_NO_BF - -DOPENSSL_NO_CAMELLIA - -DOPENSSL_NO_CAST - -DOPENSSL_NO_EC - -DOPENSSL_NO_ECDH - -DOPENSSL_NO_ECDSA - -DOPENSSL_NO_EC_NISTP_64_GCC_128 - -DOPENSSL_NO_GMP - -DOPENSSL_NO_GOST - -DOPENSSL_NO_HW - -DOPENSSL_NO_JPAKE - -DOPENSSL_NO_IDEA - -DOPENSSL_NO_KRB5 - -DOPENSSL_NO_MD2 - -DOPENSSL_NO_MDC2 - -DOPENSSL_NO_MD4 - -DOPENSSL_NO_RC2 - -DOPENSSL_NO_RC4 - -DOPENSSL_NO_RC5 - -DOPENSSL_NO_RFC3779 - -DOPENSSL_NO_SCTP - -DOPENSSL_NO_STORE - -DOPENSSL_NO_SEED - -DOPENSSL_NO_WHIRLPOOL - -DOPENSSL_NO_RIPEMD - ) - - include_directories( - ${OPENSSL_SOURCES_DIR} - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/include - ) - - set(OPENSSL_SOURCES_SUBDIRS - ${OPENSSL_SOURCES_DIR}/crypto - ${OPENSSL_SOURCES_DIR}/crypto/aes - ${OPENSSL_SOURCES_DIR}/crypto/asn1 - ${OPENSSL_SOURCES_DIR}/crypto/bio - ${OPENSSL_SOURCES_DIR}/crypto/bn - ${OPENSSL_SOURCES_DIR}/crypto/buffer - ${OPENSSL_SOURCES_DIR}/crypto/cmac - ${OPENSSL_SOURCES_DIR}/crypto/cms - ${OPENSSL_SOURCES_DIR}/crypto/comp - ${OPENSSL_SOURCES_DIR}/crypto/conf - ${OPENSSL_SOURCES_DIR}/crypto/des - ${OPENSSL_SOURCES_DIR}/crypto/dh - ${OPENSSL_SOURCES_DIR}/crypto/dsa - ${OPENSSL_SOURCES_DIR}/crypto/dso - ${OPENSSL_SOURCES_DIR}/crypto/engine - ${OPENSSL_SOURCES_DIR}/crypto/err - ${OPENSSL_SOURCES_DIR}/crypto/evp - ${OPENSSL_SOURCES_DIR}/crypto/hmac - ${OPENSSL_SOURCES_DIR}/crypto/lhash - ${OPENSSL_SOURCES_DIR}/crypto/md5 - ${OPENSSL_SOURCES_DIR}/crypto/modes - ${OPENSSL_SOURCES_DIR}/crypto/objects - ${OPENSSL_SOURCES_DIR}/crypto/ocsp - ${OPENSSL_SOURCES_DIR}/crypto/pem - ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 - ${OPENSSL_SOURCES_DIR}/crypto/pqueue - ${OPENSSL_SOURCES_DIR}/crypto/rand - ${OPENSSL_SOURCES_DIR}/crypto/rsa - ${OPENSSL_SOURCES_DIR}/crypto/sha - ${OPENSSL_SOURCES_DIR}/crypto/srp - ${OPENSSL_SOURCES_DIR}/crypto/stack - ${OPENSSL_SOURCES_DIR}/crypto/ts - ${OPENSSL_SOURCES_DIR}/crypto/txt_db - ${OPENSSL_SOURCES_DIR}/crypto/ui - ${OPENSSL_SOURCES_DIR}/crypto/x509 - ${OPENSSL_SOURCES_DIR}/crypto/x509v3 - ${OPENSSL_SOURCES_DIR}/ssl - ) - - foreach(d ${OPENSSL_SOURCES_SUBDIRS}) - AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) - endforeach() - - list(REMOVE_ITEM OPENSSL_SOURCES - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c - ${OPENSSL_SOURCES_DIR}/crypto/armcap.c - ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c - ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des.c - ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c - ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp - ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c - ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c - ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c - ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c - ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c - ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c - ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c - ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c - ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c - ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c - ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c - ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c - ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c - ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c - ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c - ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c - ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c - ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c - ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c - ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c - ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c - - ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c - ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c - ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c - ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c - ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c - ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c - ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c - ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c - ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c - ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c - ) - - if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") - set_source_files_properties( - ${OPENSSL_SOURCES} - PROPERTIES COMPILE_DEFINITIONS - "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") - - elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - execute_process( - COMMAND patch ui_openssl.c ${CMAKE_SOURCE_DIR}/Resources/Patches/openssl-lsb.diff - WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui - ) - - endif() - - #add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) - #link_libraries(OpenSSL) - -else() - include(FindOpenSSL) - - if (NOT ${OPENSSL_FOUND}) - message(FATAL_ERROR "Unable to find OpenSSL") - endif() - - include_directories(${OPENSSL_INCLUDE_DIR}) - link_libraries(${OPENSSL_LIBRARIES}) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL) + SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.1g) + DownloadPackage( + "de62b43dfcd858e66a74bee1c834e959" + "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.1g.tar.gz" + "${OPENSSL_SOURCES_DIR}") + + if (NOT EXISTS "${OPENSSL_SOURCES_DIR}/include/PATCHED") + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + message("Patching the symbolic links") + # Patch the symbolic links by copying the files + file(GLOB headers "${OPENSSL_SOURCES_DIR}/include/openssl/*.h") + foreach(header ${headers}) + message(${header}) + file(READ "${header}" symbolicLink) + message(${symbolicLink}) + configure_file("${OPENSSL_SOURCES_DIR}/include/openssl/${symbolicLink}" "${header}" COPYONLY) + endforeach() + file(WRITE "${OPENSSL_SOURCES_DIR}/include/PATCHED") + endif() + endif() + + add_definitions( + -DOPENSSL_THREADS + -DOPENSSL_IA32_SSE2 + -DOPENSSL_NO_ASM + -DOPENSSL_NO_DYNAMIC_ENGINE + -DNO_WINDOWS_BRAINDEATH + + -DOPENSSL_NO_BF + -DOPENSSL_NO_CAMELLIA + -DOPENSSL_NO_CAST + -DOPENSSL_NO_EC + -DOPENSSL_NO_ECDH + -DOPENSSL_NO_ECDSA + -DOPENSSL_NO_EC_NISTP_64_GCC_128 + -DOPENSSL_NO_GMP + -DOPENSSL_NO_GOST + -DOPENSSL_NO_HW + -DOPENSSL_NO_JPAKE + -DOPENSSL_NO_IDEA + -DOPENSSL_NO_KRB5 + -DOPENSSL_NO_MD2 + -DOPENSSL_NO_MDC2 + -DOPENSSL_NO_MD4 + -DOPENSSL_NO_RC2 + -DOPENSSL_NO_RC4 + -DOPENSSL_NO_RC5 + -DOPENSSL_NO_RFC3779 + -DOPENSSL_NO_SCTP + -DOPENSSL_NO_STORE + -DOPENSSL_NO_SEED + -DOPENSSL_NO_WHIRLPOOL + -DOPENSSL_NO_RIPEMD + ) + + include_directories( + ${OPENSSL_SOURCES_DIR} + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/include + ) + + set(OPENSSL_SOURCES_SUBDIRS + ${OPENSSL_SOURCES_DIR}/crypto + ${OPENSSL_SOURCES_DIR}/crypto/aes + ${OPENSSL_SOURCES_DIR}/crypto/asn1 + ${OPENSSL_SOURCES_DIR}/crypto/bio + ${OPENSSL_SOURCES_DIR}/crypto/bn + ${OPENSSL_SOURCES_DIR}/crypto/buffer + ${OPENSSL_SOURCES_DIR}/crypto/cmac + ${OPENSSL_SOURCES_DIR}/crypto/cms + ${OPENSSL_SOURCES_DIR}/crypto/comp + ${OPENSSL_SOURCES_DIR}/crypto/conf + ${OPENSSL_SOURCES_DIR}/crypto/des + ${OPENSSL_SOURCES_DIR}/crypto/dh + ${OPENSSL_SOURCES_DIR}/crypto/dsa + ${OPENSSL_SOURCES_DIR}/crypto/dso + ${OPENSSL_SOURCES_DIR}/crypto/engine + ${OPENSSL_SOURCES_DIR}/crypto/err + ${OPENSSL_SOURCES_DIR}/crypto/evp + ${OPENSSL_SOURCES_DIR}/crypto/hmac + ${OPENSSL_SOURCES_DIR}/crypto/lhash + ${OPENSSL_SOURCES_DIR}/crypto/md5 + ${OPENSSL_SOURCES_DIR}/crypto/modes + ${OPENSSL_SOURCES_DIR}/crypto/objects + ${OPENSSL_SOURCES_DIR}/crypto/ocsp + ${OPENSSL_SOURCES_DIR}/crypto/pem + ${OPENSSL_SOURCES_DIR}/crypto/pkcs12 + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7 + ${OPENSSL_SOURCES_DIR}/crypto/pqueue + ${OPENSSL_SOURCES_DIR}/crypto/rand + ${OPENSSL_SOURCES_DIR}/crypto/rsa + ${OPENSSL_SOURCES_DIR}/crypto/sha + ${OPENSSL_SOURCES_DIR}/crypto/srp + ${OPENSSL_SOURCES_DIR}/crypto/stack + ${OPENSSL_SOURCES_DIR}/crypto/ts + ${OPENSSL_SOURCES_DIR}/crypto/txt_db + ${OPENSSL_SOURCES_DIR}/crypto/ui + ${OPENSSL_SOURCES_DIR}/crypto/x509 + ${OPENSSL_SOURCES_DIR}/crypto/x509v3 + ${OPENSSL_SOURCES_DIR}/ssl + ) + + foreach(d ${OPENSSL_SOURCES_SUBDIRS}) + AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES) + endforeach() + + list(REMOVE_ITEM OPENSSL_SOURCES + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c + ${OPENSSL_SOURCES_DIR}/crypto/armcap.c + ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c + ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des.c + ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c + ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp + ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c + ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c + ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c + ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c + ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c + ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c + ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c + ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c + ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c + ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c + ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c + ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c + ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c + ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c + ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c + ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c + ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c + ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c + ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c + ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c + ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c + + ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c + ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c + ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c + ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c + ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c + ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c + ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c + ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c + ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c + ) + + if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + set_source_files_properties( + ${OPENSSL_SOURCES} + PROPERTIES COMPILE_DEFINITIONS + "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN") + + elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + execute_process( + COMMAND patch ui_openssl.c ${CMAKE_SOURCE_DIR}/Resources/Patches/openssl-lsb.diff + WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui + ) + + endif() + + #add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) + #link_libraries(OpenSSL) + +else() + include(FindOpenSSL) + + if (NOT ${OPENSSL_FOUND}) + message(FATAL_ERROR "Unable to find OpenSSL") + endif() + + include_directories(${OPENSSL_INCLUDE_DIR}) + link_libraries(${OPENSSL_LIBRARIES}) +endif()
--- a/Resources/CMake/SQLiteConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/SQLiteConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,43 +1,43 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) - SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) - DownloadPackage( - "5fbeff9645ab035a1f580e90b279a16d" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" - "${SQLITE_SOURCES_DIR}") - - list(APPEND THIRD_PARTY_SOURCES - ${SQLITE_SOURCES_DIR}/sqlite3.c - ) - - add_definitions( - # For SQLite to run in the "Serialized" thread-safe mode - # http://www.sqlite.org/threadsafe.html - -DSQLITE_THREADSAFE=1 - -DSQLITE_OMIT_LOAD_EXTENSION # Disable SQLite plugins - ) - - include_directories( - ${SQLITE_SOURCES_DIR} - ) - - source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) - -else() - CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) - if (NOT HAVE_SQLITE_H) - message(FATAL_ERROR "Please install the libsqlite3-dev package") - endif() - - # Autodetection of the version of SQLite - file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") - string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) - - message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") - - IF (${SQLITE_VERSION_NUMBER} LESS 3007000) - # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0 - message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") - ENDIF() - - link_libraries(sqlite3) -endif() +if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3071300) + DownloadPackage( + "5fbeff9645ab035a1f580e90b279a16d" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip" + "${SQLITE_SOURCES_DIR}") + + list(APPEND THIRD_PARTY_SOURCES + ${SQLITE_SOURCES_DIR}/sqlite3.c + ) + + add_definitions( + # For SQLite to run in the "Serialized" thread-safe mode + # http://www.sqlite.org/threadsafe.html + -DSQLITE_THREADSAFE=1 + -DSQLITE_OMIT_LOAD_EXTENSION # Disable SQLite plugins + ) + + include_directories( + ${SQLITE_SOURCES_DIR} + ) + + source_group(ThirdParty\\SQLite REGULAR_EXPRESSION ${SQLITE_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(sqlite3.h HAVE_SQLITE_H) + if (NOT HAVE_SQLITE_H) + message(FATAL_ERROR "Please install the libsqlite3-dev package") + endif() + + # Autodetection of the version of SQLite + file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") + string(REGEX REPLACE "#define SQLITE_VERSION_NUMBER(.*)$" "\\1" SQLITE_VERSION_NUMBER ${SQLITE_VERSION_NUMBER1}) + + message("Detected version of SQLite: ${SQLITE_VERSION_NUMBER}") + + IF (${SQLITE_VERSION_NUMBER} LESS 3007000) + # "sqlite3_create_function_v2" is not defined in SQLite < 3.7.0 + message(FATAL_ERROR "SQLite version must be above 3.7.0. Please set the CMake variable USE_SYSTEM_SQLITE to OFF.") + ENDIF() + + link_libraries(sqlite3) +endif()
--- a/Resources/CMake/ZlibConfiguration.cmake Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/CMake/ZlibConfiguration.cmake Wed Apr 16 16:34:09 2014 +0200 @@ -1,42 +1,42 @@ -# This is the minizip distribution to create ZIP files -list(APPEND THIRD_PARTY_SOURCES - ${ORTHANC_ROOT}/Resources/minizip/ioapi.c - ${ORTHANC_ROOT}/Resources/minizip/zip.c - ) - -if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB) - SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) - DownloadPackage( - "60df6a37c56e7c1366cca812414f7b85" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" - "${ZLIB_SOURCES_DIR}") - - include_directories( - ${ZLIB_SOURCES_DIR} - ) - - list(APPEND THIRD_PARTY_SOURCES - ${ZLIB_SOURCES_DIR}/adler32.c - ${ZLIB_SOURCES_DIR}/compress.c - ${ZLIB_SOURCES_DIR}/crc32.c - ${ZLIB_SOURCES_DIR}/deflate.c - ${ZLIB_SOURCES_DIR}/gzclose.c - ${ZLIB_SOURCES_DIR}/gzlib.c - ${ZLIB_SOURCES_DIR}/gzread.c - ${ZLIB_SOURCES_DIR}/gzwrite.c - ${ZLIB_SOURCES_DIR}/infback.c - ${ZLIB_SOURCES_DIR}/inffast.c - ${ZLIB_SOURCES_DIR}/inflate.c - ${ZLIB_SOURCES_DIR}/inftrees.c - ${ZLIB_SOURCES_DIR}/trees.c - ${ZLIB_SOURCES_DIR}/uncompr.c - ${ZLIB_SOURCES_DIR}/zutil.c - ) - -else() - include(FindZLIB) - include_directories(${ZLIB_INCLUDE_DIRS}) - link_libraries(${ZLIB_LIBRARIES}) -endif() - -source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*) +# This is the minizip distribution to create ZIP files +list(APPEND THIRD_PARTY_SOURCES + ${ORTHANC_ROOT}/Resources/minizip/ioapi.c + ${ORTHANC_ROOT}/Resources/minizip/zip.c + ) + +if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB) + SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7) + DownloadPackage( + "60df6a37c56e7c1366cca812414f7b85" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz" + "${ZLIB_SOURCES_DIR}") + + include_directories( + ${ZLIB_SOURCES_DIR} + ) + + list(APPEND THIRD_PARTY_SOURCES + ${ZLIB_SOURCES_DIR}/adler32.c + ${ZLIB_SOURCES_DIR}/compress.c + ${ZLIB_SOURCES_DIR}/crc32.c + ${ZLIB_SOURCES_DIR}/deflate.c + ${ZLIB_SOURCES_DIR}/gzclose.c + ${ZLIB_SOURCES_DIR}/gzlib.c + ${ZLIB_SOURCES_DIR}/gzread.c + ${ZLIB_SOURCES_DIR}/gzwrite.c + ${ZLIB_SOURCES_DIR}/infback.c + ${ZLIB_SOURCES_DIR}/inffast.c + ${ZLIB_SOURCES_DIR}/inflate.c + ${ZLIB_SOURCES_DIR}/inftrees.c + ${ZLIB_SOURCES_DIR}/trees.c + ${ZLIB_SOURCES_DIR}/uncompr.c + ${ZLIB_SOURCES_DIR}/zutil.c + ) + +else() + include(FindZLIB) + include_directories(${ZLIB_INCLUDE_DIRS}) + link_libraries(${ZLIB_LIBRARIES}) +endif() + +source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
--- a/Resources/Configuration.json Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Configuration.json Wed Apr 16 16:34:09 2014 +0200 @@ -1,136 +1,164 @@ -{ - /** - * General configuration of Orthanc - **/ - - // The logical name of this instance of Orthanc. This one is - // displayed in Orthanc Explorer and at the URI "/system". - "Name" : "MyOrthanc", - - // Path to the directory that holds the heavyweight files - // (i.e. the raw DICOM instances) - "StorageDirectory" : "OrthancStorage", - - // Path to the directory that holds the SQLite index (if unset, - // the value of StorageDirectory is used). This index could be - // stored on a RAM-drive or a SSD device for performance reasons. - "IndexDirectory" : "OrthancStorage", - - // Enable the transparent compression of the DICOM instances - "StorageCompression" : false, - - // Maximum size of the storage in MB (a value of "0" indicates no - // limit on the storage size) - "MaximumStorageSize" : 0, - - // Maximum number of patients that can be stored at a given time - // in the storage (a value of "0" indicates no limit on the number - // of patients) - "MaximumPatientCount" : 0, - - // List of paths to the custom Lua scripts to load into this - // instance of Orthanc - "LuaScripts" : [ - ], - - - - /** - * Configuration of the HTTP server - **/ - - // HTTP port for the REST services and for the GUI - "HttpPort" : 8042, - - - - /** - * Configuration of the DICOM server - **/ - - // The DICOM Application Entity Title - "DicomAet" : "ORTHANC", - - // Check whether the called AET corresponds during a DICOM request - "DicomCheckCalledAet" : false, - - // The DICOM port - "DicomPort" : 4242, - - - - /** - * Security-related options for the HTTP server - **/ - - // Whether remote hosts can connect to the HTTP server - "RemoteAccessAllowed" : false, - - // Whether or not SSL is enabled - "SslEnabled" : false, - - // Path to the SSL certificate (meaningful only if SSL is enabled) - "SslCertificate" : "certificate.pem", - - // Whether or not the password protection is enabled - "AuthenticationEnabled" : false, - - // The list of the registered users. Because Orthanc uses HTTP - // Basic Authentication, the passwords are stored as plain text. - "RegisteredUsers" : { - // "alice" : "alicePassword" - }, - - - - /** - * Network topology - **/ - - // The list of the known DICOM modalities - "DicomModalities" : { - /** - * Uncommenting the following line would enable Orthanc to - * connect to an instance of the "storescp" open-source DICOM - * store (shipped in the DCMTK distribution) started by the - * command line "storescp 2000". - **/ - // "sample" : [ "STORESCP", "localhost", 2000 ] - - /** - * A fourth parameter is available to enable patches for a - * specific PACS manufacturer. The allowed values are currently - * "Generic" (default value), "ClearCanvas" and "MedInria". This - * parameter is case-sensitive. - **/ - // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] - }, - - // The list of the known Orthanc peers - "OrthancPeers" : { - /** - * Each line gives the base URL of an Orthanc peer, possibly - * followed by the username/password pair (if the password - * protection is enabled on the peer). - **/ - // "peer" : [ "http://localhost:8043/", "alice", "alicePassword" ] - // "peer2" : [ "http://localhost:8044/" ] - }, - - - - /** - * Advanced options - **/ - - // Dictionary of symbolic names for the user-defined metadata. Each - // entry must map a number between 1024 and 65535 to an unique - // string. - "UserMetadata" : { - // "Sample" : 1024 - }, - - // Number of seconds without receiving any instance before a - // patient, a study or a series is considered as stable. - "StableAge" : 60 -} +{ + /** + * General configuration of Orthanc + **/ + + // The logical name of this instance of Orthanc. This one is + // displayed in Orthanc Explorer and at the URI "/system". + "Name" : "MyOrthanc", + + // Path to the directory that holds the heavyweight files + // (i.e. the raw DICOM instances) + "StorageDirectory" : "OrthancStorage", + + // Path to the directory that holds the SQLite index (if unset, + // the value of StorageDirectory is used). This index could be + // stored on a RAM-drive or a SSD device for performance reasons. + "IndexDirectory" : "OrthancStorage", + + // Enable the transparent compression of the DICOM instances + "StorageCompression" : false, + + // Maximum size of the storage in MB (a value of "0" indicates no + // limit on the storage size) + "MaximumStorageSize" : 0, + + // Maximum number of patients that can be stored at a given time + // in the storage (a value of "0" indicates no limit on the number + // of patients) + "MaximumPatientCount" : 0, + + // List of paths to the custom Lua scripts to load into this + // instance of Orthanc + "LuaScripts" : [ + ], + + + + /** + * Configuration of the HTTP server + **/ + + // HTTP port for the REST services and for the GUI + "HttpPort" : 8042, + + + + /** + * Configuration of the DICOM server + **/ + + // The DICOM Application Entity Title + "DicomAet" : "ORTHANC", + + // Check whether the called AET corresponds during a DICOM request + "DicomCheckCalledAet" : false, + + // The DICOM port + "DicomPort" : 4242, + + + + /** + * Security-related options for the HTTP server + **/ + + // Whether remote hosts can connect to the HTTP server + "RemoteAccessAllowed" : false, + + // Whether or not SSL is enabled + "SslEnabled" : false, + + // Path to the SSL certificate (meaningful only if SSL is enabled) + "SslCertificate" : "certificate.pem", + + // Whether or not the password protection is enabled + "AuthenticationEnabled" : false, + + // The list of the registered users. Because Orthanc uses HTTP + // Basic Authentication, the passwords are stored as plain text. + "RegisteredUsers" : { + // "alice" : "alicePassword" + }, + + + + /** + * Network topology + **/ + + // The list of the known DICOM modalities + "DicomModalities" : { + /** + * Uncommenting the following line would enable Orthanc to + * connect to an instance of the "storescp" open-source DICOM + * store (shipped in the DCMTK distribution) started by the + * command line "storescp 2000". + **/ + // "sample" : [ "STORESCP", "localhost", 2000 ] + + /** + * A fourth parameter is available to enable patches for a + * specific PACS manufacturer. The allowed values are currently + * "Generic" (default value), "ClearCanvas", "MedInria" and + * "Dcm4Chee". This parameter is case-sensitive. + **/ + // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] + }, + + // The list of the known Orthanc peers + "OrthancPeers" : { + /** + * Each line gives the base URL of an Orthanc peer, possibly + * followed by the username/password pair (if the password + * protection is enabled on the peer). + **/ + // "peer" : [ "http://localhost:8043/", "alice", "alicePassword" ] + // "peer2" : [ "http://localhost:8044/" ] + }, + + + + /** + * Advanced options + **/ + + // Dictionary of symbolic names for the user-defined metadata. Each + // entry must map a number between 1024 and 65535 to an unique + // string. + "UserMetadata" : { + // "Sample" : 1024 + }, + + // Dictionary of symbolic names for the user-defined types of + // attached files. Each entry must map a number between 1024 and + // 65535 to an unique string. + "UserContentType" : { + // "sample" : 1024 + }, + + // Number of seconds without receiving any instance before a + // patient, a study or a series is considered as stable. + "StableAge" : 60, + + // Enable the HTTP server. If this parameter is set to "false", + // Orthanc acts as a pure DICOM server. The REST API and Orthanc + // Explorer will not be available. + "HttpServerEnabled" : true, + + // Enable the DICOM server. If this parameter is set to "false", + // Orthanc acts as a pure REST server. It will not be possible to + // receive files or to do query/retrieve through the DICOM protocol. + "DicomServerEnabled" : true, + + // By default, Orthanc compares AET (Application Entity Titles) in a + // case-insensitive way. Setting this option to "true" will enable + // case-sensitive matching. + "StrictAetComparison" : false, + + // When the following option is "true", the MD5 of the DICOM files + // will be computed and stored in the Orthanc database. This + // information can be used to detect disk corruption, at the price + // of a small performance overhead. + "StoreMD5ForAttachments" : true +}
--- a/Resources/EmbedResources.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/EmbedResources.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,5 +1,5 @@ # Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, +# Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, # Belgium # # This program is free software: you can redistribute it and/or
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,5 +1,23 @@ #!/usr/bin/python +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + import os import sys import os.path @@ -21,16 +39,19 @@ URL = 'http://%s:%d/instances' % (sys.argv[1], int(sys.argv[2])) -success = 0 +success_count = 0 +total_file_count = 0 # This function will upload a single file to Orthanc through the REST API def UploadFile(path): - global success + global success_count + global total_file_count f = open(path, "rb") content = f.read() f.close() + total_file_count += 1 try: sys.stdout.write("Importing %s" % path) @@ -51,14 +72,14 @@ # not always work) # http://en.wikipedia.org/wiki/Basic_access_authentication headers['authorization'] = 'Basic ' + base64.b64encode(username + ':' + password) - + resp, content = h.request(URL, 'POST', body = content, headers = headers) if resp.status == 200: sys.stdout.write(" => success\n") - success += 1 + success_count += 1 else: sys.stdout.write(" => failure (Is it a DICOM file?)\n") @@ -74,6 +95,8 @@ for root, dirs, files in os.walk(sys.argv[3]): for f in files: UploadFile(os.path.join(root, f)) - -print("\nSummary: %d DICOM file(s) have been imported" % success) +if success_count == total_file_count: + print("\nSummary: all %d DICOM file(s) have been imported successfully" % success_count) +else: + print("\nSummary: %d out of %d files have been imported successfully as DICOM instances" % (success_count, total_file_count))
--- a/Resources/Samples/OrthancClient/Basic/main.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/OrthancClient/Basic/main.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Permission is hereby granted, free of charge, to any person @@ -57,6 +57,10 @@ for (unsigned int l = 0; l < series.GetInstanceCount(); l++) { std::cout << " Instance: " << series.GetInstance(l).GetId() << std::endl; + + // Load and display some raw DICOM tag + series.GetInstance(l).LoadTagContent("0020-000d"); + std::cout << " SOP instance UID: " << series.GetInstance(l).GetLoadedTagContent() << std::endl; } } }
--- a/Resources/Samples/OrthancClient/Vtk/main.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/OrthancClient/Vtk/main.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,6 @@ /** * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * Permission is hereby granted, free of charge, to any person
--- a/Resources/Samples/Python/AnonymizeAllPatients.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/Python/AnonymizeAllPatients.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042'
--- a/Resources/Samples/Python/ChangesLoop.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/Python/ChangesLoop.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,5 +1,24 @@ #!/usr/bin/python +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + import time import sys import RestToolbox
--- a/Resources/Samples/Python/DownloadAnonymized.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/Python/DownloadAnonymized.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042'
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,6 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + URL = 'http://localhost:8042' TARGET = 'sample'
--- a/Resources/Samples/Python/RestToolbox.py Fri Jan 24 17:40:45 2014 +0100 +++ b/Resources/Samples/Python/RestToolbox.py Wed Apr 16 16:34:09 2014 +0200 @@ -1,3 +1,21 @@ +# 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. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + import httplib2 import json from urllib import urlencode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Tools/CMakeLists.txt Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8) + +project(OrthancTools) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # Linking with "pthread" is necessary, otherwise the software crashes + # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 + link_libraries(pthread dl) +endif() + +set(STATIC_BUILD ON) +set(ALLOW_DOWNLOADS ON) + +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) + +add_library(CommonLibraries + ${BOOST_SOURCES} + ${THIRD_PARTY_SOURCES} + ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Toolbox.cpp + ${ORTHANC_ROOT}/Core/Uuid.cpp + ${ORTHANC_ROOT}/Resources/md5/md5.c + ${ORTHANC_ROOT}/Resources/base64/base64.cpp + ) + +add_executable(RecoverCompressedFile + RecoverCompressedFile.cpp + ${ORTHANC_ROOT}/Core/Compression/BufferCompressor.cpp + ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp + ) + +target_link_libraries(RecoverCompressedFile CommonLibraries)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,76 @@ +/** + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../../Core/Compression/ZlibCompressor.h" +#include "../../../Core/Toolbox.h" +#include "../../../Core/OrthancException.h" + +#include <stdio.h> + +int main(int argc, const char* argv[]) +{ + if (argc != 2 && argc != 3) + { + fprintf(stderr, "Maintenance tool to recover a DICOM file that was compressed by Orthanc.\n\n"); + fprintf(stderr, "Usage: %s <input> [output]\n", argv[0]); + fprintf(stderr, "If \"output\" is not given, the data will be output to stdout\n"); + return -1; + } + + try + { + fprintf(stderr, "Reading the file into memory...\n"); + fflush(stderr); + + std::string content; + Orthanc::Toolbox::ReadFile(content, argv[1]); + + fprintf(stderr, "Decompressing the content of the file...\n"); + fflush(stderr); + + Orthanc::ZlibCompressor compressor; + std::string uncompressed; + compressor.Uncompress(uncompressed, content); + + fprintf(stderr, "Writing the uncompressed data...\n"); + fflush(stderr); + + if (argc == 3) + { + Orthanc::Toolbox::WriteFile(uncompressed, argv[2]); + } + else + { + if (uncompressed.size() > 0) + { + fwrite(&uncompressed[0], uncompressed.size(), 1, stdout); + } + } + + fprintf(stderr, "Done!\n"); + } + catch (Orthanc::OrthancException& e) + { + fprintf(stderr, "Error: %s\n", e.What()); + return -1; + } + + return 0; +}
--- a/THANKS Fri Jan 24 17:40:45 2014 +0100 +++ b/THANKS Wed Apr 16 16:34:09 2014 +0200 @@ -19,6 +19,7 @@ * Peter Somlo <peter.somlo@gmail.com>, for ClearCanvas and JPEG support. * 12maksqwe@gmail.com, for fixing issue #8. * Julien Nabet, for various suggestions to improve the source code. +* Karsten Hilbert <Karsten.Hilbert@gmx.net>, for in-depth testing. Debian/Ubuntu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/DicomMap.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,97 @@ +#include "gtest/gtest.h" + +#include "../Core/Uuid.h" +#include "../Core/OrthancException.h" +#include "../Core/DicomFormat/DicomMap.h" +#include "../Core/DicomFormat/DicomNullValue.h" + +#include <memory> + +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<DicomTag> 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<DicomMap> 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)); +}
--- a/UnitTestsSources/FileStorage.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/FileStorage.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -15,6 +15,16 @@ using namespace Orthanc; + +static void StringToVector(std::vector<uint8_t>& 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("FileStorageUnitTests"); @@ -25,6 +35,21 @@ 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("FileStorageUnitTests"); + + std::vector<uint8_t> 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) @@ -99,6 +124,27 @@ } +TEST(FileStorageAccessor, NoCompression2) +{ + FileStorage s("FileStorageUnitTests"); + CompressedFileStorageAccessor accessor(s); + + accessor.SetCompressionForNextOperations(CompressionType_None); + std::vector<uint8_t> 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("FileStorageUnitTests");
--- a/UnitTestsSources/Lua.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/Lua.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -3,7 +3,7 @@ #include "../Core/Lua/LuaFunctionCall.h" -TEST(Lua, Simple) +TEST(Lua, Json) { Orthanc::LuaContext lua; lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); @@ -70,3 +70,34 @@ 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(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/MultiThreading.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,187 @@ +#include "gtest/gtest.h" + +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/MultiThreading/ArrayFilledByThreads.h" +#include "../Core/MultiThreading/ThreadedCommandProcessor.h" + +using namespace Orthanc; + +namespace +{ + class DynamicInteger : public ICommand + { + private: + int value_; + std::set<int>& target_; + + public: + DynamicInteger(int value, std::set<int>& 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<int> 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<int> GetSet() + { + return set_; + } + }; +} + + + + +TEST(MultiThreading, SharedMessageQueueBasic) +{ + std::set<int> 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<DynamicInteger> i; + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); + ASSERT_FALSE(q.WaitEmpty(1)); + i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); + ASSERT_TRUE(q.WaitEmpty(0)); + ASSERT_EQ(NULL, q.Dequeue(1)); +} + + +TEST(MultiThreading, SharedMessageQueueClean) +{ + std::set<int> 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<DynamicInteger&>(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<DynamicInteger&>(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<DynamicInteger&>(a.GetItem(i)).GetValue()); + } +} + + +TEST(MultiThreading, CommandProcessor) +{ + ThreadedCommandProcessor p(4); + + std::set<int> 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()); + } +}
--- a/UnitTestsSources/Png.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/Png.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -4,6 +4,7 @@ #include "../Core/FileFormats/PngReader.h" #include "../Core/FileFormats/PngWriter.h" #include "../Core/Toolbox.h" +#include "../Core/Uuid.h" TEST(PngWriter, ColorPattern) @@ -107,19 +108,46 @@ std::string s; w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - Orthanc::PngReader r; - r.ReadFromMemory(s); + { + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); - ASSERT_EQ(r.GetWidth(), width); - ASSERT_EQ(r.GetHeight(), height); + v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetBuffer(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } - v = 0; - for (int y = 0; y < height; y++) { - uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r.GetBuffer() + y * r.GetPitch()); - for (int x = 0; x < width; x++, 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++) { - ASSERT_EQ(*p, v); + uint16_t *p = reinterpret_cast<uint16_t*>((uint8_t*) r2.GetBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetBuffer(y)); + for (int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } } } }
--- a/UnitTestsSources/RestApi.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/RestApi.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -3,6 +3,8 @@ #include <ctype.h> #include <glog/logging.h> +#include "../Core/ChunkedBuffer.h" +#include "../Core/HttpClient.h" #include "../Core/RestApi/RestApi.h" #include "../Core/Uuid.h" #include "../Core/OrthancException.h" @@ -10,6 +12,51 @@ 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;
--- a/UnitTestsSources/SQLite.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/SQLite.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -236,3 +236,66 @@ 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_FLOAT_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()); + } +}
--- a/UnitTestsSources/ServerIndex.cpp Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,497 +0,0 @@ -#include "gtest/gtest.h" - -#include "../OrthancServer/DatabaseWrapper.h" -#include "../Core/Uuid.h" - -#include <ctype.h> -#include <glog/logging.h> -#include <algorithm> - -using namespace Orthanc; - -namespace -{ - class ServerIndexListener : public IServerIndexListener - { - public: - std::vector<std::string> deletedFiles_; - std::string ancestorId_; - ResourceType ancestorType_; - - void Reset() - { - ancestorId_ = ""; - deletedFiles_.clear(); - } - - virtual void SignalRemainingAncestor(ResourceType type, - const std::string& publicId) - { - ancestorId_ = publicId; - ancestorType_ = type; - } - - virtual void SignalFileDeleted(const FileInfo& info) - { - const std::string fileUuid = info.GetUuid(); - deletedFiles_.push_back(fileUuid); - LOG(INFO) << "A file must be removed: " << fileUuid; - } - }; -} - - -TEST(DatabaseWrapper, Simple) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - int64_t a[] = { - index.CreateResource("a", ResourceType_Patient), // 0 - index.CreateResource("b", ResourceType_Study), // 1 - index.CreateResource("c", ResourceType_Series), // 2 - index.CreateResource("d", ResourceType_Instance), // 3 - index.CreateResource("e", ResourceType_Instance), // 4 - index.CreateResource("f", ResourceType_Instance), // 5 - index.CreateResource("g", ResourceType_Study) // 6 - }; - - ASSERT_EQ("a", index.GetPublicId(a[0])); - ASSERT_EQ("b", index.GetPublicId(a[1])); - ASSERT_EQ("c", index.GetPublicId(a[2])); - ASSERT_EQ("d", index.GetPublicId(a[3])); - ASSERT_EQ("e", index.GetPublicId(a[4])); - ASSERT_EQ("f", index.GetPublicId(a[5])); - ASSERT_EQ("g", index.GetPublicId(a[6])); - - ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0])); - ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1])); - ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4])); - ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5])); - ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6])); - - { - Json::Value t; - index.GetAllPublicIds(t, ResourceType_Patient); - - ASSERT_EQ(1u, t.size()); - ASSERT_EQ("a", t[0u].asString()); - - index.GetAllPublicIds(t, ResourceType_Series); - ASSERT_EQ(1u, t.size()); - ASSERT_EQ("c", t[0u].asString()); - - index.GetAllPublicIds(t, ResourceType_Study); - ASSERT_EQ(2u, t.size()); - - index.GetAllPublicIds(t, ResourceType_Instance); - ASSERT_EQ(3u, t.size()); - } - - index.SetGlobalProperty(GlobalProperty_FlushSleep, "World"); - - index.AttachChild(a[0], a[1]); - index.AttachChild(a[1], a[2]); - index.AttachChild(a[2], a[3]); - index.AttachChild(a[2], a[4]); - index.AttachChild(a[6], a[5]); - - int64_t parent; - ASSERT_FALSE(index.LookupParent(parent, a[0])); - ASSERT_TRUE(index.LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent); - ASSERT_TRUE(index.LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent); - ASSERT_TRUE(index.LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent); - ASSERT_TRUE(index.LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent); - ASSERT_TRUE(index.LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent); - ASSERT_FALSE(index.LookupParent(parent, a[6])); - - std::string s; - - ASSERT_FALSE(index.GetParentPublicId(s, a[0])); - ASSERT_FALSE(index.GetParentPublicId(s, a[6])); - ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s); - ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s); - - std::list<std::string> l; - index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front()); - index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front()); - index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); - index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front()); - - index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); - if (l.front() == "d") - { - ASSERT_EQ("e", l.back()); - } - else - { - ASSERT_EQ("d", l.back()); - ASSERT_EQ("e", l.front()); - } - - std::list<MetadataType> md; - index.ListAvailableMetadata(md, a[4]); - ASSERT_EQ(0u, md.size()); - - index.AddAttachment(a[4], FileInfo("my json file", FileContentType_Json, 42, CompressionType_Zlib, 21)); - index.AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42)); - index.AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44)); - index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE"); - - index.ListAvailableMetadata(md, a[4]); - ASSERT_EQ(1u, md.size()); - ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); - index.SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU"); - index.ListAvailableMetadata(md, a[4]); - ASSERT_EQ(2u, md.size()); - index.DeleteMetadata(a[4], MetadataType_ModifiedFrom); - index.ListAvailableMetadata(md, a[4]); - ASSERT_EQ(1u, md.size()); - ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); - - ASSERT_EQ(21u + 42u + 44u, index.GetTotalCompressedSize()); - ASSERT_EQ(42u + 42u + 44u, index.GetTotalUncompressedSize()); - - DicomMap m; - m.SetValue(0x0010, 0x0010, "PatientName"); - index.SetMainDicomTags(a[3], m); - - int64_t b; - ResourceType t; - ASSERT_TRUE(index.LookupResource("g", b, t)); - ASSERT_EQ(7, b); - ASSERT_EQ(ResourceType_Study, t); - - ASSERT_TRUE(index.LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet)); - ASSERT_FALSE(index.LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries)); - ASSERT_EQ("PINNACLE", s); - ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet)); - ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None")); - - ASSERT_TRUE(index.LookupGlobalProperty(s, GlobalProperty_FlushSleep)); - ASSERT_FALSE(index.LookupGlobalProperty(s, static_cast<GlobalProperty>(42))); - ASSERT_EQ("World", s); - ASSERT_EQ("World", index.GetGlobalProperty(GlobalProperty_FlushSleep)); - ASSERT_EQ("None", index.GetGlobalProperty(static_cast<GlobalProperty>(42), "None")); - - FileInfo att; - ASSERT_TRUE(index.LookupAttachment(att, a[4], FileContentType_Json)); - ASSERT_EQ("my json file", att.GetUuid()); - ASSERT_EQ(21u, att.GetCompressedSize()); - ASSERT_EQ(42u, att.GetUncompressedSize()); - ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType()); - - ASSERT_EQ(0u, listener.deletedFiles_.size()); - ASSERT_EQ(7u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(3u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(1u, index.GetTableRecordCount("Metadata")); - ASSERT_EQ(1u, index.GetTableRecordCount("MainDicomTags")); - index.DeleteResource(a[0]); - - ASSERT_EQ(2u, listener.deletedFiles_.size()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "my json file") == listener.deletedFiles_.end()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "my dicom file") == listener.deletedFiles_.end()); - - ASSERT_EQ(2u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("Metadata")); - ASSERT_EQ(1u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(0u, index.GetTableRecordCount("MainDicomTags")); - index.DeleteResource(a[5]); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("AttachedFiles")); - ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties")); - - ASSERT_EQ(3u, listener.deletedFiles_.size()); - ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), - listener.deletedFiles_.end(), - "world") == listener.deletedFiles_.end()); -} - - - - -TEST(DatabaseWrapper, Upward) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - int64_t a[] = { - index.CreateResource("a", ResourceType_Patient), // 0 - index.CreateResource("b", ResourceType_Study), // 1 - index.CreateResource("c", ResourceType_Series), // 2 - index.CreateResource("d", ResourceType_Instance), // 3 - index.CreateResource("e", ResourceType_Instance), // 4 - index.CreateResource("f", ResourceType_Study), // 5 - index.CreateResource("g", ResourceType_Series), // 6 - index.CreateResource("h", ResourceType_Series) // 7 - }; - - index.AttachChild(a[0], a[1]); - index.AttachChild(a[1], a[2]); - index.AttachChild(a[2], a[3]); - index.AttachChild(a[2], a[4]); - index.AttachChild(a[1], a[6]); - index.AttachChild(a[0], a[5]); - index.AttachChild(a[5], a[7]); - - { - Json::Value j; - index.GetChildren(j, a[0]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") || - (j[1u] == "b" && j[0u] == "f")); - - index.GetChildren(j, a[1]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") || - (j[1u] == "c" && j[0u] == "g")); - - index.GetChildren(j, a[2]); - ASSERT_EQ(2u, j.size()); - ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") || - (j[1u] == "d" && j[0u] == "e")); - - index.GetChildren(j, a[3]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[4]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString()); - index.GetChildren(j, a[6]); ASSERT_EQ(0u, j.size()); - index.GetChildren(j, a[7]); ASSERT_EQ(0u, j.size()); - } - - listener.Reset(); - index.DeleteResource(a[3]); - ASSERT_EQ("c", listener.ancestorId_); - ASSERT_EQ(ResourceType_Series, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[4]); - ASSERT_EQ("b", listener.ancestorId_); - ASSERT_EQ(ResourceType_Study, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[7]); - ASSERT_EQ("a", listener.ancestorId_); - ASSERT_EQ(ResourceType_Patient, listener.ancestorType_); - - listener.Reset(); - index.DeleteResource(a[6]); - ASSERT_EQ("", listener.ancestorId_); // No more ancestor -} - - -TEST(DatabaseWrapper, PatientRecycling) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - std::vector<int64_t> patients; - for (int i = 0; i < 10; i++) - { - std::string p = "Patient " + boost::lexical_cast<std::string>(i); - patients.push_back(index.CreateResource(p, ResourceType_Patient)); - index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); - ASSERT_FALSE(index.IsProtectedPatient(patients[i])); - } - - ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); - - listener.Reset(); - - index.DeleteResource(patients[5]); - index.DeleteResource(patients[0]); - ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(2u, listener.deletedFiles_.size()); - ASSERT_EQ("Patient 5", listener.deletedFiles_[0]); - ASSERT_EQ("Patient 0", listener.deletedFiles_[1]); - - int64_t p; - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); - index.DeleteResource(p); - index.DeleteResource(patients[8]); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); - index.DeleteResource(p); - ASSERT_FALSE(index.SelectPatientToRecycle(p)); - - ASSERT_EQ(10u, listener.deletedFiles_.size()); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); -} - - -TEST(DatabaseWrapper, PatientProtection) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - std::vector<int64_t> patients; - for (int i = 0; i < 5; i++) - { - std::string p = "Patient " + boost::lexical_cast<std::string>(i); - patients.push_back(index.CreateResource(p, ResourceType_Patient)); - index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10)); - ASSERT_FALSE(index.IsProtectedPatient(patients[i])); - } - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[2], false); - ASSERT_FALSE(index.IsProtectedPatient(patients[2])); - ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); - index.SetProtectedPatient(patients[3], true); - ASSERT_TRUE(index.IsProtectedPatient(patients[3])); - ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); - - ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, listener.deletedFiles_.size()); - - // Unprotecting a patient puts it at the last position in the recycling queue - int64_t p; - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); - index.DeleteResource(p); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); - index.DeleteResource(p); - ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[2])); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); - index.DeleteResource(p); - // "patients[3]" is still protected - ASSERT_FALSE(index.SelectPatientToRecycle(p)); - - ASSERT_EQ(4u, listener.deletedFiles_.size()); - ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); - - index.SetProtectedPatient(patients[3], false); - ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); - ASSERT_FALSE(index.SelectPatientToRecycle(p, patients[3])); - ASSERT_TRUE(index.SelectPatientToRecycle(p, patients[2])); - ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); - index.DeleteResource(p); - - ASSERT_EQ(5u, listener.deletedFiles_.size()); - ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); - ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); -} - - - -TEST(DatabaseWrapper, Sequence) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - ASSERT_EQ(1u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(2u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(3u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); - ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); -} - - - -TEST(DatabaseWrapper, LookupTagValue) -{ - ServerIndexListener listener; - DatabaseWrapper index(listener); - - int64_t a[] = { - index.CreateResource("a", ResourceType_Study), // 0 - index.CreateResource("b", ResourceType_Study), // 1 - index.CreateResource("c", ResourceType_Study), // 2 - index.CreateResource("d", ResourceType_Series) // 3 - }; - - DicomMap m; - m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[0], m); - m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index.SetMainDicomTags(a[1], m); - m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index.SetMainDicomTags(a[2], m); - m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index.SetMainDicomTags(a[3], m); - - std::list<int64_t> s; - - index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); - ASSERT_EQ(2u, s.size()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); - - index.LookupTagValue(s, "0"); - ASSERT_EQ(3u, s.size()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end()); - - index.LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); - ASSERT_EQ(1u, s.size()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); - - index.LookupTagValue(s, "1"); - ASSERT_EQ(1u, s.size()); - ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); - - - /*{ - std::list<std::string> s; - context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); - for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++) - { - std::cout << "*** " << *i << std::endl;; - } - }*/ - - -} - - -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)); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ServerIndexTests.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,573 @@ +#include "gtest/gtest.h" + +#include "../OrthancServer/DatabaseWrapper.h" +#include "../OrthancServer/ServerContext.h" +#include "../OrthancServer/ServerIndex.h" +#include "../Core/Uuid.h" +#include "../Core/DicomFormat/DicomNullValue.h" + +#include <ctype.h> +#include <glog/logging.h> +#include <algorithm> + +using namespace Orthanc; + +namespace +{ + enum DatabaseWrapperClass + { + DatabaseWrapperClass_SQLite + }; + + + class ServerIndexListener : public IServerIndexListener + { + public: + std::vector<std::string> deletedFiles_; + std::string ancestorId_; + ResourceType ancestorType_; + + void Reset() + { + ancestorId_ = ""; + deletedFiles_.clear(); + } + + virtual void SignalRemainingAncestor(ResourceType type, + const std::string& publicId) + { + ancestorId_ = publicId; + ancestorType_ = type; + } + + virtual void SignalFileDeleted(const FileInfo& info) + { + const std::string fileUuid = info.GetUuid(); + deletedFiles_.push_back(fileUuid); + LOG(INFO) << "A file must be removed: " << fileUuid; + } + }; + + + class DatabaseWrapperTest : public ::testing::TestWithParam<DatabaseWrapperClass> + { + protected: + std::auto_ptr<ServerIndexListener> listener_; + std::auto_ptr<DatabaseWrapper> index_; + + DatabaseWrapperTest() + { + } + + virtual void SetUp() + { + listener_.reset(new ServerIndexListener); + index_.reset(new DatabaseWrapper(*listener_)); + } + + virtual void TearDown() + { + index_.reset(NULL); + listener_.reset(NULL); + } + }; +} + + +INSTANTIATE_TEST_CASE_P(DatabaseWrapperName, + DatabaseWrapperTest, + ::testing::Values(DatabaseWrapperClass_SQLite)); + + +TEST_P(DatabaseWrapperTest, Simple) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Patient), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Series), // 2 + index_->CreateResource("d", ResourceType_Instance), // 3 + index_->CreateResource("e", ResourceType_Instance), // 4 + index_->CreateResource("f", ResourceType_Instance), // 5 + index_->CreateResource("g", ResourceType_Study) // 6 + }; + + ASSERT_EQ("a", index_->GetPublicId(a[0])); + ASSERT_EQ("b", index_->GetPublicId(a[1])); + ASSERT_EQ("c", index_->GetPublicId(a[2])); + ASSERT_EQ("d", index_->GetPublicId(a[3])); + ASSERT_EQ("e", index_->GetPublicId(a[4])); + ASSERT_EQ("f", index_->GetPublicId(a[5])); + ASSERT_EQ("g", index_->GetPublicId(a[6])); + + ASSERT_EQ(ResourceType_Patient, index_->GetResourceType(a[0])); + ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[1])); + ASSERT_EQ(ResourceType_Series, index_->GetResourceType(a[2])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[3])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[4])); + ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[5])); + ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[6])); + + { + Json::Value t; + index_->GetAllPublicIds(t, ResourceType_Patient); + + ASSERT_EQ(1u, t.size()); + ASSERT_EQ("a", t[0u].asString()); + + index_->GetAllPublicIds(t, ResourceType_Series); + ASSERT_EQ(1u, t.size()); + ASSERT_EQ("c", t[0u].asString()); + + index_->GetAllPublicIds(t, ResourceType_Study); + ASSERT_EQ(2u, t.size()); + + index_->GetAllPublicIds(t, ResourceType_Instance); + ASSERT_EQ(3u, t.size()); + } + + index_->SetGlobalProperty(GlobalProperty_FlushSleep, "World"); + + index_->AttachChild(a[0], a[1]); + index_->AttachChild(a[1], a[2]); + index_->AttachChild(a[2], a[3]); + index_->AttachChild(a[2], a[4]); + index_->AttachChild(a[6], a[5]); + + int64_t parent; + ASSERT_FALSE(index_->LookupParent(parent, a[0])); + ASSERT_TRUE(index_->LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent); + ASSERT_TRUE(index_->LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent); + ASSERT_FALSE(index_->LookupParent(parent, a[6])); + + std::string s; + + ASSERT_FALSE(index_->GetParentPublicId(s, a[0])); + ASSERT_FALSE(index_->GetParentPublicId(s, a[6])); + ASSERT_TRUE(index_->GetParentPublicId(s, a[1])); ASSERT_EQ("a", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[2])); ASSERT_EQ("b", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[3])); ASSERT_EQ("c", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[4])); ASSERT_EQ("c", s); + ASSERT_TRUE(index_->GetParentPublicId(s, a[5])); ASSERT_EQ("g", s); + + std::list<std::string> l; + index_->GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front()); + index_->GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front()); + index_->GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); + index_->GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front()); + + index_->GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); + if (l.front() == "d") + { + ASSERT_EQ("e", l.back()); + } + else + { + ASSERT_EQ("d", l.back()); + ASSERT_EQ("e", l.front()); + } + + std::list<MetadataType> md; + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(0u, md.size()); + + index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", + CompressionType_Zlib, 21, "compressedMD5")); + index_->AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5")); + index_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5")); + index_->SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE"); + + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); + index_->SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU"); + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(2u, md.size()); + index_->DeleteMetadata(a[4], MetadataType_ModifiedFrom); + index_->ListAvailableMetadata(md, a[4]); + ASSERT_EQ(1u, md.size()); + ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front()); + + ASSERT_EQ(21u + 42u + 44u, index_->GetTotalCompressedSize()); + ASSERT_EQ(42u + 42u + 44u, index_->GetTotalUncompressedSize()); + + DicomMap m; + m.SetValue(0x0010, 0x0010, "PatientName"); + index_->SetMainDicomTags(a[3], m); + + int64_t b; + ResourceType t; + ASSERT_TRUE(index_->LookupResource("g", b, t)); + ASSERT_EQ(7, b); + ASSERT_EQ(ResourceType_Study, t); + + ASSERT_TRUE(index_->LookupMetadata(s, a[4], MetadataType_Instance_RemoteAet)); + ASSERT_FALSE(index_->LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries)); + ASSERT_EQ("PINNACLE", s); + ASSERT_EQ("PINNACLE", index_->GetMetadata(a[4], MetadataType_Instance_RemoteAet)); + ASSERT_EQ("None", index_->GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None")); + + ASSERT_TRUE(index_->LookupGlobalProperty(s, GlobalProperty_FlushSleep)); + ASSERT_FALSE(index_->LookupGlobalProperty(s, static_cast<GlobalProperty>(42))); + ASSERT_EQ("World", s); + ASSERT_EQ("World", index_->GetGlobalProperty(GlobalProperty_FlushSleep)); + ASSERT_EQ("None", index_->GetGlobalProperty(static_cast<GlobalProperty>(42), "None")); + + FileInfo att; + ASSERT_TRUE(index_->LookupAttachment(att, a[4], FileContentType_DicomAsJson)); + ASSERT_EQ("my json file", att.GetUuid()); + ASSERT_EQ(21u, att.GetCompressedSize()); + ASSERT_EQ("md5", att.GetUncompressedMD5()); + ASSERT_EQ("compressedMD5", att.GetCompressedMD5()); + ASSERT_EQ(42u, att.GetUncompressedSize()); + ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType()); + + ASSERT_TRUE(index_->LookupAttachment(att, a[6], FileContentType_Dicom)); + ASSERT_EQ("world", att.GetUuid()); + ASSERT_EQ(44u, att.GetCompressedSize()); + ASSERT_EQ("md5", att.GetUncompressedMD5()); + ASSERT_EQ("md5", att.GetCompressedMD5()); + ASSERT_EQ(44u, att.GetUncompressedSize()); + ASSERT_EQ(CompressionType_None, att.GetCompressionType()); + + ASSERT_EQ(0u, listener_->deletedFiles_.size()); + ASSERT_EQ(7u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(3u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(1u, index_->GetTableRecordCount("Metadata")); + ASSERT_EQ(1u, index_->GetTableRecordCount("MainDicomTags")); + index_->DeleteResource(a[0]); + + ASSERT_EQ(2u, listener_->deletedFiles_.size()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "my json file") == listener_->deletedFiles_.end()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "my dicom file") == listener_->deletedFiles_.end()); + + ASSERT_EQ(2u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("Metadata")); + ASSERT_EQ(1u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(0u, index_->GetTableRecordCount("MainDicomTags")); + index_->DeleteResource(a[5]); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("AttachedFiles")); + ASSERT_EQ(2u, index_->GetTableRecordCount("GlobalProperties")); + + ASSERT_EQ(3u, listener_->deletedFiles_.size()); + ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), + listener_->deletedFiles_.end(), + "world") == listener_->deletedFiles_.end()); +} + + + + +TEST_P(DatabaseWrapperTest, Upward) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Patient), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Series), // 2 + index_->CreateResource("d", ResourceType_Instance), // 3 + index_->CreateResource("e", ResourceType_Instance), // 4 + index_->CreateResource("f", ResourceType_Study), // 5 + index_->CreateResource("g", ResourceType_Series), // 6 + index_->CreateResource("h", ResourceType_Series) // 7 + }; + + index_->AttachChild(a[0], a[1]); + index_->AttachChild(a[1], a[2]); + index_->AttachChild(a[2], a[3]); + index_->AttachChild(a[2], a[4]); + index_->AttachChild(a[1], a[6]); + index_->AttachChild(a[0], a[5]); + index_->AttachChild(a[5], a[7]); + + { + Json::Value j; + index_->GetChildren(j, a[0]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "b" && j[1u] == "f") || + (j[1u] == "b" && j[0u] == "f")); + + index_->GetChildren(j, a[1]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "c" && j[1u] == "g") || + (j[1u] == "c" && j[0u] == "g")); + + index_->GetChildren(j, a[2]); + ASSERT_EQ(2u, j.size()); + ASSERT_TRUE((j[0u] == "d" && j[1u] == "e") || + (j[1u] == "d" && j[0u] == "e")); + + index_->GetChildren(j, a[3]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[4]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[5]); ASSERT_EQ(1u, j.size()); ASSERT_EQ("h", j[0u].asString()); + index_->GetChildren(j, a[6]); ASSERT_EQ(0u, j.size()); + index_->GetChildren(j, a[7]); ASSERT_EQ(0u, j.size()); + } + + listener_->Reset(); + index_->DeleteResource(a[3]); + ASSERT_EQ("c", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Series, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[4]); + ASSERT_EQ("b", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Study, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[7]); + ASSERT_EQ("a", listener_->ancestorId_); + ASSERT_EQ(ResourceType_Patient, listener_->ancestorType_); + + listener_->Reset(); + index_->DeleteResource(a[6]); + ASSERT_EQ("", listener_->ancestorId_); // No more ancestor +} + + +TEST_P(DatabaseWrapperTest, PatientRecycling) +{ + std::vector<int64_t> patients; + for (int i = 0; i < 10; i++) + { + std::string p = "Patient " + boost::lexical_cast<std::string>(i); + patients.push_back(index_->CreateResource(p, ResourceType_Patient)); + index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, + "md5-" + boost::lexical_cast<std::string>(i))); + ASSERT_FALSE(index_->IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(10u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(10u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + listener_->Reset(); + + index_->DeleteResource(patients[5]); + index_->DeleteResource(patients[0]); + ASSERT_EQ(8u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(8u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(2u, listener_->deletedFiles_.size()); + ASSERT_EQ("Patient 5", listener_->deletedFiles_[0]); + ASSERT_EQ("Patient 0", listener_->deletedFiles_[1]); + + int64_t p; + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]); + index_->DeleteResource(p); + index_->DeleteResource(patients[8]); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]); + index_->DeleteResource(p); + ASSERT_FALSE(index_->SelectPatientToRecycle(p)); + + ASSERT_EQ(10u, listener_->deletedFiles_.size()); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); +} + + +TEST_P(DatabaseWrapperTest, PatientProtection) +{ + std::vector<int64_t> patients; + for (int i = 0; i < 5; i++) + { + std::string p = "Patient " + boost::lexical_cast<std::string>(i); + patients.push_back(index_->CreateResource(p, ResourceType_Patient)); + index_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, + "md5-" + boost::lexical_cast<std::string>(i))); + ASSERT_FALSE(index_->IsProtectedPatient(patients[i])); + } + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[2], false); + ASSERT_FALSE(index_->IsProtectedPatient(patients[2])); + ASSERT_EQ(5u, index_->GetTableRecordCount("PatientRecyclingOrder")); + index_->SetProtectedPatient(patients[3], true); + ASSERT_TRUE(index_->IsProtectedPatient(patients[3])); + ASSERT_EQ(4u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + ASSERT_EQ(5u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, listener_->deletedFiles_.size()); + + // Unprotecting a patient puts it at the last position in the recycling queue + int64_t p; + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]); + index_->DeleteResource(p); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]); + index_->DeleteResource(p); + ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[2])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]); + index_->DeleteResource(p); + // "patients[3]" is still protected + ASSERT_FALSE(index_->SelectPatientToRecycle(p)); + + ASSERT_EQ(4u, listener_->deletedFiles_.size()); + ASSERT_EQ(1u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); + + index_->SetProtectedPatient(patients[3], false); + ASSERT_EQ(1u, index_->GetTableRecordCount("PatientRecyclingOrder")); + ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[3])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[2])); + ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]); + index_->DeleteResource(p); + + ASSERT_EQ(5u, listener_->deletedFiles_.size()); + ASSERT_EQ(0u, index_->GetTableRecordCount("Resources")); + ASSERT_EQ(0u, index_->GetTableRecordCount("PatientRecyclingOrder")); +} + + + +TEST_P(DatabaseWrapperTest, Sequence) +{ + ASSERT_EQ(1u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(2u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(3u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); + ASSERT_EQ(4u, index_->IncrementGlobalSequence(GlobalProperty_AnonymizationSequence)); +} + + + +TEST_P(DatabaseWrapperTest, LookupTagValue) +{ + int64_t a[] = { + index_->CreateResource("a", ResourceType_Study), // 0 + index_->CreateResource("b", ResourceType_Study), // 1 + index_->CreateResource("c", ResourceType_Study), // 2 + index_->CreateResource("d", ResourceType_Series) // 3 + }; + + DicomMap m; + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[0], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "1"); index_->SetMainDicomTags(a[1], m); + m.Clear(); m.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[2], m); + m.Clear(); m.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "0"); index_->SetMainDicomTags(a[3], m); + + std::list<int64_t> s; + + index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "0"); + ASSERT_EQ(2u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + + index_->LookupTagValue(s, "0"); + ASSERT_EQ(3u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end()); + + index_->LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + index_->LookupTagValue(s, "1"); + ASSERT_EQ(1u, s.size()); + ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end()); + + + /*{ + std::list<std::string> s; + context.GetIndex().LookupTagValue(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059"); + for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++) + { + std::cout << "*** " << *i << std::endl;; + } + }*/ +} + + + +TEST(ServerIndex, AttachmentRecycling) +{ + const std::string path = "OrthancStorageUnitTests"; + Toolbox::RemoveFile(path + "/index"); + ServerContext context(path, ":memory:"); // The SQLite DB is in memory + ServerIndex& index = context.GetIndex(); + + index.SetMaximumStorageSize(10); + + Json::Value tmp; + index.ComputeStatistics(tmp); + ASSERT_EQ(0, tmp["CountPatients"].asInt()); + ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + + ServerIndex::Attachments attachments; + + std::vector<std::string> ids; + for (int i = 0; i < 10; i++) + { + std::string id = boost::lexical_cast<std::string>(i); + DicomMap instance; + instance.SetValue(DICOM_TAG_PATIENT_ID, "patient-" + id); + instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id); + instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id); + instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id); + ASSERT_EQ(StoreStatus_Success, index.Store(instance, attachments, "")); + + DicomInstanceHasher hasher(instance); + ids.push_back(hasher.HashPatient()); + ids.push_back(hasher.HashStudy()); + ids.push_back(hasher.HashSeries()); + ids.push_back(hasher.HashInstance()); + } + + index.ComputeStatistics(tmp); + ASSERT_EQ(10, tmp["CountPatients"].asInt()); + ASSERT_EQ(0, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + + for (size_t i = 0; i < ids.size(); i++) + { + FileInfo info(Toolbox::GenerateUuid(), FileContentType_Dicom, 1, "md5"); + index.AddAttachment(info, ids[i]); + + index.ComputeStatistics(tmp); + ASSERT_GE(10, boost::lexical_cast<int>(tmp["TotalDiskSize"].asString())); + } + + // Because the DB is in memory, the SQLite index must not have been created + ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -0,0 +1,610 @@ +#include "../Core/EnumerationDictionary.h" + +#include "gtest/gtest.h" + +#include <ctype.h> + +#include "../Core/Compression/ZlibCompressor.h" +#include "../Core/DicomFormat/DicomTag.h" +#include "../Core/HttpServer/HttpHandler.h" +#include "../Core/OrthancException.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" +#include "../OrthancServer/FromDcmtkBridge.h" +#include "../OrthancServer/OrthancInitialization.h" + +using namespace Orthanc; + + +TEST(Uuid, Generation) +{ + for (int i = 0; i < 10; i++) + { + std::string s = Toolbox::GenerateUuid(); + ASSERT_TRUE(Toolbox::IsUuid(s)); + } +} + +TEST(Uuid, Test) +{ + ASSERT_FALSE(Toolbox::IsUuid("")); + ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345")); + ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_FALSE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-44665544000_")); + ASSERT_FALSE(Toolbox::IsUuid("01234567890123456789012345678901234_")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000")); + ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok")); + ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok")); +} + +TEST(Toolbox, IsSHA1) +{ + ASSERT_FALSE(Toolbox::IsSHA1("")); + ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123")); + ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); + ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); + + std::string s; + Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); + ASSERT_TRUE(Toolbox::IsSHA1(s)); + ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); + + ASSERT_FALSE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b_")); +} + +static void StringToVector(std::vector<uint8_t>& v, + const std::string& s) +{ + v.resize(s.size()); + for (size_t i = 0; i < s.size(); i++) + v[i] = s[i]; +} + + +TEST(Zlib, Basic) +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed, compressed2; + ZlibCompressor c; + c.Compress(compressed, s); + + std::vector<uint8_t> v, vv; + StringToVector(v, s); + c.Compress(compressed2, v); + ASSERT_EQ(compressed, compressed2); + + std::string uncompressed; + c.Uncompress(uncompressed, compressed); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); + + StringToVector(vv, compressed); + c.Uncompress(uncompressed, vv); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); +} + + +TEST(Zlib, Level) +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed, compressed2; + ZlibCompressor c; + c.SetCompressionLevel(9); + c.Compress(compressed, s); + + c.SetCompressionLevel(0); + c.Compress(compressed2, s); + + ASSERT_TRUE(compressed.size() < compressed2.size()); +} + + +TEST(Zlib, DISABLED_Corrupted) // Disabled because it may result in a crash +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed; + ZlibCompressor c; + c.Compress(compressed, s); + + compressed[compressed.size() - 1] = 'a'; + std::string u; + + ASSERT_THROW(c.Uncompress(u, compressed), OrthancException); +} + + +TEST(Zlib, Empty) +{ + std::string s = ""; + std::vector<uint8_t> v, vv; + + std::string compressed, compressed2; + ZlibCompressor c; + c.Compress(compressed, s); + c.Compress(compressed2, v); + ASSERT_EQ(compressed, compressed2); + + std::string uncompressed; + c.Uncompress(uncompressed, compressed); + ASSERT_EQ(0u, uncompressed.size()); + + StringToVector(vv, compressed); + c.Uncompress(uncompressed, vv); + ASSERT_EQ(0u, uncompressed.size()); +} + + +TEST(ParseGetQuery, Basic) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c"); + ASSERT_EQ(3u, a.size()); + ASSERT_EQ(a["aaa"], "baaa"); + ASSERT_EQ(a["bb"], "a"); + ASSERT_EQ(a["aa"], "c"); +} + +TEST(ParseGetQuery, BasicEmpty) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa"); + ASSERT_EQ(3u, a.size()); + ASSERT_EQ(a["aaa"], ""); + ASSERT_EQ(a["bb"], "aa"); + ASSERT_EQ(a["aa"], ""); +} + +TEST(ParseGetQuery, Single) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa=baaa"); + ASSERT_EQ(1u, a.size()); + ASSERT_EQ(a["aaa"], "baaa"); +} + +TEST(ParseGetQuery, SingleEmpty) +{ + HttpHandler::Arguments a; + HttpHandler::ParseGetQuery(a, "aaa"); + ASSERT_EQ(1u, a.size()); + ASSERT_EQ(a["aaa"], ""); +} + +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(Uri, SplitUriComponents) +{ + UriComponents c; + Toolbox::SplitUriComponents(c, "/cou/hello/world"); + ASSERT_EQ(3u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + + Toolbox::SplitUriComponents(c, "/cou/hello/world/"); + ASSERT_EQ(3u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + + Toolbox::SplitUriComponents(c, "/cou/hello/world/a"); + ASSERT_EQ(4u, c.size()); + ASSERT_EQ("cou", c[0]); + ASSERT_EQ("hello", c[1]); + ASSERT_EQ("world", c[2]); + ASSERT_EQ("a", c[3]); + + Toolbox::SplitUriComponents(c, "/"); + ASSERT_EQ(0u, c.size()); + + Toolbox::SplitUriComponents(c, "/hello"); + ASSERT_EQ(1u, c.size()); + ASSERT_EQ("hello", c[0]); + + Toolbox::SplitUriComponents(c, "/hello/"); + ASSERT_EQ(1u, c.size()); + ASSERT_EQ("hello", c[0]); + + ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException); + ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException); + ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException); + + c.clear(); + c.push_back("test"); + ASSERT_EQ("/", Toolbox::FlattenUri(c, 10)); +} + + +TEST(Uri, Child) +{ + UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world"); + UriComponents c2; Toolbox::SplitUriComponents(c2, "/hello/hello"); + UriComponents c3; Toolbox::SplitUriComponents(c3, "/hello"); + UriComponents c4; Toolbox::SplitUriComponents(c4, "/world"); + UriComponents c5; Toolbox::SplitUriComponents(c5, "/"); + + ASSERT_TRUE(Toolbox::IsChildUri(c1, c1)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c1, c5)); + + ASSERT_FALSE(Toolbox::IsChildUri(c2, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c2, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c2, c5)); + + ASSERT_TRUE(Toolbox::IsChildUri(c3, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c3, c2)); + ASSERT_TRUE(Toolbox::IsChildUri(c3, c3)); + ASSERT_FALSE(Toolbox::IsChildUri(c3, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c3, c5)); + + ASSERT_FALSE(Toolbox::IsChildUri(c4, c1)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c2)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c3)); + ASSERT_TRUE(Toolbox::IsChildUri(c4, c4)); + ASSERT_FALSE(Toolbox::IsChildUri(c4, c5)); + + ASSERT_TRUE(Toolbox::IsChildUri(c5, c1)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c2)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c3)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c4)); + ASSERT_TRUE(Toolbox::IsChildUri(c5, c5)); +} + +TEST(Uri, AutodetectMimeType) +{ + ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("/")); + ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a")); + + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt")); + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt")); + ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml")); + + ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js")); + ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json")); + ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf")); + ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css")); + ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html")); + ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt")); + ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml")); + ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif")); + ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg")); + ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg")); + ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png")); +} + +TEST(Toolbox, ComputeMD5) +{ + std::string s; + + // # echo -n "Hello" | md5sum + + Toolbox::ComputeMD5(s, "Hello"); + ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s); + Toolbox::ComputeMD5(s, ""); + ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s); +} + +TEST(Toolbox, ComputeSHA1) +{ + std::string s; + + Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); + ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); + Toolbox::ComputeSHA1(s, ""); + ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s); +} + + +TEST(Toolbox, Base64) +{ + ASSERT_EQ("", Toolbox::EncodeBase64("")); + ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a")); + + const std::string hello = "SGVsbG8gd29ybGQ="; + ASSERT_EQ(hello, Toolbox::EncodeBase64("Hello world")); + ASSERT_EQ("Hello world", Toolbox::DecodeBase64(hello)); +} + +TEST(Toolbox, PathToExecutable) +{ + printf("[%s]\n", Toolbox::GetPathToExecutable().c_str()); + printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str()); +} + +TEST(Toolbox, StripSpaces) +{ + ASSERT_EQ("", Toolbox::StripSpaces(" \t \r \n ")); + ASSERT_EQ("coucou", Toolbox::StripSpaces(" coucou \t \r \n ")); + ASSERT_EQ("cou cou", Toolbox::StripSpaces(" cou cou \n ")); + ASSERT_EQ("c", Toolbox::StripSpaces(" \n\t c\r \n ")); +} + +TEST(Toolbox, Case) +{ + std::string s = "CoU"; + std::string ss; + + Toolbox::ToUpperCase(ss, s); + ASSERT_EQ("COU", ss); + Toolbox::ToLowerCase(ss, s); + ASSERT_EQ("cou", ss); + + s = "CoU"; + Toolbox::ToUpperCase(s); + ASSERT_EQ("COU", s); + + s = "CoU"; + Toolbox::ToLowerCase(s); + ASSERT_EQ("cou", s); +} + + +#include <glog/logging.h> + +TEST(Logger, Basic) +{ + LOG(INFO) << "I say hello"; +} + +TEST(Toolbox, ConvertFromLatin1) +{ + // This is a Latin-1 test string + const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; + + /*FILE* f = fopen("/tmp/tutu", "w"); + fwrite(&data[0], 9, 1, f); + fclose(f);*/ + + std::string s((char*) &data[0], 10); + ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); + + // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" + std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1"); + ASSERT_EQ(15u, utf8.size()); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0])); + ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2])); + ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4])); + ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6])); + ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7])); + ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8])); + ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9])); + ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10])); + ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11])); + ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12])); + ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13])); + ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14])); // Null-terminated string +} + +TEST(Toolbox, UrlDecode) +{ + std::string s; + + s = "Hello%20World"; + Toolbox::UrlDecode(s); + ASSERT_EQ("Hello World", s); + + s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff"; + Toolbox::UrlDecode(s); + std::string ss = "!#$&'()*+,/:;=?@[]"; + ss.push_back((char) 144); + ss.push_back((char) 255); + ASSERT_EQ(ss, s); + + s = "(2000%2C00A4)+Other"; + Toolbox::UrlDecode(s); + ASSERT_EQ("(2000,00A4) Other", s); +} + + +#if defined(__linux) +TEST(OrthancInitialization, AbsoluteDirectory) +{ + ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello")); + ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp")); +} +#endif + + + +#include "../OrthancServer/ServerEnumerations.h" + +TEST(EnumerationDictionary, Simple) +{ + Toolbox::EnumerationDictionary<MetadataType> d; + + ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException); + ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5")); + ASSERT_EQ(256, d.Translate("256")); + + d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); + + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate")); + ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2")); + ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate)); + + ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException); + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used + ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers + d.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); // ok +} + + +TEST(EnumerationDictionary, ServerEnumerations) +{ + ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient)); + ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study)); + ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series)); + ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance)); + + ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries)); + + ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure)); + ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success)); + + ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries)); + + ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries)); + ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate)); + + ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT")); + ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy")); + ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE")); + ASSERT_THROW(StringToResourceType("heLLo"), OrthancException); + + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException); + ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024) + ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535) + RegisterUserMetadata(2047, "Ceci est un test"); + ASSERT_EQ(2047, StringToMetadata("2047")); + ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); +} + + + +TEST(Toolbox, WriteFile) +{ + std::string path; + + { + Toolbox::TemporaryFile tmp; + path = tmp.GetPath(); + + std::string s; + s.append("Hello"); + s.push_back('\0'); + s.append("World"); + ASSERT_EQ(11u, s.size()); + + Toolbox::WriteFile(s, path.c_str()); + + std::string t; + Toolbox::ReadFile(t, path.c_str()); + + ASSERT_EQ(11u, t.size()); + ASSERT_EQ(0, t[5]); + ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); + } + + std::string u; + ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException); +} + + +TEST(Toolbox, Wildcard) +{ + ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd")); + ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd")); + ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd")); + ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d")); + ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]")); +} + + +TEST(Toolbox, Tokenize) +{ + std::vector<std::string> t; + + Toolbox::TokenizeString(t, "", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("", t[0]); + + Toolbox::TokenizeString(t, "abc", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("abc", t[0]); + + Toolbox::TokenizeString(t, "ab,cd,ef,", ','); + ASSERT_EQ(4, t.size()); + ASSERT_EQ("ab", t[0]); + ASSERT_EQ("cd", t[1]); + ASSERT_EQ("ef", t[2]); + ASSERT_EQ("", t[3]); +} + + + +#if defined(__linux) +#include <endian.h> +#endif + +TEST(Toolbox, Endianness) +{ + // Parts of this test come from Adam Conrad + // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5 + +#if defined(_WIN32) + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + +#elif defined(__linux) + +#if !defined(__BYTE_ORDER) +# error Support your platform here +#endif + +# if __BYTE_ORDER == __BIG_ENDIAN + ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness()); +# else // __LITTLE_ENDIAN + ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); +# endif + +#else +#error Support your platform here +#endif +} + + + +int main(int argc, char **argv) +{ + // Initialize Google's logging library. + FLAGS_logtostderr = true; + FLAGS_minloglevel = 0; + + // Go to trace-level verbosity + //FLAGS_v = 1; + + Toolbox::DetectEndianness(); + + google::InitGoogleLogging("Orthanc"); + + OrthancInitialize(); + ::testing::InitGoogleTest(&argc, argv); + int result = RUN_ALL_TESTS(); + OrthancFinalize(); + return result; +}
--- a/UnitTestsSources/Versions.cpp Fri Jan 24 17:40:45 2014 +0100 +++ b/UnitTestsSources/Versions.cpp Wed Apr 16 16:34:09 2014 +0200 @@ -9,6 +9,7 @@ #include <boost/version.hpp> #include <sqlite3.h> #include <lua.h> +#include <openssl/opensslv.h> TEST(Versions, Zlib) @@ -57,7 +58,7 @@ TEST(Versions, BoostStatic) { - ASSERT_STREQ("1_54", BOOST_LIB_VERSION); + ASSERT_STREQ("1_55", BOOST_LIB_VERSION); } TEST(Versions, CurlStatic) @@ -90,5 +91,10 @@ { ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); } + +TEST(Version, OpenSslStatic) +{ + ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); +} + #endif -
--- a/UnitTestsSources/main.cpp Fri Jan 24 17:40:45 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,575 +0,0 @@ -#include "../Core/EnumerationDictionary.h" - -#include "gtest/gtest.h" - -#include <ctype.h> - -#include "../Core/Compression/ZlibCompressor.h" -#include "../Core/DicomFormat/DicomTag.h" -#include "../Core/HttpServer/HttpHandler.h" -#include "../Core/OrthancException.h" -#include "../Core/Toolbox.h" -#include "../Core/Uuid.h" -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/OrthancInitialization.h" -#include "../Core/MultiThreading/SharedMessageQueue.h" - -using namespace Orthanc; - - -TEST(Uuid, Generation) -{ - for (int i = 0; i < 10; i++) - { - std::string s = Toolbox::GenerateUuid(); - ASSERT_TRUE(Toolbox::IsUuid(s)); - } -} - -TEST(Uuid, Test) -{ - ASSERT_FALSE(Toolbox::IsUuid("")); - ASSERT_FALSE(Toolbox::IsUuid("012345678901234567890123456789012345")); - ASSERT_TRUE(Toolbox::IsUuid("550e8400-e29b-41d4-a716-446655440000")); - ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-44665544000")); - ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000")); - ASSERT_TRUE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000 ok")); - ASSERT_FALSE(Toolbox::StartsWithUuid("550e8400-e29b-41d4-a716-446655440000ok")); -} - -TEST(Toolbox, IsSHA1) -{ - ASSERT_FALSE(Toolbox::IsSHA1("")); - ASSERT_FALSE(Toolbox::IsSHA1("01234567890123456789012345678901234567890123")); - ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); - ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); - - std::string s; - Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); - ASSERT_TRUE(Toolbox::IsSHA1(s)); - ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); -} - -TEST(Zlib, Basic) -{ - std::string s = Toolbox::GenerateUuid(); - s = s + s + s + s; - - std::string compressed; - ZlibCompressor c; - c.Compress(compressed, s); - - std::string uncompressed; - c.Uncompress(uncompressed, compressed); - - ASSERT_EQ(s.size(), uncompressed.size()); - ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); -} - -TEST(Zlib, Empty) -{ - std::string s = ""; - - std::string compressed; - ZlibCompressor c; - c.Compress(compressed, s); - - std::string uncompressed; - c.Uncompress(uncompressed, compressed); - - ASSERT_EQ(0u, uncompressed.size()); -} - -TEST(ParseGetQuery, Basic) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c"); - ASSERT_EQ(3u, a.size()); - ASSERT_EQ(a["aaa"], "baaa"); - ASSERT_EQ(a["bb"], "a"); - ASSERT_EQ(a["aa"], "c"); -} - -TEST(ParseGetQuery, BasicEmpty) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa"); - ASSERT_EQ(3u, a.size()); - ASSERT_EQ(a["aaa"], ""); - ASSERT_EQ(a["bb"], "aa"); - ASSERT_EQ(a["aa"], ""); -} - -TEST(ParseGetQuery, Single) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa=baaa"); - ASSERT_EQ(1u, a.size()); - ASSERT_EQ(a["aaa"], "baaa"); -} - -TEST(ParseGetQuery, SingleEmpty) -{ - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(a, "aaa"); - ASSERT_EQ(1u, a.size()); - ASSERT_EQ(a["aaa"], ""); -} - -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(Uri, SplitUriComponents) -{ - UriComponents c; - Toolbox::SplitUriComponents(c, "/cou/hello/world"); - ASSERT_EQ(3u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - - Toolbox::SplitUriComponents(c, "/cou/hello/world/"); - ASSERT_EQ(3u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - - Toolbox::SplitUriComponents(c, "/cou/hello/world/a"); - ASSERT_EQ(4u, c.size()); - ASSERT_EQ("cou", c[0]); - ASSERT_EQ("hello", c[1]); - ASSERT_EQ("world", c[2]); - ASSERT_EQ("a", c[3]); - - Toolbox::SplitUriComponents(c, "/"); - ASSERT_EQ(0u, c.size()); - - Toolbox::SplitUriComponents(c, "/hello"); - ASSERT_EQ(1u, c.size()); - ASSERT_EQ("hello", c[0]); - - Toolbox::SplitUriComponents(c, "/hello/"); - ASSERT_EQ(1u, c.size()); - ASSERT_EQ("hello", c[0]); - - ASSERT_THROW(Toolbox::SplitUriComponents(c, ""), OrthancException); - ASSERT_THROW(Toolbox::SplitUriComponents(c, "a"), OrthancException); - ASSERT_THROW(Toolbox::SplitUriComponents(c, "/coucou//coucou"), OrthancException); -} - - -TEST(Uri, Child) -{ - UriComponents c1; Toolbox::SplitUriComponents(c1, "/hello/world"); - UriComponents c2; Toolbox::SplitUriComponents(c2, "/hello/hello"); - UriComponents c3; Toolbox::SplitUriComponents(c3, "/hello"); - UriComponents c4; Toolbox::SplitUriComponents(c4, "/world"); - UriComponents c5; Toolbox::SplitUriComponents(c5, "/"); - - ASSERT_TRUE(Toolbox::IsChildUri(c1, c1)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c1, c5)); - - ASSERT_FALSE(Toolbox::IsChildUri(c2, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c2, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c2, c5)); - - ASSERT_TRUE(Toolbox::IsChildUri(c3, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c3, c2)); - ASSERT_TRUE(Toolbox::IsChildUri(c3, c3)); - ASSERT_FALSE(Toolbox::IsChildUri(c3, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c3, c5)); - - ASSERT_FALSE(Toolbox::IsChildUri(c4, c1)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c2)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c3)); - ASSERT_TRUE(Toolbox::IsChildUri(c4, c4)); - ASSERT_FALSE(Toolbox::IsChildUri(c4, c5)); - - ASSERT_TRUE(Toolbox::IsChildUri(c5, c1)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c2)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c3)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c4)); - ASSERT_TRUE(Toolbox::IsChildUri(c5, c5)); -} - -TEST(Uri, AutodetectMimeType) -{ - ASSERT_EQ("", Toolbox::AutodetectMimeType("../NOTES")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("/")); - ASSERT_EQ("", Toolbox::AutodetectMimeType("a/a")); - - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../NOTES.txt")); - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("../coucou.xml/NOTES.txt")); - ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("../.xml")); - - ASSERT_EQ("application/javascript", Toolbox::AutodetectMimeType("NOTES.js")); - ASSERT_EQ("application/json", Toolbox::AutodetectMimeType("NOTES.json")); - ASSERT_EQ("application/pdf", Toolbox::AutodetectMimeType("NOTES.pdf")); - ASSERT_EQ("text/css", Toolbox::AutodetectMimeType("NOTES.css")); - ASSERT_EQ("text/html", Toolbox::AutodetectMimeType("NOTES.html")); - ASSERT_EQ("text/plain", Toolbox::AutodetectMimeType("NOTES.txt")); - ASSERT_EQ("text/xml", Toolbox::AutodetectMimeType("NOTES.xml")); - ASSERT_EQ("image/gif", Toolbox::AutodetectMimeType("NOTES.gif")); - ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpg")); - ASSERT_EQ("image/jpeg", Toolbox::AutodetectMimeType("NOTES.jpeg")); - ASSERT_EQ("image/png", Toolbox::AutodetectMimeType("NOTES.png")); -} - -TEST(Toolbox, ComputeMD5) -{ - std::string s; - - // # echo -n "Hello" | md5sum - - Toolbox::ComputeMD5(s, "Hello"); - ASSERT_EQ("8b1a9953c4611296a827abf8c47804d7", s); - Toolbox::ComputeMD5(s, ""); - ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", s); -} - -TEST(Toolbox, ComputeSHA1) -{ - std::string s; - - Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); - ASSERT_EQ("2fd4e1c6-7a2d28fc-ed849ee1-bb76e739-1b93eb12", s); - Toolbox::ComputeSHA1(s, ""); - ASSERT_EQ("da39a3ee-5e6b4b0d-3255bfef-95601890-afd80709", s); -} - - -TEST(Toolbox, Base64) -{ - ASSERT_EQ("", Toolbox::EncodeBase64("")); - ASSERT_EQ("YQ==", Toolbox::EncodeBase64("a")); - ASSERT_EQ("SGVsbG8gd29ybGQ=", Toolbox::EncodeBase64("Hello world")); -} - -TEST(Toolbox, PathToExecutable) -{ - printf("[%s]\n", Toolbox::GetPathToExecutable().c_str()); - printf("[%s]\n", Toolbox::GetDirectoryOfExecutable().c_str()); -} - -TEST(Toolbox, StripSpaces) -{ - ASSERT_EQ("", Toolbox::StripSpaces(" \t \r \n ")); - ASSERT_EQ("coucou", Toolbox::StripSpaces(" coucou \t \r \n ")); - ASSERT_EQ("cou cou", Toolbox::StripSpaces(" cou cou \n ")); - ASSERT_EQ("c", Toolbox::StripSpaces(" \n\t c\r \n ")); -} - - -#include <glog/logging.h> - -TEST(Logger, Basic) -{ - LOG(INFO) << "I say hello"; -} - -TEST(Toolbox, ConvertFromLatin1) -{ - // This is a Latin-1 test string - const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; - - /*FILE* f = fopen("/tmp/tutu", "w"); - fwrite(&data[0], 9, 1, f); - fclose(f);*/ - - std::string s((char*) &data[0], 10); - ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); - - // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" - std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1"); - ASSERT_EQ(15u, utf8.size()); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0])); - ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[2])); - ASSERT_EQ(0xa9, static_cast<unsigned char>(utf8[3])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[4])); - ASSERT_EQ(0xaa, static_cast<unsigned char>(utf8[5])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[6])); - ASSERT_EQ(0xa7, static_cast<unsigned char>(utf8[7])); - ASSERT_EQ(0x26, static_cast<unsigned char>(utf8[8])); - ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[9])); - ASSERT_EQ(0x86, static_cast<unsigned char>(utf8[10])); - ASSERT_EQ(0x61, static_cast<unsigned char>(utf8[11])); - ASSERT_EQ(0x62, static_cast<unsigned char>(utf8[12])); - ASSERT_EQ(0x63, static_cast<unsigned char>(utf8[13])); - ASSERT_EQ(0x00, static_cast<unsigned char>(utf8[14])); // Null-terminated string -} - -TEST(Toolbox, UrlDecode) -{ - std::string s; - - s = "Hello%20World"; - Toolbox::UrlDecode(s); - ASSERT_EQ("Hello World", s); - - s = "%21%23%24%26%27%28%29%2A%2B%2c%2f%3A%3b%3d%3f%40%5B%5D%90%ff"; - Toolbox::UrlDecode(s); - std::string ss = "!#$&'()*+,/:;=?@[]"; - ss.push_back((char) 144); - ss.push_back((char) 255); - ASSERT_EQ(ss, s); - - s = "(2000%2C00A4)+Other"; - Toolbox::UrlDecode(s); - ASSERT_EQ("(2000,00A4) Other", s); -} - - -#if defined(__linux) -TEST(OrthancInitialization, AbsoluteDirectory) -{ - ASSERT_EQ("/tmp/hello", InterpretRelativePath("/tmp", "hello")); - ASSERT_EQ("/tmp", InterpretRelativePath("/tmp", "/tmp")); -} -#endif - - - -#include "../OrthancServer/ServerEnumerations.h" - -TEST(EnumerationDictionary, Simple) -{ - Toolbox::EnumerationDictionary<MetadataType> d; - - ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException); - ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5")); - ASSERT_EQ(256, d.Translate("256")); - - d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate"); - - ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate")); - ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2")); - ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate)); - - ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException); - ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used - ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers - d.Add(MetadataType_ModifiedFrom, "ModifiedFrom"); // ok -} - - -TEST(EnumerationDictionary, ServerEnumerations) -{ - ASSERT_STREQ("Patient", EnumerationToString(ResourceType_Patient)); - ASSERT_STREQ("Study", EnumerationToString(ResourceType_Study)); - ASSERT_STREQ("Series", EnumerationToString(ResourceType_Series)); - ASSERT_STREQ("Instance", EnumerationToString(ResourceType_Instance)); - - ASSERT_STREQ("ModifiedSeries", EnumerationToString(ChangeType_ModifiedSeries)); - - ASSERT_STREQ("Failure", EnumerationToString(StoreStatus_Failure)); - ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success)); - - ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries)); - - ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries)); - ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate)); - - ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT")); - ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy")); - ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs")); - ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance")); - ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE")); - ASSERT_THROW(StringToResourceType("heLLo"), OrthancException); - - ASSERT_EQ(2047, StringToMetadata("2047")); - ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException); - ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024) - ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535) - RegisterUserMetadata(2047, "Ceci est un test"); - ASSERT_EQ(2047, StringToMetadata("2047")); - ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); -} - - - -class DynamicInteger : public IDynamicObject -{ -private: - int value_; - -public: - DynamicInteger(int value) : value_(value) - { - } - - int GetValue() const - { - return value_; - } -}; - - -TEST(SharedMessageQueue, Basic) -{ - SharedMessageQueue q; - ASSERT_TRUE(q.WaitEmpty(0)); - q.Enqueue(new DynamicInteger(10)); - ASSERT_FALSE(q.WaitEmpty(1)); - q.Enqueue(new DynamicInteger(20)); - q.Enqueue(new DynamicInteger(30)); - q.Enqueue(new DynamicInteger(40)); - - std::auto_ptr<DynamicInteger> i; - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue()); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue()); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue()); - ASSERT_FALSE(q.WaitEmpty(1)); - i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue()); - ASSERT_TRUE(q.WaitEmpty(0)); - ASSERT_EQ(NULL, q.Dequeue(1)); -} - - -TEST(SharedMessageQueue, Clean) -{ - try - { - SharedMessageQueue q; - q.Enqueue(new DynamicInteger(10)); - q.Enqueue(new DynamicInteger(20)); - throw OrthancException("Nope"); - } - catch (OrthancException&) - { - } -} - - -TEST(Toolbox, WriteFile) -{ - std::string path; - - { - Toolbox::TemporaryFile tmp; - path = tmp.GetPath(); - - std::string s; - s.append("Hello"); - s.push_back('\0'); - s.append("World"); - ASSERT_EQ(11u, s.size()); - - Toolbox::WriteFile(s, path.c_str()); - - std::string t; - Toolbox::ReadFile(t, path.c_str()); - - ASSERT_EQ(11u, t.size()); - ASSERT_EQ(0, t[5]); - ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); - } - - std::string u; - ASSERT_THROW(Toolbox::ReadFile(u, path.c_str()), OrthancException); -} - - -TEST(Toolbox, Wildcard) -{ - ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd")); - ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd")); - ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd")); - ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d")); - ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]")); -} - - -TEST(Toolbox, Tokenize) -{ - std::vector<std::string> t; - - Toolbox::TokenizeString(t, "", ','); - ASSERT_EQ(1, t.size()); - ASSERT_EQ("", t[0]); - - Toolbox::TokenizeString(t, "abc", ','); - ASSERT_EQ(1, t.size()); - ASSERT_EQ("abc", t[0]); - - Toolbox::TokenizeString(t, "ab,cd,ef,", ','); - ASSERT_EQ(4, t.size()); - ASSERT_EQ("ab", t[0]); - ASSERT_EQ("cd", t[1]); - ASSERT_EQ("ef", t[2]); - ASSERT_EQ("", t[3]); -} - - - -#if defined(__linux) -#include <endian.h> -#endif - -TEST(Toolbox, Endianness) -{ - // Parts of this test come from Adam Conrad - // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=728822#5 - -#if defined(_WIN32) - ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); - -#elif defined(__linux) - -#if !defined(__BYTE_ORDER) -# error Support your platform here -#endif - -# if __BYTE_ORDER == __BIG_ENDIAN - ASSERT_EQ(Endianness_Big, Toolbox::DetectEndianness()); -# else // __LITTLE_ENDIAN - ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); -# endif - -#else -#error Support your platform here -#endif -} - - - -int main(int argc, char **argv) -{ - // Initialize Google's logging library. - FLAGS_logtostderr = true; - FLAGS_minloglevel = 0; - - // Go to trace-level verbosity - //FLAGS_v = 1; - - Toolbox::DetectEndianness(); - - google::InitGoogleLogging("Orthanc"); - - OrthancInitialize(); - ::testing::InitGoogleTest(&argc, argv); - int result = RUN_ALL_TESTS(); - OrthancFinalize(); - return result; -}