Mercurial > hg > orthanc
changeset 1660:4a5c79e31b60 db-changes
integration mainline->db-changes
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 30 Sep 2015 13:23:31 +0200 |
parents | 61ce8147f30d (current diff) 87a606265de8 (diff) |
children | 1a80777ad462 |
files | Core/Compression/BufferCompressor.cpp Core/Compression/BufferCompressor.h Core/FileStorage/CompressedFileStorageAccessor.cpp Core/FileStorage/CompressedFileStorageAccessor.h Core/FileStorage/FileStorageAccessor.cpp Core/FileStorage/FileStorageAccessor.h Core/HttpServer/HttpHandler.cpp Core/HttpServer/HttpHandler.h Core/ImageFormats/ImageAccessor.cpp Core/ImageFormats/ImageAccessor.h Core/ImageFormats/ImageBuffer.cpp Core/ImageFormats/ImageBuffer.h Core/ImageFormats/ImageProcessing.cpp Core/ImageFormats/ImageProcessing.h Core/ImageFormats/PngReader.cpp Core/ImageFormats/PngReader.h Core/ImageFormats/PngWriter.cpp Core/ImageFormats/PngWriter.h Core/Lua/LuaException.h Core/MultiThreading/ArrayFilledByThreads.cpp Core/MultiThreading/ArrayFilledByThreads.h Core/MultiThreading/ThreadedCommandProcessor.cpp Core/MultiThreading/ThreadedCommandProcessor.h Core/OrthancException.cpp OrthancCppClient/Instance.cpp OrthancCppClient/Instance.h OrthancCppClient/OrthancClientException.h OrthancCppClient/OrthancConnection.cpp OrthancCppClient/OrthancConnection.h OrthancCppClient/OrthancCppClient.cpp OrthancCppClient/Patient.cpp OrthancCppClient/Patient.h OrthancCppClient/Series.cpp OrthancCppClient/Series.h OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc OrthancCppClient/SharedLibrary/ConfigurationCpp.json OrthancCppClient/SharedLibrary/Generate.sh OrthancCppClient/SharedLibrary/Laaw/VersionScript.map OrthancCppClient/SharedLibrary/Laaw/laaw/laaw-exports.h OrthancCppClient/SharedLibrary/Laaw/laaw/laaw.h OrthancCppClient/SharedLibrary/Product.json OrthancCppClient/SharedLibrary/SharedLibrary.cpp OrthancCppClient/Study.cpp OrthancCppClient/Study.h OrthancExplorer/libs/images/ajax-loader.gif OrthancExplorer/libs/images/ajax-loader.png OrthancExplorer/libs/images/ajax-loader2.gif OrthancExplorer/libs/jquery-1.7.2.min.js OrthancExplorer/libs/jquery.mobile-1.1.0.min.css OrthancExplorer/libs/jquery.mobile-1.1.0.min.js OrthancExplorer/libs/jquery.mobile.structure-1.1.0.min.css OrthancExplorer/libs/jquery.mobile.theme-1.1.0.min.css OrthancServer/IServerIndexListener.h Plugins/Include/OrthancCDatabasePlugin.h Plugins/Include/OrthancCPlugin.h Plugins/Include/OrthancCppDatabasePlugin.h Resources/MinGW64Toolchain.cmake Resources/OrthancClient.doxygen Resources/Patches/boost-1.55.0-clang-atomic.patch Resources/Samples/OrthancClient/Basic/CMakeLists.txt Resources/Samples/OrthancClient/Basic/main.cpp Resources/Samples/OrthancClient/Vtk/CMakeLists.txt Resources/Samples/OrthancClient/Vtk/main.cpp UnitTestsSources/PngTests.cpp |
diffstat | 380 files changed, 28495 insertions(+), 21374 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -5,6 +5,14 @@ # Version of the build, should always be "mainline" except in release branches set(ORTHANC_VERSION "mainline") +# Version of the database schema. History: +# * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning +# * Orthanc 0.3.1 = version 2 +# * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3 +# * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4 +# * Orthanc 0.8.5 -> mainline = version 5 +set(ORTHANC_DATABASE_VERSION 5) + ##################################################################### ## CMake parameters tunable at the command line @@ -14,12 +22,14 @@ SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") 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 "" 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") +SET(ENABLE_GOOGLE_LOG OFF CACHE BOOL "Enable Google Log (otherwise, an internal logger is used)") SET(ENABLE_JPEG ON CACHE BOOL "Enable JPEG decompression") SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") +SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") +SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Build the ServeFolders plugin") # Advanced parameters to fine-tune linking against system libraries SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") @@ -30,7 +40,8 @@ SET(USE_SYSTEM_LUA ON CACHE BOOL "Use the system version of Lua") SET(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK") SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of LibPng") +SET(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") +SET(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") SET(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl") SET(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL") SET(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib") @@ -41,9 +52,13 @@ # Distribution-specific settings SET(USE_GTEST_DEBIAN_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +SET(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") +SET(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)") + mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE) -SET(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) +mark_as_advanced(USE_BOOST_ICONV) +mark_as_advanced(USE_PUGIXML) # Path to the root folder of the Orthanc distribution set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}) @@ -52,6 +67,7 @@ include(CheckIncludeFiles) include(CheckIncludeFileCXX) include(CheckLibraryExists) +include(FindPythonInterp) include(${CMAKE_SOURCE_DIR}/Resources/CMake/AutoGeneratedCode.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/DownloadPackage.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/Compiler.cmake) @@ -66,12 +82,13 @@ set(ORTHANC_CORE_SOURCES Core/Cache/MemoryCache.cpp + Core/Cache/SharedArchive.cpp Core/ChunkedBuffer.cpp - Core/Compression/BufferCompressor.cpp + Core/Compression/DeflateBaseCompressor.cpp + Core/Compression/GzipCompressor.cpp + Core/Compression/HierarchicalZipWriter.cpp + Core/Compression/ZipWriter.cpp Core/Compression/ZlibCompressor.cpp - Core/Compression/ZipWriter.cpp - Core/Compression/HierarchicalZipWriter.cpp - Core/OrthancException.cpp Core/DicomFormat/DicomArray.cpp Core/DicomFormat/DicomMap.cpp Core/DicomFormat/DicomTag.cpp @@ -81,34 +98,39 @@ Core/Enumerations.cpp Core/FileStorage/FilesystemStorage.cpp Core/FileStorage/StorageAccessor.cpp - Core/FileStorage/CompressedFileStorageAccessor.cpp - Core/FileStorage/FileStorageAccessor.cpp Core/HttpClient.cpp + Core/HttpServer/BufferHttpSender.cpp Core/HttpServer/EmbeddedResourceHttpHandler.cpp Core/HttpServer/FilesystemHttpHandler.cpp - Core/HttpServer/HttpHandler.cpp + Core/HttpServer/HttpToolbox.cpp Core/HttpServer/HttpOutput.cpp + Core/HttpServer/StringHttpOutput.cpp Core/HttpServer/MongooseServer.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/FilesystemHttpSender.cpp + Core/HttpServer/HttpStreamTranscoder.cpp + Core/Logging.cpp Core/RestApi/RestApiCall.cpp Core/RestApi/RestApiGetCall.cpp Core/RestApi/RestApiHierarchy.cpp Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApi.cpp - Core/MultiThreading/ArrayFilledByThreads.cpp Core/MultiThreading/BagOfRunnablesBySteps.cpp Core/MultiThreading/Mutex.cpp Core/MultiThreading/ReaderWriterLock.cpp Core/MultiThreading/Semaphore.cpp Core/MultiThreading/SharedMessageQueue.cpp - Core/MultiThreading/ThreadedCommandProcessor.cpp - Core/ImageFormats/ImageAccessor.cpp - Core/ImageFormats/ImageBuffer.cpp - Core/ImageFormats/ImageProcessing.cpp - Core/ImageFormats/PngReader.cpp - Core/ImageFormats/PngWriter.cpp + Core/Images/Font.cpp + Core/Images/FontRegistry.cpp + Core/Images/ImageAccessor.cpp + Core/Images/ImageBuffer.cpp + Core/Images/ImageProcessing.cpp + Core/Images/JpegErrorManager.cpp + Core/Images/JpegReader.cpp + Core/Images/JpegWriter.cpp + Core/Images/PngReader.cpp + Core/Images/PngWriter.cpp Core/SQLite/Connection.cpp Core/SQLite/FunctionContext.cpp Core/SQLite/Statement.cpp @@ -119,17 +141,6 @@ Core/Uuid.cpp Core/Lua/LuaContext.cpp Core/Lua/LuaFunctionCall.cpp - - OrthancCppClient/OrthancConnection.cpp - OrthancCppClient/Study.cpp - OrthancCppClient/Series.cpp - OrthancCppClient/Instance.cpp - OrthancCppClient/Patient.cpp - - Plugins/Engine/SharedLibrary.cpp - Plugins/Engine/PluginsManager.cpp - Plugins/Engine/OrthancPlugins.cpp - Plugins/Engine/OrthancPluginDatabase.cpp ) @@ -166,6 +177,11 @@ OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/ExportedResource.cpp + OrthancServer/ResourceFinder.cpp + OrthancServer/DicomFindQuery.cpp + OrthancServer/QueryRetrieveHandler.cpp + OrthancServer/LuaScripting.cpp + OrthancServer/OrthancHttpHandler.cpp # From "lua-scripting" branch OrthancServer/DicomInstanceToStore.cpp @@ -185,7 +201,7 @@ UnitTestsSources/FileStorageTests.cpp UnitTestsSources/FromDcmtkTests.cpp UnitTestsSources/MemoryCacheTests.cpp - UnitTestsSources/PngTests.cpp + UnitTestsSources/ImageTests.cpp UnitTestsSources/RestApiTests.cpp UnitTestsSources/SQLiteTests.cpp UnitTestsSources/SQLiteChromiumTests.cpp @@ -197,10 +213,26 @@ UnitTestsSources/UnitTestsMain.cpp UnitTestsSources/ImageProcessingTests.cpp UnitTestsSources/JpegLosslessTests.cpp - UnitTestsSources/PluginsTests.cpp + UnitTestsSources/StreamTests.cpp ) +if (ENABLE_PLUGINS) + list(APPEND ORTHANC_SERVER_SOURCES + Plugins/Engine/OrthancPluginDatabase.cpp + Plugins/Engine/OrthancPlugins.cpp + Plugins/Engine/PluginsEnumerations.cpp + Plugins/Engine/PluginsErrorDictionary.cpp + Plugins/Engine/PluginsManager.cpp + Plugins/Engine/SharedLibrary.cpp + ) + + list(APPEND ORTHANC_UNIT_TESTS_SOURCES + UnitTestsSources/PluginsTests.cpp + ) +endif() + + set(ORTHANC_EMBEDDED_FILES PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql UPGRADE_DATABASE_3_TO_4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/Upgrade3To4.sql @@ -208,6 +240,7 @@ CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json DICOM_CONFORMANCE_STATEMENT ${CMAKE_CURRENT_SOURCE_DIR}/Resources/DicomConformanceStatement.txt LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua + FONT_UBUNTU_MONO_BOLD_16 ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Fonts/UbuntuMonoBold-16.json ) @@ -216,32 +249,28 @@ ## Inclusion of third-party dependencies ##################################################################### -# Configuration of the standalone builds -if (CMAKE_CROSSCOMPILING) - # Cross-compilation implies the standalone build - SET(STANDALONE_BUILD ON) +if (ENABLE_GOOGLE_LOG) + include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake) endif() -# Prepare the third-party dependencies -SET(THIRD_PARTY_SOURCES - ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/md5/md5.c - ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp - ) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibJpegConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/PugixmlConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake) +# These are the two most heavyweight dependencies. We put them as the +# last includes to quickly spot problems when configuring static +# builds. include(${CMAKE_SOURCE_DIR}/Resources/CMake/BoostConfiguration.cmake) include(${CMAKE_SOURCE_DIR}/Resources/CMake/DcmtkConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/MongooseConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/ZlibConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/SQLiteConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Resources/CMake/PugixmlConfiguration.cmake) -if (${ENABLE_SSL}) +if (ENABLE_SSL) add_definitions(-DORTHANC_SSL_ENABLED=1) include(${CMAKE_SOURCE_DIR}/Resources/CMake/OpenSslConfiguration.cmake) else() @@ -263,12 +292,19 @@ endif() +if (ENABLE_PLUGINS) + add_definitions(-DORTHANC_PLUGINS_ENABLED=1) +else() + add_definitions(-DORTHANC_PLUGINS_ENABLED=0) +endif() + + ##################################################################### ## Autogeneration of files ##################################################################### -if (${STANDALONE_BUILD}) +if (STANDALONE_BUILD) # We embed all the resources in the binaries for standalone builds add_definitions(-DORTHANC_STANDALONE=1) EmbedResources( @@ -286,6 +322,22 @@ ) endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/Orthanc.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND ORTHANC_RESOURCES ${AUTOGENERATED_DIR}/Orthanc.rc) +endif() + ##################################################################### @@ -293,7 +345,7 @@ ##################################################################### # Setup precompiled headers for Microsoft Visual Studio -if (${MSVC}) +if (MSVC) add_definitions(-DORTHANC_USE_PRECOMPILED_HEADERS=1) ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS( @@ -309,6 +361,8 @@ add_definitions( -DORTHANC_VERSION="${ORTHANC_VERSION}" + -DORTHANC_DATABASE_VERSION=${ORTHANC_DATABASE_VERSION} + -DORTHANC_ENABLE_LOGGING=1 ) list(LENGTH OPENSSL_SOURCES OPENSSL_SOURCES_LENGTH) @@ -318,13 +372,31 @@ add_library(CoreLibrary STATIC + ${ORTHANC_CORE_SOURCES} ${AUTOGENERATED_SOURCES} - ${THIRD_PARTY_SOURCES} + + ${BOOST_SOURCES} ${CURL_SOURCES} - ${ORTHANC_CORE_SOURCES} + ${GOOGLE_LOG_SOURCES} + ${JSONCPP_SOURCES} + ${LIBPNG_SOURCES} + ${LIBJPEG_SOURCES} + ${LUA_SOURCES} + ${MONGOOSE_SOURCES} + ${PUGIXML_SOURCES} + ${SQLITE_SOURCES} + ${ZLIB_SOURCES} + + ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/md5/md5.c + ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp + + # This is the minizip distribution to create ZIP files using zlib + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c + ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c ) + ##################################################################### ## Build the Orthanc server ##################################################################### @@ -340,9 +412,10 @@ add_executable(Orthanc OrthancServer/main.cpp + ${ORTHANC_RESOURCES} ) -target_link_libraries(Orthanc ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) +target_link_libraries(Orthanc ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(Orthanc OpenSSL) @@ -365,13 +438,16 @@ add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0) endif() -add_definitions(-DORTHANC_BUILD_UNIT_TESTS=1) +add_definitions( + -DORTHANC_BUILD_UNIT_TESTS=1 + ) + include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake) add_executable(UnitTests ${GTEST_SOURCES} ${ORTHANC_UNIT_TESTS_SOURCES} ) -target_link_libraries(UnitTests ServerLibrary CoreLibrary ${STATIC_LUA} ${STATIC_GOOGLE_LOG}) +target_link_libraries(UnitTests ServerLibrary CoreLibrary ${DCMTK_LIBRARIES}) if (${OPENSSL_SOURCES_LENGTH} GREATER 0) target_link_libraries(UnitTests OpenSSL) @@ -380,112 +456,47 @@ ##################################################################### -## Create the standalone DLL containing the Orthanc Client API +## Build the "ServeFolders" plugin ##################################################################### -if (BUILD_CLIENT_LIBRARY) - include_directories(${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw) - - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - if (CMAKE_CROSSCOMPILING) - # Remove the default "lib" prefix from "libOrthancClient.dll" if cross-compiling - set(CMAKE_SHARED_LIBRARY_PREFIX "") +if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS) + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ServeFolders ServeFolders.dll "Orthanc plugin to serve additional folders" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ServeFolders.rc + ) - if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) - set(ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def) - elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 8) - set(ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def) - else() - message(FATAL_ERROR "Support your platform here") - endif() - else() - # Nothing to do if using Visual Studio - endif() - - if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) - set(CMAKE_SHARED_LIBRARY_SUFFIX "_Windows32.dll") - list(APPEND ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc) - elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 8) - set(CMAKE_SHARED_LIBRARY_SUFFIX "_Windows64.dll") - list(APPEND ORTHANC_CPP_CLIENT_AUX ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc) - else() - message(FATAL_ERROR "Support your platform here") - endif() - - else() - set(ORTHANC_CPP_CLIENT_AUX ${OPENSSL_SOURCES}) + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") endif() - add_library(OrthancClient SHARED - ${ORTHANC_ROOT}/OrthancCppClient/OrthancCppClient.cpp - ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/SharedLibrary.cpp - ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c - ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp - ${ORTHANC_CPP_CLIENT_AUX} - ${THIRD_PARTY_SOURCES} - ${CURL_SOURCES} - ${GOOGLE_LOG_SOURCES} + add_definitions(-DSERVE_FOLDERS_VERSION="${ORTHANC_VERSION}") + + include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) + + add_library(ServeFolders SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + Plugins/Samples/ServeFolders/Plugin.cpp + ${AUTOGENERATED_DIR}/ServeFolders.rc ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") - set_target_properties(OrthancClient - PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed -Wl,--version-script=${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map" - ) - target_link_libraries(OrthancClient pthread) - - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - target_link_libraries(OrthancClient OpenSSL ws2_32) - - if (CMAKE_CROSSCOMPILING) - set_target_properties(OrthancClient - PROPERTIES LINK_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++" - ) - endif() - - elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - # TODO - target_link_libraries(OrthancClient pthread) - - else() - message(FATAL_ERROR "Support your platform here") - endif() - - - # Set the version of the "Orthanc Client" shared library - file(STRINGS - ${CMAKE_SOURCE_DIR}/OrthancCppClient/SharedLibrary/Product.json - ORTHANC_CLIENT_VERSION_TMP - REGEX "^[ \t]*\"Version\"[ \t]*") - - string(REGEX REPLACE "^.*\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\"" "\\1.\\2" - ORTHANC_CLIENT_VERSION ${ORTHANC_CLIENT_VERSION_TMP}) - - message("Setting the version of the library to ${ORTHANC_CLIENT_VERSION}") - - set_target_properties(OrthancClient PROPERTIES - VERSION ${ORTHANC_CLIENT_VERSION} - SOVERSION ${ORTHANC_CLIENT_VERSION}) - - - install( - TARGETS OrthancClient - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION lib # Destination for Linux + set_target_properties( + ServeFolders PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} ) install( - FILES - ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h - ${ORTHANC_ROOT}/Plugins/Include/OrthancCPlugin.h - ${ORTHANC_ROOT}/Plugins/Include/OrthancCDatabasePlugin.h - ${ORTHANC_ROOT}/Plugins/Include/OrthancCppDatabasePlugin.h - DESTINATION include/orthanc + TARGETS ServeFolders + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux ) endif() - ##################################################################### ## Generate the documentation if Doxygen is present @@ -519,31 +530,28 @@ DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancPluginDocumentation/doc/ DESTINATION share/doc/orthanc/OrthancPlugin ) - - if (BUILD_CLIENT_LIBRARY) - configure_file( - ${CMAKE_SOURCE_DIR}/Resources/OrthancClient.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/OrthancClient.doxygen - @ONLY) - - add_custom_command(TARGET OrthancClient - POST_BUILD - COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancClient.doxygen - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating client documentation with Doxygen" VERBATIM - ) - - install( - DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/OrthancClientDocumentation/doc/ - DESTINATION share/doc/orthanc/OrthancClient - ) - endif() - else() message("Doxygen not found. The documentation will not be built.") endif() + +##################################################################### +## Install the plugin SDK +##################################################################### + +if (ENABLE_PLUGINS) + install( + FILES + Plugins/Include/orthanc/OrthancCPlugin.h + Plugins/Include/orthanc/OrthancCDatabasePlugin.h + Plugins/Include/orthanc/OrthancCppDatabasePlugin.h + DESTINATION include/orthanc + ) +endif() + + + ##################################################################### ## Prepare the "uninstall" target ## http://www.cmake.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F
--- a/Core/Cache/MemoryCache.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Cache/MemoryCache.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,9 +33,7 @@ #include "../PrecompiledHeaders.h" #include "MemoryCache.h" -#include <stdlib.h> // This fixes a problem in glog for recent - // releases of MinGW -#include <glog/logging.h> +#include "../Logging.h" namespace Orthanc {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/SharedArchive.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,134 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "SharedArchive.h" + + +#include "../Uuid.h" + + +namespace Orthanc +{ + void SharedArchive::RemoveInternal(const std::string& id) + { + Archive::iterator it = archive_.find(id); + + if (it != archive_.end()) + { + delete it->second; + archive_.erase(it); + } + } + + + SharedArchive::Accessor::Accessor(SharedArchive& that, + const std::string& id) : + lock_(that.mutex_) + { + Archive::iterator it = that.archive_.find(id); + + if (it == that.archive_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + that.lru_.MakeMostRecent(id); + item_ = it->second; + } + } + + + SharedArchive::SharedArchive(size_t maxSize) : + maxSize_(maxSize) + { + if (maxSize == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + SharedArchive::~SharedArchive() + { + for (Archive::iterator it = archive_.begin(); + it != archive_.end(); ++it) + { + delete it->second; + } + } + + + std::string SharedArchive::Add(IDynamicObject* obj) + { + boost::mutex::scoped_lock lock(mutex_); + + if (archive_.size() == maxSize_) + { + // The quota has been reached, remove the oldest element + std::string oldest = lru_.RemoveOldest(); + RemoveInternal(oldest); + } + + std::string id = Toolbox::GenerateUuid(); + RemoveInternal(id); // Should never be useful because of UUID + archive_[id] = obj; + lru_.Add(id); + + return id; + } + + + void SharedArchive::Remove(const std::string& id) + { + boost::mutex::scoped_lock lock(mutex_); + RemoveInternal(id); + lru_.Invalidate(id); + } + + + void SharedArchive::List(std::list<std::string>& items) + { + items.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + for (Archive::const_iterator it = archive_.begin(); + it != archive_.end(); ++it) + { + items.push_back(it->first); + } + } +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Cache/SharedArchive.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "LeastRecentlyUsedIndex.h" +#include "../IDynamicObject.h" + +#include <map> +#include <boost/thread.hpp> + +namespace Orthanc +{ + class SharedArchive : public boost::noncopyable + { + private: + typedef std::map<std::string, IDynamicObject*> Archive; + + size_t maxSize_; + boost::mutex mutex_; + Archive archive_; + Orthanc::LeastRecentlyUsedIndex<std::string> lru_; + + void RemoveInternal(const std::string& id); + + public: + class Accessor : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + IDynamicObject* item_; + + public: + Accessor(SharedArchive& that, + const std::string& id); + + IDynamicObject& GetItem() const + { + return *item_; + } + }; + + + SharedArchive(size_t maxSize); + + ~SharedArchive(); + + std::string Add(IDynamicObject* obj); // Takes the ownership + + void Remove(const std::string& id); + + void List(std::list<std::string>& items); + }; +} + +
--- a/Core/Compression/BufferCompressor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "BufferCompressor.h" - -namespace Orthanc -{ - void BufferCompressor::Compress(std::string& output, - const std::vector<uint8_t>& input) - { - if (input.size() > 0) - Compress(output, &input[0], input.size()); - else - Compress(output, NULL, 0); - } - - void BufferCompressor::Uncompress(std::string& output, - const std::vector<uint8_t>& input) - { - if (input.size() > 0) - Uncompress(output, &input[0], input.size()); - else - Uncompress(output, NULL, 0); - } - - void BufferCompressor::Compress(std::string& output, - const std::string& input) - { - if (input.size() > 0) - Compress(output, &input[0], input.size()); - else - Compress(output, NULL, 0); - } - - void BufferCompressor::Uncompress(std::string& output, - const std::string& input) - { - if (input.size() > 0) - Uncompress(output, &input[0], input.size()); - else - Uncompress(output, NULL, 0); - } -}
--- a/Core/Compression/BufferCompressor.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <string> -#include <cstddef> -#include <stdint.h> -#include <vector> - -namespace Orthanc -{ - class BufferCompressor - { - public: - virtual ~BufferCompressor() - { - } - - virtual void Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize) = 0; - - virtual void Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize) = 0; - - virtual void Compress(std::string& compressed, - const std::vector<uint8_t>& uncompressed); - - virtual void Uncompress(std::string& uncompressed, - const std::vector<uint8_t>& compressed); - - virtual void Compress(std::string& compressed, - const std::string& uncompressed); - - virtual void Uncompress(std::string& uncompressed, - const std::string& compressed); - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compression/DeflateBaseCompressor.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "DeflateBaseCompressor.h" + +#include "../OrthancException.h" +#include "../Logging.h" + +#include <string.h> + +namespace Orthanc +{ + void DeflateBaseCompressor::SetCompressionLevel(uint8_t level) + { + if (level >= 10) + { + LOG(ERROR) << "Zlib compression level must be between 0 (no compression) and 9 (highest compression)"; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + compressionLevel_ = level; + } + + + uint64_t DeflateBaseCompressor::ReadUncompressedSizePrefix(const void* compressed, + size_t compressedSize) + { + if (compressedSize == 0) + { + return 0; + } + + if (compressedSize < sizeof(uint64_t)) + { + LOG(ERROR) << "The compressed buffer is ill-formed"; + throw OrthancException(ErrorCode_CorruptedFile); + } + + uint64_t size; + memcpy(&size, compressed, sizeof(uint64_t)); + + return size; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compression/DeflateBaseCompressor.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,75 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "IBufferCompressor.h" + +#include <stdint.h> + +namespace Orthanc +{ + class DeflateBaseCompressor : public IBufferCompressor + { + private: + uint8_t compressionLevel_; + bool prefixWithUncompressedSize_; + + protected: + uint64_t ReadUncompressedSizePrefix(const void* compressed, + size_t compressedSize); + + public: + DeflateBaseCompressor() : + compressionLevel_(6), + prefixWithUncompressedSize_(false) + { + } + + void SetCompressionLevel(uint8_t level); + + void SetPrefixWithUncompressedSize(bool prefix) + { + prefixWithUncompressedSize_ = prefix; + } + + bool HasPrefixWithUncompressedSize() const + { + return prefixWithUncompressedSize_; + } + + uint8_t GetCompressionLevel() const + { + return compressionLevel_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compression/GzipCompressor.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,277 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "GzipCompressor.h" + +#include <stdio.h> +#include <string.h> +#include <zlib.h> + +#include "../OrthancException.h" +#include "../Logging.h" + +namespace Orthanc +{ + uint64_t GzipCompressor::GuessUncompressedSize(const void* compressed, + size_t compressedSize) + { + /** + * "Is there a way to find out the size of the original file which + * is inside a GZIP file? [...] There is no truly reliable way, + * other than gunzipping the stream. You do not need to save the + * result of the decompression, so you can determine the size by + * simply reading and decoding the entire file without taking up + * space with the decompressed result. + * + * There is an unreliable way to determine the uncompressed size, + * which is to look at the last four bytes of the gzip file, which + * is the uncompressed length of that entry modulo 232 in little + * endian order. + * + * It is unreliable because a) the uncompressed data may be longer + * than 2^32 bytes, and b) the gzip file may consist of multiple + * gzip streams, in which case you would find the length of only + * the last of those streams. + * + * If you are in control of the source of the gzip files, you know + * that they consist of single gzip streams, and you know that + * they are less than 2^32 bytes uncompressed, then and only then + * can you use those last four bytes with confidence." + * + * http://stackoverflow.com/a/9727599/881731 + **/ + + if (compressedSize < 4) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const uint8_t* p = reinterpret_cast<const uint8_t*>(compressed) + compressedSize - 4; + + return ((static_cast<uint32_t>(p[0]) << 0) + + (static_cast<uint32_t>(p[1]) << 8) + + (static_cast<uint32_t>(p[2]) << 16) + + (static_cast<uint32_t>(p[3]) << 24)); + } + + + + void GzipCompressor::Compress(std::string& compressed, + const void* uncompressed, + size_t uncompressedSize) + { + uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */; + if (compressedSize == 0) + { + compressedSize = 1; + } + + uint8_t* target; + if (HasPrefixWithUncompressedSize()) + { + compressed.resize(compressedSize + sizeof(uint64_t)); + target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t); + } + else + { + compressed.resize(compressedSize); + target = reinterpret_cast<uint8_t*>(&compressed[0]); + } + + z_stream stream; + memset(&stream, 0, sizeof(stream)); + + stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(uncompressed)); + stream.next_out = reinterpret_cast<Bytef*>(target); + + stream.avail_in = static_cast<uInt>(uncompressedSize); + stream.avail_out = static_cast<uInt>(compressedSize); + + // Ensure no overflow (if the buffer is too large for the current archicture) + if (static_cast<size_t>(stream.avail_in) != uncompressedSize || + static_cast<size_t>(stream.avail_out) != compressedSize) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Initialize the compression engine + int error = deflateInit2(&stream, + GetCompressionLevel(), + Z_DEFLATED, + MAX_WBITS + 16, // ask for gzip output + 8, // default memory level + Z_DEFAULT_STRATEGY); + + if (error != Z_OK) + { + // Cannot initialize zlib + compressed.clear(); + throw OrthancException(ErrorCode_InternalError); + } + + // Compress the input buffer + error = deflate(&stream, Z_FINISH); + + if (error != Z_STREAM_END) + { + deflateEnd(&stream); + compressed.clear(); + + switch (error) + { + case Z_MEM_ERROR: + throw OrthancException(ErrorCode_NotEnoughMemory); + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + size_t size = stream.total_out; + + if (deflateEnd(&stream) != Z_OK) + { + throw OrthancException(ErrorCode_InternalError); + } + + // The compression was successful + if (HasPrefixWithUncompressedSize()) + { + uint64_t s = static_cast<uint64_t>(uncompressedSize); + memcpy(&compressed[0], &s, sizeof(uint64_t)); + compressed.resize(size + sizeof(uint64_t)); + } + else + { + compressed.resize(size); + } + } + + + void GzipCompressor::Uncompress(std::string& uncompressed, + const void* compressed, + size_t compressedSize) + { + uint64_t uncompressedSize; + const uint8_t* source = reinterpret_cast<const uint8_t*>(compressed); + + if (HasPrefixWithUncompressedSize()) + { + uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize); + source += sizeof(uint64_t); + compressedSize -= sizeof(uint64_t); + } + else + { + uncompressedSize = GuessUncompressedSize(compressed, compressedSize); + } + + try + { + uncompressed.resize(static_cast<size_t>(uncompressedSize)); + } + catch (...) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + z_stream stream; + memset(&stream, 0, sizeof(stream)); + + char dummy = '\0'; // zlib does not like NULL output buffers (even if the uncompressed data is empty) + stream.next_in = const_cast<Bytef*>(source); + stream.next_out = reinterpret_cast<Bytef*>(uncompressedSize == 0 ? &dummy : &uncompressed[0]); + + stream.avail_in = static_cast<uInt>(compressedSize); + stream.avail_out = static_cast<uInt>(uncompressedSize); + + // Ensure no overflow (if the buffer is too large for the current archicture) + if (static_cast<size_t>(stream.avail_in) != compressedSize || + static_cast<size_t>(stream.avail_out) != uncompressedSize) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Initialize the compression engine + int error = inflateInit2(&stream, + MAX_WBITS + 16); // this is a gzip input + + if (error != Z_OK) + { + // Cannot initialize zlib + uncompressed.clear(); + throw OrthancException(ErrorCode_InternalError); + } + + // Uncompress the input buffer + error = inflate(&stream, Z_FINISH); + + if (error != Z_STREAM_END) + { + inflateEnd(&stream); + uncompressed.clear(); + + switch (error) + { + case Z_MEM_ERROR: + throw OrthancException(ErrorCode_NotEnoughMemory); + + case Z_BUF_ERROR: + case Z_NEED_DICT: + throw OrthancException(ErrorCode_BadFileFormat); + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + size_t size = stream.total_out; + + if (inflateEnd(&stream) != Z_OK) + { + uncompressed.clear(); + throw OrthancException(ErrorCode_InternalError); + } + + if (size != uncompressedSize) + { + uncompressed.clear(); + + // The uncompressed size was not that properly guess, presumably + // because of a file size over 4GB. Should fallback to + // stream-based decompression. + LOG(ERROR) << "The uncompressed size of a gzip-encoded buffer was not properly guessed"; + throw OrthancException(ErrorCode_NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compression/GzipCompressor.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "DeflateBaseCompressor.h" + +namespace Orthanc +{ + class GzipCompressor : public DeflateBaseCompressor + { + private: + uint64_t GuessUncompressedSize(const void* compressed, + size_t compressedSize); + + public: + GzipCompressor() + { + SetPrefixWithUncompressedSize(false); + } + + virtual void Compress(std::string& compressed, + const void* uncompressed, + size_t uncompressedSize); + + virtual void Uncompress(std::string& uncompressed, + const void* compressed, + size_t compressedSize); + }; +}
--- a/Core/Compression/HierarchicalZipWriter.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Compression/HierarchicalZipWriter.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -53,7 +53,7 @@ if (c == '^') c = ' '; - if (c < 128 && + if (c <= 127 && c >= 0) { if (isspace(c))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Compression/IBufferCompressor.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class IBufferCompressor : public boost::noncopyable + { + public: + virtual ~IBufferCompressor() + { + } + + virtual void Compress(std::string& compressed, + const void* uncompressed, + size_t uncompressedSize) = 0; + + virtual void Uncompress(std::string& uncompressed, + const void* compressed, + size_t compressedSize) = 0; + + static void Compress(std::string& compressed, + IBufferCompressor& compressor, + const std::string& uncompressed) + { + compressor.Compress(compressed, + uncompressed.size() == 0 ? NULL : uncompressed.c_str(), + uncompressed.size()); + } + + static void Uncompress(std::string& uncompressed, + IBufferCompressor& compressor, + const std::string& compressed) + { + compressor.Uncompress(uncompressed, + compressed.size() == 0 ? NULL : compressed.c_str(), + compressed.size()); + } + }; +}
--- a/Core/Compression/ZipWriter.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Compression/ZipWriter.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -44,6 +44,7 @@ #include "../../Resources/ThirdParty/minizip/zip.h" #include "../OrthancException.h" +#include "../Logging.h" static void PrepareFileInfo(zip_fileinfo& zfi) @@ -122,7 +123,8 @@ if (path_.size() == 0) { - throw OrthancException("Please call SetOutputPath() before creating the file"); + LOG(ERROR) << "Please call SetOutputPath() before creating the file"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); } hasFileInZip_ = false; @@ -165,7 +167,8 @@ { if (level >= 10) { - throw OrthancException("ZIP compression level must be between 0 (no compression) and 9 (highest compression"); + LOG(ERROR) << "ZIP compression level must be between 0 (no compression) and 9 (highest compression)"; + throw OrthancException(ErrorCode_ParameterOutOfRange); } Close(); @@ -224,7 +227,8 @@ { if (!hasFileInZip_) { - throw OrthancException("Call first OpenFile()"); + LOG(ERROR) << "Call first OpenFile()"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); } const size_t maxBytesInAStep = std::numeric_limits<int32_t>::max();
--- a/Core/Compression/ZlibCompressor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Compression/ZlibCompressor.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,24 +33,15 @@ #include "../PrecompiledHeaders.h" #include "ZlibCompressor.h" +#include "../OrthancException.h" +#include "../Logging.h" + #include <stdio.h> #include <string.h> #include <zlib.h> -#include "../OrthancException.h" namespace Orthanc { - void ZlibCompressor::SetCompressionLevel(uint8_t level) - { - if (level >= 10) - { - throw OrthancException("Zlib compression level must be between 0 (no compression) and 9 (highest compression"); - } - - compressionLevel_ = level; - } - - void ZlibCompressor::Compress(std::string& compressed, const void* uncompressed, size_t uncompressedSize) @@ -61,25 +52,32 @@ return; } - uLongf compressedSize = compressBound(uncompressedSize); - compressed.resize(compressedSize + sizeof(size_t)); + uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */; + if (compressedSize == 0) + { + compressedSize = 1; + } - int error = compress2 - (reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(size_t), - &compressedSize, - const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), - uncompressedSize, - compressionLevel_); - - memcpy(&compressed[0], &uncompressedSize, sizeof(size_t)); - - if (error == Z_OK) + uint8_t* target; + if (HasPrefixWithUncompressedSize()) { - compressed.resize(compressedSize + sizeof(size_t)); - return; + compressed.resize(compressedSize + sizeof(uint64_t)); + target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t); } else { + compressed.resize(compressedSize); + target = reinterpret_cast<uint8_t*>(&compressed[0]); + } + + int error = compress2(target, + &compressedSize, + const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), + uncompressedSize, + GetCompressionLevel()); + + if (error != Z_OK) + { compressed.clear(); switch (error) @@ -91,6 +89,18 @@ throw OrthancException(ErrorCode_InternalError); } } + + // The compression was successful + if (HasPrefixWithUncompressedSize()) + { + uint64_t s = static_cast<uint64_t>(uncompressedSize); + memcpy(&compressed[0], &s, sizeof(uint64_t)); + compressed.resize(compressedSize + sizeof(uint64_t)); + } + else + { + compressed.resize(compressedSize); + } } @@ -104,29 +114,29 @@ return; } - if (compressedSize < sizeof(size_t)) + if (!HasPrefixWithUncompressedSize()) { - throw OrthancException("Zlib: The compressed buffer is ill-formed"); + LOG(ERROR) << "Cannot guess the uncompressed size of a zlib-encoded buffer"; + throw OrthancException(ErrorCode_InternalError); } - size_t uncompressedLength; - memcpy(&uncompressedLength, compressed, sizeof(size_t)); + uint64_t uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize); try { - uncompressed.resize(uncompressedLength); + uncompressed.resize(static_cast<size_t>(uncompressedSize)); } catch (...) { - throw OrthancException("Zlib: Corrupted compressed buffer"); + throw OrthancException(ErrorCode_NotEnoughMemory); } - uLongf tmp = uncompressedLength; + uLongf tmp = static_cast<uLongf>(uncompressedSize); int error = uncompress (reinterpret_cast<uint8_t*>(&uncompressed[0]), &tmp, - reinterpret_cast<const uint8_t*>(compressed) + sizeof(size_t), - compressedSize - sizeof(size_t)); + reinterpret_cast<const uint8_t*>(compressed) + sizeof(uint64_t), + compressedSize - sizeof(uint64_t)); if (error != Z_OK) { @@ -135,7 +145,7 @@ switch (error) { case Z_DATA_ERROR: - throw OrthancException("Zlib: Corrupted or incomplete compressed buffer"); + throw OrthancException(ErrorCode_CorruptedFile); case Z_MEM_ERROR: throw OrthancException(ErrorCode_NotEnoughMemory);
--- a/Core/Compression/ZlibCompressor.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Compression/ZlibCompressor.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,29 +32,16 @@ #pragma once -#include "BufferCompressor.h" +#include "DeflateBaseCompressor.h" namespace Orthanc { - class ZlibCompressor : public BufferCompressor + class ZlibCompressor : public DeflateBaseCompressor { - private: - uint8_t compressionLevel_; - public: - using BufferCompressor::Compress; - using BufferCompressor::Uncompress; - ZlibCompressor() { - compressionLevel_ = 6; - } - - void SetCompressionLevel(uint8_t level); - - uint8_t GetCompressionLevel() const - { - return compressionLevel_; + SetPrefixWithUncompressedSize(true); } virtual void Compress(std::string& compressed,
--- a/Core/DicomFormat/DicomMap.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -273,6 +273,16 @@ result.SetValue(DICOM_TAG_ACCESSION_NUMBER, ""); result.SetValue(DICOM_TAG_PATIENT_ID, ""); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, ""); + + // These tags are considered as "main" by Orthanc, but are not in the Series module + result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer + result.Remove(DicomTag(0x0008, 0x1010)); // Station name + result.Remove(DicomTag(0x0018, 0x0024)); // Sequence name + result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES); + result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION); + result.Remove(DICOM_TAG_NUMBER_OF_SLICES); + result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS); + result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES); } void DicomMap::SetupFindInstanceTemplate(DicomMap& result) @@ -406,4 +416,16 @@ DicomArray a(*this); a.Print(fp); } + + + void DicomMap::GetTags(std::set<DicomTag>& tags) const + { + tags.clear(); + + for (Map::const_iterator it = map_.begin(); + it != map_.end(); ++it) + { + tags.insert(it->first); + } + } }
--- a/Core/DicomFormat/DicomMap.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/DicomFormat/DicomMap.h Wed Sep 30 13:23:31 2015 +0200 @@ -77,6 +77,11 @@ { Clear(); } + + size_t GetSize() const + { + return map_.size(); + } DicomMap* Clone() const; @@ -166,5 +171,7 @@ static void GetMainDicomTags(std::set<DicomTag>& result); void Print(FILE* fp) const; + + void GetTags(std::set<DicomTag>& tags) const; }; }
--- a/Core/DicomFormat/DicomTag.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/DicomFormat/DicomTag.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -118,11 +118,10 @@ } - void DicomTag::GetTagsForModule(std::set<DicomTag>& target, + void DicomTag::AddTagsForModule(std::set<DicomTag>& target, DicomModule module) { // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions - target.clear(); switch (module) {
--- a/Core/DicomFormat/DicomTag.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/DicomFormat/DicomTag.h Wed Sep 30 13:23:31 2015 +0200 @@ -84,7 +84,7 @@ friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); - static void GetTagsForModule(std::set<DicomTag>& target, + static void AddTagsForModule(std::set<DicomTag>& target, DicomModule module); bool IsIdentifier() const; @@ -106,8 +106,8 @@ static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090); static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002); - static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010); + static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011); // The following is used for "modify/anonymize" operations static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); @@ -134,4 +134,18 @@ static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006); static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); + + // Tags related to date and time + static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022); + static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032); + static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023); + static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033); + static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012); + static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013); + static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030); + static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032); + static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021); + static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031); + static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020); + static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030); }
--- a/Core/Enumerations.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Enumerations.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -36,8 +36,294 @@ #include "OrthancException.h" #include "Toolbox.h" +#include <string.h> + namespace Orthanc { + // This function is autogenerated by the script + // "Resources/GenerateErrorCodes.py" + const char* EnumerationToString(ErrorCode error) + { + if (error >= ErrorCode_START_PLUGINS) + { + return "Error encountered within some plugin"; + } + + switch (error) + { + case ErrorCode_InternalError: + return "Internal error"; + + case ErrorCode_Success: + return "Success"; + + case ErrorCode_Plugin: + return "Error encountered within the plugin engine"; + + case ErrorCode_NotImplemented: + return "Not implemented yet"; + + case ErrorCode_ParameterOutOfRange: + return "Parameter out of range"; + + case ErrorCode_NotEnoughMemory: + return "Not enough memory"; + + case ErrorCode_BadParameterType: + return "Bad type for a parameter"; + + case ErrorCode_BadSequenceOfCalls: + return "Bad sequence of calls"; + + case ErrorCode_InexistentItem: + return "Accessing an inexistent item"; + + case ErrorCode_BadRequest: + return "Bad request"; + + case ErrorCode_NetworkProtocol: + return "Error in the network protocol"; + + case ErrorCode_SystemCommand: + return "Error while calling a system command"; + + case ErrorCode_Database: + return "Error with the database engine"; + + case ErrorCode_UriSyntax: + return "Badly formatted URI"; + + case ErrorCode_InexistentFile: + return "Inexistent file"; + + case ErrorCode_CannotWriteFile: + return "Cannot write to file"; + + case ErrorCode_BadFileFormat: + return "Bad file format"; + + case ErrorCode_Timeout: + return "Timeout"; + + case ErrorCode_UnknownResource: + return "Unknown resource"; + + case ErrorCode_IncompatibleDatabaseVersion: + return "Incompatible version of the database"; + + case ErrorCode_FullStorage: + return "The file storage is full"; + + case ErrorCode_CorruptedFile: + return "Corrupted file (e.g. inconsistent MD5 hash)"; + + case ErrorCode_InexistentTag: + return "Inexistent tag"; + + case ErrorCode_ReadOnly: + return "Cannot modify a read-only data structure"; + + case ErrorCode_IncompatibleImageFormat: + return "Incompatible format of the images"; + + case ErrorCode_IncompatibleImageSize: + return "Incompatible size of the images"; + + case ErrorCode_SharedLibrary: + return "Error while using a shared library (plugin)"; + + case ErrorCode_UnknownPluginService: + return "Plugin invoking an unknown service"; + + case ErrorCode_UnknownDicomTag: + return "Unknown DICOM tag"; + + case ErrorCode_BadJson: + return "Cannot parse a JSON document"; + + case ErrorCode_Unauthorized: + return "Bad credentials were provided to an HTTP request"; + + case ErrorCode_BadFont: + return "Badly formatted font file"; + + case ErrorCode_DatabasePlugin: + return "The plugin implementing a custom database back-end does not fulfill the proper interface"; + + case ErrorCode_StorageAreaPlugin: + return "Error in the plugin implementing a custom storage area"; + + case ErrorCode_SQLiteNotOpened: + return "SQLite: The database is not opened"; + + case ErrorCode_SQLiteAlreadyOpened: + return "SQLite: Connection is already open"; + + case ErrorCode_SQLiteCannotOpen: + return "SQLite: Unable to open the database"; + + case ErrorCode_SQLiteStatementAlreadyUsed: + return "SQLite: This cached statement is already being referred to"; + + case ErrorCode_SQLiteExecute: + return "SQLite: Cannot execute a command"; + + case ErrorCode_SQLiteRollbackWithoutTransaction: + return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"; + + case ErrorCode_SQLiteCommitWithoutTransaction: + return "SQLite: Committing a nonexistent transaction"; + + case ErrorCode_SQLiteRegisterFunction: + return "SQLite: Unable to register a function"; + + case ErrorCode_SQLiteFlush: + return "SQLite: Unable to flush the database"; + + case ErrorCode_SQLiteCannotRun: + return "SQLite: Cannot run a cached statement"; + + case ErrorCode_SQLiteCannotStep: + return "SQLite: Cannot step over a cached statement"; + + case ErrorCode_SQLiteBindOutOfRange: + return "SQLite: Bing a value while out of range (serious error)"; + + case ErrorCode_SQLitePrepareStatement: + return "SQLite: Cannot prepare a cached statement"; + + case ErrorCode_SQLiteTransactionAlreadyStarted: + return "SQLite: Beginning the same transaction twice"; + + case ErrorCode_SQLiteTransactionCommit: + return "SQLite: Failure when committing the transaction"; + + case ErrorCode_SQLiteTransactionBegin: + return "SQLite: Cannot start a transaction"; + + case ErrorCode_DirectoryOverFile: + return "The directory to be created is already occupied by a regular file"; + + case ErrorCode_FileStorageCannotWrite: + return "Unable to create a subdirectory or a file in the file storage"; + + case ErrorCode_DirectoryExpected: + return "The specified path does not point to a directory"; + + case ErrorCode_HttpPortInUse: + return "The TCP port of the HTTP server is already in use"; + + case ErrorCode_DicomPortInUse: + return "The TCP port of the DICOM server is already in use"; + + case ErrorCode_BadHttpStatusInRest: + return "This HTTP status is not allowed in a REST API"; + + case ErrorCode_RegularFileExpected: + return "The specified path does not point to a regular file"; + + case ErrorCode_PathToExecutable: + return "Unable to get the path to the executable"; + + case ErrorCode_MakeDirectory: + return "Cannot create a directory"; + + case ErrorCode_BadApplicationEntityTitle: + return "An application entity title (AET) cannot be empty or be longer than 16 characters"; + + case ErrorCode_NoCFindHandler: + return "No request handler factory for DICOM C-FIND SCP"; + + case ErrorCode_NoCMoveHandler: + return "No request handler factory for DICOM C-MOVE SCP"; + + case ErrorCode_NoCStoreHandler: + return "No request handler factory for DICOM C-STORE SCP"; + + case ErrorCode_NoApplicationEntityFilter: + return "No application entity filter"; + + case ErrorCode_NoSopClassOrInstance: + return "DicomUserConnection: Unable to find the SOP class and instance"; + + case ErrorCode_NoPresentationContext: + return "DicomUserConnection: No acceptable presentation context for modality"; + + case ErrorCode_DicomFindUnavailable: + return "DicomUserConnection: The C-FIND command is not supported by the remote SCP"; + + case ErrorCode_DicomMoveUnavailable: + return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP"; + + case ErrorCode_CannotStoreInstance: + return "Cannot store an instance"; + + case ErrorCode_CreateDicomNotString: + return "Only string values are supported when creating DICOM instances"; + + case ErrorCode_CreateDicomOverrideTag: + return "Trying to override a value inherited from a parent module"; + + case ErrorCode_CreateDicomUseContent: + return "Use \"Content\" to inject an image into a new DICOM instance"; + + case ErrorCode_CreateDicomNoPayload: + return "No payload is present for one instance in the series"; + + case ErrorCode_CreateDicomUseDataUriScheme: + return "The payload of the DICOM instance must be specified according to Data URI scheme"; + + case ErrorCode_CreateDicomBadParent: + return "Trying to attach a new DICOM instance to an inexistent resource"; + + case ErrorCode_CreateDicomParentIsInstance: + return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)"; + + case ErrorCode_CreateDicomParentEncoding: + return "Unable to get the encoding of the parent resource"; + + case ErrorCode_UnknownModality: + return "Unknown modality"; + + case ErrorCode_BadJobOrdering: + return "Bad ordering of filters in a job"; + + case ErrorCode_JsonToLuaTable: + return "Cannot convert the given JSON object to a Lua table"; + + case ErrorCode_CannotCreateLua: + return "Cannot create the Lua context"; + + case ErrorCode_CannotExecuteLua: + return "Cannot execute a Lua command"; + + case ErrorCode_LuaAlreadyExecuted: + return "Arguments cannot be pushed after the Lua function is executed"; + + case ErrorCode_LuaBadOutput: + return "The Lua function does not give the expected number of outputs"; + + case ErrorCode_NotLuaPredicate: + return "The Lua function is not a predicate (only true/false outputs allowed)"; + + case ErrorCode_LuaReturnsNoString: + return "The Lua function does not return a string"; + + case ErrorCode_StorageAreaAlreadyRegistered: + return "Another plugin has already registered a custom storage area"; + + case ErrorCode_DatabaseBackendAlreadyRegistered: + return "Another plugin has already registered a custom database back-end"; + + case ErrorCode_DatabaseNotInitialized: + return "Plugin trying to call the database during its initialization"; + + default: + return "Unknown error code"; + } + } + + const char* EnumerationToString(HttpMethod method) { switch (method) @@ -289,6 +575,9 @@ case Encoding_Cyrillic: return "Cyrillic"; + case Encoding_Windows1251: + return "Windows1251"; + case Encoding_Arabic: return "Arabic"; @@ -365,6 +654,53 @@ } + const char* EnumerationToString(RequestOrigin origin) + { + switch (origin) + { + case RequestOrigin_Unknown: + return "Unknown"; + + case RequestOrigin_DicomProtocol: + return "DicomProtocol"; + + case RequestOrigin_Http: + return "Http"; + + case RequestOrigin_Plugins: + return "Plugins"; + + case RequestOrigin_Lua: + return "Lua"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(LogLevel level) + { + switch (level) + { + case LogLevel_Error: + return "ERROR"; + + case LogLevel_Warning: + return "WARNING"; + + case LogLevel_Info: + return "INFO"; + + case LogLevel_Trace: + return "TRACE"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -410,6 +746,11 @@ return Encoding_Cyrillic; } + if (s == "WINDOWS1251") + { + return Encoding_Windows1251; + } + if (s == "ARABIC") { return Encoding_Arabic; @@ -485,6 +826,31 @@ } + LogLevel StringToLogLevel(const char *level) + { + if (strcmp(level, "ERROR") == 0) + { + return LogLevel_Error; + } + else if (strcmp(level, "WARNING") == 0) + { + return LogLevel_Warning; + } + else if (strcmp(level, "INFO") == 0) + { + return LogLevel_Info; + } + else if (strcmp(level, "TRACE") == 0) + { + return LogLevel_Trace; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) @@ -617,4 +983,178 @@ return "application/octet-stream"; } } + + + const char* GetFileExtension(FileContentType type) + { + switch (type) + { + case FileContentType_Dicom: + return ".dcm"; + + case FileContentType_DicomAsJson: + return ".json"; + + default: + // Unknown file type + return ""; + } + } + + + ResourceType GetChildResourceType(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return ResourceType_Study; + + case ResourceType_Study: + return ResourceType_Series; + + case ResourceType_Series: + return ResourceType_Instance; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ResourceType GetParentResourceType(ResourceType type) + { + switch (type) + { + case ResourceType_Study: + return ResourceType_Patient; + + case ResourceType_Series: + return ResourceType_Study; + + case ResourceType_Instance: + return ResourceType_Series; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + DicomModule GetModule(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return DicomModule_Patient; + + case ResourceType_Study: + return DicomModule_Study; + + case ResourceType_Series: + return DicomModule_Series; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + + const char* GetDicomSpecificCharacterSet(Encoding encoding) + { + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + switch (encoding) + { + case Encoding_Utf8: + case Encoding_Ascii: + return "ISO_IR 192"; + + case Encoding_Latin1: + return "ISO_IR 100"; + + case Encoding_Latin2: + return "ISO_IR 101"; + + case Encoding_Latin3: + return "ISO_IR 109"; + + case Encoding_Latin4: + return "ISO_IR 110"; + + case Encoding_Latin5: + return "ISO_IR 148"; + + case Encoding_Cyrillic: + return "ISO_IR 144"; + + case Encoding_Arabic: + return "ISO_IR 127"; + + case Encoding_Greek: + return "ISO_IR 126"; + + case Encoding_Hebrew: + return "ISO_IR 138"; + + case Encoding_Japanese: + return "ISO_IR 13"; + + case Encoding_Chinese: + return "GB18030"; + + case Encoding_Thai: + return "ISO_IR 166"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + // This function is autogenerated by the script + // "Resources/GenerateErrorCodes.py" + HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error) + { + switch (error) + { + case ErrorCode_Success: + return HttpStatus_200_Ok; + + case ErrorCode_ParameterOutOfRange: + return HttpStatus_400_BadRequest; + + case ErrorCode_BadParameterType: + return HttpStatus_400_BadRequest; + + case ErrorCode_InexistentItem: + return HttpStatus_404_NotFound; + + case ErrorCode_BadRequest: + return HttpStatus_400_BadRequest; + + case ErrorCode_UriSyntax: + return HttpStatus_400_BadRequest; + + case ErrorCode_InexistentFile: + return HttpStatus_404_NotFound; + + case ErrorCode_BadFileFormat: + return HttpStatus_400_BadRequest; + + case ErrorCode_UnknownResource: + return HttpStatus_404_NotFound; + + case ErrorCode_InexistentTag: + return HttpStatus_404_NotFound; + + case ErrorCode_BadJson: + return HttpStatus_400_BadRequest; + + case ErrorCode_Unauthorized: + return HttpStatus_401_Unauthorized; + + default: + return HttpStatus_500_InternalServerError; + } + } }
--- a/Core/Enumerations.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Enumerations.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,8 +32,6 @@ #pragma once -#include <laaw/laaw.h> - namespace Orthanc { enum Endianness @@ -43,45 +41,115 @@ Endianness_Little }; + // This enumeration is autogenerated by the script + // "Resources/GenerateErrorCodes.py" enum ErrorCode { - // Generic error codes - ErrorCode_Success, - ErrorCode_Custom, - ErrorCode_InternalError, - ErrorCode_NotImplemented, - ErrorCode_ParameterOutOfRange, - ErrorCode_NotEnoughMemory, - ErrorCode_BadParameterType, - ErrorCode_BadSequenceOfCalls, - ErrorCode_InexistentItem, - ErrorCode_BadRequest, - ErrorCode_NetworkProtocol, - ErrorCode_SystemCommand, - ErrorCode_Database, + ErrorCode_InternalError = -1 /*!< Internal error */, + ErrorCode_Success = 0 /*!< Success */, + ErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + ErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + ErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + ErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + ErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + ErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + ErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + ErrorCode_BadRequest = 8 /*!< Bad request */, + ErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + ErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + ErrorCode_Database = 11 /*!< Error with the database engine */, + ErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + ErrorCode_InexistentFile = 13 /*!< Inexistent file */, + ErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + ErrorCode_BadFileFormat = 15 /*!< Bad file format */, + ErrorCode_Timeout = 16 /*!< Timeout */, + ErrorCode_UnknownResource = 17 /*!< Unknown resource */, + ErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + ErrorCode_FullStorage = 19 /*!< The file storage is full */, + ErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + ErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + ErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + ErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + ErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + ErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + ErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + ErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + ErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + ErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + ErrorCode_BadFont = 30 /*!< Badly formatted font file */, + ErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + ErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + ErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + ErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + ErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + ErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + ErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + ErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + ErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + ErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + ErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + ErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + ErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + ErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + ErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + ErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + ErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + ErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + ErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, + ErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + ErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + ErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + ErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + ErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + ErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + ErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + ErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + ErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + ErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + ErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + ErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + ErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + ErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + ErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + ErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + ErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + ErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + ErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + ErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + ErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + ErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + ErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + ErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + ErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + ErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + ErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + ErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + ErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + ErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + ErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + ErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + ErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + ErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + ErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + ErrorCode_START_PLUGINS = 1000000 + }; - // Specific error codes - ErrorCode_UriSyntax, - ErrorCode_InexistentFile, - ErrorCode_CannotWriteFile, - ErrorCode_BadFileFormat, - ErrorCode_Timeout, - ErrorCode_UnknownResource, - ErrorCode_IncompatibleDatabaseVersion, - ErrorCode_FullStorage, - ErrorCode_CorruptedFile, - ErrorCode_InexistentTag, - ErrorCode_ReadOnly, - ErrorCode_IncompatibleImageFormat, - ErrorCode_IncompatibleImageSize, - ErrorCode_SharedLibrary, - ErrorCode_Plugin + enum LogLevel + { + LogLevel_Error, + LogLevel_Warning, + LogLevel_Info, + LogLevel_Trace }; + /** * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.} **/ - enum LAAW_API PixelFormat + enum PixelFormat { /** * {summary}{Color image in RGB24 format.} @@ -120,7 +188,7 @@ /** * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.} **/ - enum LAAW_API ImageExtractionMode + enum ImageExtractionMode { /** * {summary}{Rescaled to 8bpp.} @@ -232,6 +300,15 @@ }; + // https://en.wikipedia.org/wiki/HTTP_compression + enum HttpCompression + { + HttpCompression_None, + HttpCompression_Deflate, + HttpCompression_Gzip + }; + + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ enum Encoding { @@ -243,6 +320,7 @@ Encoding_Latin4, Encoding_Latin5, // Turkish Encoding_Cyrillic, + Encoding_Windows1251, // Windows-1251 (commonly used for Cyrillic) Encoding_Arabic, Encoding_Greek, Encoding_Hebrew, @@ -283,6 +361,15 @@ DicomModule_Image }; + enum RequestOrigin + { + RequestOrigin_Unknown, + RequestOrigin_DicomProtocol, + RequestOrigin_Http, + RequestOrigin_Plugins, + RequestOrigin_Lua + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -292,8 +379,23 @@ enum CompressionType { + /** + * Buffer/file that is stored as-is, in a raw fashion, without + * compression. + **/ CompressionType_None = 1, - CompressionType_Zlib = 2 + + /** + * Buffer that is compressed using the "deflate" algorithm (RFC + * 1951), wrapped inside the zlib data format (RFC 1950), prefixed + * with a "uint64_t" (8 bytes) that encodes the size of the + * uncompressed buffer. If the compressed buffer is empty, its + * represents an empty uncompressed buffer. This format is + * internal to Orthanc. If the 8 first bytes are skipped AND the + * buffer is non-empty, the buffer is compatible with the + * "deflate" HTTP compression. + **/ + CompressionType_ZlibWithSize = 2 }; enum FileContentType @@ -318,6 +420,8 @@ }; + const char* EnumerationToString(ErrorCode code); + const char* EnumerationToString(HttpMethod method); const char* EnumerationToString(HttpStatus status); @@ -330,16 +434,34 @@ const char* EnumerationToString(PhotometricInterpretation photometric); + const char* EnumerationToString(LogLevel level); + + const char* EnumerationToString(RequestOrigin origin); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); ImageFormat StringToImageFormat(const char* format); + LogLevel StringToLogLevel(const char* format); + unsigned int GetBytesPerPixel(PixelFormat format); bool GetDicomEncoding(Encoding& encoding, const char* specificCharacterSet); const char* GetMimeType(FileContentType type); + + const char* GetFileExtension(FileContentType type); + + ResourceType GetChildResourceType(ResourceType type); + + ResourceType GetParentResourceType(ResourceType type); + + DicomModule GetModule(ResourceType type); + + const char* GetDicomSpecificCharacterSet(Encoding encoding); + + HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error); }
--- a/Core/FileStorage/CompressedFileStorageAccessor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "CompressedFileStorageAccessor.h" - -#include "../OrthancException.h" -#include "FileStorageAccessor.h" -#include "../HttpServer/BufferHttpSender.h" -#include "../Uuid.h" - -#include <memory> -#include <glog/logging.h> - -namespace Orthanc -{ - FileInfo CompressedFileStorageAccessor::WriteInternal(const void* data, - size_t size, - FileContentType type) - { - std::string uuid = Toolbox::GenerateUuid(); - - std::string md5; - - if (storeMD5_) - { - Toolbox::ComputeMD5(md5, data, size); - } - - switch (compressionType_) - { - case CompressionType_None: - { - GetStorageArea().Create(uuid.c_str(), data, size, type); - 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); - } - - if (compressed.size() > 0) - { - GetStorageArea().Create(uuid.c_str(), &compressed[0], compressed.size(), type); - } - else - { - GetStorageArea().Create(uuid.c_str(), NULL, 0, type); - } - - return FileInfo(uuid, type, size, md5, - CompressionType_Zlib, compressed.size(), compressedMD5); - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - CompressedFileStorageAccessor::CompressedFileStorageAccessor() : - storage_(NULL), - compressionType_(CompressionType_None) - { - } - - - CompressedFileStorageAccessor::CompressedFileStorageAccessor(IStorageArea& storage) : - storage_(&storage), - compressionType_(CompressionType_None) - { - } - - - IStorageArea& CompressedFileStorageAccessor::GetStorageArea() - { - if (storage_ == NULL) - { - LOG(ERROR) << "No storage area is currently available"; - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - return *storage_; - } - - - void CompressedFileStorageAccessor::Read(std::string& content, - const std::string& uuid, - FileContentType type) - { - switch (compressionType_) - { - case CompressionType_None: - GetStorageArea().Read(content, uuid, type); - break; - - case CompressionType_Zlib: - { - std::string compressed; - GetStorageArea().Read(compressed, uuid, type); - zlib_.Uncompress(content, compressed); - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - HttpFileSender* CompressedFileStorageAccessor::ConstructHttpFileSender(const std::string& uuid, - FileContentType type) - { - switch (compressionType_) - { - case CompressionType_None: - { - FileStorageAccessor uncompressedAccessor(GetStorageArea()); - return uncompressedAccessor.ConstructHttpFileSender(uuid, type); - } - - case CompressionType_Zlib: - { - std::string compressed; - GetStorageArea().Read(compressed, uuid, type); - - std::auto_ptr<BufferHttpSender> sender(new BufferHttpSender); - zlib_.Uncompress(sender->GetBuffer(), compressed); - - return sender.release(); - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void CompressedFileStorageAccessor::Remove(const std::string& uuid, - FileContentType type) - { - GetStorageArea().Remove(uuid, type); - } -}
--- a/Core/FileStorage/CompressedFileStorageAccessor.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "IStorageArea.h" -#include "StorageAccessor.h" -#include "../Compression/ZlibCompressor.h" - -namespace Orthanc -{ - class CompressedFileStorageAccessor : public StorageAccessor - { - private: - IStorageArea* storage_; - ZlibCompressor zlib_; - CompressionType compressionType_; - - protected: - virtual FileInfo WriteInternal(const void* data, - size_t size, - FileContentType type); - - public: - CompressedFileStorageAccessor(); - - CompressedFileStorageAccessor(IStorageArea& storage); - - void SetStorageArea(IStorageArea& storage) - { - storage_ = &storage; - } - - bool HasStorageArea() const - { - return storage_ != NULL; - } - - IStorageArea& GetStorageArea(); - - void SetCompressionForNextOperations(CompressionType compression) - { - compressionType_ = compression; - } - - CompressionType GetCompressionForNextOperations() - { - return compressionType_; - } - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type); - - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, - FileContentType type); - - virtual void Remove(const std::string& uuid, - FileContentType type); - }; -}
--- a/Core/FileStorage/FileStorageAccessor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "FileStorageAccessor.h" - -#include "../HttpServer/BufferHttpSender.h" -#include "../Uuid.h" - -#include <memory> -#include <stdio.h> - -namespace Orthanc -{ - FileInfo FileStorageAccessor::WriteInternal(const void* data, - size_t size, - FileContentType type) - { - std::string md5; - - if (storeMD5_) - { - Toolbox::ComputeMD5(md5, data, size); - } - - std::string uuid = Toolbox::GenerateUuid(); - storage_.Create(uuid.c_str(), data, size, type); - - return FileInfo(uuid, type, size, md5); - } - - - HttpFileSender* FileStorageAccessor::ConstructHttpFileSender(const std::string& uuid, - FileContentType type) - { - std::auto_ptr<BufferHttpSender> sender(new BufferHttpSender); - - storage_.Read(sender->GetBuffer(), uuid, type); - - return sender.release(); - } - -}
--- a/Core/FileStorage/FileStorageAccessor.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "StorageAccessor.h" -#include "IStorageArea.h" - -namespace Orthanc -{ - class FileStorageAccessor : public StorageAccessor - { - private: - IStorageArea& storage_; - - protected: - virtual FileInfo WriteInternal(const void* data, - size_t size, - FileContentType type); - - public: - FileStorageAccessor(IStorageArea& storage) : storage_(storage) - { - } - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type) - { - storage_.Read(content, uuid, type); - } - - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, - FileContentType type); - - virtual void Remove(const std::string& uuid, - FileContentType type) - { - storage_.Remove(uuid, type); - } - }; -}
--- a/Core/FileStorage/FilesystemStorage.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/FileStorage/FilesystemStorage.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -36,12 +36,13 @@ // http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system // http://stackoverflow.com/questions/446358/storing-a-large-number-of-images +#include "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" #include "../Uuid.h" #include <boost/filesystem/fstream.hpp> -#include <glog/logging.h> + static std::string ToString(const boost::filesystem::path& p) { @@ -82,7 +83,7 @@ //root_ = boost::filesystem::absolute(root).string(); root_ = root; - Toolbox::CreateDirectory(root); + Toolbox::MakeDirectory(root); } void FilesystemStorage::Create(const std::string& uuid, @@ -105,14 +106,14 @@ { if (!boost::filesystem::is_directory(path.parent_path())) { - throw OrthancException("The subdirectory to be created is already occupied by a regular file"); + throw OrthancException(ErrorCode_DirectoryOverFile); } } else { if (!boost::filesystem::create_directories(path.parent_path())) { - throw OrthancException("Unable to create a subdirectory in the file storage"); + throw OrthancException(ErrorCode_FileStorageCannotWrite); } } @@ -120,7 +121,7 @@ f.open(path, std::ofstream::out | std::ios::binary); if (!f.good()) { - throw OrthancException("Unable to create a new file in the file storage"); + throw OrthancException(ErrorCode_FileStorageCannotWrite); } if (size != 0) @@ -129,7 +130,7 @@ if (!f.good()) { f.close(); - throw OrthancException("Unable to write to the new file in the file storage"); + throw OrthancException(ErrorCode_FileStorageCannotWrite); } } @@ -212,7 +213,10 @@ void FilesystemStorage::Remove(const std::string& uuid, FileContentType /*type*/) { +#if ORTHANC_ENABLE_GOOGLE_LOG == 1 LOG(INFO) << "Deleting file " << uuid; +#endif + namespace fs = boost::filesystem; fs::path p = GetPath(uuid);
--- a/Core/FileStorage/FilesystemStorage.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/FileStorage/FilesystemStorage.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,6 +34,7 @@ #include "IStorageArea.h" +#include <stdint.h> #include <boost/filesystem.hpp> #include <set>
--- a/Core/FileStorage/IStorageArea.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/FileStorage/IStorageArea.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,6 +34,7 @@ #include "../Enumerations.h" +#include <string> #include <boost/noncopyable.hpp> namespace Orthanc
--- a/Core/FileStorage/StorageAccessor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/FileStorage/StorageAccessor.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,31 +33,126 @@ #include "../PrecompiledHeaders.h" #include "StorageAccessor.h" +#include "../Compression/ZlibCompressor.h" +#include "../OrthancException.h" +#include "../HttpServer/HttpStreamTranscoder.h" + namespace Orthanc { - FileInfo StorageAccessor::Write(const std::vector<uint8_t>& content, - FileContentType type) + FileInfo StorageAccessor::Write(const void* data, + size_t size, + FileContentType type, + CompressionType compression, + bool storeMd5) { - if (content.size() == 0) + std::string uuid = Toolbox::GenerateUuid(); + + std::string md5; + + if (storeMd5) + { + Toolbox::ComputeMD5(md5, data, size); + } + + switch (compression) { - return WriteInternal(NULL, 0, type); - } - else - { - return WriteInternal(&content[0], content.size(), type); + case CompressionType_None: + { + area_.Create(uuid, data, size, type); + return FileInfo(uuid, type, size, md5); + } + + case CompressionType_ZlibWithSize: + { + ZlibCompressor zlib; + + std::string compressed; + zlib.Compress(compressed, data, size); + + std::string compressedMD5; + + if (storeMd5) + { + Toolbox::ComputeMD5(compressedMD5, compressed); + } + + if (compressed.size() > 0) + { + area_.Create(uuid, &compressed[0], compressed.size(), type); + } + else + { + area_.Create(uuid, NULL, 0, type); + } + + return FileInfo(uuid, type, size, md5, + CompressionType_ZlibWithSize, compressed.size(), compressedMD5); + } + + default: + throw OrthancException(ErrorCode_NotImplemented); } } - FileInfo StorageAccessor::Write(const std::string& content, - FileContentType type) + + void StorageAccessor::Read(std::string& content, + const FileInfo& info) { - if (content.size() == 0) + switch (info.GetCompressionType()) { - return WriteInternal(NULL, 0, type); + case CompressionType_None: + { + area_.Read(content, info.GetUuid(), info.GetContentType()); + break; + } + + case CompressionType_ZlibWithSize: + { + ZlibCompressor zlib; + + std::string compressed; + area_.Read(compressed, info.GetUuid(), info.GetContentType()); + IBufferCompressor::Uncompress(content, zlib, compressed); + break; + } + + default: + { + throw OrthancException(ErrorCode_NotImplemented); + } } - else - { - return WriteInternal(&content[0], content.size(), type); - } + + // TODO Check the validity of the uncompressed MD5? + } + + + void StorageAccessor::SetupSender(BufferHttpSender& sender, + const FileInfo& info) + { + area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType()); + sender.SetContentType(GetMimeType(info.GetContentType())); + sender.SetContentFilename(info.GetUuid() + std::string(GetFileExtension(info.GetContentType()))); + } + + + void StorageAccessor::AnswerFile(HttpOutput& output, + const FileInfo& info) + { + BufferHttpSender sender; + SetupSender(sender, info); + + HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); + output.Answer(transcoder); + } + + + void StorageAccessor::AnswerFile(RestApiOutput& output, + const FileInfo& info) + { + BufferHttpSender sender; + SetupSender(sender, info); + + HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); + output.AnswerStream(transcoder); } }
--- a/Core/FileStorage/StorageAccessor.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/FileStorage/StorageAccessor.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,8 +32,10 @@ #pragma once +#include "IStorageArea.h" #include "FileInfo.h" -#include "../HttpServer/HttpFileSender.h" +#include "../HttpServer/BufferHttpSender.h" +#include "../RestApi/RestApiOutput.h" #include <vector> #include <string> @@ -44,54 +46,44 @@ { class StorageAccessor : boost::noncopyable { - protected: - bool storeMD5_; + private: + IStorageArea& area_; - virtual FileInfo WriteInternal(const void* data, - size_t size, - FileContentType type) = 0; + void SetupSender(BufferHttpSender& sender, + const FileInfo& info); public: - StorageAccessor() - { - storeMD5_ = true; - } - - virtual ~StorageAccessor() + StorageAccessor(IStorageArea& area) : area_(area) { } - void SetStoreMD5(bool storeMD5) - { - storeMD5_ = storeMD5; - } - - bool IsStoreMD5() const - { - return storeMD5_; - } - FileInfo Write(const void* data, size_t size, - FileContentType type) + FileContentType type, + CompressionType compression, + bool storeMd5); + + FileInfo Write(const std::string& data, + FileContentType type, + CompressionType compression, + bool storeMd5) { - return WriteInternal(data, size, type); + return Write((data.size() == 0 ? NULL : data.c_str()), + data.size(), type, compression, storeMd5); } - FileInfo Write(const std::vector<uint8_t>& content, - FileContentType type); - - FileInfo Write(const std::string& content, - FileContentType type); + void Read(std::string& content, + const FileInfo& info); - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type) = 0; + void Remove(const FileInfo& info) + { + area_.Remove(info.GetUuid(), info.GetContentType()); + } - virtual void Remove(const std::string& uuid, - FileContentType type) = 0; + void AnswerFile(HttpOutput& output, + const FileInfo& info); - virtual HttpFileSender* ConstructHttpFileSender(const std::string& uuid, - FileContentType type) = 0; + void AnswerFile(RestApiOutput& output, + const FileInfo& info); }; }
--- a/Core/HttpClient.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpClient.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,11 +33,46 @@ #include "PrecompiledHeaders.h" #include "HttpClient.h" -#include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" +#include "Toolbox.h" +#include "OrthancException.h" +#include "Logging.h" #include <string.h> #include <curl/curl.h> +#include <boost/algorithm/string/predicate.hpp> + + +static std::string globalCACertificates_; +static bool globalVerifyPeers_ = true; +static long globalTimeout_ = 0; + +extern "C" +{ + static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status) + { + if (code == CURLE_OK) + { + code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status); + return code; + } + else + { + *status = 0; + return code; + } + } + + // This is a dummy wrapper function to suppress any OpenSSL-related + // problem in valgrind. Inlining is prevented. +#if defined(__GNUC__) || defined(__clang__) + __attribute__((noinline)) +#endif + static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status) + { + return GetHttpStatus(curl_easy_perform(curl), curl, status); + } +} + namespace Orthanc @@ -49,11 +84,32 @@ }; + static void ThrowException(HttpStatus status) + { + switch (status) + { + case HttpStatus_400_BadRequest: + throw OrthancException(ErrorCode_BadRequest); + + case HttpStatus_401_Unauthorized: + throw OrthancException(ErrorCode_Unauthorized); + + case HttpStatus_404_NotFound: + throw OrthancException(ErrorCode_InexistentItem); + + default: + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + + static CURLcode CheckCode(CURLcode code) { if (code != CURLE_OK) { - throw OrthancException("libCURL error: " + std::string(curl_easy_strerror(code))); + LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code)); + throw OrthancException(ErrorCode_NetworkProtocol); } return code; @@ -96,10 +152,6 @@ CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); -#if ORTHANC_SSL_ENABLED == 1 - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); -#endif - // This fixes the "longjmp causes uninitialized stack frame" crash // that happens on modern Linux versions. // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame @@ -109,7 +161,8 @@ method_ = HttpMethod_Get; lastStatus_ = HttpStatus_200_Ok; isVerbose_ = false; - timeout_ = 0; + timeout_ = globalTimeout_; + verifyPeers_ = globalVerifyPeers_; } @@ -163,6 +216,19 @@ CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); + // Setup HTTPS-related options +#if ORTHANC_SSL_ENABLED == 1 + if (IsHttpsVerifyPeers()) + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, GetHttpsCACertificates().c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); + } + else + { + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); + } +#endif + // Reset the parameters from previous calls to Apply() CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, NULL)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); @@ -229,10 +295,10 @@ if (method_ == HttpMethod_Post || method_ == HttpMethod_Put) { - if (postData_.size() > 0) + if (body_.size() > 0) { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, postData_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, postData_.size())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str())); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size())); } else { @@ -243,10 +309,19 @@ // Do the actual request - CheckCode(curl_easy_perform(pimpl_->curl_)); + CURLcode code; + long status = 0; - long status; - CheckCode(curl_easy_getinfo(pimpl_->curl_, CURLINFO_RESPONSE_CODE, &status)); + if (boost::starts_with(url_, "https://")) + { + code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status); + } + else + { + code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status); + } + + CheckCode(code); if (status == 0) { @@ -284,13 +359,74 @@ } - void HttpClient::GlobalInitialize() + const std::string& HttpClient::GetHttpsCACertificates() const + { + if (caCertificates_.empty()) + { + return globalCACertificates_; + } + else + { + return caCertificates_; + } + } + + + void HttpClient::GlobalInitialize(bool httpsVerifyPeers, + const std::string& httpsVerifyCertificates) { + globalVerifyPeers_ = httpsVerifyPeers; + globalCACertificates_ = httpsVerifyCertificates; + +#if ORTHANC_SSL_ENABLED == 1 + if (httpsVerifyPeers) + { + if (globalCACertificates_.empty()) + { + LOG(WARNING) << "No certificates are provided to validate peers, " + << "set \"HttpsCACertificates\" if you need to do HTTPS requests"; + } + else + { + LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << globalCACertificates_; + } + } + else + { + LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled!"; + } +#endif + CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT)); } + void HttpClient::GlobalFinalize() { curl_global_cleanup(); } + + + void HttpClient::SetDefaultTimeout(long timeout) + { + LOG(INFO) << "Setting the default timeout for HTTP client connections: " << timeout << " seconds"; + globalTimeout_ = timeout; + } + + + void HttpClient::ApplyAndThrowException(std::string& answer) + { + if (!Apply(answer)) + { + ThrowException(GetLastStatus()); + } + } + + void HttpClient::ApplyAndThrowException(Json::Value& answer) + { + if (!Apply(answer)) + { + ThrowException(GetLastStatus()); + } + } }
--- a/Core/HttpClient.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpClient.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,7 +32,7 @@ #pragma once -#include "../Core/Enumerations.h" +#include "Enumerations.h" #include <string> #include <boost/shared_ptr.hpp> @@ -50,10 +50,12 @@ std::string credentials_; HttpMethod method_; HttpStatus lastStatus_; - std::string postData_; + std::string body_; // This only makes sense for POST and PUT requests bool isVerbose_; long timeout_; std::string proxy_; + bool verifyPeers_; + std::string caCertificates_; void Setup(); @@ -101,19 +103,19 @@ return timeout_; } - void SetPostData(const std::string& data) + void SetBody(const std::string& data) { - postData_ = data; + body_ = data; } - std::string& AccessPostData() + std::string& GetBody() { - return postData_; + return body_; } - const std::string& AccessPostData() const + const std::string& GetBody() const { - return postData_; + return body_; } void SetVerbose(bool isVerbose); @@ -140,8 +142,32 @@ proxy_ = proxy; } - static void GlobalInitialize(); + void SetHttpsVerifyPeers(bool verify) + { + verifyPeers_ = verify; + } + + bool IsHttpsVerifyPeers() const + { + return verifyPeers_; + } + + void SetHttpsCACertificates(const std::string& certificates) + { + caCertificates_ = certificates; + } + + const std::string& GetHttpsCACertificates() const; + + static void GlobalInitialize(bool httpsVerifyPeers, + const std::string& httpsCACertificates); static void GlobalFinalize(); + + static void SetDefaultTimeout(long timeout); + + void ApplyAndThrowException(std::string& answer); + + void ApplyAndThrowException(Json::Value& answer); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/BufferHttpSender.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,84 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "BufferHttpSender.h" + +#include "../OrthancException.h" + +#include <cassert> + +namespace Orthanc +{ + BufferHttpSender::BufferHttpSender() : + position_(0), + chunkSize_(0), + currentChunkSize_(0) + { + } + + + bool BufferHttpSender::ReadNextChunk() + { + assert(position_ + currentChunkSize_ <= buffer_.size()); + + position_ += currentChunkSize_; + + if (position_ == buffer_.size()) + { + return false; + } + else + { + currentChunkSize_ = buffer_.size() - position_; + + if (chunkSize_ != 0 && + currentChunkSize_ > chunkSize_) + { + currentChunkSize_ = chunkSize_; + } + + return true; + } + } + + + const char* BufferHttpSender::GetChunkContent() + { + return buffer_.c_str() + position_; + } + + + size_t BufferHttpSender::GetChunkSize() + { + return currentChunkSize_; + } +}
--- a/Core/HttpServer/BufferHttpSender.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/BufferHttpSender.h Wed Sep 30 13:23:31 2015 +0200 @@ -38,25 +38,14 @@ class BufferHttpSender : public HttpFileSender { private: - std::string buffer_; - - protected: - virtual uint64_t GetFileSize() - { - return buffer_.size(); - } - - virtual bool SendData(HttpOutput& output) - { - if (buffer_.size()) - { - output.SendBody(&buffer_[0], buffer_.size()); - } - - return true; - } + std::string buffer_; + size_t position_; + size_t chunkSize_; + size_t currentChunkSize_; public: + BufferHttpSender(); + std::string& GetBuffer() { return buffer_; @@ -66,5 +55,28 @@ { return buffer_; } + + // This is for test purpose. If "chunkSize" is set to "0" (the + // default), the entire buffer is consumed at once. + void SetChunkSize(size_t chunkSize) + { + chunkSize_ = chunkSize; + } + + + /** + * Implementation of the IHttpStreamAnswer interface. + **/ + + virtual uint64_t GetContentLength() + { + return buffer_.size(); + } + + virtual bool ReadNextChunk(); + + virtual const char* GetChunkContent(); + + virtual size_t GetChunkSize(); }; }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,11 +33,11 @@ #include "../PrecompiledHeaders.h" #include "EmbeddedResourceHttpHandler.h" +#include "../Logging.h" #include "../OrthancException.h" #include "HttpOutput.h" #include <stdio.h> -#include <glog/logging.h> namespace Orthanc @@ -53,11 +53,15 @@ bool EmbeddedResourceHttpHandler::Handle( HttpOutput& output, + RequestOrigin /*origin*/, + const char* /*remoteIp*/, + const char* /*username*/, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, - const std::string&) + const GetArguments& arguments, + const char* /*bodyData*/, + size_t /*bodySize*/) { if (!Toolbox::IsChildUri(baseUri_, uri)) { @@ -80,7 +84,7 @@ size_t size = EmbeddedResources::GetDirectoryResourceSize(resourceId_, resourcePath.c_str()); output.SetContentType(contentType.c_str()); - output.SendBody(buffer, size); + output.Answer(buffer, size); } catch (OrthancException&) {
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,14 +32,14 @@ #pragma once -#include "HttpHandler.h" +#include "IHttpHandler.h" #include <EmbeddedResources.h> // Autogenerated file #include <boost/shared_ptr.hpp> namespace Orthanc { - class EmbeddedResourceHttpHandler : public HttpHandler + class EmbeddedResourceHttpHandler : public IHttpHandler { private: UriComponents baseUri_; @@ -52,10 +52,14 @@ virtual bool Handle( HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, - const std::string&); + const GetArguments& arguments, + const char* /*bodyData*/, + size_t /*bodySize*/); }; }
--- a/Core/HttpServer/FilesystemHttpHandler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -50,13 +50,12 @@ static void OutputDirectoryContent(HttpOutput& output, + const IHttpHandler::Arguments& headers, const UriComponents& uri, const boost::filesystem::path& p) { namespace fs = boost::filesystem; - output.SetContentType("text/html"); - std::string s; s += "<html>"; s += " <body>"; @@ -104,7 +103,8 @@ s += " </body>"; s += "</html>"; - output.SendBody(s); + output.SetContentType("text/html"); + output.Answer(s); } @@ -119,18 +119,22 @@ if (!fs::exists(pimpl_->root_) || !fs::is_directory(pimpl_->root_)) { - throw OrthancException("The path does not point to a directory"); + throw OrthancException(ErrorCode_DirectoryExpected); } } bool FilesystemHttpHandler::Handle( HttpOutput& output, + RequestOrigin /*origin*/, + const char* /*remoteIp*/, + const char* /*username*/, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, - const std::string&) + const GetArguments& arguments, + const char* /*bodyData*/, + size_t /*bodySize*/) { if (!Toolbox::IsChildUri(pimpl_->baseUri_, uri)) { @@ -154,15 +158,14 @@ if (fs::exists(p) && fs::is_regular_file(p)) { - FilesystemHttpSender(p).Send(output); - - //output.AnswerFileAutodetectContentType(p.string()); + FilesystemHttpSender sender(p); + output.Answer(sender); // TODO COMPRESSION } else if (listDirectoryContent_ && fs::exists(p) && fs::is_directory(p)) { - OutputDirectoryContent(output, uri, p); + OutputDirectoryContent(output, headers, uri, p); } else {
--- a/Core/HttpServer/FilesystemHttpHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,13 +32,13 @@ #pragma once -#include "HttpHandler.h" +#include "IHttpHandler.h" #include <boost/shared_ptr.hpp> namespace Orthanc { - class FilesystemHttpHandler : public HttpHandler + class FilesystemHttpHandler : public IHttpHandler { private: // PImpl idiom to avoid the inclusion of boost::filesystem @@ -54,11 +54,15 @@ virtual bool Handle( HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& arguments, - const std::string&); + const GetArguments& arguments, + const char* /*bodyData*/, + size_t /*bodySize*/); bool IsListDirectoryContent() const {
--- a/Core/HttpServer/FilesystemHttpSender.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -32,72 +32,45 @@ #include "../PrecompiledHeaders.h" #include "FilesystemHttpSender.h" -#include "../Toolbox.h" +#include "../OrthancException.h" -#include <stdio.h> +static const size_t CHUNK_SIZE = 64 * 1024; // Use 64KB chunks namespace Orthanc { - void FilesystemHttpSender::Setup() + void FilesystemHttpSender::Initialize(const boost::filesystem::path& path) { - //SetDownloadFilename(path_.filename().string()); - -#if BOOST_HAS_FILESYSTEM_V3 == 1 - SetContentType(Toolbox::AutodetectMimeType(path_.filename().string())); -#else - SetContentType(Toolbox::AutodetectMimeType(path_.filename())); -#endif - } + SetContentFilename(path.filename().string()); + file_.open(path.string().c_str(), std::ifstream::binary); - uint64_t FilesystemHttpSender::GetFileSize() - { - return Toolbox::GetFileSize(path_.string()); - } - - bool FilesystemHttpSender::SendData(HttpOutput& output) - { - FILE* fp = fopen(path_.string().c_str(), "rb"); - if (!fp) + if (!file_.is_open()) { - return false; + throw OrthancException(ErrorCode_InexistentFile); } - std::vector<uint8_t> buffer(1024 * 1024); // Chunks of 1MB - - for (;;) - { - size_t nbytes = fread(&buffer[0], 1, buffer.size(), fp); - if (nbytes == 0) - { - break; - } - else - { - output.SendBody(&buffer[0], nbytes); - } - } - - fclose(fp); - - return true; + file_.seekg(0, file_.end); + size_ = file_.tellg(); + file_.seekg(0, file_.beg); } - FilesystemHttpSender::FilesystemHttpSender(const char* path) - { - path_ = std::string(path); - Setup(); - } - FilesystemHttpSender::FilesystemHttpSender(const boost::filesystem::path& path) + bool FilesystemHttpSender::ReadNextChunk() { - path_ = path; - Setup(); - } + if (chunk_.size() == 0) + { + chunk_.resize(CHUNK_SIZE); + } + + file_.read(&chunk_[0], chunk_.size()); - FilesystemHttpSender::FilesystemHttpSender(const FilesystemStorage& storage, - const std::string& uuid) - { - path_ = storage.GetPath(uuid).string(); - Setup(); + if ((file_.flags() & std::istream::failbit) || + file_.gcount() < 0) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + + chunkSize_ = file_.gcount(); + + return chunkSize_ > 0; } }
--- a/Core/HttpServer/FilesystemHttpSender.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/FilesystemHttpSender.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,28 +32,59 @@ #pragma once #include "HttpFileSender.h" +#include "BufferHttpSender.h" #include "../FileStorage/FilesystemStorage.h" +#include <fstream> + namespace Orthanc { class FilesystemHttpSender : public HttpFileSender { private: - boost::filesystem::path path_; - - void Setup(); + std::ifstream file_; + uint64_t size_; + std::string chunk_; + size_t chunkSize_; - protected: - virtual uint64_t GetFileSize(); - - virtual bool SendData(HttpOutput& output); + void Initialize(const boost::filesystem::path& path); public: - FilesystemHttpSender(const char* path); + FilesystemHttpSender(const std::string& path) + { + Initialize(path); + } - FilesystemHttpSender(const boost::filesystem::path& path); + FilesystemHttpSender(const boost::filesystem::path& path) + { + Initialize(path); + } FilesystemHttpSender(const FilesystemStorage& storage, - const std::string& uuid); + const std::string& uuid) + { + Initialize(storage.GetPath(uuid)); + } + + /** + * Implementation of the IHttpStreamAnswer interface. + **/ + + virtual uint64_t GetContentLength() + { + return size_; + } + + virtual bool ReadNextChunk(); + + virtual const char* GetChunkContent() + { + return chunk_.c_str(); + } + + virtual size_t GetChunkSize() + { + return chunkSize_; + } }; }
--- a/Core/HttpServer/HttpFileSender.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/HttpFileSender.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,34 +34,45 @@ #include "HttpFileSender.h" #include "../OrthancException.h" +#include "../Toolbox.h" #include <boost/lexical_cast.hpp> namespace Orthanc { - void HttpFileSender::SendHeader(HttpOutput& output) + void HttpFileSender::SetContentFilename(const std::string& filename) { - if (contentType_.size() > 0) - { - output.SetContentType(contentType_.c_str()); - } + filename_ = filename; - if (downloadFilename_.size() > 0) + if (contentType_.empty()) { - output.SetContentFilename(downloadFilename_.c_str()); + contentType_ = Toolbox::AutodetectMimeType(filename); } - - output.SetContentLength(GetFileSize()); } - void HttpFileSender::Send(HttpOutput& output) + + bool HttpFileSender::HasContentFilename(std::string& filename) { - SendHeader(output); - - if (!SendData(output)) + if (filename_.empty()) + { + return false; + } + else { - throw OrthancException(ErrorCode_InternalError); - //output.SendHeader(HttpStatus_500_InternalServerError); + filename = filename_; + return true; + } + } + + std::string HttpFileSender::GetContentType() + { + if (contentType_.empty()) + { + return "application/octet-stream"; + } + else + { + return contentType_; } } }
--- a/Core/HttpServer/HttpFileSender.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/HttpFileSender.h Wed Sep 30 13:23:31 2015 +0200 @@ -36,29 +36,13 @@ namespace Orthanc { - class HttpFileSender + class HttpFileSender : public IHttpStreamAnswer { private: std::string contentType_; - std::string downloadFilename_; - - void SendHeader(HttpOutput& output); - - protected: - virtual uint64_t GetFileSize() = 0; - - virtual bool SendData(HttpOutput& output) = 0; + std::string filename_; public: - virtual ~HttpFileSender() - { - } - - void ResetContentType() - { - contentType_.clear(); - } - void SetContentType(const std::string& contentType) { contentType_ = contentType; @@ -69,21 +53,26 @@ return contentType_; } - void ResetDownloadFilename() + void SetContentFilename(const std::string& filename); + + const std::string& GetContentFilename() const { - downloadFilename_.clear(); + return filename_; } - void SetDownloadFilename(const std::string& filename) + + /** + * Implementation of the IHttpStreamAnswer interface. + **/ + + virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, + bool /*deflateAllowed*/) { - downloadFilename_ = filename; + return HttpCompression_None; } - const std::string& GetDownloadFilename() const - { - return downloadFilename_; - } - - void Send(HttpOutput& output); + virtual bool HasContentFilename(std::string& filename); + + virtual std::string GetContentType(); }; }
--- a/Core/HttpServer/HttpHandler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "HttpHandler.h" - -#include <string.h> -#include <iostream> - - -namespace Orthanc -{ - static void SplitGETNameValue(HttpHandler::Arguments& result, - const char* start, - const char* end) - { - std::string name, value; - - const char* equal = strchr(start, '='); - if (equal == NULL || equal >= end) - { - name = std::string(start, end - start); - //value = ""; - } - else - { - name = std::string(start, equal - start); - value = std::string(equal + 1, end); - } - - Toolbox::UrlDecode(name); - Toolbox::UrlDecode(value); - - result.insert(std::make_pair(name, value)); - } - - - void HttpHandler::ParseGetArguments(HttpHandler::Arguments& result, const char* query) - { - const char* pos = query; - - while (pos != NULL) - { - const char* ampersand = strchr(pos, '&'); - if (ampersand) - { - SplitGETNameValue(result, pos, ampersand); - pos = ampersand + 1; - } - else - { - // No more ampersand, this is the last argument - SplitGETNameValue(result, pos, pos + strlen(pos)); - pos = NULL; - } - } - } - - - void HttpHandler::ParseGetQuery(UriComponents& uri, - HttpHandler::Arguments& getArguments, - const char* query) - { - const char *questionMark = ::strchr(query, '?'); - if (questionMark == NULL) - { - // No question mark in the string - Toolbox::SplitUriComponents(uri, query); - getArguments.clear(); - } - else - { - Toolbox::SplitUriComponents(uri, std::string(query, questionMark)); - HttpHandler::ParseGetArguments(getArguments, questionMark + 1); - } - } - - - std::string HttpHandler::GetArgument(const Arguments& getArguments, - const std::string& name, - const std::string& defaultValue) - { - Arguments::const_iterator it = getArguments.find(name); - if (it == getArguments.end()) - { - return defaultValue; - } - else - { - return it->second; - } - } - - - - void HttpHandler::ParseCookies(HttpHandler::Arguments& result, - const HttpHandler::Arguments& httpHeaders) - { - result.clear(); - - HttpHandler::Arguments::const_iterator it = httpHeaders.find("cookie"); - if (it != httpHeaders.end()) - { - const std::string& cookies = it->second; - - size_t pos = 0; - while (pos != std::string::npos) - { - size_t nextSemicolon = cookies.find(";", pos); - std::string cookie; - - if (nextSemicolon == std::string::npos) - { - cookie = cookies.substr(pos); - pos = std::string::npos; - } - else - { - cookie = cookies.substr(pos, nextSemicolon - pos); - pos = nextSemicolon + 1; - } - - size_t equal = cookie.find("="); - if (equal != std::string::npos) - { - std::string name = Toolbox::StripSpaces(cookie.substr(0, equal)); - std::string value = Toolbox::StripSpaces(cookie.substr(equal + 1)); - result[name] = value; - } - } - } - } -}
--- a/Core/HttpServer/HttpHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 <map> -#include <vector> -#include <stdint.h> -#include "../Toolbox.h" - -namespace Orthanc -{ - class HttpOutput; - - class HttpHandler - { - public: - typedef std::map<std::string, std::string> Arguments; - - virtual ~HttpHandler() - { - } - - virtual bool Handle(HttpOutput& output, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const Arguments& getArguments, - const std::string& postData) = 0; - - static void ParseGetArguments(HttpHandler::Arguments& result, - const char* query); - - static void ParseGetQuery(UriComponents& uri, - HttpHandler::Arguments& getArguments, - const char* query); - - static std::string GetArgument(const Arguments& getArguments, - const std::string& name, - const std::string& defaultValue); - - static void ParseCookies(HttpHandler::Arguments& result, - const HttpHandler::Arguments& httpHeaders); - }; -}
--- a/Core/HttpServer/HttpOutput.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/HttpOutput.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,13 +33,17 @@ #include "../PrecompiledHeaders.h" #include "HttpOutput.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../Compression/GzipCompressor.h" +#include "../Compression/ZlibCompressor.h" + #include <iostream> #include <vector> #include <stdio.h> -#include <glog/logging.h> #include <boost/lexical_cast.hpp> -#include "../OrthancException.h" -#include "../Toolbox.h" + namespace Orthanc { @@ -151,6 +155,11 @@ } } + if (state_ == State_WritingMultipart) + { + throw OrthancException(ErrorCode_InternalError); + } + if (state_ == State_WritingHeader) { // Send the HTTP header before writing the body @@ -206,6 +215,69 @@ } + void HttpOutput::StateMachine::CloseBody() + { + switch (state_) + { + case State_WritingHeader: + SetContentLength(0); + SendBody(NULL, 0); + break; + + case State_WritingBody: + if (!hasContentLength_ || + contentPosition_ == contentLength_) + { + state_ = State_Done; + } + else + { + LOG(ERROR) << "The body size has not reached what was declared with SetContentSize()"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + break; + + case State_WritingMultipart: + LOG(ERROR) << "Cannot invoke CloseBody() with multipart outputs"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + + case State_Done: + return; // Ignore + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const + { +#if 0 + // TODO Do not compress small files? + if (bodySize < 512) + { + return HttpCompression_None; + } +#endif + + // Prefer "gzip" over "deflate" if the choice is offered + + if (isGzipAllowed_) + { + return HttpCompression_Gzip; + } + else if (isDeflateAllowed_) + { + return HttpCompression_Deflate; + } + else + { + return HttpCompression_None; + } + } + + void HttpOutput::SendMethodNotAllowed(const std::string& allowed) { stateMachine_.ClearHeaders(); @@ -215,7 +287,9 @@ } - void HttpOutput::SendStatus(HttpStatus status) + void HttpOutput::SendStatus(HttpStatus status, + const char* message, + size_t messageSize) { if (status == HttpStatus_200_Ok || status == HttpStatus_301_MovedPermanently || @@ -228,7 +302,7 @@ stateMachine_.ClearHeaders(); stateMachine_.SetHttpStatus(status); - stateMachine_.SendBody(NULL, 0); + stateMachine_.SendBody(message, messageSize); } @@ -249,25 +323,229 @@ stateMachine_.SendBody(NULL, 0); } - void HttpOutput::SendBody(const void* buffer, size_t length) + void HttpOutput::Answer(const void* buffer, + size_t length) { - stateMachine_.SendBody(buffer, length); - } + if (length == 0) + { + AnswerEmpty(); + return; + } + + HttpCompression compression = GetPreferredCompression(length); + + if (compression == HttpCompression_None) + { + stateMachine_.SetContentLength(length); + stateMachine_.SendBody(buffer, length); + return; + } + + std::string compressed, encoding; - void HttpOutput::SendBody(const std::string& str) - { - if (str.size() == 0) + switch (compression) { - stateMachine_.SendBody(NULL, 0); + case HttpCompression_Deflate: + { + encoding = "deflate"; + ZlibCompressor compressor; + // Do not prefix the buffer with its uncompressed size, to be compatible with "deflate" + compressor.SetPrefixWithUncompressedSize(false); + compressor.Compress(compressed, buffer, length); + break; + } + + case HttpCompression_Gzip: + { + encoding = "gzip"; + GzipCompressor compressor; + compressor.Compress(compressed, buffer, length); + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + LOG(TRACE) << "Compressing a HTTP answer using " << encoding; + + // The body is empty, do not use HTTP compression + if (compressed.size() == 0) + { + AnswerEmpty(); } else { - stateMachine_.SendBody(str.c_str(), str.size()); + stateMachine_.AddHeader("Content-Encoding", encoding); + stateMachine_.SetContentLength(compressed.size()); + stateMachine_.SendBody(compressed.c_str(), compressed.size()); + } + + stateMachine_.CloseBody(); + } + + + void HttpOutput::Answer(const std::string& str) + { + Answer(str.size() == 0 ? NULL : str.c_str(), str.size()); + } + + + void HttpOutput::AnswerEmpty() + { + stateMachine_.CloseBody(); + } + + + void HttpOutput::StateMachine::StartMultipart(const std::string& subType, + const std::string& contentType) + { + if (subType != "mixed" && + subType != "related") + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (keepAlive_) + { + LOG(ERROR) << "Multipart answers are not implemented together with keep-alive connections"; + throw OrthancException(ErrorCode_NotImplemented); + } + + if (state_ != State_WritingHeader) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (status_ != HttpStatus_200_Ok) + { + SendBody(NULL, 0); + return; + } + + stream_.OnHttpStatusReceived(status_); + + std::string header = "HTTP/1.1 200 OK\r\n"; + + // Possibly add the cookies + for (std::list<std::string>::const_iterator + it = headers_.begin(); it != headers_.end(); ++it) + { + if (!Toolbox::StartsWith(*it, "Set-Cookie: ")) + { + LOG(ERROR) << "The only headers that can be set in multipart answers are Set-Cookie (here: " << *it << " is set)"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + header += *it; + } + + multipartBoundary_ = Toolbox::GenerateUuid(); + multipartContentType_ = contentType; + header += "Content-Type: multipart/related; type=multipart/" + subType + "; boundary=" + multipartBoundary_ + "\r\n\r\n"; + + stream_.Send(true, header.c_str(), header.size()); + state_ = State_WritingMultipart; + } + + + void HttpOutput::StateMachine::SendMultipartItem(const void* item, size_t length) + { + std::string header = "--" + multipartBoundary_ + "\n"; + header += "Content-Type: " + multipartContentType_ + "\n"; + header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\n"; + header += "MIME-Version: 1.0\n\n"; + + stream_.Send(false, header.c_str(), header.size()); + + if (length > 0) + { + stream_.Send(false, item, length); + } + + stream_.Send(false, "\n", 1); + } + + + void HttpOutput::StateMachine::CloseMultipart() + { + if (state_ != State_WritingMultipart) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + // The two lines below might throw an exception, if the client has + // closed the connection. Such an error is ignored. + try + { + std::string header = "--" + multipartBoundary_ + "--\n"; + stream_.Send(false, header.c_str(), header.size()); + } + catch (OrthancException&) + { + } + + state_ = State_Done; + } + + + void HttpOutput::SendMultipartItem(const std::string& item) + { + if (item.size() > 0) + { + stateMachine_.SendMultipartItem(item.c_str(), item.size()); + } + else + { + stateMachine_.SendMultipartItem(NULL, 0); } } - void HttpOutput::SendBody() + + void HttpOutput::Answer(IHttpStreamAnswer& stream) { - stateMachine_.SendBody(NULL, 0); + HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_); + + switch (compression) + { + case HttpCompression_None: + break; + + case HttpCompression_Gzip: + stateMachine_.AddHeader("Content-Encoding", "gzip"); + break; + + case HttpCompression_Deflate: + stateMachine_.AddHeader("Content-Encoding", "deflate"); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + stateMachine_.SetContentLength(stream.GetContentLength()); + + std::string contentType = stream.GetContentType(); + if (contentType.empty()) + { + contentType = "application/octet-stream"; + } + + stateMachine_.SetContentType(contentType.c_str()); + + std::string filename; + if (stream.HasContentFilename(filename)) + { + SetContentFilename(filename.c_str()); + } + + while (stream.ReadNextChunk()) + { + stateMachine_.SendBody(stream.GetChunkContent(), + stream.GetChunkSize()); + } + + stateMachine_.CloseBody(); } + }
--- a/Core/HttpServer/HttpOutput.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/HttpOutput.h Wed Sep 30 13:23:31 2015 +0200 @@ -37,7 +37,8 @@ #include <stdint.h> #include "../Enumerations.h" #include "IHttpOutputStream.h" -#include "HttpHandler.h" +#include "IHttpStreamAnswer.h" +#include "../Uuid.h" namespace Orthanc { @@ -48,14 +49,16 @@ class StateMachine : public boost::noncopyable { - private: + public: enum State { State_WritingHeader, State_WritingBody, + State_WritingMultipart, State_Done }; + private: IHttpOutputStream& stream_; State state_; @@ -66,6 +69,9 @@ bool keepAlive_; std::list<std::string> headers_; + std::string multipartBoundary_; + std::string multipartContentType_; + public: StateMachine(IHttpOutputStream& stream, bool isKeepAlive); @@ -89,18 +95,71 @@ void ClearHeaders(); void SendBody(const void* buffer, size_t length); + + void StartMultipart(const std::string& subType, + const std::string& contentType); + + void SendMultipartItem(const void* item, size_t length); + + void CloseMultipart(); + + void CloseBody(); + + State GetState() const + { + return state_; + } }; StateMachine stateMachine_; + bool isDeflateAllowed_; + bool isGzipAllowed_; + + HttpCompression GetPreferredCompression(size_t bodySize) const; public: HttpOutput(IHttpOutputStream& stream, bool isKeepAlive) : - stateMachine_(stream, isKeepAlive) + stateMachine_(stream, isKeepAlive), + isDeflateAllowed_(false), + isGzipAllowed_(false) { } - void SendStatus(HttpStatus status); + void SetDeflateAllowed(bool allowed) + { + isDeflateAllowed_ = allowed; + } + + bool IsDeflateAllowed() const + { + return isDeflateAllowed_; + } + + void SetGzipAllowed(bool allowed) + { + isGzipAllowed_ = allowed; + } + + bool IsGzipAllowed() const + { + return isGzipAllowed_; + } + + void SendStatus(HttpStatus status, + const char* message, + size_t messageSize); + + void SendStatus(HttpStatus status) + { + SendStatus(status, NULL, 0); + } + + void SendStatus(HttpStatus status, + const std::string& message) + { + SendStatus(status, message.c_str(), message.size()); + } void SetContentType(const char* contentType) { @@ -112,11 +171,6 @@ stateMachine_.SetContentFilename(filename); } - void SetContentLength(uint64_t length) - { - stateMachine_.SetContentLength(length); - } - void SetCookie(const std::string& cookie, const std::string& value) { @@ -129,16 +183,42 @@ stateMachine_.AddHeader(key, value); } - void SendBody(const void* buffer, size_t length); + void Answer(const void* buffer, + size_t length); - void SendBody(const std::string& str); + void Answer(const std::string& str); - void SendBody(); + void AnswerEmpty(); void SendMethodNotAllowed(const std::string& allowed); void Redirect(const std::string& path); void SendUnauthorized(const std::string& realm); + + void StartMultipart(const std::string& subType, + const std::string& contentType) + { + stateMachine_.StartMultipart(subType, contentType); + } + + void SendMultipartItem(const std::string& item); + + void SendMultipartItem(const void* item, size_t size) + { + stateMachine_.SendMultipartItem(item, size); + } + + void CloseMultipart() + { + stateMachine_.CloseMultipart(); + } + + bool IsWritingMultipart() const + { + return stateMachine_.GetState() == StateMachine::State_WritingMultipart; + } + + void Answer(IHttpStreamAnswer& stream); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpStreamTranscoder.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,250 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "HttpStreamTranscoder.h" + +#include "../OrthancException.h" +#include "../Compression/ZlibCompressor.h" + +#include <string.h> // For memcpy() +#include <cassert> + +#include <stdio.h> + +namespace Orthanc +{ + void HttpStreamTranscoder::ReadSource(std::string& buffer) + { + if (source_.SetupHttpCompression(false, false) != HttpCompression_None) + { + throw OrthancException(ErrorCode_InternalError); + } + + uint64_t size = source_.GetContentLength(); + if (static_cast<uint64_t>(static_cast<size_t>(size)) != size) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + buffer.resize(static_cast<size_t>(size)); + size_t offset = 0; + + while (source_.ReadNextChunk()) + { + size_t chunkSize = static_cast<size_t>(source_.GetChunkSize()); + memcpy(&buffer[offset], source_.GetChunkContent(), chunkSize); + offset += chunkSize; + } + + if (offset != size) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + HttpCompression HttpStreamTranscoder::SetupZlibCompression(bool deflateAllowed) + { + uint64_t size = source_.GetContentLength(); + + if (size == 0) + { + return HttpCompression_None; + } + + if (size < sizeof(uint64_t)) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + + if (deflateAllowed) + { + bytesToSkip_ = sizeof(uint64_t); + + return HttpCompression_Deflate; + } + else + { + // TODO Use stream-based zlib decoding to reduce memory usage + std::string compressed; + ReadSource(compressed); + + uncompressed_.reset(new BufferHttpSender); + + ZlibCompressor compressor; + IBufferCompressor::Uncompress(uncompressed_->GetBuffer(), compressor, compressed); + + return HttpCompression_None; + } + } + + + HttpCompression HttpStreamTranscoder::SetupHttpCompression(bool gzipAllowed, + bool deflateAllowed) + { + if (ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + ready_ = true; + + switch (sourceCompression_) + { + case CompressionType_None: + return HttpCompression_None; + + case CompressionType_ZlibWithSize: + return SetupZlibCompression(deflateAllowed); + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + uint64_t HttpStreamTranscoder::GetContentLength() + { + if (!ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (uncompressed_.get() != NULL) + { + return uncompressed_->GetContentLength(); + } + else + { + uint64_t length = source_.GetContentLength(); + if (length < bytesToSkip_) + { + throw OrthancException(ErrorCode_InternalError); + } + + return length - bytesToSkip_; + } + } + + + bool HttpStreamTranscoder::ReadNextChunk() + { + if (!ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (uncompressed_.get() != NULL) + { + return uncompressed_->ReadNextChunk(); + } + + assert(skipped_ <= bytesToSkip_); + if (skipped_ == bytesToSkip_) + { + // We have already skipped the first bytes of the stream + currentChunkOffset_ = 0; + return source_.ReadNextChunk(); + } + + // This condition can only be true on the first call to "ReadNextChunk()" + for (;;) + { + assert(skipped_ < bytesToSkip_); + + bool ok = source_.ReadNextChunk(); + if (!ok) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + + size_t remaining = static_cast<size_t>(bytesToSkip_ - skipped_); + size_t s = source_.GetChunkSize(); + + if (s < remaining) + { + skipped_ += s; + } + else if (s == remaining) + { + // We have skipped enough bytes, but we must read a new chunk + currentChunkOffset_ = 0; + skipped_ = bytesToSkip_; + return source_.ReadNextChunk(); + } + else + { + // We have skipped enough bytes, and we have enough data in the current chunk + assert(s > remaining); + currentChunkOffset_ = remaining; + skipped_ = bytesToSkip_; + return true; + } + } + } + + + const char* HttpStreamTranscoder::GetChunkContent() + { + if (!ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (uncompressed_.get() != NULL) + { + return uncompressed_->GetChunkContent(); + } + else + { + return source_.GetChunkContent() + currentChunkOffset_; + } + } + + size_t HttpStreamTranscoder::GetChunkSize() + { + if (!ready_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (uncompressed_.get() != NULL) + { + return uncompressed_->GetChunkSize(); + } + else + { + return static_cast<size_t>(source_.GetChunkSize() - currentChunkOffset_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpStreamTranscoder.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,90 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "BufferHttpSender.h" + +#include <memory> // For std::auto_ptr + +namespace Orthanc +{ + class HttpStreamTranscoder : public IHttpStreamAnswer + { + private: + IHttpStreamAnswer& source_; + CompressionType sourceCompression_; + uint64_t bytesToSkip_; + uint64_t skipped_; + uint64_t currentChunkOffset_; + bool ready_; + + std::auto_ptr<BufferHttpSender> uncompressed_; + + void ReadSource(std::string& buffer); + + HttpCompression SetupZlibCompression(bool deflateAllowed); + + public: + HttpStreamTranscoder(IHttpStreamAnswer& source, + CompressionType compression) : + source_(source), + sourceCompression_(compression), + bytesToSkip_(0), + skipped_(0), + ready_(false) + { + } + + // This is the first method to be called + virtual HttpCompression SetupHttpCompression(bool gzipAllowed, + bool deflateAllowed); + + virtual bool HasContentFilename(std::string& filename) + { + return source_.HasContentFilename(filename); + } + + virtual std::string GetContentType() + { + return source_.GetContentType(); + } + + virtual uint64_t GetContentLength(); + + virtual bool ReadNextChunk(); + + virtual const char* GetChunkContent(); + + virtual size_t GetChunkSize(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpToolbox.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,296 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "HttpToolbox.h" + +#include <stdio.h> +#include <string.h> +#include <iostream> + +#include "HttpOutput.h" +#include "StringHttpOutput.h" + + +static const char* LOCALHOST = "localhost"; + + + +namespace Orthanc +{ + static void SplitGETNameValue(IHttpHandler::GetArguments& result, + const char* start, + const char* end) + { + std::string name, value; + + const char* equal = strchr(start, '='); + if (equal == NULL || equal >= end) + { + name = std::string(start, end - start); + //value = ""; + } + else + { + name = std::string(start, equal - start); + value = std::string(equal + 1, end); + } + + Toolbox::UrlDecode(name); + Toolbox::UrlDecode(value); + + result.push_back(std::make_pair(name, value)); + } + + + void HttpToolbox::ParseGetArguments(IHttpHandler::GetArguments& result, + const char* query) + { + const char* pos = query; + + while (pos != NULL) + { + const char* ampersand = strchr(pos, '&'); + if (ampersand) + { + SplitGETNameValue(result, pos, ampersand); + pos = ampersand + 1; + } + else + { + // No more ampersand, this is the last argument + SplitGETNameValue(result, pos, pos + strlen(pos)); + pos = NULL; + } + } + } + + + void HttpToolbox::ParseGetQuery(UriComponents& uri, + IHttpHandler::GetArguments& getArguments, + const char* query) + { + const char *questionMark = ::strchr(query, '?'); + if (questionMark == NULL) + { + // No question mark in the string + Toolbox::SplitUriComponents(uri, query); + getArguments.clear(); + } + else + { + Toolbox::SplitUriComponents(uri, std::string(query, questionMark)); + HttpToolbox::ParseGetArguments(getArguments, questionMark + 1); + } + } + + + std::string HttpToolbox::GetArgument(const IHttpHandler::Arguments& getArguments, + const std::string& name, + const std::string& defaultValue) + { + IHttpHandler::Arguments::const_iterator it = getArguments.find(name); + if (it == getArguments.end()) + { + return defaultValue; + } + else + { + return it->second; + } + } + + + std::string HttpToolbox::GetArgument(const IHttpHandler::GetArguments& getArguments, + const std::string& name, + const std::string& defaultValue) + { + for (size_t i = 0; i < getArguments.size(); i++) + { + if (getArguments[i].first == name) + { + return getArguments[i].second; + } + } + + return defaultValue; + } + + + + void HttpToolbox::ParseCookies(IHttpHandler::Arguments& result, + const IHttpHandler::Arguments& httpHeaders) + { + result.clear(); + + IHttpHandler::Arguments::const_iterator it = httpHeaders.find("cookie"); + if (it != httpHeaders.end()) + { + const std::string& cookies = it->second; + + size_t pos = 0; + while (pos != std::string::npos) + { + size_t nextSemicolon = cookies.find(";", pos); + std::string cookie; + + if (nextSemicolon == std::string::npos) + { + cookie = cookies.substr(pos); + pos = std::string::npos; + } + else + { + cookie = cookies.substr(pos, nextSemicolon - pos); + pos = nextSemicolon + 1; + } + + size_t equal = cookie.find("="); + if (equal != std::string::npos) + { + std::string name = Toolbox::StripSpaces(cookie.substr(0, equal)); + std::string value = Toolbox::StripSpaces(cookie.substr(equal + 1)); + result[name] = value; + } + } + } + } + + + void HttpToolbox::CompileGetArguments(IHttpHandler::Arguments& compiled, + const IHttpHandler::GetArguments& source) + { + compiled.clear(); + + for (size_t i = 0; i < source.size(); i++) + { + compiled[source[i].first] = source[i].second; + } + } + + + bool HttpToolbox::SimpleGet(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri) + { + IHttpHandler::Arguments headers; // No HTTP header + + UriComponents curi; + IHttpHandler::GetArguments getArguments; + ParseGetQuery(curi, getArguments, uri.c_str()); + + StringHttpOutput stream; + HttpOutput http(stream, false /* no keep alive */); + + if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Get, curi, + headers, getArguments, NULL /* no body for GET */, 0)) + { + stream.GetOutput(result); + return true; + } + else + { + return false; + } + } + + + static bool SimplePostOrPut(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + HttpMethod method, + const std::string& uri, + const char* bodyData, + size_t bodySize) + { + IHttpHandler::Arguments headers; // No HTTP header + IHttpHandler::GetArguments getArguments; // No GET argument for POST/PUT + + UriComponents curi; + Toolbox::SplitUriComponents(curi, uri); + + StringHttpOutput stream; + HttpOutput http(stream, false /* no keep alive */); + + if (handler.Handle(http, origin, LOCALHOST, "", method, curi, + headers, getArguments, bodyData, bodySize)) + { + stream.GetOutput(result); + return true; + } + else + { + return false; + } + } + + + bool HttpToolbox::SimplePost(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri, + const char* bodyData, + size_t bodySize) + { + return SimplePostOrPut(result, handler, origin, HttpMethod_Post, uri, bodyData, bodySize); + } + + + bool HttpToolbox::SimplePut(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri, + const char* bodyData, + size_t bodySize) + { + return SimplePostOrPut(result, handler, origin, HttpMethod_Put, uri, bodyData, bodySize); + } + + + bool HttpToolbox::SimpleDelete(IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri) + { + UriComponents curi; + Toolbox::SplitUriComponents(curi, uri); + + IHttpHandler::Arguments headers; // No HTTP header + IHttpHandler::GetArguments getArguments; // No GET argument for DELETE + + StringHttpOutput stream; + HttpOutput http(stream, false /* no keep alive */); + + return handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Delete, curi, + headers, getArguments, NULL /* no body for DELETE */, 0); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/HttpToolbox.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "IHttpHandler.h" + +namespace Orthanc +{ + class HttpToolbox + { + public: + static void ParseGetArguments(IHttpHandler::GetArguments& result, + const char* query); + + static void ParseGetQuery(UriComponents& uri, + IHttpHandler::GetArguments& getArguments, + const char* query); + + static std::string GetArgument(const IHttpHandler::Arguments& getArguments, + const std::string& name, + const std::string& defaultValue); + + static std::string GetArgument(const IHttpHandler::GetArguments& getArguments, + const std::string& name, + const std::string& defaultValue); + + static void ParseCookies(IHttpHandler::Arguments& result, + const IHttpHandler::Arguments& httpHeaders); + + static void CompileGetArguments(IHttpHandler::Arguments& compiled, + const IHttpHandler::GetArguments& source); + + static bool SimpleGet(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri); + + static bool SimplePost(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri, + const char* bodyData, + size_t bodySize); + + static bool SimplePut(std::string& result, + IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri, + const char* bodyData, + size_t bodySize); + + static bool SimpleDelete(IHttpHandler& handler, + RequestOrigin origin, + const std::string& uri); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/IHttpHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../Enumerations.h" +#include "HttpOutput.h" + +#include <map> +#include <set> +#include <vector> +#include <string> + +namespace Orthanc +{ + class IHttpHandler : public boost::noncopyable + { + public: + typedef std::map<std::string, std::string> Arguments; + typedef std::vector< std::pair<std::string, std::string> > GetArguments; + + virtual ~IHttpHandler() + { + } + + virtual bool Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers, + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/IHttpStreamAnswer.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,66 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../Enumerations.h" + +#include <stdint.h> +#include <boost/noncopyable.hpp> +#include <string> + +namespace Orthanc +{ + class IHttpStreamAnswer : public boost::noncopyable + { + public: + virtual ~IHttpStreamAnswer() + { + } + + // This is the first method to be called + virtual HttpCompression SetupHttpCompression(bool gzipAllowed, + bool deflateAllowed) = 0; + + virtual bool HasContentFilename(std::string& filename) = 0; + + virtual std::string GetContentType() = 0; + + virtual uint64_t GetContentLength() = 0; + + virtual bool ReadNextChunk() = 0; + + virtual const char* GetChunkContent() = 0; + + virtual size_t GetChunkSize() = 0; + }; +}
--- a/Core/HttpServer/MongooseServer.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/MongooseServer.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -35,6 +35,11 @@ #include "../PrecompiledHeaders.h" #include "MongooseServer.h" +#include "../Logging.h" +#include "../ChunkedBuffer.h" +#include "HttpToolbox.h" +#include "mongoose.h" + #include <algorithm> #include <string.h> #include <boost/lexical_cast.hpp> @@ -43,12 +48,6 @@ #include <string.h> #include <stdio.h> #include <boost/thread.hpp> -#include <glog/logging.h> - -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" -#include "HttpOutput.h" -#include "mongoose.h" #if ORTHANC_SSL_ENABLED == 1 #include <openssl/opensslv.h> @@ -82,7 +81,12 @@ { if (length > 0) { - mg_write(connection_, buffer, length); + int status = mg_write(connection_, buffer, length); + if (status != static_cast<int>(length)) + { + // status == 0 when the connection has been closed, -1 on error + throw OrthancException(ErrorCode_NetworkProtocol); + } } } @@ -262,9 +266,9 @@ static PostDataStatus ReadBody(std::string& postData, struct mg_connection *connection, - const HttpHandler::Arguments& headers) + const IHttpHandler::Arguments& headers) { - HttpHandler::Arguments::const_iterator cs = headers.find("content-length"); + IHttpHandler::Arguments::const_iterator cs = headers.find("content-length"); if (cs == headers.end()) { return PostDataStatus_NoLength; @@ -308,7 +312,7 @@ static PostDataStatus ParseMultipartPost(std::string &completedFile, struct mg_connection *connection, - const HttpHandler::Arguments& headers, + const IHttpHandler::Arguments& headers, const std::string& contentType, ChunkStore& chunkStore) { @@ -322,13 +326,13 @@ return status; } - /*for (HttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) + /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) { std::cout << "Header [" << i->first << "] = " << i->second << "\n"; } printf("CHUNK\n");*/ - typedef HttpHandler::Arguments::const_iterator ArgumentIterator; + typedef IHttpHandler::Arguments::const_iterator ArgumentIterator; ArgumentIterator requestedWith = headers.find("x-requested-with"); ArgumentIterator fileName = headers.find("x-file-name"); @@ -410,11 +414,11 @@ static bool IsAccessGranted(const MongooseServer& that, - const HttpHandler::Arguments& headers) + const IHttpHandler::Arguments& headers) { bool granted = false; - HttpHandler::Arguments::const_iterator auth = headers.find("authorization"); + IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); if (auth != headers.end()) { std::string s = auth->second; @@ -430,9 +434,9 @@ } - static std::string GetAuthenticatedUsername(const HttpHandler::Arguments& headers) + static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers) { - HttpHandler::Arguments::const_iterator auth = headers.find("authorization"); + IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); if (auth == headers.end()) { @@ -465,15 +469,15 @@ static bool ExtractMethod(HttpMethod& method, const struct mg_request_info *request, - const HttpHandler::Arguments& headers, - const HttpHandler::Arguments& argumentsGET) + const IHttpHandler::Arguments& headers, + const IHttpHandler::GetArguments& argumentsGET) { std::string overriden; // Check whether some PUT/DELETE faking is done // 1. Faking with Google's approach - HttpHandler::Arguments::const_iterator methodOverride = + IHttpHandler::Arguments::const_iterator methodOverride = headers.find("x-http-method-override"); if (methodOverride != headers.end()) @@ -484,10 +488,13 @@ { // 2. Faking with Ruby on Rail's approach // GET /my/resource?_method=delete <=> DELETE /my/resource - methodOverride = argumentsGET.find("_method"); - if (methodOverride != argumentsGET.end()) + for (size_t i = 0; i < argumentsGET.size(); i++) { - overriden = methodOverride->second; + if (argumentsGET[i].first == "_method") + { + overriden = argumentsGET[i].second; + break; + } } } @@ -540,10 +547,39 @@ } + static void ConfigureHttpCompression(HttpOutput& output, + const IHttpHandler::Arguments& headers) + { + // Look if the client wishes HTTP compression + // https://en.wikipedia.org/wiki/HTTP_compression + IHttpHandler::Arguments::const_iterator it = headers.find("accept-encoding"); + if (it != headers.end()) + { + std::vector<std::string> encodings; + Toolbox::TokenizeString(encodings, it->second, ','); + + for (size_t i = 0; i < encodings.size(); i++) + { + std::string s = Toolbox::StripSpaces(encodings[i]); + + if (s == "deflate") + { + output.SetDeflateAllowed(true); + } + else if (s == "gzip") + { + output.SetGzipAllowed(true); + } + } + } + } + + static void InternalCallback(struct mg_connection *connection, const struct mg_request_info *request) { MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data); + MongooseOutputStream stream(connection); HttpOutput output(stream, that->IsKeepAliveEnabled()); @@ -557,7 +593,7 @@ // Extract the HTTP headers - HttpHandler::Arguments headers; + IHttpHandler::Arguments headers; for (int i = 0; i < request->num_headers; i++) { std::string name = request->http_headers[i].name; @@ -565,12 +601,17 @@ headers.insert(std::make_pair(name, request->http_headers[i].value)); } + if (that->IsHttpCompressionEnabled()) + { + ConfigureHttpCompression(output, headers); + } + // Extract the GET arguments - HttpHandler::Arguments argumentsGET; + IHttpHandler::GetArguments argumentsGET; if (!strcmp(request->request_method, "GET")) { - HttpHandler::ParseGetArguments(argumentsGET, request->query_string); + HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); } @@ -592,18 +633,18 @@ // Apply the filter, if it is installed + char remoteIp[24]; + sprintf(remoteIp, "%d.%d.%d.%d", + reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], + reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); + + std::string username = GetAuthenticatedUsername(headers); + const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter(); if (filter != NULL) { - std::string username = GetAuthenticatedUsername(headers); - - char remoteIp[24]; - sprintf(remoteIp, "%d.%d.%d.%d", - reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], - reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], - reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], - reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); - if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str())) { output.SendUnauthorized(ORTHANC_REALM); @@ -613,13 +654,16 @@ // Extract the body of the request for PUT and POST + + // TODO Avoid unneccessary memcopy of the body + std::string body; if (method == HttpMethod_Post || method == HttpMethod_Put) { PostDataStatus status; - HttpHandler::Arguments::const_iterator ct = headers.find("content-type"); + IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); if (ct == headers.end()) { // No content-type specified. Assume no multi-part content occurs at this point. @@ -650,7 +694,7 @@ return; case PostDataStatus_Pending: - output.SendBody(); + output.AnswerEmpty(); return; default: @@ -672,58 +716,58 @@ } - // Loop over the candidate handlers for this URI LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); - bool found = false; + - for (MongooseServer::Handlers::const_iterator it = - that->GetHandlers().begin(); it != that->GetHandlers().end() && !found; ++it) + try { + bool found = false; + try { - found = (*it)->Handle(output, method, uri, headers, argumentsGET, body); - } - catch (OrthancException& e) - { - // Using this candidate handler results in an exception - LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); - - switch (e.GetErrorCode()) + if (that->HasHandler()) { - case ErrorCode_InexistentFile: - case ErrorCode_InexistentItem: - case ErrorCode_UnknownResource: - output.SendStatus(HttpStatus_404_NotFound); - break; - - case ErrorCode_BadRequest: - case ErrorCode_UriSyntax: - output.SendStatus(HttpStatus_400_BadRequest); - break; - - default: - output.SendStatus(HttpStatus_500_InternalServerError); + found = that->GetHandler().Handle(output, RequestOrigin_Http, remoteIp, username.c_str(), + method, uri, headers, argumentsGET, body.c_str(), body.size()); } - - return; } catch (boost::bad_lexical_cast&) { - LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast"; - output.SendStatus(HttpStatus_400_BadRequest); - return; + throw OrthancException(ErrorCode_BadParameterType); } catch (std::runtime_error&) { - LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request"; - output.SendStatus(HttpStatus_400_BadRequest); - return; + // Presumably an error while parsing the JSON body + throw OrthancException(ErrorCode_BadRequest); + } + + if (!found) + { + throw OrthancException(ErrorCode_UnknownResource); } } + catch (OrthancException& e) + { + // Using this candidate handler results in an exception + try + { + if (that->GetExceptionFormatter() == NULL) + { + LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); + output.SendStatus(e.GetHttpStatus()); + } + else + { + that->GetExceptionFormatter()->Format(output, e, method, request->uri); + } + } + catch (OrthancException&) + { + // An exception here reflects the fact that the status code + // was already set by the HTTP handler. + } - if (!found) - { - output.SendStatus(HttpStatus_404_NotFound); + return; } } @@ -773,12 +817,15 @@ MongooseServer::MongooseServer() : pimpl_(new PImpl) { pimpl_->context_ = NULL; + handler_ = NULL; remoteAllowed_ = false; authentication_ = false; ssl_ = false; port_ = 8000; filter_ = NULL; keepAlive_ = false; + httpCompression_ = true; + exceptionFormatter_ = NULL; #if ORTHANC_SSL_ENABLED == 1 // Check for the Heartbleed exploit @@ -795,7 +842,6 @@ MongooseServer::~MongooseServer() { Stop(); - ClearHandlers(); } @@ -845,7 +891,7 @@ if (!pimpl_->context_) { - throw OrthancException("Unable to launch the Mongoose server"); + throw OrthancException(ErrorCode_HttpPortInUse); } } } @@ -860,20 +906,6 @@ } - void MongooseServer::RegisterHandler(HttpHandler& handler) - { - Stop(); - - handlers_.push_back(&handler); - } - - - void MongooseServer::ClearHandlers() - { - Stop(); - } - - void MongooseServer::ClearUsers() { Stop(); @@ -937,14 +969,47 @@ remoteAllowed_ = allowed; } + void MongooseServer::SetHttpCompressionEnabled(bool enabled) + { + Stop(); + httpCompression_ = enabled; + LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled"); + } + void MongooseServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) { Stop(); filter_ = &filter; } + + void MongooseServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter) + { + Stop(); + exceptionFormatter_ = &formatter; + } + + bool MongooseServer::IsValidBasicHttpAuthentication(const std::string& basic) const { return registeredUsers_.find(basic) != registeredUsers_.end(); } + + + void MongooseServer::Register(IHttpHandler& handler) + { + Stop(); + handler_ = &handler; + } + + + IHttpHandler& MongooseServer::GetHandler() const + { + if (handler_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *handler_; + } }
--- a/Core/HttpServer/MongooseServer.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/HttpServer/MongooseServer.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,7 +32,9 @@ #pragma once -#include "HttpHandler.h" +#include "IHttpHandler.h" + +#include "../OrthancException.h" #include <list> #include <map> @@ -57,17 +59,29 @@ const char* username) const = 0; }; - class MongooseServer + + class IHttpExceptionFormatter { public: - typedef std::list<HttpHandler*> Handlers; + virtual ~IHttpExceptionFormatter() + { + } + virtual void Format(HttpOutput& output, + const OrthancException& exception, + HttpMethod method, + const char* uri) = 0; + }; + + + class MongooseServer + { private: // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom struct PImpl; boost::shared_ptr<PImpl> pimpl_; - Handlers handlers_; + IHttpHandler *handler_; typedef std::set<std::string> RegisteredUsers; RegisteredUsers registeredUsers_; @@ -79,6 +93,8 @@ uint16_t port_; IIncomingHttpRequestFilter* filter_; bool keepAlive_; + bool httpCompression_; + IHttpExceptionFormatter* exceptionFormatter_; bool IsRunning() const; @@ -103,8 +119,6 @@ void RegisterUser(const char* username, const char* password); - void RegisterHandler(HttpHandler& handler); - bool IsAuthenticationEnabled() const { return authentication_; @@ -140,6 +154,13 @@ void SetRemoteAccessAllowed(bool allowed); + bool IsHttpCompressionEnabled() const + { + return httpCompression_;; + } + + void SetHttpCompressionEnabled(bool enabled); + const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const { return filter_; @@ -147,15 +168,24 @@ void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter); - void ClearHandlers(); - ChunkStore& GetChunkStore(); bool IsValidBasicHttpAuthentication(const std::string& basic) const; - const Handlers& GetHandlers() const + void Register(IHttpHandler& handler); + + bool HasHandler() const { - return handlers_; + return handler_ != NULL; + } + + IHttpHandler& GetHandler() const; + + void SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter); + + IHttpExceptionFormatter* GetExceptionFormatter() + { + return exceptionFormatter_; } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/StringHttpOutput.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "StringHttpOutput.h" + +#include "../OrthancException.h" + +namespace Orthanc +{ + void StringHttpOutput::OnHttpStatusReceived(HttpStatus status) + { + if (status != HttpStatus_200_Ok) + { + throw OrthancException(ErrorCode_BadRequest); + } + } + + void StringHttpOutput::Send(bool isHeader, const void* buffer, size_t length) + { + if (!isHeader) + { + buffer_.AddChunk(reinterpret_cast<const char*>(buffer), length); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/HttpServer/StringHttpOutput.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,56 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "IHttpOutputStream.h" + +#include "../ChunkedBuffer.h" + +namespace Orthanc +{ + class StringHttpOutput : public IHttpOutputStream + { + private: + ChunkedBuffer buffer_; + + public: + virtual void OnHttpStatusReceived(HttpStatus status); + + virtual void Send(bool isHeader, const void* buffer, size_t length); + + void GetOutput(std::string& output) + { + buffer_.Flatten(output); + } + }; +}
--- a/Core/ImageFormats/ImageAccessor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "ImageAccessor.h" - -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" - -#include <stdint.h> -#include <cassert> -#include <glog/logging.h> -#include <boost/lexical_cast.hpp> - -namespace Orthanc -{ - template <typename PixelType> - static void ToMatlabStringInternal(ChunkedBuffer& target, - const ImageAccessor& source) - { - target.AddChunk("double([ "); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); - - std::string s; - if (y > 0) - { - s = "; "; - } - - s.reserve(source.GetWidth() * 8); - - for (unsigned int x = 0; x < source.GetWidth(); x++, p++) - { - s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; - } - - target.AddChunk(s); - } - - target.AddChunk("])"); - } - - - static void RGB24ToMatlabString(ChunkedBuffer& target, - const ImageAccessor& source) - { - assert(source.GetFormat() == PixelFormat_RGB24); - - target.AddChunk("double(permute(reshape([ "); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); - - std::string s; - s.reserve(source.GetWidth() * 3 * 8); - - for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++) - { - s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; - } - - target.AddChunk(s); - } - - target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) + - " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))"); - } - - - void* ImageAccessor::GetBuffer() const - { - if (readOnly_) - { - LOG(ERROR) << "Trying to write on a read-only image"; - throw OrthancException(ErrorCode_ReadOnly); - } - - return buffer_; - } - - - const void* ImageAccessor::GetConstRow(unsigned int y) const - { - if (buffer_ != NULL) - { - return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; - } - else - { - return NULL; - } - } - - - void* ImageAccessor::GetRow(unsigned int y) const - { - if (readOnly_) - { - LOG(ERROR) << "Trying to write on a read-only image"; - throw OrthancException(ErrorCode_ReadOnly); - } - - if (buffer_ != NULL) - { - return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; - } - else - { - return NULL; - } - } - - - void ImageAccessor::AssignEmpty(PixelFormat format) - { - readOnly_ = false; - format_ = format; - width_ = 0; - height_ = 0; - pitch_ = 0; - buffer_ = NULL; - } - - - void ImageAccessor::AssignReadOnly(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - const void *buffer) - { - readOnly_ = true; - format_ = format; - width_ = width; - height_ = height; - pitch_ = pitch; - buffer_ = const_cast<void*>(buffer); - - assert(GetBytesPerPixel() * width_ <= pitch_); - } - - - void ImageAccessor::AssignWritable(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - void *buffer) - { - readOnly_ = false; - format_ = format; - width_ = width; - height_ = height; - pitch_ = pitch; - buffer_ = buffer; - - assert(GetBytesPerPixel() * width_ <= pitch_); - } - - - void ImageAccessor::ToMatlabString(std::string& target) const - { - ChunkedBuffer buffer; - - switch (GetFormat()) - { - case PixelFormat_Grayscale8: - ToMatlabStringInternal<uint8_t>(buffer, *this); - break; - - case PixelFormat_Grayscale16: - ToMatlabStringInternal<uint16_t>(buffer, *this); - break; - - case PixelFormat_SignedGrayscale16: - ToMatlabStringInternal<int16_t>(buffer, *this); - break; - - case PixelFormat_RGB24: - RGB24ToMatlabString(buffer, *this); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - buffer.Flatten(target); - } - -}
--- a/Core/ImageFormats/ImageAccessor.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Enumerations.h" - -namespace Orthanc -{ - class ImageAccessor - { - private: - bool readOnly_; - PixelFormat format_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - void *buffer_; - - public: - ImageAccessor() - { - AssignEmpty(PixelFormat_Grayscale8); - } - - bool IsReadOnly() const - { - return readOnly_; - } - - PixelFormat GetFormat() const - { - return format_; - } - - unsigned int GetBytesPerPixel() const - { - return ::Orthanc::GetBytesPerPixel(format_); - } - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetPitch() const - { - return pitch_; - } - - unsigned int GetSize() const - { - return GetHeight() * GetPitch(); - } - - const void* GetConstBuffer() const - { - return buffer_; - } - - void* GetBuffer() const; - - const void* GetConstRow(unsigned int y) const; - - void* GetRow(unsigned int y) const; - - void AssignEmpty(PixelFormat format); - - void AssignReadOnly(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - const void *buffer); - - void AssignWritable(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - void *buffer); - - void ToMatlabString(std::string& target) const; - }; -}
--- a/Core/ImageFormats/ImageBuffer.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "ImageBuffer.h" - -#include "../OrthancException.h" - -#include <stdio.h> -#include <stdlib.h> - -namespace Orthanc -{ - void ImageBuffer::Allocate() - { - if (changed_) - { - Deallocate(); - - /* - if (forceMinimalPitch_) - { - TODO: Align pitch and memory buffer to optimal size for SIMD. - } - */ - - pitch_ = GetBytesPerPixel() * width_; - size_t size = pitch_ * height_; - - if (size == 0) - { - buffer_ = NULL; - } - else - { - buffer_ = malloc(size); - if (buffer_ == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - changed_ = false; - } - } - - - void ImageBuffer::Deallocate() - { - if (buffer_ != NULL) - { - free(buffer_); - buffer_ = NULL; - changed_ = true; - } - } - - - ImageBuffer::ImageBuffer(unsigned int width, - unsigned int height, - PixelFormat format) - { - Initialize(); - SetWidth(width); - SetHeight(height); - SetFormat(format); - } - - - void ImageBuffer::Initialize() - { - changed_ = false; - forceMinimalPitch_ = true; - format_ = PixelFormat_Grayscale8; - width_ = 0; - height_ = 0; - pitch_ = 0; - buffer_ = NULL; - } - - - void ImageBuffer::SetFormat(PixelFormat format) - { - if (format != format_) - { - changed_ = true; - format_ = format; - } - } - - - void ImageBuffer::SetWidth(unsigned int width) - { - if (width != width_) - { - changed_ = true; - width_ = width; - } - } - - - void ImageBuffer::SetHeight(unsigned int height) - { - if (height != height_) - { - changed_ = true; - height_ = height; - } - } - - - ImageAccessor ImageBuffer::GetAccessor() - { - Allocate(); - - ImageAccessor accessor; - accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); - return accessor; - } - - - ImageAccessor ImageBuffer::GetConstAccessor() - { - Allocate(); - - ImageAccessor accessor; - accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); - return accessor; - } - - - void ImageBuffer::SetMinimalPitchForced(bool force) - { - if (force != forceMinimalPitch_) - { - changed_ = true; - forceMinimalPitch_ = force; - } - } - - - void ImageBuffer::AcquireOwnership(ImageBuffer& other) - { - // Remove the content of the current image - Deallocate(); - - // Force the allocation of the other image (if not already - // allocated) - other.Allocate(); - - // Transfer the content of the other image - changed_ = false; - forceMinimalPitch_ = other.forceMinimalPitch_; - format_ = other.format_; - width_ = other.width_; - height_ = other.height_; - pitch_ = other.pitch_; - buffer_ = other.buffer_; - - // Force the reinitialization of the other image - other.Initialize(); - } -}
--- a/Core/ImageFormats/ImageBuffer.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "ImageAccessor.h" - -#include <vector> -#include <stdint.h> -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class ImageBuffer : public boost::noncopyable - { - private: - bool changed_; - - bool forceMinimalPitch_; // Currently unused - PixelFormat format_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - void *buffer_; - - void Initialize(); - - void Allocate(); - - void Deallocate(); - - public: - ImageBuffer(unsigned int width, - unsigned int height, - PixelFormat format); - - ImageBuffer() - { - Initialize(); - } - - ~ImageBuffer() - { - Deallocate(); - } - - PixelFormat GetFormat() const - { - return format_; - } - - void SetFormat(PixelFormat format); - - unsigned int GetWidth() const - { - return width_; - } - - void SetWidth(unsigned int width); - - unsigned int GetHeight() const - { - return height_; - } - - void SetHeight(unsigned int height); - - unsigned int GetBytesPerPixel() const - { - return ::Orthanc::GetBytesPerPixel(format_); - } - - ImageAccessor GetAccessor(); - - ImageAccessor GetConstAccessor(); - - bool IsMinimalPitchForced() const - { - return forceMinimalPitch_; - } - - void SetMinimalPitchForced(bool force); - - void AcquireOwnership(ImageBuffer& other); - }; -}
--- a/Core/ImageFormats/ImageProcessing.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,532 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "ImageProcessing.h" - -#include "../OrthancException.h" - -#include <boost/math/special_functions/round.hpp> - -#include <cassert> -#include <string.h> -#include <limits> -#include <stdint.h> - -namespace Orthanc -{ - template <typename TargetType, typename SourceType> - static void ConvertInternal(ImageAccessor& target, - const ImageAccessor& source) - { - const TargetType minValue = std::numeric_limits<TargetType>::min(); - const TargetType maxValue = std::numeric_limits<TargetType>::max(); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); - const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); - - for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) - { - if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue)) - { - *t = minValue; - } - else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue)) - { - *t = maxValue; - } - else - { - *t = static_cast<TargetType>(*s); - } - } - } - } - - - template <typename TargetType> - static void ConvertColorToGrayscale(ImageAccessor& target, - const ImageAccessor& source) - { - assert(source.GetFormat() == PixelFormat_RGB24); - - const TargetType minValue = std::numeric_limits<TargetType>::min(); - const TargetType maxValue = std::numeric_limits<TargetType>::max(); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); - const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); - - for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3) - { - // Y = 0.2126 R + 0.7152 G + 0.0722 B - int32_t v = (2126 * static_cast<int32_t>(s[0]) + - 7152 * static_cast<int32_t>(s[1]) + - 0722 * static_cast<int32_t>(s[2])) / 1000; - - if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) - { - *t = minValue; - } - else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue)) - { - *t = maxValue; - } - else - { - *t = static_cast<TargetType>(v); - } - } - } - } - - - template <typename PixelType> - static void SetInternal(ImageAccessor& image, - int64_t constant) - { - for (unsigned int y = 0; y < image.GetHeight(); y++) - { - PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); - - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - *p = static_cast<PixelType>(constant); - } - } - } - - - template <typename PixelType> - static void GetMinMaxValueInternal(PixelType& minValue, - PixelType& maxValue, - const ImageAccessor& source) - { - // Deal with the special case of empty image - if (source.GetWidth() == 0 || - source.GetHeight() == 0) - { - minValue = 0; - maxValue = 0; - return; - } - - minValue = std::numeric_limits<PixelType>::max(); - maxValue = std::numeric_limits<PixelType>::min(); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); - - for (unsigned int x = 0; x < source.GetWidth(); x++, p++) - { - if (*p < minValue) - { - minValue = *p; - } - - if (*p > maxValue) - { - maxValue = *p; - } - } - } - } - - - - template <typename PixelType> - static void AddConstantInternal(ImageAccessor& image, - int64_t constant) - { - if (constant == 0) - { - return; - } - - const int64_t minValue = std::numeric_limits<PixelType>::min(); - const int64_t maxValue = std::numeric_limits<PixelType>::max(); - - for (unsigned int y = 0; y < image.GetHeight(); y++) - { - PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); - - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - int64_t v = static_cast<int64_t>(*p) + constant; - - if (v > maxValue) - { - *p = std::numeric_limits<PixelType>::max(); - } - else if (v < minValue) - { - *p = std::numeric_limits<PixelType>::min(); - } - else - { - *p = static_cast<PixelType>(v); - } - } - } - } - - - - template <typename PixelType> - void MultiplyConstantInternal(ImageAccessor& image, - float factor) - { - if (abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) - { - return; - } - - const int64_t minValue = std::numeric_limits<PixelType>::min(); - const int64_t maxValue = std::numeric_limits<PixelType>::max(); - - for (unsigned int y = 0; y < image.GetHeight(); y++) - { - PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); - - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - int64_t v = boost::math::llround(static_cast<float>(*p) * factor); - - if (v > maxValue) - { - *p = std::numeric_limits<PixelType>::max(); - } - else if (v < minValue) - { - *p = std::numeric_limits<PixelType>::min(); - } - else - { - *p = static_cast<PixelType>(v); - } - } - } - } - - - template <typename PixelType> - void ShiftScaleInternal(ImageAccessor& image, - float offset, - float scaling) - { - const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min()); - const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max()); - - for (unsigned int y = 0; y < image.GetHeight(); y++) - { - PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); - - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - float v = (static_cast<float>(*p) + offset) * scaling; - - if (v > maxValue) - { - *p = std::numeric_limits<PixelType>::max(); - } - else if (v < minValue) - { - *p = std::numeric_limits<PixelType>::min(); - } - else - { - *p = static_cast<PixelType>(boost::math::iround(v)); - } - } - } - } - - - void ImageProcessing::Copy(ImageAccessor& target, - const ImageAccessor& source) - { - if (target.GetWidth() != source.GetWidth() || - target.GetHeight() != source.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - if (target.GetFormat() != source.GetFormat()) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat); - } - - unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); - - assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); - } - } - - - void ImageProcessing::Convert(ImageAccessor& target, - const ImageAccessor& source) - { - if (target.GetWidth() != source.GetWidth() || - target.GetHeight() != source.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - if (source.GetFormat() == target.GetFormat()) - { - Copy(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_Grayscale8) - { - ConvertInternal<uint16_t, uint8_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_Grayscale8) - { - ConvertInternal<int16_t, uint8_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_Grayscale16) - { - ConvertInternal<uint8_t, uint16_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_Grayscale16) - { - ConvertInternal<int16_t, uint16_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - ConvertInternal<uint8_t, int16_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - ConvertInternal<uint16_t, int16_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale<uint8_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale<uint16_t>(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale<int16_t>(target, source); - return; - } - - throw OrthancException(ErrorCode_NotImplemented); - } - - - - void ImageProcessing::Set(ImageAccessor& image, - int64_t value) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - SetInternal<uint8_t>(image, value); - return; - - case PixelFormat_Grayscale16: - SetInternal<uint16_t>(image, value); - return; - - case PixelFormat_SignedGrayscale16: - SetInternal<int16_t>(image, value); - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::ShiftRight(ImageAccessor& image, - unsigned int shift) - { - if (image.GetWidth() == 0 || - image.GetHeight() == 0 || - shift == 0) - { - // Nothing to do - return; - } - - throw OrthancException(ErrorCode_NotImplemented); - } - - - void ImageProcessing::GetMinMaxValue(int64_t& minValue, - int64_t& maxValue, - const ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - { - uint8_t a, b; - GetMinMaxValueInternal<uint8_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } - - case PixelFormat_Grayscale16: - { - uint16_t a, b; - GetMinMaxValueInternal<uint16_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } - - case PixelFormat_SignedGrayscale16: - { - int16_t a, b; - GetMinMaxValueInternal<int16_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - - void ImageProcessing::AddConstant(ImageAccessor& image, - int64_t value) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - AddConstantInternal<uint8_t>(image, value); - return; - - case PixelFormat_Grayscale16: - AddConstantInternal<uint16_t>(image, value); - return; - - case PixelFormat_SignedGrayscale16: - AddConstantInternal<int16_t>(image, value); - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::MultiplyConstant(ImageAccessor& image, - float factor) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - MultiplyConstantInternal<uint8_t>(image, factor); - return; - - case PixelFormat_Grayscale16: - MultiplyConstantInternal<uint16_t>(image, factor); - return; - - case PixelFormat_SignedGrayscale16: - MultiplyConstantInternal<int16_t>(image, factor); - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::ShiftScale(ImageAccessor& image, - float offset, - float scaling) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - ShiftScaleInternal<uint8_t>(image, offset, scaling); - return; - - case PixelFormat_Grayscale16: - ShiftScaleInternal<uint16_t>(image, offset, scaling); - return; - - case PixelFormat_SignedGrayscale16: - ShiftScaleInternal<int16_t>(image, offset, scaling); - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } -}
--- a/Core/ImageFormats/ImageProcessing.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "ImageAccessor.h" - -#include <stdint.h> - -namespace Orthanc -{ - class ImageProcessing - { - public: - static void Copy(ImageAccessor& target, - const ImageAccessor& source); - - static void Convert(ImageAccessor& target, - const ImageAccessor& source); - - static void Set(ImageAccessor& image, - int64_t value); - - static void ShiftRight(ImageAccessor& target, - unsigned int shift); - - static void GetMinMaxValue(int64_t& minValue, - int64_t& maxValue, - const ImageAccessor& image); - - static void AddConstant(ImageAccessor& image, - int64_t value); - - static void MultiplyConstant(ImageAccessor& image, - float factor); - - static void ShiftScale(ImageAccessor& image, - float offset, - float scaling); - }; -}
--- a/Core/ImageFormats/PngReader.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,313 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "PngReader.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include <png.h> -#include <string.h> // For memcpy() - -namespace Orthanc -{ - namespace - { - struct FileRabi - { - FILE* fp_; - - FileRabi(const char* filename) - { - fp_ = fopen(filename, "rb"); - if (!fp_) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - - ~FileRabi() - { - if (fp_) - fclose(fp_); - } - }; - } - - - struct PngReader::PngRabi - { - png_structp png_; - png_infop info_; - png_infop endInfo_; - - void Destruct() - { - if (png_) - { - png_destroy_read_struct(&png_, &info_, &endInfo_); - - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - } - } - - PngRabi() - { - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - - png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - info_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, NULL, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - endInfo_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, &info_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - ~PngRabi() - { - Destruct(); - } - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size); - }; - - - void PngReader::CheckHeader(const void* header) - { - int is_png = !png_sig_cmp((png_bytep) header, 0, 8); - if (!is_png) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - PngReader::PngReader() - { - } - - void PngReader::Read(PngRabi& rabi) - { - png_set_sig_bytes(rabi.png_, 8); - - png_read_info(rabi.png_, rabi.info_); - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type; - int compression_type, filter_method; - // get size and bit-depth of the PNG-image - png_get_IHDR(rabi.png_, rabi.info_, - &width, &height, - &bit_depth, &color_type, &interlace_type, - &compression_type, &filter_method); - - PixelFormat format; - unsigned int pitch; - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) - { - format = PixelFormat_Grayscale8; - pitch = width; - } - else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) - { - format = PixelFormat_Grayscale16; - pitch = 2 * width; - - if (Toolbox::DetectEndianness() == Endianness_Little) - { - png_set_swap(rabi.png_); - } - } - else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) - { - format = PixelFormat_RGB24; - pitch = 3 * width; - } - else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8) - { - format = PixelFormat_RGBA32; - pitch = 4 * width; - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - - data_.resize(height * pitch); - - if (height == 0 || width == 0) - { - // Empty image, we are done - AssignEmpty(format); - return; - } - - png_read_update_info(rabi.png_, rabi.info_); - - std::vector<png_bytep> rows(height); - for (size_t i = 0; i < height; i++) - { - rows[i] = &data_[0] + i * pitch; - } - - png_read_image(rabi.png_, &rows[0]); - - AssignReadOnly(format, width, height, pitch, &data_[0]); - } - - void PngReader::ReadFromFile(const char* filename) - { - FileRabi f(filename); - - char header[8]; - if (fread(header, 1, 8, f.fp_) != 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(header); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - png_init_io(rabi.png_, f.fp_); - - Read(rabi); - } - - - namespace - { - struct MemoryBuffer - { - const uint8_t* buffer_; - size_t size_; - size_t pos_; - bool ok_; - }; - } - - - void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, - png_bytep outBytes, - png_size_t byteCountToRead) - { - MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); - - if (!from->ok_) - { - return; - } - - if (from->pos_ + byteCountToRead > from->size_) - { - from->ok_ = false; - return; - } - - memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); - - from->pos_ += byteCountToRead; - } - - - void PngReader::ReadFromMemory(const void* buffer, - size_t size) - { - if (size < 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(buffer); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - MemoryBuffer tmp; - tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header - tmp.size_ = size - 8; - tmp.pos_ = 0; - tmp.ok_ = true; - - png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); - - Read(rabi); - - if (!tmp.ok_) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - void PngReader::ReadFromMemory(const std::string& buffer) - { - if (buffer.size() != 0) - { - ReadFromMemory(&buffer[0], buffer.size()); - } - else - { - ReadFromMemory(NULL, 0); - } - } -}
--- a/Core/ImageFormats/PngReader.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "ImageAccessor.h" - -#include "../Enumerations.h" - -#include <vector> -#include <stdint.h> -#include <boost/shared_ptr.hpp> - -namespace Orthanc -{ - class PngReader : public ImageAccessor - { - private: - struct PngRabi; - - std::vector<uint8_t> data_; - - void CheckHeader(const void* header); - - void Read(PngRabi& rabi); - - public: - PngReader(); - - void ReadFromFile(const char* filename); - - void ReadFromFile(const std::string& filename) - { - ReadFromFile(filename.c_str()); - } - - void ReadFromMemory(const void* buffer, - size_t size); - - void ReadFromMemory(const std::string& buffer); - }; -}
--- a/Core/ImageFormats/PngWriter.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,269 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "PngWriter.h" - -#include <vector> -#include <stdint.h> -#include <png.h> -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" -#include "../Toolbox.h" - - -// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 -// http://zarb.org/~gc/html/libpng.html -/* - void write_row_callback(png_ptr, png_uint_32 row, int pass) - { - }*/ - - - - -/* bool isError_; - -// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 - -static void ErrorHandler(png_structp png, png_const_charp message) -{ -printf("** [%s]\n", message); - -PngWriter* that = (PngWriter*) png_get_error_ptr(png); -that->isError_ = true; -printf("** %d\n", (int)that); - -//((PngWriter*) payload)->isError_ = true; -} - -static void WarningHandler(png_structp png, png_const_charp message) -{ - printf("++ %d\n", (int)message); -}*/ - - -namespace Orthanc -{ - struct PngWriter::PImpl - { - png_structp png_; - png_infop info_; - - // Filled by Prepare() - std::vector<uint8_t*> rows_; - int bitDepth_; - int colorType_; - }; - - - - PngWriter::PngWriter() : pimpl_(new PImpl) - { - pimpl_->png_ = NULL; - pimpl_->info_ = NULL; - - pimpl_->png_ = png_create_write_struct - (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); - if (!pimpl_->png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - pimpl_->info_ = png_create_info_struct(pimpl_->png_); - if (!pimpl_->info_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - PngWriter::~PngWriter() - { - if (pimpl_->info_) - { - png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); - } - - if (pimpl_->png_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - } - } - - - - void PngWriter::Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - pimpl_->rows_.resize(height); - for (unsigned int y = 0; y < height; y++) - { - pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; - } - - switch (format) - { - case PixelFormat_RGB24: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; - break; - - case PixelFormat_RGBA32: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA; - break; - - case PixelFormat_Grayscale8: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - pimpl_->bitDepth_ = 16; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PngWriter::Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format) - { - png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, - pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(pimpl_->png_, pimpl_->info_); - - if (height > 0) - { - switch (format) - { - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - int transforms = 0; - if (Toolbox::DetectEndianness() == Endianness_Little) - { - transforms = PNG_TRANSFORM_SWAP_ENDIAN; - } - - png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); - png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); - - break; - } - - default: - png_write_image(pimpl_->png_, &pimpl_->rows_[0]); - } - } - - png_write_end(pimpl_->png_, NULL); - } - - - void PngWriter::WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - Prepare(width, height, pitch, format, buffer); - - FILE* fp = fopen(filename, "wb"); - if (!fp) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - png_init_io(pimpl_->png_, fp); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_CannotWriteFile); - } - - Compress(width, height, pitch, format); - - fclose(fp); - } - - - - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size) - { - ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); - buffer->AddChunk(reinterpret_cast<const char*>(data), size); - } - - - - void PngWriter::WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - ChunkedBuffer chunks; - - Prepare(width, height, pitch, format, buffer); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_InternalError); - } - - png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); - - Compress(width, height, pitch, format); - - chunks.Flatten(png); - } -}
--- a/Core/ImageFormats/PngWriter.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "ImageAccessor.h" - -#include <boost/shared_ptr.hpp> -#include <string> - -namespace Orthanc -{ - class PngWriter - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - void Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format); - - void Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - public: - PngWriter(); - - ~PngWriter(); - - void WriteToFile(const char* filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToMemory(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - void WriteToFile(const char* filename, - const ImageAccessor& accessor) - { - WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } - - void WriteToMemory(std::string& png, - const ImageAccessor& accessor) - { - WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/Font.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,301 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "Font.h" + +#include "../Toolbox.h" +#include "../OrthancException.h" + +#include <stdio.h> +#include <memory> +#include <boost/lexical_cast.hpp> + +namespace Orthanc +{ + Font::~Font() + { + for (Characters::iterator it = characters_.begin(); + it != characters_.end(); ++it) + { + delete it->second; + } + } + + + void Font::LoadFromMemory(const std::string& font) + { + Json::Value v; + Json::Reader reader; + if (!reader.parse(font, v) || + v.type() != Json::objectValue || + !v.isMember("Name") || + !v.isMember("Size") || + !v.isMember("Characters") || + v["Name"].type() != Json::stringValue || + v["Size"].type() != Json::intValue || + v["Characters"].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFont); + } + + name_ = v["Name"].asString(); + size_ = v["Size"].asUInt(); + maxHeight_ = 0; + + Json::Value::Members characters = v["Characters"].getMemberNames(); + + for (size_t i = 0; i < characters.size(); i++) + { + const Json::Value& info = v["Characters"][characters[i]]; + if (info.type() != Json::objectValue || + !info.isMember("Advance") || + !info.isMember("Bitmap") || + !info.isMember("Height") || + !info.isMember("Top") || + !info.isMember("Width") || + info["Advance"].type() != Json::intValue || + info["Bitmap"].type() != Json::arrayValue || + info["Height"].type() != Json::intValue || + info["Top"].type() != Json::intValue || + info["Width"].type() != Json::intValue) + { + throw OrthancException(ErrorCode_BadFont); + } + + std::auto_ptr<Character> c(new Character); + + c->advance_ = info["Advance"].asUInt(); + c->height_ = info["Height"].asUInt(); + c->top_ = info["Top"].asUInt(); + c->width_ = info["Width"].asUInt(); + c->bitmap_.resize(info["Bitmap"].size()); + + if (c->height_ > maxHeight_) + { + maxHeight_ = c->height_; + } + + for (Json::Value::ArrayIndex j = 0; j < info["Bitmap"].size(); j++) + { + if (info["Bitmap"][j].type() != Json::intValue) + { + throw OrthancException(ErrorCode_BadFont); + } + + int value = info["Bitmap"][j].asInt(); + if (value < 0 || value > 255) + { + throw OrthancException(ErrorCode_BadFont); + } + + c->bitmap_[j] = static_cast<uint8_t>(value); + } + + int index = boost::lexical_cast<int>(characters[i]); + if (index < 0 || index > 255) + { + throw OrthancException(ErrorCode_BadFont); + } + + characters_[static_cast<char>(index)] = c.release(); + } + } + + + void Font::LoadFromFile(const std::string& path) + { + std::string font; + Toolbox::ReadFile(font, path); + LoadFromMemory(font); + } + + + static unsigned int MyMin(unsigned int a, + unsigned int b) + { + return a < b ? a : b; + } + + + void Font::DrawCharacter(ImageAccessor& target, + const Character& character, + int x, + int y, + const uint8_t color[4]) const + { + // Compute the bounds of the character + if (x >= static_cast<int>(target.GetWidth()) || + y >= static_cast<int>(target.GetHeight())) + { + // The character is out of the image + return; + } + + unsigned int left = x < 0 ? -x : 0; + unsigned int top = y < 0 ? -y : 0; + unsigned int width = MyMin(character.width_, target.GetWidth() - x); + unsigned int height = MyMin(character.height_, target.GetHeight() - y); + + unsigned int bpp = target.GetBytesPerPixel(); + + // Blit the font bitmap OVER the target image + // https://en.wikipedia.org/wiki/Alpha_compositing + + for (unsigned int cy = top; cy < height; cy++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(target.GetRow(y + cy)) + (x + left) * bpp; + unsigned int pos = cy * character.width_ + left; + + switch (target.GetFormat()) + { + case PixelFormat_Grayscale8: + { + assert(bpp == 1); + for (unsigned int cx = left; cx < width; cx++, pos++, p++) + { + uint16_t alpha = character.bitmap_[pos]; + uint16_t value = alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(*p); + *p = static_cast<uint8_t>(value >> 8); + } + + break; + } + + case PixelFormat_RGB24: + { + assert(bpp == 3); + for (unsigned int cx = left; cx < width; cx++, pos++, p += 3) + { + uint16_t alpha = character.bitmap_[pos]; + for (uint8_t i = 0; i < 3; i++) + { + uint16_t value = alpha * static_cast<uint16_t>(color[i]) + (255 - alpha) * static_cast<uint16_t>(p[i]); + p[i] = static_cast<uint8_t>(value >> 8); + } + } + + break; + } + + case PixelFormat_RGBA32: + { + assert(bpp == 4); + + for (unsigned int cx = left; cx < width; cx++, pos++, p += 4) + { + float alpha = static_cast<float>(character.bitmap_[pos]) / 255.0f; + float beta = (1.0f - alpha) * static_cast<float>(p[3]) / 255.0f; + float denom = 1.0f / (alpha + beta); + + for (uint8_t i = 0; i < 3; i++) + { + p[i] = static_cast<uint8_t>((alpha * static_cast<float>(color[i]) + + beta * static_cast<float>(p[i])) * denom); + } + + p[3] = static_cast<uint8_t>(255.0f * (alpha + beta)); + } + + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + } + + + void Font::DrawInternal(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + const uint8_t color[4]) const + { + if (target.GetFormat() != PixelFormat_Grayscale8 && + target.GetFormat() != PixelFormat_RGB24 && + target.GetFormat() != PixelFormat_RGBA32) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + int a = x; + + std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1); + + for (size_t i = 0; i < s.size(); i++) + { + if (s[i] == '\n') + { + // Go to the next line + a = x; + y += maxHeight_ + 1; + } + else + { + Characters::const_iterator c = characters_.find(s[i]); + if (c != characters_.end()) + { + DrawCharacter(target, *c->second, a, y + static_cast<int>(c->second->top_), color); + a += c->second->advance_; + } + } + } + } + + + void Font::Draw(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + uint8_t grayscale) const + { + uint8_t color[4] = { grayscale, grayscale, grayscale, 255 }; + DrawInternal(target, utf8, x, y, color); + } + + + void Font::Draw(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + uint8_t r, + uint8_t g, + uint8_t b) const + { + uint8_t color[4] = { r, g, b, 255 }; + DrawInternal(target, utf8, x, y, color); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/Font.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <stdint.h> +#include <vector> +#include <map> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class Font : public boost::noncopyable + { + private: + struct Character + { + unsigned int width_; + unsigned int height_; + unsigned int top_; + unsigned int advance_; + std::vector<uint8_t> bitmap_; + }; + + typedef std::map<char, Character*> Characters; + + std::string name_; + unsigned int size_; + Characters characters_; + unsigned int maxHeight_; + + void DrawCharacter(ImageAccessor& target, + const Character& character, + int x, + int y, + const uint8_t color[4]) const; + + void DrawInternal(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + const uint8_t color[4]) const; + + public: + Font() : + size_(0), + maxHeight_(0) + { + } + + ~Font(); + + void LoadFromMemory(const std::string& font); + + void LoadFromFile(const std::string& path); + + const std::string& GetName() const + { + return name_; + } + + unsigned int GetSize() const + { + return size_; + } + + void Draw(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + uint8_t grayscale) const; + + void Draw(ImageAccessor& target, + const std::string& utf8, + int x, + int y, + uint8_t r, + uint8_t g, + uint8_t b) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/FontRegistry.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "FontRegistry.h" + +#include "../OrthancException.h" + +#include <memory> + +namespace Orthanc +{ + FontRegistry::~FontRegistry() + { + for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) + { + delete *it; + } + } + + + void FontRegistry::AddFromMemory(const std::string& font) + { + std::auto_ptr<Font> f(new Font); + f->LoadFromMemory(font); + fonts_.push_back(f.release()); + } + + + void FontRegistry::AddFromFile(const std::string& path) + { + std::auto_ptr<Font> f(new Font); + f->LoadFromFile(path); + fonts_.push_back(f.release()); + } + + + void FontRegistry::AddFromResource(EmbeddedResources::FileResourceId resource) + { + std::string content; + EmbeddedResources::GetFileResource(content, resource); + AddFromMemory(content); + } + + + const Font& FontRegistry::GetFont(size_t i) const + { + if (i >= fonts_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + return *fonts_[i]; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/FontRegistry.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "Font.h" + +#include <EmbeddedResources.h> // Autogenerated file + +namespace Orthanc +{ + class FontRegistry : public boost::noncopyable + { + private: + typedef std::vector<Font*> Fonts; + + Fonts fonts_; + + public: + ~FontRegistry(); + + void AddFromMemory(const std::string& font); + + void AddFromFile(const std::string& path); + + void AddFromResource(EmbeddedResources::FileResourceId resource); + + size_t GetSize() const + { + return fonts_.size(); + } + + const Font& GetFont(size_t i) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/Image.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" +#include "ImageBuffer.h" + +namespace Orthanc +{ + class Image : public ImageAccessor + { + private: + ImageBuffer image_; + + public: + Image(PixelFormat format, + unsigned int width, + unsigned int height) : + image_(format, width, height) + { + ImageAccessor accessor = image_.GetAccessor(); + AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer()); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageAccessor.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,229 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "ImageAccessor.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" + +#include <stdint.h> +#include <cassert> +#include <boost/lexical_cast.hpp> + + + +namespace Orthanc +{ + template <typename PixelType> + static void ToMatlabStringInternal(ChunkedBuffer& target, + const ImageAccessor& source) + { + target.AddChunk("double([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); + + std::string s; + if (y > 0) + { + s = "; "; + } + + s.reserve(source.GetWidth() * 8); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("])"); + } + + + static void RGB24ToMatlabString(ChunkedBuffer& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + target.AddChunk("double(permute(reshape([ "); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + + std::string s; + s.reserve(source.GetWidth() * 3 * 8); + + for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++) + { + s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " "; + } + + target.AddChunk(s); + } + + target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) + + " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))"); + } + + + void* ImageAccessor::GetBuffer() const + { + if (readOnly_) + { +#if ORTHANC_ENABLE_LOGGING == 1 + LOG(ERROR) << "Trying to write on a read-only image"; +#endif + + throw OrthancException(ErrorCode_ReadOnly); + } + + return buffer_; + } + + + const void* ImageAccessor::GetConstRow(unsigned int y) const + { + if (buffer_ != NULL) + { + return reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void* ImageAccessor::GetRow(unsigned int y) const + { + if (readOnly_) + { +#if ORTHANC_ENABLE_LOGGING == 1 + LOG(ERROR) << "Trying to write on a read-only image"; +#endif + + throw OrthancException(ErrorCode_ReadOnly); + } + + if (buffer_ != NULL) + { + return reinterpret_cast<uint8_t*>(buffer_) + y * pitch_; + } + else + { + return NULL; + } + } + + + void ImageAccessor::AssignEmpty(PixelFormat format) + { + readOnly_ = false; + format_ = format; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageAccessor::AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer) + { + readOnly_ = true; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = const_cast<void*>(buffer); + + assert(GetBytesPerPixel() * width_ <= pitch_); + } + + + void ImageAccessor::AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer) + { + readOnly_ = false; + format_ = format; + width_ = width; + height_ = height; + pitch_ = pitch; + buffer_ = buffer; + + assert(GetBytesPerPixel() * width_ <= pitch_); + } + + + void ImageAccessor::ToMatlabString(std::string& target) const + { + ChunkedBuffer buffer; + + switch (GetFormat()) + { + case PixelFormat_Grayscale8: + ToMatlabStringInternal<uint8_t>(buffer, *this); + break; + + case PixelFormat_Grayscale16: + ToMatlabStringInternal<uint16_t>(buffer, *this); + break; + + case PixelFormat_SignedGrayscale16: + ToMatlabStringInternal<int16_t>(buffer, *this); + break; + + case PixelFormat_RGB24: + RGB24ToMatlabString(buffer, *this); + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + buffer.Flatten(target); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageAccessor.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,123 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../Enumerations.h" + +#include <string> + +namespace Orthanc +{ + class ImageAccessor + { + private: + bool readOnly_; + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + public: + ImageAccessor() + { + AssignEmpty(PixelFormat_Grayscale8); + } + + virtual ~ImageAccessor() + { + } + + bool IsReadOnly() const + { + return readOnly_; + } + + PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + unsigned int GetSize() const + { + return GetHeight() * GetPitch(); + } + + const void* GetConstBuffer() const + { + return buffer_; + } + + void* GetBuffer() const; + + const void* GetConstRow(unsigned int y) const; + + void* GetRow(unsigned int y) const; + + void AssignEmpty(PixelFormat format); + + void AssignReadOnly(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + const void *buffer); + + void AssignWritable(PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int pitch, + void *buffer); + + void ToMatlabString(std::string& target) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageBuffer.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,192 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "ImageBuffer.h" + +#include "../OrthancException.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace Orthanc +{ + void ImageBuffer::Allocate() + { + if (changed_) + { + Deallocate(); + + /* + if (forceMinimalPitch_) + { + TODO: Align pitch and memory buffer to optimal size for SIMD. + } + */ + + pitch_ = GetBytesPerPixel() * width_; + size_t size = pitch_ * height_; + + if (size == 0) + { + buffer_ = NULL; + } + else + { + buffer_ = malloc(size); + if (buffer_ == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + changed_ = false; + } + } + + + void ImageBuffer::Deallocate() + { + if (buffer_ != NULL) + { + free(buffer_); + buffer_ = NULL; + changed_ = true; + } + } + + + ImageBuffer::ImageBuffer(PixelFormat format, + unsigned int width, + unsigned int height) + { + Initialize(); + SetWidth(width); + SetHeight(height); + SetFormat(format); + } + + + void ImageBuffer::Initialize() + { + changed_ = false; + forceMinimalPitch_ = true; + format_ = PixelFormat_Grayscale8; + width_ = 0; + height_ = 0; + pitch_ = 0; + buffer_ = NULL; + } + + + void ImageBuffer::SetFormat(PixelFormat format) + { + if (format != format_) + { + changed_ = true; + format_ = format; + } + } + + + void ImageBuffer::SetWidth(unsigned int width) + { + if (width != width_) + { + changed_ = true; + width_ = width; + } + } + + + void ImageBuffer::SetHeight(unsigned int height) + { + if (height != height_) + { + changed_ = true; + height_ = height; + } + } + + + ImageAccessor ImageBuffer::GetAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + ImageAccessor ImageBuffer::GetConstAccessor() + { + Allocate(); + + ImageAccessor accessor; + accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); + return accessor; + } + + + void ImageBuffer::SetMinimalPitchForced(bool force) + { + if (force != forceMinimalPitch_) + { + changed_ = true; + forceMinimalPitch_ = force; + } + } + + + void ImageBuffer::AcquireOwnership(ImageBuffer& other) + { + // Remove the content of the current image + Deallocate(); + + // Force the allocation of the other image (if not already + // allocated) + other.Allocate(); + + // Transfer the content of the other image + changed_ = false; + forceMinimalPitch_ = other.forceMinimalPitch_; + format_ = other.format_; + width_ = other.width_; + height_ = other.height_; + pitch_ = other.pitch_; + buffer_ = other.buffer_; + + // Force the reinitialization of the other image + other.Initialize(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageBuffer.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ImageBuffer : public boost::noncopyable + { + private: + bool changed_; + + bool forceMinimalPitch_; // Currently unused + PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void *buffer_; + + void Initialize(); + + void Allocate(); + + void Deallocate(); + + public: + ImageBuffer(PixelFormat format, + unsigned int width, + unsigned int height); + + ImageBuffer() + { + Initialize(); + } + + ~ImageBuffer() + { + Deallocate(); + } + + PixelFormat GetFormat() const + { + return format_; + } + + void SetFormat(PixelFormat format); + + unsigned int GetWidth() const + { + return width_; + } + + void SetWidth(unsigned int width); + + unsigned int GetHeight() const + { + return height_; + } + + void SetHeight(unsigned int height); + + unsigned int GetBytesPerPixel() const + { + return ::Orthanc::GetBytesPerPixel(format_); + } + + ImageAccessor GetAccessor(); + + ImageAccessor GetConstAccessor(); + + bool IsMinimalPitchForced() const + { + return forceMinimalPitch_; + } + + void SetMinimalPitchForced(bool force); + + void AcquireOwnership(ImageBuffer& other); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageProcessing.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,592 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "ImageProcessing.h" + +#include "../OrthancException.h" + +#include <boost/math/special_functions/round.hpp> + +#include <cassert> +#include <string.h> +#include <limits> +#include <stdint.h> + +namespace Orthanc +{ + template <typename TargetType, typename SourceType> + static void ConvertInternal(ImageAccessor& target, + const ImageAccessor& source) + { + const TargetType minValue = std::numeric_limits<TargetType>::min(); + const TargetType maxValue = std::numeric_limits<TargetType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); + const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++) + { + if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue)) + { + *t = minValue; + } + else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast<TargetType>(*s); + } + } + } + } + + + template <typename TargetType> + static void ConvertColorToGrayscale(ImageAccessor& target, + const ImageAccessor& source) + { + assert(source.GetFormat() == PixelFormat_RGB24); + + const TargetType minValue = std::numeric_limits<TargetType>::min(); + const TargetType maxValue = std::numeric_limits<TargetType>::max(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); + const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3) + { + // Y = 0.2126 R + 0.7152 G + 0.0722 B + int32_t v = (2126 * static_cast<int32_t>(s[0]) + + 7152 * static_cast<int32_t>(s[1]) + + 0722 * static_cast<int32_t>(s[2])) / 1000; + + if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) + { + *t = minValue; + } + else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue)) + { + *t = maxValue; + } + else + { + *t = static_cast<TargetType>(v); + } + } + } + } + + + template <typename PixelType> + static void SetInternal(ImageAccessor& image, + int64_t constant) + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = static_cast<PixelType>(constant); + } + } + } + + + template <typename PixelType> + static void GetMinMaxValueInternal(PixelType& minValue, + PixelType& maxValue, + const ImageAccessor& source) + { + // Deal with the special case of empty image + if (source.GetWidth() == 0 || + source.GetHeight() == 0) + { + minValue = 0; + maxValue = 0; + return; + } + + minValue = std::numeric_limits<PixelType>::max(); + maxValue = std::numeric_limits<PixelType>::min(); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y)); + + for (unsigned int x = 0; x < source.GetWidth(); x++, p++) + { + if (*p < minValue) + { + minValue = *p; + } + + if (*p > maxValue) + { + maxValue = *p; + } + } + } + } + + + + template <typename PixelType> + static void AddConstantInternal(ImageAccessor& image, + int64_t constant) + { + if (constant == 0) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = static_cast<int64_t>(*p) + constant; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + + template <typename PixelType> + void MultiplyConstantInternal(ImageAccessor& image, + float factor) + { + if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon()) + { + return; + } + + const int64_t minValue = std::numeric_limits<PixelType>::min(); + const int64_t maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + int64_t v = boost::math::llround(static_cast<float>(*p) * factor); + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(v); + } + } + } + } + + + template <typename PixelType> + void ShiftScaleInternal(ImageAccessor& image, + float offset, + float scaling) + { + const float minValue = static_cast<float>(std::numeric_limits<PixelType>::min()); + const float maxValue = static_cast<float>(std::numeric_limits<PixelType>::max()); + + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); + + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + float v = (static_cast<float>(*p) + offset) * scaling; + + if (v > maxValue) + { + *p = std::numeric_limits<PixelType>::max(); + } + else if (v < minValue) + { + *p = std::numeric_limits<PixelType>::min(); + } + else + { + *p = static_cast<PixelType>(boost::math::iround(v)); + } + } + } + } + + + void ImageProcessing::Copy(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (target.GetFormat() != source.GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat); + } + + unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth(); + + assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); + + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); + } + } + + + void ImageProcessing::Convert(ImageAccessor& target, + const ImageAccessor& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize); + } + + if (source.GetFormat() == target.GetFormat()) + { + Copy(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<uint16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale8) + { + ConvertInternal<int16_t, uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<uint8_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_Grayscale16) + { + ConvertInternal<int16_t, uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint8_t, int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_SignedGrayscale16) + { + ConvertInternal<uint16_t, int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<uint8_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<uint16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_SignedGrayscale16 && + source.GetFormat() == PixelFormat_RGB24) + { + ConvertColorToGrayscale<int16_t>(target, source); + return; + } + + if (target.GetFormat() == PixelFormat_Grayscale8 && + source.GetFormat() == PixelFormat_RGBA32) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++, q++) + { + *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) + + 7152 * static_cast<uint32_t>(p[1]) + + 0722 * static_cast<uint32_t>(p[2])) / 10000); + p += 4; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_RGB24 && + source.GetFormat() == PixelFormat_RGBA32) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + p += 4; + q += 3; + } + } + + return; + } + + if (target.GetFormat() == PixelFormat_RGBA32 && + source.GetFormat() == PixelFormat_RGB24) + { + for (unsigned int y = 0; y < source.GetHeight(); y++) + { + const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetWidth(); x++) + { + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + q[3] = 255; // Set the alpha channel to full opacity + p += 3; + q += 4; + } + } + + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + + void ImageProcessing::Set(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + SetInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + SetInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + SetInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftRight(ImageAccessor& image, + unsigned int shift) + { + if (image.GetWidth() == 0 || + image.GetHeight() == 0 || + shift == 0) + { + // Nothing to do + return; + } + + throw OrthancException(ErrorCode_NotImplemented); + } + + + void ImageProcessing::GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + { + uint8_t a, b; + GetMinMaxValueInternal<uint8_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_Grayscale16: + { + uint16_t a, b; + GetMinMaxValueInternal<uint16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + case PixelFormat_SignedGrayscale16: + { + int16_t a, b; + GetMinMaxValueInternal<int16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + + void ImageProcessing::AddConstant(ImageAccessor& image, + int64_t value) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + AddConstantInternal<uint8_t>(image, value); + return; + + case PixelFormat_Grayscale16: + AddConstantInternal<uint16_t>(image, value); + return; + + case PixelFormat_SignedGrayscale16: + AddConstantInternal<int16_t>(image, value); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::MultiplyConstant(ImageAccessor& image, + float factor) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + MultiplyConstantInternal<uint8_t>(image, factor); + return; + + case PixelFormat_Grayscale16: + MultiplyConstantInternal<uint16_t>(image, factor); + return; + + case PixelFormat_SignedGrayscale16: + MultiplyConstantInternal<int16_t>(image, factor); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ImageProcessing::ShiftScale(ImageAccessor& image, + float offset, + float scaling) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + ShiftScaleInternal<uint8_t>(image, offset, scaling); + return; + + case PixelFormat_Grayscale16: + ShiftScaleInternal<uint16_t>(image, offset, scaling); + return; + + case PixelFormat_SignedGrayscale16: + ShiftScaleInternal<int16_t>(image, offset, scaling); + return; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/ImageProcessing.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,70 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <stdint.h> + +namespace Orthanc +{ + class ImageProcessing + { + public: + static void Copy(ImageAccessor& target, + const ImageAccessor& source); + + static void Convert(ImageAccessor& target, + const ImageAccessor& source); + + static void Set(ImageAccessor& image, + int64_t value); + + static void ShiftRight(ImageAccessor& target, + unsigned int shift); + + static void GetMinMaxValue(int64_t& minValue, + int64_t& maxValue, + const ImageAccessor& image); + + static void AddConstant(ImageAccessor& image, + int64_t value); + + static void MultiplyConstant(ImageAccessor& image, + float factor); + + static void ShiftScale(ImageAccessor& image, + float offset, + float scaling); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegErrorManager.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,69 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "JpegErrorManager.h" + +namespace Orthanc +{ + namespace Internals + { + void JpegErrorManager::OutputMessage(j_common_ptr cinfo) + { + char message[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, message); + + JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err); + that->message = std::string(message); + } + + + void JpegErrorManager::ErrorExit(j_common_ptr cinfo) + { + (*cinfo->err->output_message) (cinfo); + + JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err); + longjmp(that->setjmp_buffer, 1); + } + + + JpegErrorManager::JpegErrorManager() + { + memset(&pub, 0, sizeof(struct jpeg_error_mgr)); + memset(&setjmp_buffer, 0, sizeof(jmp_buf)); + + jpeg_std_error(&pub); + pub.error_exit = ErrorExit; + pub.output_message = OutputMessage; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegErrorManager.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,74 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#pragma once + +#include <string.h> +#include <stdio.h> +#include <jpeglib.h> +#include <setjmp.h> +#include <string> + +namespace Orthanc +{ + namespace Internals + { + class JpegErrorManager + { + private: + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ + std::string message; + + static void OutputMessage(j_common_ptr cinfo); + + static void ErrorExit(j_common_ptr cinfo); + + public: + JpegErrorManager(); + + struct jpeg_error_mgr* GetPublic() + { + return &pub; + } + + jmp_buf& GetJumpBuffer() + { + return setjmp_buffer; + } + + const std::string& GetMessage() const + { + return message; + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegReader.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,184 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "JpegReader.h" + +#include "JpegErrorManager.h" +#include "../OrthancException.h" +#include "../Logging.h" + +namespace Orthanc +{ + static void Uncompress(struct jpeg_decompress_struct& cinfo, + std::string& content, + ImageAccessor& accessor) + { + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + PixelFormat format; + if (cinfo.output_components == 1 && + cinfo.out_color_space == JCS_GRAYSCALE) + { + format = PixelFormat_Grayscale8; + } + else if (cinfo.output_components == 3 && + cinfo.out_color_space == JCS_RGB) + { + format = PixelFormat_RGB24; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + unsigned int pitch = cinfo.output_width * cinfo.output_components; + + /* Make a one-row-high sample array that will go away when done with image */ + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1); + + try + { + content.resize(pitch * cinfo.output_height); + } + catch (...) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + accessor.AssignWritable(format, cinfo.output_width, cinfo.output_height, pitch, + content.empty() ? NULL : &content[0]); + + uint8_t* target = reinterpret_cast<uint8_t*>(&content[0]); + while (cinfo.output_scanline < cinfo.output_height) + { + jpeg_read_scanlines(&cinfo, buffer, 1); + memcpy(target, buffer[0], pitch); + target += pitch; + } + + // Everything went fine, "setjmp()" didn't get called + + jpeg_finish_decompress(&cinfo); + } + + + void JpegReader::ReadFromFile(const char* filename) + { + FILE* fp = fopen(filename, "rb"); + if (!fp) + { + throw OrthancException(ErrorCode_InexistentFile); + } + + struct jpeg_decompress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct)); + + Internals::JpegErrorManager jerr; + cinfo.err = jerr.GetPublic(); + + if (setjmp(jerr.GetJumpBuffer())) + { + jpeg_destroy_decompress(&cinfo); + fclose(fp); + LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + throw OrthancException(ErrorCode_InternalError); + } + + // Below this line, we are under the scope of a "setjmp" + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, fp); + + try + { + Uncompress(cinfo, content_, *this); + } + catch (OrthancException&) + { + jpeg_destroy_decompress(&cinfo); + fclose(fp); + throw; + } + + jpeg_destroy_decompress(&cinfo); + fclose(fp); + } + + + void JpegReader::ReadFromMemory(const void* buffer, + size_t size) + { + struct jpeg_decompress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct)); + + Internals::JpegErrorManager jerr; + cinfo.err = jerr.GetPublic(); + + if (setjmp(jerr.GetJumpBuffer())) + { + jpeg_destroy_decompress(&cinfo); + LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + throw OrthancException(ErrorCode_InternalError); + } + + // Below this line, we are under the scope of a "setjmp" + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(buffer)), size); + + try + { + Uncompress(cinfo, content_, *this); + } + catch (OrthancException&) + { + jpeg_destroy_decompress(&cinfo); + throw; + } + + jpeg_destroy_decompress(&cinfo); + } + + + void JpegReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.empty()) + { + ReadFromMemory(NULL, 0); + } + else + { + ReadFromMemory(buffer.c_str(), buffer.size()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegReader.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <string> + +namespace Orthanc +{ + class JpegReader : public ImageAccessor + { + private: + std::string content_; + + public: + void ReadFromFile(const char* filename); + + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegWriter.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,202 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "JpegWriter.h" + +#include "../OrthancException.h" +#include "../Logging.h" + +#include "JpegErrorManager.h" + +#include <vector> + +namespace Orthanc +{ + static void GetLines(std::vector<uint8_t*>& lines, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + if (format != PixelFormat_Grayscale8 && + format != PixelFormat_RGB24) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + lines.resize(height); + + uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)); + for (unsigned int y = 0; y < height; y++) + { + lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch); + } + } + + + static void Compress(struct jpeg_compress_struct& cinfo, + std::vector<uint8_t*>& lines, + unsigned int width, + unsigned int height, + PixelFormat format, + uint8_t quality) + { + cinfo.image_width = width; + cinfo.image_height = height; + + switch (format) + { + case PixelFormat_Grayscale8: + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + break; + + case PixelFormat_RGB24: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + jpeg_write_scanlines(&cinfo, &lines[0], height); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + } + + + void JpegWriter::SetQuality(uint8_t quality) + { + if (quality <= 0 || quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + quality_ = quality; + } + + + void JpegWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + FILE* fp = fopen(filename, "wb"); + if (fp == NULL) + { + throw OrthancException(ErrorCode_FullStorage); + } + + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + Internals::JpegErrorManager jerr; + cinfo.err = jerr.GetPublic(); + + if (setjmp(jerr.GetJumpBuffer())) + { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_compress(&cinfo); + fclose(fp); + LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + throw OrthancException(ErrorCode_InternalError); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, fp); + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + fclose(fp); + } + + + void JpegWriter::WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + std::vector<uint8_t*> lines; + GetLines(lines, height, pitch, format, buffer); + + struct jpeg_compress_struct cinfo; + memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); + + Internals::JpegErrorManager jerr; + + unsigned char* data = NULL; + unsigned long size; + + if (setjmp(jerr.GetJumpBuffer())) + { + jpeg_destroy_compress(&cinfo); + + if (data != NULL) + { + free(data); + } + + LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage(); + throw OrthancException(ErrorCode_InternalError); + } + + // Do not allocate data on the stack below this line! + + jpeg_create_compress(&cinfo); + cinfo.err = jerr.GetPublic(); + jpeg_mem_dest(&cinfo, &data, &size); + + Compress(cinfo, lines, width, height, format, quality_); + + // Everything went fine, "setjmp()" didn't get called + + jpeg.assign(reinterpret_cast<const char*>(data), size); + free(data); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/JpegWriter.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,87 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <string> +#include <stdint.h> + +namespace Orthanc +{ + class JpegWriter + { + private: + uint8_t quality_; + + public: + JpegWriter() : quality_(90) + { + } + + void SetQuality(uint8_t quality); + + uint8_t GetQuality() const + { + return quality_; + } + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& jpeg, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToFile(const char* filename, + const ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& jpeg, + const ImageAccessor& accessor) + { + WriteToMemory(jpeg, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PngReader.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,313 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "PngReader.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +#include <png.h> +#include <string.h> // For memcpy() + +namespace Orthanc +{ + namespace + { + struct FileRabi + { + FILE* fp_; + + FileRabi(const char* filename) + { + fp_ = fopen(filename, "rb"); + if (!fp_) + { + throw OrthancException(ErrorCode_InexistentFile); + } + } + + ~FileRabi() + { + if (fp_) + fclose(fp_); + } + }; + } + + + struct PngReader::PngRabi + { + png_structp png_; + png_infop info_; + png_infop endInfo_; + + void Destruct() + { + if (png_) + { + png_destroy_read_struct(&png_, &info_, &endInfo_); + + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + } + } + + PngRabi() + { + png_ = NULL; + info_ = NULL; + endInfo_ = NULL; + + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + info_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, NULL, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + endInfo_ = png_create_info_struct(png_); + if (!info_) + { + png_destroy_read_struct(&png_, &info_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + ~PngRabi() + { + Destruct(); + } + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size); + }; + + + void PngReader::CheckHeader(const void* header) + { + int is_png = !png_sig_cmp((png_bytep) header, 0, 8); + if (!is_png) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + PngReader::PngReader() + { + } + + void PngReader::Read(PngRabi& rabi) + { + png_set_sig_bytes(rabi.png_, 8); + + png_read_info(rabi.png_, rabi.info_); + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type, filter_method; + // get size and bit-depth of the PNG-image + png_get_IHDR(rabi.png_, rabi.info_, + &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_method); + + PixelFormat format; + unsigned int pitch; + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) + { + format = PixelFormat_Grayscale8; + pitch = width; + } + else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) + { + format = PixelFormat_Grayscale16; + pitch = 2 * width; + + if (Toolbox::DetectEndianness() == Endianness_Little) + { + png_set_swap(rabi.png_); + } + } + else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) + { + format = PixelFormat_RGB24; + pitch = 3 * width; + } + else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8) + { + format = PixelFormat_RGBA32; + pitch = 4 * width; + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + + data_.resize(height * pitch); + + if (height == 0 || width == 0) + { + // Empty image, we are done + AssignEmpty(format); + return; + } + + png_read_update_info(rabi.png_, rabi.info_); + + std::vector<png_bytep> rows(height); + for (size_t i = 0; i < height; i++) + { + rows[i] = &data_[0] + i * pitch; + } + + png_read_image(rabi.png_, &rows[0]); + + AssignWritable(format, width, height, pitch, &data_[0]); + } + + void PngReader::ReadFromFile(const char* filename) + { + FileRabi f(filename); + + char header[8]; + if (fread(header, 1, 8, f.fp_) != 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(header); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + png_init_io(rabi.png_, f.fp_); + + Read(rabi); + } + + + namespace + { + struct MemoryBuffer + { + const uint8_t* buffer_; + size_t size_; + size_t pos_; + bool ok_; + }; + } + + + void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) + { + MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr)); + + if (!from->ok_) + { + return; + } + + if (from->pos_ + byteCountToRead > from->size_) + { + from->ok_ = false; + return; + } + + memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); + + from->pos_ += byteCountToRead; + } + + + void PngReader::ReadFromMemory(const void* buffer, + size_t size) + { + if (size < 8) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + CheckHeader(buffer); + + PngRabi rabi; + + if (setjmp(png_jmpbuf(rabi.png_))) + { + rabi.Destruct(); + throw OrthancException(ErrorCode_BadFileFormat); + } + + MemoryBuffer tmp; + tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8; // We skip the header + tmp.size_ = size - 8; + tmp.pos_ = 0; + tmp.ok_ = true; + + png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); + + Read(rabi); + + if (!tmp.ok_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + void PngReader::ReadFromMemory(const std::string& buffer) + { + if (buffer.size() != 0) + { + ReadFromMemory(&buffer[0], buffer.size()); + } + else + { + ReadFromMemory(NULL, 0); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PngReader.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,71 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include "../Enumerations.h" + +#include <vector> +#include <stdint.h> +#include <boost/shared_ptr.hpp> + +namespace Orthanc +{ + class PngReader : public ImageAccessor + { + private: + struct PngRabi; + + std::vector<uint8_t> data_; + + void CheckHeader(const void* header); + + void Read(PngRabi& rabi); + + public: + PngReader(); + + void ReadFromFile(const char* filename); + + void ReadFromFile(const std::string& filename) + { + ReadFromFile(filename.c_str()); + } + + void ReadFromMemory(const void* buffer, + size_t size); + + void ReadFromMemory(const std::string& buffer); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PngWriter.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,269 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../PrecompiledHeaders.h" +#include "PngWriter.h" + +#include <vector> +#include <stdint.h> +#include <png.h> +#include "../OrthancException.h" +#include "../ChunkedBuffer.h" +#include "../Toolbox.h" + + +// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 +// http://zarb.org/~gc/html/libpng.html +/* + void write_row_callback(png_ptr, png_uint_32 row, int pass) + { + }*/ + + + + +/* bool isError_; + +// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 + +static void ErrorHandler(png_structp png, png_const_charp message) +{ +printf("** [%s]\n", message); + +PngWriter* that = (PngWriter*) png_get_error_ptr(png); +that->isError_ = true; +printf("** %d\n", (int)that); + +//((PngWriter*) payload)->isError_ = true; +} + +static void WarningHandler(png_structp png, png_const_charp message) +{ + printf("++ %d\n", (int)message); +}*/ + + +namespace Orthanc +{ + struct PngWriter::PImpl + { + png_structp png_; + png_infop info_; + + // Filled by Prepare() + std::vector<uint8_t*> rows_; + int bitDepth_; + int colorType_; + }; + + + + PngWriter::PngWriter() : pimpl_(new PImpl) + { + pimpl_->png_ = NULL; + pimpl_->info_ = NULL; + + pimpl_->png_ = png_create_write_struct + (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); + if (!pimpl_->png_) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + pimpl_->info_ = png_create_info_struct(pimpl_->png_); + if (!pimpl_->info_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + PngWriter::~PngWriter() + { + if (pimpl_->info_) + { + png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); + } + + if (pimpl_->png_) + { + png_destroy_write_struct(&pimpl_->png_, NULL); + } + } + + + + void PngWriter::Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + pimpl_->rows_.resize(height); + for (unsigned int y = 0; y < height; y++) + { + pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch; + } + + switch (format) + { + case PixelFormat_RGB24: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; + break; + + case PixelFormat_RGBA32: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA; + break; + + case PixelFormat_Grayscale8: + pimpl_->bitDepth_ = 8; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + pimpl_->bitDepth_ = 16; + pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void PngWriter::Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format) + { + png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, + pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(pimpl_->png_, pimpl_->info_); + + if (height > 0) + { + switch (format) + { + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + int transforms = 0; + if (Toolbox::DetectEndianness() == Endianness_Little) + { + transforms = PNG_TRANSFORM_SWAP_ENDIAN; + } + + png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); + png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); + + break; + } + + default: + png_write_image(pimpl_->png_, &pimpl_->rows_[0]); + } + } + + png_write_end(pimpl_->png_, NULL); + } + + + void PngWriter::WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + Prepare(width, height, pitch, format, buffer); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + png_init_io(pimpl_->png_, fp); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_CannotWriteFile); + } + + Compress(width, height, pitch, format); + + fclose(fp); + } + + + + + static void MemoryCallback(png_structp png_ptr, + png_bytep data, + png_size_t size) + { + ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr)); + buffer->AddChunk(reinterpret_cast<const char*>(data), size); + } + + + + void PngWriter::WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer) + { + ChunkedBuffer chunks; + + Prepare(width, height, pitch, format, buffer); + + if (setjmp(png_jmpbuf(pimpl_->png_))) + { + // Error during writing PNG + throw OrthancException(ErrorCode_InternalError); + } + + png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); + + Compress(width, height, pitch, format); + + chunks.Flatten(png); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Images/PngWriter.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ImageAccessor.h" + +#include <boost/shared_ptr.hpp> +#include <string> + +namespace Orthanc +{ + class PngWriter + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + void Compress(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format); + + void Prepare(unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + public: + PngWriter(); + + ~PngWriter(); + + void WriteToFile(const char* filename, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToMemory(std::string& png, + unsigned int width, + unsigned int height, + unsigned int pitch, + PixelFormat format, + const void* buffer); + + void WriteToFile(const char* filename, + const ImageAccessor& accessor) + { + WriteToFile(filename, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + + void WriteToMemory(std::string& png, + const ImageAccessor& accessor) + { + WriteToMemory(png, accessor.GetWidth(), accessor.GetHeight(), + accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Logging.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,421 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeaders.h" +#include "Logging.h" + +#if ORTHANC_ENABLE_LOGGING != 1 + +namespace Orthanc +{ + namespace Logging + { + void Initialize() + { + } + + void Finalize() + { + } + + void EnableInfoLevel(bool enabled) + { + } + + void EnableTraceLevel(bool enabled) + { + } + + void SetTargetFolder(const std::string& path) + { + } + } +} + +#elif ORTHANC_ENABLE_GOOGLE_LOG == 1 + +/********************************************************* + * Wrapper around Google Log + *********************************************************/ + +namespace Orthanc +{ + namespace Logging + { + void Initialize() + { + // Initialize Google's logging library. + FLAGS_logtostderr = true; + FLAGS_minloglevel = 1; // Do not print LOG(INFO) by default + FLAGS_v = 0; // Do not print trace-level VLOG(1) by default + + google::InitGoogleLogging("Orthanc"); + } + + void Finalize() + { + google::ShutdownGoogleLogging(); + } + + void EnableInfoLevel(bool enabled) + { + FLAGS_minloglevel = (enabled ? 0 : 1); + } + + void EnableTraceLevel(bool enabled) + { + if (enabled) + { + FLAGS_minloglevel = 0; + FLAGS_v = 1; + } + else + { + FLAGS_v = 0; + } + } + + void SetTargetFolder(const std::string& path) + { + FLAGS_logtostderr = false; + FLAGS_log_dir = path; + } + } +} + +#else + +/********************************************************* + * Use internal logger, not Google Log + *********************************************************/ + +#include "OrthancException.h" +#include "Enumerations.h" +#include "Toolbox.h" + +#include <fstream> +#include <boost/filesystem.hpp> +#include <boost/thread.hpp> + +#if BOOST_HAS_DATE_TIME == 1 +# include <boost/date_time/posix_time/posix_time.hpp> +#else +# error Boost::date_time is required +#endif + + +namespace +{ + struct LoggingState + { + bool infoEnabled_; + bool traceEnabled_; + + std::ostream* error_; + std::ostream* warning_; + std::ostream* info_; + + std::auto_ptr<std::ofstream> errorFile_; + std::auto_ptr<std::ofstream> warningFile_; + std::auto_ptr<std::ofstream> infoFile_; + + LoggingState() : + infoEnabled_(false), + traceEnabled_(false), + error_(&std::cerr), + warning_(&std::cerr), + info_(&std::cerr) + { + } + }; +} + + + +static std::auto_ptr<LoggingState> loggingState_; +static boost::mutex loggingMutex_; + + + +namespace Orthanc +{ + namespace Logging + { + static void GetLogPath(boost::filesystem::path& log, + boost::filesystem::path& link, + const char* level, + const std::string& directory) + { + /** + From Google Log documentation: + + Unless otherwise specified, logs will be written to the filename + "<program name>.<hostname>.<user name>.log.<severity level>.", + followed by the date, time, and pid (you can't prevent the date, + time, and pid from being in the filename). + + In this implementation : "hostname" and "username" are not used + **/ + + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + boost::filesystem::path root(directory); + boost::filesystem::path exe(Toolbox::GetPathToExecutable()); + + if (!boost::filesystem::exists(root) || + !boost::filesystem::is_directory(root)) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + char date[64]; + sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d", + static_cast<int>(now.date().year()), + now.date().month().as_number(), + now.date().day().as_number(), + now.time_of_day().hours(), + now.time_of_day().minutes(), + now.time_of_day().seconds(), + Toolbox::GetProcessId()); + + std::string programName = exe.filename().replace_extension("").string(); + + log = (root / (programName + ".log." + + std::string(level) + "." + + std::string(date))); + + link = (root / (programName + "." + std::string(level))); + } + + + static void PrepareLogFile(std::ostream*& stream, + std::auto_ptr<std::ofstream>& file, + const char* level, + const std::string& directory) + { + boost::filesystem::path log, link; + GetLogPath(log, link, level, directory); + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) + boost::filesystem::remove(link); + boost::filesystem::create_symlink(log.filename(), link); +#endif + + file.reset(new std::ofstream(log.string().c_str())); + stream = file.get(); + } + + + void Initialize() + { + boost::mutex::scoped_lock lock(loggingMutex_); + loggingState_.reset(new LoggingState); + } + + void Finalize() + { + boost::mutex::scoped_lock lock(loggingMutex_); + loggingState_.reset(NULL); + } + + void EnableInfoLevel(bool enabled) + { + boost::mutex::scoped_lock lock(loggingMutex_); + assert(loggingState_.get() != NULL); + + loggingState_->infoEnabled_ = enabled; + } + + void EnableTraceLevel(bool enabled) + { + boost::mutex::scoped_lock lock(loggingMutex_); + assert(loggingState_.get() != NULL); + + loggingState_->traceEnabled_ = enabled; + + if (enabled) + { + // Also enable the "INFO" level when trace-level debugging is enabled + loggingState_->infoEnabled_ = true; + } + } + + void SetTargetFolder(const std::string& path) + { + boost::mutex::scoped_lock lock(loggingMutex_); + assert(loggingState_.get() != NULL); + + PrepareLogFile(loggingState_->error_, loggingState_->errorFile_, "ERROR", path); + PrepareLogFile(loggingState_->warning_, loggingState_->warningFile_, "WARNING", path); + PrepareLogFile(loggingState_->info_, loggingState_->infoFile_, "INFO", path); + } + + InternalLogger::InternalLogger(const char* level, + const char* file, + int line) : + lock_(loggingMutex_), + stream_(&null_) // By default, logging to "/dev/null" is simulated + { + if (loggingState_.get() == NULL) + { + fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); + return; + } + + LogLevel l = StringToLogLevel(level); + + if ((l == LogLevel_Info && !loggingState_->infoEnabled_) || + (l == LogLevel_Trace && !loggingState_->traceEnabled_)) + { + // This logging level is disabled, directly exit and unlock + // the mutex to speed-up things. The stream is set to "/dev/null" + lock_.unlock(); + return; + } + + // Compute the header of the line, temporary release the lock as + // this is a time-consuming operation + lock_.unlock(); + std::string header; + + { + boost::filesystem::path path(file); + boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration duration = now.time_of_day(); + + /** + From Google Log documentation: + + "Log lines have this form: + + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... + + where the fields are defined as follows: + + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) + file The file name + line The line number + msg The user-supplied message" + + In this implementation, "threadid" is not printed. + **/ + + char date[32]; + sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", + level[0], + now.date().month().as_number(), + now.date().day().as_number(), + duration.hours(), + duration.minutes(), + duration.seconds(), + static_cast<int>(duration.fractional_seconds())); + + header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] "; + } + + + // The header is computed, we now re-lock the mutex to access + // the stream objects. Pay attention that "loggingState_", + // "infoEnabled_" or "traceEnabled_" might have changed while + // the mutex was unlocked. + lock_.lock(); + + if (loggingState_.get() == NULL) + { + fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); + return; + } + + switch (l) + { + case LogLevel_Error: + stream_ = loggingState_->error_; + break; + + case LogLevel_Warning: + stream_ = loggingState_->warning_; + break; + + case LogLevel_Info: + if (loggingState_->infoEnabled_) + { + stream_ = loggingState_->info_; + } + + break; + + case LogLevel_Trace: + if (loggingState_->traceEnabled_) + { + stream_ = loggingState_->info_; + } + + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (stream_ == &null_) + { + // The logging is disabled for this level. The stream is the + // "null_" member of this object, so we can release the global + // mutex. + lock_.unlock(); + } + + (*stream_) << header; + } + + + InternalLogger::~InternalLogger() + { + if (stream_ != &null_) + { +#if defined(_WIN32) + *stream_ << "\r\n"; +#else + *stream_ << "\n"; +#endif + + stream_->flush(); + } + } + + + } +} + +#endif // ORTHANC_ENABLE_LOGGING
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/Logging.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,112 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <iostream> + +namespace Orthanc +{ + namespace Logging + { + void Initialize(); + + void Finalize(); + + void EnableInfoLevel(bool enabled); + + void EnableTraceLevel(bool enabled); + + void SetTargetFolder(const std::string& path); + + struct NullStream : public std::ostream + { + NullStream() : + std::ios(0), + std::ostream(0) + { + } + + std::ostream& operator<< (const std::string& message) + { + return *this; + } + }; + } +} + + +#if ORTHANC_ENABLE_LOGGING != 1 + +# define LOG(level) ::Orthanc::Logging::NullStream() +# define VLOG(level) ::Orthanc::Logging::NullStream() + +#else /* ORTHANC_ENABLE_LOGGING == 1 */ + +#if ORTHANC_ENABLE_GOOGLE_LOG == 1 +# include <stdlib.h> // Including this fixes a problem in glog for recent releases of MinGW +# include <glog/logging.h> +#else +# include <boost/thread/mutex.hpp> +# define LOG(level) ::Orthanc::Logging::InternalLogger(#level, __FILE__, __LINE__) +# define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__) +#endif + +#if ORTHANC_ENABLE_GOOGLE_LOG != 1 +namespace Orthanc +{ + namespace Logging + { + class InternalLogger + { + private: + boost::mutex::scoped_lock lock_; + NullStream null_; + std::ostream* stream_; + + public: + InternalLogger(const char* level, + const char* file, + int line); + + ~InternalLogger(); + + std::ostream& operator<< (const std::string& message) + { + return (*stream_) << message; + } + }; + } +} +#endif + +#endif // ORTHANC_ENABLE_LOGGING
--- a/Core/Lua/LuaContext.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Lua/LuaContext.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,8 +33,12 @@ #include "../PrecompiledHeaders.h" #include "LuaContext.h" -#include <glog/logging.h> +#include "../Logging.h" +#include "../OrthancException.h" + +#include <set> #include <cassert> +#include <boost/lexical_cast.hpp> extern "C" { @@ -44,16 +48,26 @@ namespace Orthanc { + static bool OnlyContainsDigits(const std::string& s) + { + for (size_t i = 0; i < s.size(); i++) + { + if (!isdigit(s[i])) + { + return false; + } + } + + return true; + } + + LuaContext& LuaContext::GetLuaContext(lua_State *state) { - // Get the pointer to the "LuaContext" underlying object - lua_getglobal(state, "_LuaContext"); - assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA); - LuaContext* that = const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(lua_topointer(state, -1))); - assert(that != NULL); - lua_pop(state, 1); + const void* value = GetGlobalVariable(state, "_LuaContext"); + assert(value != NULL); - return *that; + return *const_cast<LuaContext*>(reinterpret_cast<const LuaContext*>(value)); } int LuaContext::PrintToLog(lua_State *state) @@ -94,6 +108,64 @@ } + int LuaContext::ParseJson(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + + int nArgs = lua_gettop(state); + if (nArgs != 1 || + !lua_isstring(state, 1)) // Password + { + lua_pushnil(state); + return 1; + } + + const char* str = lua_tostring(state, 1); + + Json::Value value; + Json::Reader reader; + if (reader.parse(str, str + strlen(str), value)) + { + that.PushJson(value); + } + else + { + lua_pushnil(state); + } + + return 1; + } + + + int LuaContext::DumpJson(lua_State *state) + { + LuaContext& that = GetLuaContext(state); + + int nArgs = lua_gettop(state); + if ((nArgs != 1 && nArgs != 2) || + (nArgs == 2 && !lua_isboolean(state, 2))) + { + lua_pushnil(state); + return 1; + } + + bool keepStrings = false; + if (nArgs == 2) + { + keepStrings = lua_toboolean(state, 2) ? true : false; + } + + Json::Value json; + that.GetJson(json, 1, keepStrings); + + Json::FastWriter writer; + std::string s = writer.write(json); + lua_pushlstring(state, s.c_str(), s.size()); + + return 1; + } + + int LuaContext::SetHttpCredentials(lua_State *state) { LuaContext& that = GetLuaContext(state); @@ -126,13 +198,13 @@ { httpClient_.Apply(str); } - catch (OrthancException& e) + catch (OrthancException&) { return false; } // Return the result of the HTTP request - lua_pushstring(state, str.c_str()); + lua_pushlstring(state, str.c_str(), str.size()); return true; } @@ -147,7 +219,7 @@ if (nArgs != 1 || !lua_isstring(state, 1)) // URL { LOG(ERROR) << "Lua: Bad parameters to HttpGet()"; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); return 1; } @@ -160,7 +232,7 @@ if (!that.AnswerHttpQuery(state)) { LOG(ERROR) << "Lua: Error in HttpGet() for URL " << url; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); } return 1; @@ -179,7 +251,7 @@ (nArgs >= 2 && !lua_isstring(state, 2))) // Body data { LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()"; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); return 1; } @@ -190,18 +262,18 @@ if (nArgs >= 2) { - that.httpClient_.SetPostData(lua_tostring(state, 2)); + that.httpClient_.SetBody(lua_tostring(state, 2)); } else { - that.httpClient_.AccessPostData().clear(); + that.httpClient_.GetBody().clear(); } // Do the HTTP POST/PUT request if (!that.AnswerHttpQuery(state)) { LOG(ERROR) << "Lua: Error in HttpPost() or HttpPut() for URL " << url; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); } return 1; @@ -229,7 +301,7 @@ if (nArgs != 1 || !lua_isstring(state, 1)) // URL { LOG(ERROR) << "Lua: Bad parameters to HttpDelete()"; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); return 1; } @@ -243,7 +315,7 @@ if (!that.httpClient_.Apply(s)) { LOG(ERROR) << "Lua: Error in HttpDelete() for URL " << url; - lua_pushstring(state, "ERROR"); + lua_pushnil(state); } else { @@ -258,7 +330,8 @@ { if (value.isString()) { - lua_pushstring(lua_, value.asCString()); + const std::string s = value.asString(); + lua_pushlstring(lua_, s.c_str(), s.size()); } else if (value.isDouble()) { @@ -307,7 +380,7 @@ it = members.begin(); it != members.end(); ++it) { // Push the index of the cell - lua_pushstring(lua_, it->c_str()); + lua_pushlstring(lua_, it->c_str(), it->size()); // Push the value of the cell PushJson(value[*it]); @@ -318,7 +391,116 @@ } else { - throw LuaException("Unsupported JSON conversion"); + throw OrthancException(ErrorCode_JsonToLuaTable); + } + } + + + void LuaContext::GetJson(Json::Value& result, + int top, + bool keepStrings) + { + if (lua_istable(lua_, top)) + { + Json::Value tmp = Json::objectValue; + bool isArray = true; + size_t size = 0; + + // Code adapted from: http://stackoverflow.com/a/6142700/881731 + + // Push another reference to the table on top of the stack (so we know + // where it is, and this function can work for negative, positive and + // pseudo indices + lua_pushvalue(lua_, top); + // stack now contains: -1 => table + lua_pushnil(lua_); + // stack now contains: -1 => nil; -2 => table + while (lua_next(lua_, -2)) + { + // stack now contains: -1 => value; -2 => key; -3 => table + // copy the key so that lua_tostring does not modify the original + lua_pushvalue(lua_, -2); + // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table + std::string key(lua_tostring(lua_, -1)); + Json::Value v; + GetJson(v, -2, keepStrings); + + tmp[key] = v; + + size += 1; + try + { + if (!OnlyContainsDigits(key) || + boost::lexical_cast<size_t>(key) != size) + { + isArray = false; + } + } + catch (boost::bad_lexical_cast&) + { + isArray = false; + } + + // pop value + copy of key, leaving original key + lua_pop(lua_, 2); + // stack now contains: -1 => key; -2 => table + } + // stack now contains: -1 => table (when lua_next returns 0 it pops the key + // but does not push anything.) + // Pop table + lua_pop(lua_, 1); + + // Stack is now the same as it was on entry to this function + + if (isArray) + { + result = Json::arrayValue; + for (size_t i = 0; i < size; i++) + { + result.append(tmp[boost::lexical_cast<std::string>(i + 1)]); + } + } + else + { + result = tmp; + } + } + else if (lua_isnil(lua_, top)) + { + result = Json::nullValue; + } + else if (!keepStrings && + lua_isboolean(lua_, top)) + { + result = lua_toboolean(lua_, top) ? true : false; + } + else if (!keepStrings && + lua_isnumber(lua_, top)) + { + // Convert to "int" if truncation does not loose precision + double value = static_cast<double>(lua_tonumber(lua_, top)); + int truncated = static_cast<int>(value); + + if (std::abs(value - static_cast<double>(truncated)) <= + std::numeric_limits<double>::epsilon()) + { + result = truncated; + } + else + { + result = value; + } + } + else if (lua_isstring(lua_, top)) + { + // Caution: The "lua_isstring()" case must be the last, since + // Lua can convert most types to strings by default. + result = std::string(lua_tostring(lua_, top)); + } + else + { + LOG(WARNING) << "Unsupported Lua type when returning Json"; + result = Json::nullValue; } } @@ -328,19 +510,20 @@ lua_ = luaL_newstate(); if (!lua_) { - throw LuaException("Unable to create the Lua context"); + throw OrthancException(ErrorCode_CannotCreateLua); } luaL_openlibs(lua_); lua_register(lua_, "print", PrintToLog); + lua_register(lua_, "ParseJson", ParseJson); + lua_register(lua_, "DumpJson", DumpJson); lua_register(lua_, "HttpGet", CallHttpGet); lua_register(lua_, "HttpPost", CallHttpPost); lua_register(lua_, "HttpPut", CallHttpPut); lua_register(lua_, "HttpDelete", CallHttpDelete); lua_register(lua_, "SetHttpCredentials", SetHttpCredentials); - - lua_pushlightuserdata(lua_, this); - lua_setglobal(lua_, "_LuaContext"); + + SetGlobalVariable("_LuaContext", this); } @@ -364,7 +547,7 @@ std::string description(lua_tostring(lua_, -1)); lua_pop(lua_, 1); /* pop error message from the stack */ LOG(ERROR) << "Error while executing Lua script: " << description; - throw LuaException(description); + throw OrthancException(ErrorCode_CannotExecuteLua); } if (output != NULL) @@ -399,8 +582,33 @@ Json::Reader reader; if (!reader.parse(s, output)) { - throw OrthancException(ErrorCode_BadFileFormat); + throw OrthancException(ErrorCode_BadJson); } } + + void LuaContext::RegisterFunction(const char* name, + lua_CFunction func) + { + lua_register(lua_, name, func); + } + + + void LuaContext::SetGlobalVariable(const char* name, + void* value) + { + lua_pushlightuserdata(lua_, value); + lua_setglobal(lua_, name); + } + + + const void* LuaContext::GetGlobalVariable(lua_State* state, + const char* name) + { + lua_getglobal(state, name); + assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA); + const void* value = lua_topointer(state, -1); + lua_pop(state, 1); + return value; + } }
--- a/Core/Lua/LuaContext.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Lua/LuaContext.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,7 +32,6 @@ #pragma once -#include "LuaException.h" #include "../HttpClient.h" extern "C" @@ -54,9 +53,9 @@ std::string log_; HttpClient httpClient_; - static LuaContext& GetLuaContext(lua_State *state); - static int PrintToLog(lua_State *state); + static int ParseJson(lua_State *state); + static int DumpJson(lua_State *state); static int SetHttpCredentials(lua_State *state); @@ -72,7 +71,9 @@ void ExecuteInternal(std::string* output, const std::string& command); - void PushJson(const Json::Value& value); + void GetJson(Json::Value& result, + int top, + bool keepStrings); public: LuaContext(); @@ -107,5 +108,18 @@ { httpClient_.SetProxy(proxy); } + + void RegisterFunction(const char* name, + lua_CFunction func); + + void SetGlobalVariable(const char* name, + void* value); + + static LuaContext& GetLuaContext(lua_State *state); + + static const void* GetGlobalVariable(lua_State* state, + const char* name); + + void PushJson(const Json::Value& value); }; }
--- a/Core/Lua/LuaException.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../OrthancException.h" - -namespace Orthanc -{ - class LuaException : public OrthancException - { - public: - LuaException(const char* explanation) : - OrthancException(explanation) - { - } - - LuaException(const std::string& explanation) : - OrthancException(explanation) - { - } - }; -}
--- a/Core/Lua/LuaFunctionCall.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Lua/LuaFunctionCall.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,10 +33,12 @@ #include "../PrecompiledHeaders.h" #include "LuaFunctionCall.h" +#include "../OrthancException.h" +#include "../Logging.h" + #include <cassert> #include <stdio.h> #include <boost/lexical_cast.hpp> -#include <glog/logging.h> namespace Orthanc { @@ -44,7 +46,7 @@ { if (isExecuted_) { - throw LuaException("Arguments cannot be pushed after the function is executed"); + throw OrthancException(ErrorCode_LuaAlreadyExecuted); } } @@ -61,7 +63,7 @@ void LuaFunctionCall::PushString(const std::string& value) { CheckAlreadyExecuted(); - lua_pushstring(context_.lua_, value.c_str()); + lua_pushlstring(context_.lua_, value.c_str(), value.size()); } void LuaFunctionCall::PushBoolean(bool value) @@ -102,12 +104,14 @@ std::string description(lua_tostring(context_.lua_, -1)); lua_pop(context_.lua_, 1); /* pop error message from the stack */ - throw LuaException(description); + LOG(ERROR) << description; + + throw OrthancException(ErrorCode_CannotExecuteLua); } if (lua_gettop(context_.lua_) < numOutputs) { - throw LuaException("The function does not give the expected number of outputs"); + throw OrthancException(ErrorCode_LuaBadOutput); } isExecuted_ = true; @@ -119,104 +123,33 @@ if (!lua_isboolean(context_.lua_, 1)) { - throw LuaException("The function is not a predicate (only true/false outputs allowed)"); + throw OrthancException(ErrorCode_NotLuaPredicate); } return lua_toboolean(context_.lua_, 1) != 0; } - static void PopJson(Json::Value& result, - lua_State* lua, - int top) + void LuaFunctionCall::ExecuteToJson(Json::Value& result, + bool keepStrings) { - if (lua_istable(lua, top)) - { - Json::Value tmp = Json::objectValue; - bool isArray = true; - size_t size = 0; + ExecuteInternal(1); + context_.GetJson(result, lua_gettop(context_.lua_), keepStrings); + } - // http://stackoverflow.com/a/6142700/881731 - - // Push another reference to the table on top of the stack (so we know - // where it is, and this function can work for negative, positive and - // pseudo indices - lua_pushvalue(lua, top); - // stack now contains: -1 => table - lua_pushnil(lua); - // stack now contains: -1 => nil; -2 => table - while (lua_next(lua, -2)) - { - // stack now contains: -1 => value; -2 => key; -3 => table - // copy the key so that lua_tostring does not modify the original - lua_pushvalue(lua, -2); - // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table - std::string key(lua_tostring(lua, -1)); - Json::Value v; - PopJson(v, lua, -2); - - tmp[key] = v; - size += 1; - try - { - if (boost::lexical_cast<size_t>(key) != size) - { - isArray = false; - } - } - catch (boost::bad_lexical_cast&) - { - isArray = false; - } - - // pop value + copy of key, leaving original key - lua_pop(lua, 2); - // stack now contains: -1 => key; -2 => table - } - // stack now contains: -1 => table (when lua_next returns 0 it pops the key - // but does not push anything.) - // Pop table - lua_pop(lua, 1); - - // Stack is now the same as it was on entry to this function - - if (isArray) - { - result = Json::arrayValue; - for (size_t i = 0; i < size; i++) - { - result.append(tmp[boost::lexical_cast<std::string>(i + 1)]); - } - } - else - { - result = tmp; - } - } - else if (lua_isnumber(lua, top)) + void LuaFunctionCall::ExecuteToString(std::string& result) + { + ExecuteInternal(1); + + int top = lua_gettop(context_.lua_); + if (lua_isstring(context_.lua_, top)) { - result = static_cast<float>(lua_tonumber(lua, top)); - } - else if (lua_isstring(lua, top)) - { - result = std::string(lua_tostring(lua, top)); - } - else if (lua_isboolean(lua, top)) - { - result = static_cast<bool>(lua_toboolean(lua, top)); + result = lua_tostring(context_.lua_, top); } else { - LOG(WARNING) << "Unsupported Lua type when returning Json"; - result = Json::nullValue; + throw OrthancException(ErrorCode_LuaReturnsNoString); } } - - - void LuaFunctionCall::ExecuteToJson(Json::Value& result) - { - ExecuteInternal(1); - PopJson(result, context_.lua_, lua_gettop(context_.lua_)); - } }
--- a/Core/Lua/LuaFunctionCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Lua/LuaFunctionCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -69,6 +69,9 @@ bool ExecutePredicate(); - void ExecuteToJson(Json::Value& result); + void ExecuteToJson(Json::Value& result, + bool keepStrings); + + void ExecuteToString(std::string& result); }; }
--- a/Core/MultiThreading/ArrayFilledByThreads.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "ArrayFilledByThreads.h" - -#include "../MultiThreading/ThreadedCommandProcessor.h" -#include "../OrthancException.h" - -namespace Orthanc -{ - class ArrayFilledByThreads::Command : public ICommand - { - private: - ArrayFilledByThreads& that_; - size_t index_; - - public: - Command(ArrayFilledByThreads& that, - size_t index) : - that_(that), - index_(index) - { - } - - virtual bool Execute() - { - std::auto_ptr<IDynamicObject> obj(that_.filler_.GetFillerItem(index_)); - if (obj.get() == NULL) - { - return false; - } - else - { - boost::mutex::scoped_lock lock(that_.mutex_); - that_.array_[index_] = obj.release(); - return true; - } - } - }; - - void ArrayFilledByThreads::Clear() - { - for (size_t i = 0; i < array_.size(); i++) - { - if (array_[i]) - delete array_[i]; - } - - array_.clear(); - filled_ = false; - } - - void ArrayFilledByThreads::Update() - { - if (!filled_) - { - array_.resize(filler_.GetFillerSize()); - - Orthanc::ThreadedCommandProcessor processor(threadCount_); - for (size_t i = 0; i < array_.size(); i++) - { - processor.Post(new Command(*this, i)); - } - - processor.Join(); - filled_ = true; - } - } - - - ArrayFilledByThreads::ArrayFilledByThreads(IFiller& filler) : filler_(filler) - { - filled_ = false; - threadCount_ = 4; - } - - - ArrayFilledByThreads::~ArrayFilledByThreads() - { - Clear(); - } - - - void ArrayFilledByThreads::Reload() - { - Clear(); - Update(); - } - - - void ArrayFilledByThreads::Invalidate() - { - Clear(); - } - - - void ArrayFilledByThreads::SetThreadCount(unsigned int t) - { - if (t < 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - threadCount_ = t; - } - - - size_t ArrayFilledByThreads::GetSize() - { - Update(); - return array_.size(); - } - - - IDynamicObject& ArrayFilledByThreads::GetItem(size_t index) - { - if (index >= GetSize()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - return *array_[index]; - } -}
--- a/Core/MultiThreading/ArrayFilledByThreads.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -#pragma once - -#include <boost/thread.hpp> - -#include "../IDynamicObject.h" - -namespace Orthanc -{ - class ArrayFilledByThreads - { - public: - class IFiller - { - public: - virtual size_t GetFillerSize() = 0; - - virtual IDynamicObject* GetFillerItem(size_t index) = 0; - }; - - private: - IFiller& filler_; - boost::mutex mutex_; - std::vector<IDynamicObject*> array_; - bool filled_; - unsigned int threadCount_; - - class Command; - - void Clear(); - - void Update(); - - public: - ArrayFilledByThreads(IFiller& filler); - - ~ArrayFilledByThreads(); - - void Reload(); - - void Invalidate(); - - void SetThreadCount(unsigned int t); - - unsigned int GetThreadCount() const - { - return threadCount_; - } - - size_t GetSize(); - - IDynamicObject& GetItem(size_t index); - }; -} -
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,6 +33,8 @@ #include "../PrecompiledHeaders.h" #include "BagOfRunnablesBySteps.h" +#include "../Logging.h" + #include <stack> #include <boost/thread.hpp> @@ -128,15 +130,10 @@ BagOfRunnablesBySteps::~BagOfRunnablesBySteps() { - StopAll(); - - // Stop the finish listener - pimpl_->stopFinishListener_ = true; - pimpl_->oneThreadIsStopped_.notify_one(); // Awakens the listener - - if (pimpl_->finishListener_->joinable()) + if (!pimpl_->stopFinishListener_) { - pimpl_->finishListener_->join(); + LOG(ERROR) << "INTERNAL ERROR: BagOfRunnablesBySteps::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Finalize(); } } @@ -165,4 +162,24 @@ pimpl_->continue_ = true; } + + + + void BagOfRunnablesBySteps::Finalize() + { + if (!pimpl_->stopFinishListener_) + { + StopAll(); + + // Stop the finish listener + pimpl_->stopFinishListener_ = true; + pimpl_->oneThreadIsStopped_.notify_one(); // Awakens the listener + + if (pimpl_->finishListener_->joinable()) + { + pimpl_->finishListener_->join(); + } + } + } + }
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/MultiThreading/BagOfRunnablesBySteps.h Wed Sep 30 13:23:31 2015 +0200 @@ -58,5 +58,7 @@ void Add(IRunnableBySteps* runnable); void StopAll(); + + void Finalize(); }; }
--- a/Core/MultiThreading/Mutex.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/MultiThreading/Mutex.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -37,7 +37,7 @@ #if defined(_WIN32) #include <windows.h> -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) #include <pthread.h> #else #error Support your platform here @@ -75,7 +75,7 @@ } -#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) struct Mutex::PImpl {
--- a/Core/MultiThreading/Semaphore.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/MultiThreading/Semaphore.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "Semaphore.h" #include "../OrthancException.h"
--- a/Core/MultiThreading/ThreadedCommandProcessor.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../PrecompiledHeaders.h" -#include "ThreadedCommandProcessor.h" - -#include "../OrthancException.h" - -namespace Orthanc -{ - static const int32_t TIMEOUT = 10; - - - void ThreadedCommandProcessor::Processor(ThreadedCommandProcessor* that) - { - while (!that->done_) - { - std::auto_ptr<IDynamicObject> command(that->queue_.Dequeue(TIMEOUT)); - - if (command.get() != NULL) - { - bool success = false; - - try - { - if (that->success_) - { - // No command has failed so far - - if (that->cancel_) - { - // The commands have been canceled. Skip the execution - // of this command, yet mark it as succeeded. - success = true; - } - else - { - success = dynamic_cast<ICommand&>(*command).Execute(); - } - } - else - { - // A command has already failed. Skip the execution of this command. - } - } - catch (OrthancException) - { - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - assert(that->remainingCommands_ > 0); - that->remainingCommands_--; - - if (!success) - { - if (!that->cancel_ && that->listener_ && that->success_) - { - // This is the first command that fails - that->listener_->SignalFailure(); - } - - that->success_ = false; - } - else - { - if (!that->cancel_ && that->listener_) - { - if (that->remainingCommands_ == 0) - { - that->listener_->SignalSuccess(that->totalCommands_); - } - else - { - that->listener_->SignalProgress(that->totalCommands_ - that->remainingCommands_, - that->totalCommands_); - } - } - } - - that->processedCommand_.notify_all(); - } - } - } - } - - - ThreadedCommandProcessor::ThreadedCommandProcessor(unsigned int numThreads) - { - if (numThreads < 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - listener_ = NULL; - success_ = true; - done_ = false; - cancel_ = false; - threads_.resize(numThreads); - remainingCommands_ = 0; - totalCommands_ = 0; - - for (unsigned int i = 0; i < numThreads; i++) - { - threads_[i] = new boost::thread(Processor, this); - } - } - - - ThreadedCommandProcessor::~ThreadedCommandProcessor() - { - done_ = true; - - for (unsigned int i = 0; i < threads_.size(); i++) - { - boost::thread* t = threads_[i]; - - if (t != NULL) - { - if (t->joinable()) - { - t->join(); - } - - delete t; - } - } - } - - - void ThreadedCommandProcessor::Post(ICommand* command) - { - if (command == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - boost::mutex::scoped_lock lock(mutex_); - queue_.Enqueue(command); - remainingCommands_++; - totalCommands_++; - } - - - bool ThreadedCommandProcessor::Join() - { - boost::mutex::scoped_lock lock(mutex_); - - while (!remainingCommands_ == 0) - { - processedCommand_.wait(lock); - } - - if (cancel_ && listener_) - { - listener_->SignalCancel(); - } - - // Reset the sequence counters for subsequent commands - bool hasSucceeded = success_; - success_ = true; - totalCommands_ = 0; - cancel_ = false; - - return hasSucceeded; - } - - - void ThreadedCommandProcessor::Cancel() - { - boost::mutex::scoped_lock lock(mutex_); - - cancel_ = true; - } - - - void ThreadedCommandProcessor::SetListener(IListener& listener) - { - boost::mutex::scoped_lock lock(mutex_); - listener_ = &listener; - } -}
--- a/Core/MultiThreading/ThreadedCommandProcessor.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../ICommand.h" - -#include "SharedMessageQueue.h" - -namespace Orthanc -{ - class ThreadedCommandProcessor - { - public: - class IListener - { - public: - virtual ~IListener() - { - } - - virtual void SignalProgress(unsigned int current, - unsigned int total) = 0; - - virtual void SignalSuccess(unsigned int total) = 0; - - virtual void SignalFailure() = 0; - - virtual void SignalCancel() = 0; - }; - - private: - SharedMessageQueue queue_; - bool done_; - bool cancel_; - std::vector<boost::thread*> threads_; - IListener* listener_; - - boost::mutex mutex_; - bool success_; - unsigned int remainingCommands_, totalCommands_; - boost::condition_variable processedCommand_; - - static void Processor(ThreadedCommandProcessor* that); - - public: - ThreadedCommandProcessor(unsigned int numThreads); - - ~ThreadedCommandProcessor(); - - // This takes the ownership of the command - void Post(ICommand* command); - - bool Join(); - - void Cancel(); - - void SetListener(IListener& listener); - - IListener& GetListener() const - { - return *listener_; - } - }; -}
--- a/Core/OrthancException.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "PrecompiledHeaders.h" -#include "OrthancException.h" - -namespace Orthanc -{ - const char* OrthancException::What() const - { - if (error_ == ErrorCode_Custom) - { - return custom_.c_str(); - } - else - { - return GetDescription(error_); - } - } - - - const char* OrthancException::GetDescription(ErrorCode error) - { - switch (error) - { - case ErrorCode_Success: - return "Success"; - - case ErrorCode_ParameterOutOfRange: - return "Parameter out of range"; - - case ErrorCode_NotImplemented: - return "Not implemented yet"; - - case ErrorCode_InternalError: - return "Internal error"; - - case ErrorCode_NotEnoughMemory: - return "Not enough memory"; - - case ErrorCode_UriSyntax: - return "Badly formatted URI"; - - case ErrorCode_BadParameterType: - return "Bad type for a parameter"; - - case ErrorCode_InexistentFile: - return "Inexistent file"; - - case ErrorCode_BadFileFormat: - return "Bad file format"; - - case ErrorCode_CannotWriteFile: - return "Cannot write to file"; - - case ErrorCode_Timeout: - return "Timeout"; - - case ErrorCode_UnknownResource: - return "Unknown resource"; - - case ErrorCode_BadSequenceOfCalls: - return "Bad sequence of calls"; - - case ErrorCode_IncompatibleDatabaseVersion: - return "Incompatible version of the database"; - - case ErrorCode_FullStorage: - return "The file storage is full"; - - case ErrorCode_InexistentItem: - return "Accessing an inexistent item"; - - case ErrorCode_BadRequest: - return "Bad request"; - - 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_ReadOnly: - return "Cannot modify a read-only data structure"; - - case ErrorCode_IncompatibleImageSize: - return "Incompatible size of the images"; - - case ErrorCode_IncompatibleImageFormat: - return "Incompatible format of the images"; - - case ErrorCode_SharedLibrary: - return "Error while using a shared library (plugin)"; - - case ErrorCode_SystemCommand: - return "Error while calling a system command"; - - case ErrorCode_Plugin: - return "Error encountered inside a plugin"; - - case ErrorCode_Database: - return "Error with the database engine"; - - case ErrorCode_Custom: - default: - return "???"; - } - } -}
--- a/Core/OrthancException.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/OrthancException.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,6 +32,7 @@ #pragma once +#include <stdint.h> #include <string> #include "Enumerations.h" @@ -40,33 +41,36 @@ class OrthancException { protected: - ErrorCode error_; - std::string custom_; + ErrorCode errorCode_; + HttpStatus httpStatus_; public: - static const char* GetDescription(ErrorCode error); - - OrthancException(const char* custom) : - error_(ErrorCode_Custom), - custom_(custom) + OrthancException(ErrorCode errorCode) : + errorCode_(errorCode), + httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)) { } - OrthancException(const std::string& custom) : - error_(ErrorCode_Custom), - custom_(custom) - { - } - - OrthancException(ErrorCode error) : error_(error) + OrthancException(ErrorCode errorCode, + HttpStatus httpStatus) : + errorCode_(errorCode), + httpStatus_(httpStatus) { } ErrorCode GetErrorCode() const { - return error_; + return errorCode_; } - const char* What() const; + HttpStatus GetHttpStatus() const + { + return httpStatus_; + } + + const char* What() const + { + return EnumerationToString(errorCode_); + } }; }
--- a/Core/PrecompiledHeaders.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/PrecompiledHeaders.h Wed Sep 30 13:23:31 2015 +0200 @@ -46,7 +46,6 @@ #include <boost/thread.hpp> #include <boost/thread/shared_mutex.hpp> -#include <glog/logging.h> #include <json/value.h> #if ORTHANC_PUGIXML_ENABLED == 1 @@ -54,6 +53,7 @@ #endif #include "Enumerations.h" +#include "Logging.h" #include "OrthancException.h" #include "Toolbox.h" #include "Uuid.h"
--- a/Core/RestApi/RestApi.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApi.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,9 +33,9 @@ #include "../PrecompiledHeaders.h" #include "RestApi.h" +#include "../Logging.h" + #include <stdlib.h> // To define "_exit()" under Windows -#include <glog/logging.h> - #include <stdio.h> namespace Orthanc @@ -48,30 +48,42 @@ private: RestApi& api_; RestApiOutput& output_; + RequestOrigin origin_; + const char* remoteIp_; + const char* username_; HttpMethod method_; - const HttpHandler::Arguments& headers_; - const HttpHandler::Arguments& getArguments_; - const std::string& postData_; + const IHttpHandler::Arguments& headers_; + const IHttpHandler::Arguments& getArguments_; + const char* bodyData_; + size_t bodySize_; public: HttpHandlerVisitor(RestApi& api, RestApiOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, - const HttpHandler::Arguments& headers, - const HttpHandler::Arguments& getArguments, - const std::string& postData) : + const IHttpHandler::Arguments& headers, + const IHttpHandler::Arguments& getArguments, + const char* bodyData, + size_t bodySize) : api_(api), output_(output), + origin_(origin), + remoteIp_(remoteIp), + username_(username), method_(method), headers_(headers), getArguments_(getArguments), - postData_(postData) + bodyData_(bodyData), + bodySize_(bodySize) { } virtual bool Visit(const RestApiHierarchy::Resource& resource, const UriComponents& uri, - const HttpHandler::Arguments& components, + const IHttpHandler::Arguments& components, const UriComponents& trailing) { if (resource.HasHandler(method_)) @@ -80,28 +92,32 @@ { case HttpMethod_Get: { - RestApiGetCall call(output_, api_, headers_, components, trailing, uri, getArguments_); + RestApiGetCall call(output_, api_, origin_, remoteIp_, username_, + headers_, components, trailing, uri, getArguments_); resource.Handle(call); return true; } case HttpMethod_Post: { - RestApiPostCall call(output_, api_, headers_, components, trailing, uri, postData_); + RestApiPostCall call(output_, api_, origin_, remoteIp_, username_, + headers_, components, trailing, uri, bodyData_, bodySize_); resource.Handle(call); return true; } case HttpMethod_Delete: { - RestApiDeleteCall call(output_, api_, headers_, components, trailing, uri); + RestApiDeleteCall call(output_, api_, origin_, remoteIp_, username_, + headers_, components, trailing, uri); resource.Handle(call); return true; } case HttpMethod_Put: { - RestApiPutCall call(output_, api_, headers_, components, trailing, uri, postData_); + RestApiPutCall call(output_, api_, origin_, remoteIp_, username_, + headers_, components, trailing, uri, bodyData_, bodySize_); resource.Handle(call); return true; } @@ -157,38 +173,48 @@ bool RestApi::Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, - const std::string& postData) + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize) { - RestApiOutput wrappedOutput(output); + RestApiOutput wrappedOutput(output, method); #if ORTHANC_PUGIXML_ENABLED == 1 - // Look if the user wishes XML answers instead of JSON - // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3 - Arguments::const_iterator it = headers.find("accept"); - if (it != headers.end()) { - std::vector<std::string> accepted; - Toolbox::TokenizeString(accepted, it->second, ';'); - for (size_t i = 0; i < accepted.size(); i++) + // Look if the client wishes XML answers instead of JSON + // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3 + Arguments::const_iterator it = headers.find("accept"); + if (it != headers.end()) { - if (accepted[i] == "application/xml") + std::vector<std::string> accepted; + Toolbox::TokenizeString(accepted, it->second, ';'); + for (size_t i = 0; i < accepted.size(); i++) { - wrappedOutput.SetConvertJsonToXml(true); - } + if (accepted[i] == "application/xml") + { + wrappedOutput.SetConvertJsonToXml(true); + } - if (accepted[i] == "application/json") - { - wrappedOutput.SetConvertJsonToXml(false); + if (accepted[i] == "application/json") + { + wrappedOutput.SetConvertJsonToXml(false); + } } } } #endif - HttpHandlerVisitor visitor(*this, wrappedOutput, method, headers, getArguments, postData); + Arguments compiled; + HttpToolbox::CompileGetArguments(compiled, getArguments); + + HttpHandlerVisitor visitor(*this, wrappedOutput, origin, remoteIp, username, + method, headers, compiled, bodyData, bodySize); if (root_.LookupResource(uri, visitor)) {
--- a/Core/RestApi/RestApi.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApi.h Wed Sep 30 13:23:31 2015 +0200 @@ -38,7 +38,7 @@ namespace Orthanc { - class RestApi : public HttpHandler + class RestApi : public IHttpHandler { private: RestApiHierarchy root_; @@ -47,11 +47,15 @@ static void AutoListChildren(RestApiGetCall& call); virtual bool Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, - const std::string& postData); + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize); void Register(const std::string& path, RestApiGetCall::Handler handler);
--- a/Core/RestApi/RestApiCall.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiCall.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApiCall.h" namespace Orthanc @@ -41,4 +42,17 @@ Json::Reader reader; return reader.parse(request, result); } + + + std::string RestApiCall::FlattenUri() const + { + std::string s = "/"; + + for (size_t i = 0; i < fullUri_.size(); i++) + { + s += fullUri_[i] + "/"; + } + + return s; + } }
--- a/Core/RestApi/RestApiCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,7 +32,8 @@ #pragma once -#include "../HttpServer/HttpHandler.h" +#include "../HttpServer/IHttpHandler.h" +#include "../HttpServer/HttpToolbox.h" #include "RestApiPath.h" #include "RestApiOutput.h" @@ -47,8 +48,11 @@ private: RestApiOutput& output_; RestApi& context_; - const HttpHandler::Arguments& httpHeaders_; - const HttpHandler::Arguments& uriComponents_; + RequestOrigin origin_; + const char* remoteIp_; + const char* username_; + const IHttpHandler::Arguments& httpHeaders_; + const IHttpHandler::Arguments& uriComponents_; const UriComponents& trailing_; const UriComponents& fullUri_; @@ -59,12 +63,18 @@ public: RestApiCall(RestApiOutput& output, RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const HttpHandler::Arguments& uriComponents, + RequestOrigin origin, + const char* remoteIp, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::Arguments& uriComponents, const UriComponents& trailing, const UriComponents& fullUri) : output_(output), context_(context), + origin_(origin), + remoteIp_(remoteIp), + username_(username), httpHeaders_(httpHeaders), uriComponents_(uriComponents), trailing_(trailing), @@ -95,23 +105,40 @@ std::string GetUriComponent(const std::string& name, const std::string& defaultValue) const { - return HttpHandler::GetArgument(uriComponents_, name, defaultValue); + return HttpToolbox::GetArgument(uriComponents_, name, defaultValue); } std::string GetHttpHeader(const std::string& name, const std::string& defaultValue) const { - return HttpHandler::GetArgument(httpHeaders_, name, defaultValue); + return HttpToolbox::GetArgument(httpHeaders_, name, defaultValue); } - const HttpHandler::Arguments& GetHttpHeaders() const + const IHttpHandler::Arguments& GetHttpHeaders() const { return httpHeaders_; } - void ParseCookies(HttpHandler::Arguments& result) const + void ParseCookies(IHttpHandler::Arguments& result) const + { + HttpToolbox::ParseCookies(result, httpHeaders_); + } + + std::string FlattenUri() const; + + RequestOrigin GetRequestOrigin() const { - HttpHandler::ParseCookies(result, httpHeaders_); + return origin_; + } + + const char* GetRemoteIp() const + { + return remoteIp_; + } + + const char* GetUsername() const + { + return username_; } virtual bool ParseJsonRequest(Json::Value& result) const = 0;
--- a/Core/RestApi/RestApiDeleteCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiDeleteCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -43,11 +43,15 @@ RestApiDeleteCall(RestApiOutput& output, RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const HttpHandler::Arguments& uriComponents, + RequestOrigin origin, + const char* remoteIp, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::Arguments& uriComponents, const UriComponents& trailing, const UriComponents& fullUri) : - RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri) + RestApiCall(output, context, origin, remoteIp, username, + httpHeaders, uriComponents, trailing, fullUri) { }
--- a/Core/RestApi/RestApiGetCall.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiGetCall.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApiGetCall.h" namespace Orthanc @@ -38,7 +39,7 @@ { result.clear(); - for (HttpHandler::Arguments::const_iterator + for (IHttpHandler::Arguments::const_iterator it = getArguments_.begin(); it != getArguments_.end(); ++it) { result[it->first] = it->second;
--- a/Core/RestApi/RestApiGetCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiGetCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -39,19 +39,23 @@ class RestApiGetCall : public RestApiCall { private: - const HttpHandler::Arguments& getArguments_; + const IHttpHandler::Arguments& getArguments_; public: typedef void (*Handler) (RestApiGetCall& call); RestApiGetCall(RestApiOutput& output, RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const HttpHandler::Arguments& uriComponents, + RequestOrigin origin, + const char* remoteIp, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::Arguments& uriComponents, const UriComponents& trailing, const UriComponents& fullUri, - const HttpHandler::Arguments& getArguments) : - RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), + const IHttpHandler::Arguments& getArguments) : + RestApiCall(output, context, origin, remoteIp, username, + httpHeaders, uriComponents, trailing, fullUri), getArguments_(getArguments) { } @@ -59,7 +63,7 @@ std::string GetArgument(const std::string& name, const std::string& defaultValue) const { - return HttpHandler::GetArgument(getArguments_, name, defaultValue); + return HttpToolbox::GetArgument(getArguments_, name, defaultValue); } bool HasArgument(const std::string& name) const
--- a/Core/RestApi/RestApiHierarchy.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiHierarchy.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeaders.h" #include "RestApiHierarchy.h" #include "../OrthancException.h" @@ -199,7 +200,7 @@ } - bool RestApiHierarchy::LookupResource(HttpHandler::Arguments& components, + bool RestApiHierarchy::LookupResource(IHttpHandler::Arguments& components, const UriComponents& uri, IVisitor& visitor, size_t level) @@ -240,7 +241,7 @@ for (child = wildcardChildren_.begin(); child != wildcardChildren_.end(); ++child) { - HttpHandler::Arguments subComponents = components; + IHttpHandler::Arguments subComponents = components; subComponents[child->first] = uri[level]; if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) @@ -404,7 +405,7 @@ bool RestApiHierarchy::LookupResource(const UriComponents& uri, IVisitor& visitor) { - HttpHandler::Arguments components; + IHttpHandler::Arguments components; return LookupResource(components, uri, visitor, 0); } @@ -426,7 +427,7 @@ virtual bool Visit(const RestApiHierarchy::Resource& resource, const UriComponents& uri, - const HttpHandler::Arguments& components, + const IHttpHandler::Arguments& components, const UriComponents& trailing) { if (trailing.size() == 0) // Ignore universal handlers @@ -460,14 +461,15 @@ void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods, const UriComponents& uri) { - HttpHandler::Arguments components; + IHttpHandler::Arguments components; AcceptedMethodsVisitor visitor(methods); - LookupResource(components, uri, visitor, 0); - - Json::Value d; - if (GetDirectory(d, uri)) + if (LookupResource(components, uri, visitor, 0)) { - methods.insert(HttpMethod_Get); + Json::Value d; + if (GetDirectory(d, uri)) + { + methods.insert(HttpMethod_Get); + } } } }
--- a/Core/RestApi/RestApiHierarchy.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiHierarchy.h Wed Sep 30 13:23:31 2015 +0200 @@ -98,7 +98,7 @@ virtual bool Visit(const Resource& resource, const UriComponents& uri, - const HttpHandler::Arguments& components, + const IHttpHandler::Arguments& components, const UriComponents& trailing) = 0; }; @@ -123,7 +123,7 @@ bool CanGenerateDirectory() const; - bool LookupResource(HttpHandler::Arguments& components, + bool LookupResource(IHttpHandler::Arguments& components, const UriComponents& uri, IVisitor& visitor, size_t level);
--- a/Core/RestApi/RestApiOutput.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiOutput.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,15 +33,18 @@ #include "../PrecompiledHeaders.h" #include "RestApiOutput.h" +#include "../Logging.h" +#include "../OrthancException.h" + #include <boost/lexical_cast.hpp> -#include <glog/logging.h> -#include "../OrthancException.h" namespace Orthanc { - RestApiOutput::RestApiOutput(HttpOutput& output) : + RestApiOutput::RestApiOutput(HttpOutput& output, + HttpMethod method) : output_(output), + method_(method), convertJsonToXml_(false) { alreadySent_ = false; @@ -55,7 +58,14 @@ { if (!alreadySent_) { - output_.SendStatus(HttpStatus_404_NotFound); + if (method_ == HttpMethod_Post) + { + output_.SendStatus(HttpStatus_400_BadRequest); + } + else + { + output_.SendStatus(HttpStatus_404_NotFound); + } } } @@ -67,10 +77,11 @@ } } - void RestApiOutput::AnswerFile(HttpFileSender& sender) + + void RestApiOutput::AnswerStream(IHttpStreamAnswer& stream) { CheckStatus(); - sender.Send(output_); + output_.Answer(stream); alreadySent_ = true; } @@ -84,7 +95,7 @@ std::string s; Toolbox::JsonToXml(s, value); output_.SetContentType("application/xml"); - output_.SendBody(s); + output_.Answer(s); #else LOG(ERROR) << "Orthanc was compiled without XML support"; throw OrthancException(ErrorCode_InternalError); @@ -94,7 +105,7 @@ { Json::StyledWriter writer; output_.SetContentType("application/json"); - output_.SendBody(writer.write(value)); + output_.Answer(writer.write(value)); } alreadySent_ = true; @@ -103,10 +114,8 @@ void RestApiOutput::AnswerBuffer(const std::string& buffer, const std::string& contentType) { - CheckStatus(); - output_.SetContentType(contentType.c_str()); - output_.SendBody(buffer); - alreadySent_ = true; + AnswerBuffer(buffer.size() == 0 ? NULL : buffer.c_str(), + buffer.size(), contentType); } void RestApiOutput::AnswerBuffer(const void* buffer, @@ -115,7 +124,7 @@ { CheckStatus(); output_.SetContentType(contentType.c_str()); - output_.SendBody(buffer, length); + output_.Answer(buffer, length); alreadySent_ = true; } @@ -126,21 +135,34 @@ alreadySent_ = true; } - void RestApiOutput::SignalError(HttpStatus status) + void RestApiOutput::SignalErrorInternal(HttpStatus status, + const char* message, + size_t messageSize) { if (status != HttpStatus_400_BadRequest && status != HttpStatus_403_Forbidden && status != HttpStatus_500_InternalServerError && status != HttpStatus_415_UnsupportedMediaType) { - throw OrthancException("This HTTP status is not allowed in a REST API"); + throw OrthancException(ErrorCode_BadHttpStatusInRest); } CheckStatus(); - output_.SendStatus(status); + output_.SendStatus(status, message, messageSize); alreadySent_ = true; } + void RestApiOutput::SignalError(HttpStatus status) + { + SignalErrorInternal(status, NULL, 0); + } + + void RestApiOutput::SignalError(HttpStatus status, + const std::string& message) + { + SignalErrorInternal(status, message.c_str(), message.size()); + } + void RestApiOutput::SetCookie(const std::string& name, const std::string& value, unsigned int maxAge)
--- a/Core/RestApi/RestApiOutput.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiOutput.h Wed Sep 30 13:23:31 2015 +0200 @@ -42,27 +42,23 @@ class RestApiOutput { private: - HttpOutput& output_; - bool alreadySent_; - bool convertJsonToXml_; + HttpOutput& output_; + HttpMethod method_; + bool alreadySent_; + bool convertJsonToXml_; void CheckStatus(); + void SignalErrorInternal(HttpStatus status, + const char* message, + size_t messageSize); + public: - RestApiOutput(HttpOutput& output); + RestApiOutput(HttpOutput& output, + HttpMethod method); ~RestApiOutput(); - HttpOutput& GetLowLevelOutput() - { - return output_; - } - - void MarkLowLevelOutputDone() - { - alreadySent_ = true; - } - void SetConvertJsonToXml(bool convert) { convertJsonToXml_ = convert; @@ -73,7 +69,7 @@ return convertJsonToXml_; } - void AnswerFile(HttpFileSender& sender); + void AnswerStream(IHttpStreamAnswer& stream); void AnswerJson(const Json::Value& value); @@ -86,6 +82,9 @@ void SignalError(HttpStatus status); + void SignalError(HttpStatus status, + const std::string& message); + void Redirect(const std::string& path); void SetCookie(const std::string& name,
--- a/Core/RestApi/RestApiPath.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiPath.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -78,7 +78,7 @@ } } - bool RestApiPath::Match(HttpHandler::Arguments& components, + bool RestApiPath::Match(IHttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const { @@ -87,7 +87,7 @@ return Match(components, trailing, uri); } - bool RestApiPath::Match(HttpHandler::Arguments& components, + bool RestApiPath::Match(IHttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const { @@ -135,7 +135,7 @@ bool RestApiPath::Match(const UriComponents& uri) const { - HttpHandler::Arguments components; + IHttpHandler::Arguments components; UriComponents trailing; return Match(components, trailing, uri); }
--- a/Core/RestApi/RestApiPath.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiPath.h Wed Sep 30 13:23:31 2015 +0200 @@ -33,7 +33,7 @@ #pragma once #include "../Toolbox.h" -#include "../HttpServer/HttpHandler.h" +#include "../HttpServer/IHttpHandler.h" #include <map> @@ -50,11 +50,11 @@ RestApiPath(const std::string& uri); // This version is slower - bool Match(HttpHandler::Arguments& components, + bool Match(IHttpHandler::Arguments& components, UriComponents& trailing, const std::string& uriRaw) const; - bool Match(HttpHandler::Arguments& components, + bool Match(IHttpHandler::Arguments& components, UriComponents& trailing, const UriComponents& uri) const;
--- a/Core/RestApi/RestApiPostCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiPostCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -39,31 +39,48 @@ class RestApiPostCall : public RestApiCall { private: - const std::string& data_; + const char* bodyData_; + size_t bodySize_; public: typedef void (*Handler) (RestApiPostCall& call); RestApiPostCall(RestApiOutput& output, RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const HttpHandler::Arguments& uriComponents, + RequestOrigin origin, + const char* remoteIp, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::Arguments& uriComponents, const UriComponents& trailing, const UriComponents& fullUri, - const std::string& data) : - RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) + const char* bodyData, + size_t bodySize) : + RestApiCall(output, context, origin, remoteIp, username, + httpHeaders, uriComponents, trailing, fullUri), + bodyData_(bodyData), + bodySize_(bodySize) { } - const std::string& GetPostBody() const + const char* GetBodyData() const + { + return bodyData_; + } + + size_t GetBodySize() const { - return data_; + return bodySize_; + } + + void BodyToString(std::string& result) const + { + result.assign(bodyData_, bodySize_); } virtual bool ParseJsonRequest(Json::Value& result) const { - return ParseJsonRequestInternal(result, GetPostBody().c_str()); + return ParseJsonRequestInternal(result, bodyData_); } }; }
--- a/Core/RestApi/RestApiPutCall.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/RestApi/RestApiPutCall.h Wed Sep 30 13:23:31 2015 +0200 @@ -39,31 +39,48 @@ class RestApiPutCall : public RestApiCall { private: - const std::string& data_; + const char* bodyData_; + size_t bodySize_; public: typedef void (*Handler) (RestApiPutCall& call); RestApiPutCall(RestApiOutput& output, RestApi& context, - const HttpHandler::Arguments& httpHeaders, - const HttpHandler::Arguments& uriComponents, + RequestOrigin origin, + const char* remoteIp, + const char* username, + const IHttpHandler::Arguments& httpHeaders, + const IHttpHandler::Arguments& uriComponents, const UriComponents& trailing, const UriComponents& fullUri, - const std::string& data) : - RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri), - data_(data) + const char* bodyData, + size_t bodySize) : + RestApiCall(output, context, origin, remoteIp, username, + httpHeaders, uriComponents, trailing, fullUri), + bodyData_(bodyData), + bodySize_(bodySize) { } - const std::string& GetPutBody() const + const char* GetBodyData() const + { + return bodyData_; + } + + size_t GetBodySize() const { - return data_; + return bodySize_; + } + + void BodyToString(std::string& result) const + { + result.assign(bodyData_, bodySize_); } virtual bool ParseJsonRequest(Json::Value& result) const { - return ParseJsonRequestInternal(result, GetPutBody().c_str()); + return ParseJsonRequestInternal(result, bodyData_); } }; }
--- a/Core/SQLite/Connection.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/Connection.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -44,13 +44,14 @@ #include <memory> #include <cassert> -#include <sqlite3.h> #include <string.h> #if ORTHANC_SQLITE_STANDALONE != 1 -#include <glog/logging.h> +#include "../Logging.h" #endif +#include "sqlite3.h" + namespace Orthanc { @@ -74,7 +75,7 @@ { if (!db_) { - throw OrthancSQLiteException("SQLite: The database is not opened"); + throw OrthancSQLiteException(ErrorCode_SQLiteNotOpened); } } @@ -82,7 +83,7 @@ { if (db_) { - throw OrthancSQLiteException("SQLite: Connection is already open"); + throw OrthancSQLiteException(ErrorCode_SQLiteAlreadyOpened); } int err = sqlite3_open(path.c_str(), &db_); @@ -90,7 +91,7 @@ { Close(); db_ = NULL; - throw OrthancSQLiteException("SQLite: Unable to open the database"); + throw OrthancSQLiteException(ErrorCode_SQLiteCannotOpen); } // Execute PRAGMAs at this point @@ -136,7 +137,7 @@ { if (i->second->GetReferenceCount() >= 1) { - throw OrthancSQLiteException("SQLite: This cached statement is already being referred to"); + throw OrthancSQLiteException(ErrorCode_SQLiteStatementAlreadyUsed); } return *i->second; @@ -161,7 +162,11 @@ int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); if (error == SQLITE_ERROR) { - throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); +#if ORTHANC_SQLITE_STANDALONE != 1 + LOG(ERROR) << "SQLite execute error: " << sqlite3_errmsg(db_); +#endif + + throw OrthancSQLiteException(ErrorCode_SQLiteExecute); } else { @@ -282,7 +287,7 @@ { if (!transactionNesting_) { - throw OrthancSQLiteException("Rolling back a nonexistent transaction"); + throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); } transactionNesting_--; @@ -301,7 +306,7 @@ { if (!transactionNesting_) { - throw OrthancSQLiteException("Committing a nonexistent transaction"); + throw OrthancSQLiteException(ErrorCode_SQLiteCommitWithoutTransaction); } transactionNesting_--; @@ -369,7 +374,7 @@ if (err != SQLITE_OK) { delete func; - throw OrthancSQLiteException("SQLite: Unable to register a function"); + throw OrthancSQLiteException(ErrorCode_SQLiteRegisterFunction); } return func; @@ -386,7 +391,7 @@ if (err != SQLITE_OK) { - throw OrthancSQLiteException("SQLite: Unable to flush the database"); + throw OrthancSQLiteException(ErrorCode_SQLiteFlush); } } }
--- a/Core/SQLite/FunctionContext.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/FunctionContext.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -39,7 +39,9 @@ #include "FunctionContext.h" #include "OrthancSQLiteException.h" -#include <sqlite3.h> +#include <string> + +#include "sqlite3.h" namespace Orthanc { @@ -62,7 +64,7 @@ { if (index >= argc_) { - throw OrthancSQLiteException("Parameter out of range"); + throw OrthancSQLiteException(ErrorCode_ParameterOutOfRange); } }
--- a/Core/SQLite/OrthancSQLiteException.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/OrthancSQLiteException.h Wed Sep 30 13:23:31 2015 +0200 @@ -45,17 +45,99 @@ { namespace SQLite { + // Auto-generated by "Resources/GenerateErrorCodes.py" + enum ErrorCode + { + ErrorCode_ParameterOutOfRange, + ErrorCode_BadParameterType, + ErrorCode_SQLiteNotOpened, + ErrorCode_SQLiteAlreadyOpened, + ErrorCode_SQLiteCannotOpen, + ErrorCode_SQLiteStatementAlreadyUsed, + ErrorCode_SQLiteExecute, + ErrorCode_SQLiteRollbackWithoutTransaction, + ErrorCode_SQLiteCommitWithoutTransaction, + ErrorCode_SQLiteRegisterFunction, + ErrorCode_SQLiteFlush, + ErrorCode_SQLiteCannotRun, + ErrorCode_SQLiteCannotStep, + ErrorCode_SQLiteBindOutOfRange, + ErrorCode_SQLitePrepareStatement, + ErrorCode_SQLiteTransactionAlreadyStarted, + ErrorCode_SQLiteTransactionCommit, + ErrorCode_SQLiteTransactionBegin + }; + class OrthancSQLiteException : public ::std::runtime_error { public: - OrthancSQLiteException(const std::string& what) : - ::std::runtime_error(what) + OrthancSQLiteException(ErrorCode error) : + ::std::runtime_error(EnumerationToString(error)) { } - OrthancSQLiteException(const char* what) : - ::std::runtime_error(what) + // Auto-generated by "Resources/GenerateErrorCodes.py" + static const char* EnumerationToString(ErrorCode code) { + switch (code) + { + case ErrorCode_ParameterOutOfRange: + return "Parameter out of range"; + + case ErrorCode_BadParameterType: + return "Bad type for a parameter"; + + case ErrorCode_SQLiteNotOpened: + return "SQLite: The database is not opened"; + + case ErrorCode_SQLiteAlreadyOpened: + return "SQLite: Connection is already open"; + + case ErrorCode_SQLiteCannotOpen: + return "SQLite: Unable to open the database"; + + case ErrorCode_SQLiteStatementAlreadyUsed: + return "SQLite: This cached statement is already being referred to"; + + case ErrorCode_SQLiteExecute: + return "SQLite: Cannot execute a command"; + + case ErrorCode_SQLiteRollbackWithoutTransaction: + return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"; + + case ErrorCode_SQLiteCommitWithoutTransaction: + return "SQLite: Committing a nonexistent transaction"; + + case ErrorCode_SQLiteRegisterFunction: + return "SQLite: Unable to register a function"; + + case ErrorCode_SQLiteFlush: + return "SQLite: Unable to flush the database"; + + case ErrorCode_SQLiteCannotRun: + return "SQLite: Cannot run a cached statement"; + + case ErrorCode_SQLiteCannotStep: + return "SQLite: Cannot step over a cached statement"; + + case ErrorCode_SQLiteBindOutOfRange: + return "SQLite: Bing a value while out of range (serious error)"; + + case ErrorCode_SQLitePrepareStatement: + return "SQLite: Cannot prepare a cached statement"; + + case ErrorCode_SQLiteTransactionAlreadyStarted: + return "SQLite: Beginning the same transaction twice"; + + case ErrorCode_SQLiteTransactionCommit: + return "SQLite: Failure when committing the transaction"; + + case ErrorCode_SQLiteTransactionBegin: + return "SQLite: Cannot start a transaction"; + + default: + return "Unknown error code"; + } } }; }
--- a/Core/SQLite/Statement.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/Statement.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -42,15 +42,16 @@ #include "Statement.h" #include "Connection.h" -#include <sqlite3.h> #include <string.h> #include <stdio.h> #include <algorithm> #if ORTHANC_SQLITE_STANDALONE != 1 -#include <glog/logging.h> +#include "../Logging.h" #endif +#include "sqlite3.h" + #if defined(_MSC_VER) #define snprintf _snprintf #endif @@ -59,31 +60,39 @@ { namespace SQLite { - int Statement::CheckError(int err) const + int Statement::CheckError(int err, ErrorCode code) const { bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); if (!succeeded) { +#if ORTHANC_SQLITE_STANDALONE != 1 char buffer[128]; snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); - throw OrthancSQLiteException(buffer); + LOG(ERROR) << buffer; +#endif + + throw OrthancSQLiteException(code); } return err; } - void Statement::CheckOk(int err) const + void Statement::CheckOk(int err, ErrorCode code) const { if (err == SQLITE_RANGE) { // Binding to a non-existent variable is evidence of a serious error. - throw OrthancSQLiteException("Bind value out of range"); + throw OrthancSQLiteException(ErrorCode_SQLiteBindOutOfRange); } else if (err != SQLITE_OK) { +#if ORTHANC_SQLITE_STANDALONE != 1 char buffer[128]; snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); - throw OrthancSQLiteException(buffer); + LOG(ERROR) << buffer; +#endif + + throw OrthancSQLiteException(code); } } @@ -126,7 +135,7 @@ VLOG(1) << "SQLite::Statement::Run " << sqlite3_sql(GetStatement()); #endif - return CheckError(sqlite3_step(GetStatement())) == SQLITE_DONE; + return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotRun) == SQLITE_DONE; } bool Statement::Step() @@ -135,7 +144,7 @@ VLOG(1) << "SQLite::Statement::Step " << sqlite3_sql(GetStatement()); #endif - return CheckError(sqlite3_step(GetStatement())) == SQLITE_ROW; + return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotStep) == SQLITE_ROW; } void Statement::Reset(bool clear_bound_vars) @@ -157,7 +166,8 @@ void Statement::BindNull(int col) { - CheckOk(sqlite3_bind_null(GetStatement(), col + 1)); + CheckOk(sqlite3_bind_null(GetStatement(), col + 1), + ErrorCode_BadParameterType); } void Statement::BindBool(int col, bool val) @@ -167,22 +177,26 @@ void Statement::BindInt(int col, int val) { - CheckOk(sqlite3_bind_int(GetStatement(), col + 1, val)); + CheckOk(sqlite3_bind_int(GetStatement(), col + 1, val), + ErrorCode_BadParameterType); } void Statement::BindInt64(int col, int64_t val) { - CheckOk(sqlite3_bind_int64(GetStatement(), col + 1, val)); + CheckOk(sqlite3_bind_int64(GetStatement(), col + 1, val), + ErrorCode_BadParameterType); } void Statement::BindDouble(int col, double val) { - CheckOk(sqlite3_bind_double(GetStatement(), col + 1, val)); + CheckOk(sqlite3_bind_double(GetStatement(), col + 1, val), + ErrorCode_BadParameterType); } void Statement::BindCString(int col, const char* val) { - CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val, -1, SQLITE_TRANSIENT)); + CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val, -1, SQLITE_TRANSIENT), + ErrorCode_BadParameterType); } void Statement::BindString(int col, const std::string& val) @@ -191,7 +205,8 @@ col + 1, val.data(), val.size(), - SQLITE_TRANSIENT)); + SQLITE_TRANSIENT), + ErrorCode_BadParameterType); } /*void Statement::BindString16(int col, const string16& value) @@ -201,7 +216,8 @@ void Statement::BindBlob(int col, const void* val, int val_len) { - CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT)); + CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT), + ErrorCode_BadParameterType); }
--- a/Core/SQLite/Statement.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/Statement.h Wed Sep 30 13:23:31 2015 +0200 @@ -81,9 +81,11 @@ private: StatementReference reference_; - int CheckError(int err) const; + int CheckError(int err, + ErrorCode code) const; - void CheckOk(int err) const; + void CheckOk(int err, + ErrorCode code) const; struct sqlite3_stmt* GetStatement() const {
--- a/Core/SQLite/StatementReference.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/StatementReference.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -43,9 +43,10 @@ #include "OrthancSQLiteException.h" #if ORTHANC_SQLITE_STANDALONE != 1 -#include <glog/logging.h> +#include "../Logging.h" #endif +#include <string> #include <cassert> #include "sqlite3.h" @@ -71,7 +72,7 @@ { if (database == NULL || sql == NULL) { - throw OrthancSQLiteException("Parameter out of range"); + throw OrthancSQLiteException(ErrorCode_ParameterOutOfRange); } root_ = NULL; @@ -80,7 +81,11 @@ int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL); if (error != SQLITE_OK) { - throw OrthancSQLiteException("SQLite: " + std::string(sqlite3_errmsg(database))); +#if ORTHANC_SQLITE_STANDALONE != 1 + LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database); +#endif + + throw OrthancSQLiteException(ErrorCode_SQLitePrepareStatement); } assert(IsRoot());
--- a/Core/SQLite/Transaction.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/SQLite/Transaction.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -64,13 +64,13 @@ { if (isOpen_) { - throw OrthancSQLiteException("SQLite: Beginning a transaction twice!"); + throw OrthancSQLiteException(ErrorCode_SQLiteTransactionAlreadyStarted); } isOpen_ = connection_.BeginTransaction(); if (!isOpen_) { - throw OrthancSQLiteException("SQLite: Unable to create a transaction"); + throw OrthancSQLiteException(ErrorCode_SQLiteTransactionBegin); } } @@ -78,8 +78,7 @@ { if (!isOpen_) { - throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " - "Did you remember to call Begin()?"); + throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); } isOpen_ = false; @@ -91,15 +90,14 @@ { if (!isOpen_) { - throw OrthancSQLiteException("SQLite: Attempting to roll back a nonexistent transaction. " - "Did you remember to call Begin()?"); + throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); } isOpen_ = false; if (!connection_.CommitTransaction()) { - throw OrthancSQLiteException("SQLite: Failure when committing the transaction"); + throw OrthancSQLiteException(ErrorCode_SQLiteTransactionCommit); } } }
--- a/Core/Toolbox.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Toolbox.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,21 +34,29 @@ #include "Toolbox.h" #include "OrthancException.h" +#include "Logging.h" +#include <string> #include <stdint.h> #include <string.h> #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/uuid/sha1.hpp> +#include <boost/lexical_cast.hpp> #include <algorithm> #include <ctype.h> + +#if BOOST_HAS_DATE_TIME == 1 +#include <boost/date_time/posix_time/posix_time.hpp> +#endif + +#if BOOST_HAS_REGEX == 1 #include <boost/regex.hpp> -#include <glog/logging.h> +#endif #if defined(_WIN32) #include <windows.h> -#include <process.h> // For "_spawnvp()" +#include <process.h> // For "_spawnvp()" and "_getpid()" #else #include <unistd.h> // For "execvp()" #include <sys/wait.h> // For "waitpid()" @@ -59,7 +67,7 @@ #include <limits.h> /* PATH_MAX */ #endif -#if defined(__linux) || defined(__FreeBSD_kernel__) +#if defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) #include <limits.h> /* PATH_MAX */ #include <signal.h> #include <unistd.h> @@ -71,8 +79,15 @@ #include <boost/locale.hpp> + +#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 #include "../Resources/ThirdParty/md5/md5.h" +#endif + + +#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 #include "../Resources/ThirdParty/base64/base64.h" +#endif #if defined(_MSC_VER) && (_MSC_VER < 1800) @@ -116,7 +131,7 @@ { #if defined(_WIN32) ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000))); -#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) usleep(microSeconds); #else #error Support your platform here @@ -193,6 +208,12 @@ void Toolbox::ReadFile(std::string& content, const std::string& path) { + if (!boost::filesystem::is_regular_file(path)) + { + LOG(ERROR) << "The path does not point to a regular file: " << path; + throw OrthancException(ErrorCode_RegularFileExpected); + } + boost::filesystem::ifstream f; f.open(path, std::ifstream::in | std::ifstream::binary); if (!f.good()) @@ -215,7 +236,8 @@ } - void Toolbox::WriteFile(const std::string& content, + void Toolbox::WriteFile(const void* content, + size_t size, const std::string& path) { boost::filesystem::ofstream f; @@ -225,24 +247,35 @@ throw OrthancException(ErrorCode_CannotWriteFile); } - if (content.size() != 0) + if (size != 0) { - f.write(content.c_str(), content.size()); + f.write(reinterpret_cast<const char*>(content), size); } f.close(); } + void Toolbox::WriteFile(const std::string& content, + const std::string& path) + { + WriteFile(content.size() > 0 ? content.c_str() : NULL, + content.size(), path); + } + void Toolbox::RemoveFile(const std::string& path) { if (boost::filesystem::exists(path)) { if (boost::filesystem::is_regular_file(path)) + { boost::filesystem::remove(path); + } else - throw OrthancException("The path is not a regular file: " + path); + { + throw OrthancException(ErrorCode_RegularFileExpected); + } } } @@ -422,21 +455,26 @@ { return static_cast<uint64_t>(boost::filesystem::file_size(path)); } - catch (boost::filesystem::filesystem_error) + catch (boost::filesystem::filesystem_error&) { throw OrthancException(ErrorCode_InexistentFile); } } +#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 static char GetHexadecimalCharacter(uint8_t value) { assert(value < 16); if (value < 10) + { return value + '0'; + } else + { return (value - 10) + 'a'; + } } @@ -474,12 +512,14 @@ result.resize(32); for (unsigned int i = 0; i < 16; i++) { - result[2 * i] = GetHexadecimalCharacter(actualHash[i] / 16); - result[2 * i + 1] = GetHexadecimalCharacter(actualHash[i] % 16); + result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16)); + result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16)); } } +#endif +#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 void Toolbox::EncodeBase64(std::string& result, const std::string& data) { @@ -493,6 +533,31 @@ } +# if BOOST_HAS_REGEX == 1 + void Toolbox::DecodeDataUriScheme(std::string& mime, + std::string& content, + const std::string& source) + { + boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)", + boost::regex::icase /* case insensitive search */); + + boost::cmatch what; + if (regex_match(source.c_str(), what, pattern)) + { + mime = what[1]; + DecodeBase64(content, what[2]); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } +# endif + +#endif + + + #if defined(_WIN32) static std::string GetPathToExecutableInternal() { @@ -503,14 +568,14 @@ return std::string(&buffer[0]); } -#elif defined(__linux) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) static std::string GetPathToExecutableInternal() { std::vector<char> buffer(PATH_MAX + 1); ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1); if (bytes == 0) { - throw OrthancException("Unable to get the path to the executable"); + throw OrthancException(ErrorCode_PathToExecutable); } return std::string(&buffer[0]); @@ -546,73 +611,89 @@ } - std::string Toolbox::ConvertToUtf8(const std::string& source, - const Encoding sourceEncoding) + static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding) { - const char* encoding; - - - // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html switch (sourceEncoding) { case Encoding_Utf8: - // Already in UTF-8: No conversion is required - return source; + return "UTF-8"; case Encoding_Ascii: - return ConvertToAscii(source); + return "ASCII"; case Encoding_Latin1: - encoding = "ISO-8859-1"; + return "ISO-8859-1"; break; case Encoding_Latin2: - encoding = "ISO-8859-2"; + return "ISO-8859-2"; break; case Encoding_Latin3: - encoding = "ISO-8859-3"; + return "ISO-8859-3"; break; case Encoding_Latin4: - encoding = "ISO-8859-4"; + return "ISO-8859-4"; break; case Encoding_Latin5: - encoding = "ISO-8859-9"; + return "ISO-8859-9"; break; case Encoding_Cyrillic: - encoding = "ISO-8859-5"; + return "ISO-8859-5"; + break; + + case Encoding_Windows1251: + return "WINDOWS-1251"; break; case Encoding_Arabic: - encoding = "ISO-8859-6"; + return "ISO-8859-6"; break; case Encoding_Greek: - encoding = "ISO-8859-7"; + return "ISO-8859-7"; break; case Encoding_Hebrew: - encoding = "ISO-8859-8"; + return "ISO-8859-8"; break; case Encoding_Japanese: - encoding = "SHIFT-JIS"; + return "SHIFT-JIS"; break; case Encoding_Chinese: - encoding = "GB18030"; + return "GB18030"; break; case Encoding_Thai: - encoding = "TIS620.2533-0"; + return "TIS620.2533-0"; break; default: throw OrthancException(ErrorCode_NotImplemented); } + } + + + std::string Toolbox::ConvertToUtf8(const std::string& source, + Encoding sourceEncoding) + { + if (sourceEncoding == Encoding_Utf8) + { + // Already in UTF-8: No conversion is required + return source; + } + + if (sourceEncoding == Encoding_Ascii) + { + return ConvertToAscii(source); + } + + const char* encoding = GetBoostLocaleEncoding(sourceEncoding); try { @@ -626,6 +707,34 @@ } + std::string Toolbox::ConvertFromUtf8(const std::string& source, + Encoding targetEncoding) + { + if (targetEncoding == Encoding_Utf8) + { + // Already in UTF-8: No conversion is required + return source; + } + + if (targetEncoding == Encoding_Ascii) + { + return ConvertToAscii(source); + } + + const char* encoding = GetBoostLocaleEncoding(targetEncoding); + + try + { + return boost::locale::conv::from_utf<char>(source, encoding); + } + catch (std::runtime_error&) + { + // Bad input string or bad encoding + return ConvertToAscii(source); + } + } + + std::string Toolbox::ConvertToAscii(const std::string& source) { std::string result; @@ -633,7 +742,7 @@ result.reserve(source.size() + 1); for (size_t i = 0; i < source.size(); i++) { - if (source[i] < 128 && source[i] >= 0 && !iscntrl(source[i])) + if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i])) { result.push_back(source[i]); } @@ -668,9 +777,46 @@ digest[4]); } - bool Toolbox::IsSHA1(const std::string& str) + bool Toolbox::IsSHA1(const char* str, + size_t size) { - if (str.size() != 44) + if (size == 0) + { + return false; + } + + const char* start = str; + const char* end = str + size; + + // Trim the beginning of the string + while (start < end) + { + if (*start == '\0' || + isspace(*start)) + { + start++; + } + else + { + break; + } + } + + // Trim the trailing of the string + while (start < end) + { + if (*(end - 1) == '\0' || + isspace(*(end - 1))) + { + end--; + } + else + { + break; + } + } + + if (end - start != 44) { return false; } @@ -682,12 +828,12 @@ i == 26 || i == 35) { - if (str[i] != '-') + if (start[i] != '-') return false; } else { - if (!isalnum(str[i])) + if (!isalnum(start[i])) return false; } } @@ -695,12 +841,44 @@ return true; } + + bool Toolbox::IsSHA1(const std::string& s) + { + if (s.size() == 0) + { + return false; + } + else + { + return IsSHA1(s.c_str(), s.size()); + } + } + + +#if BOOST_HAS_DATE_TIME == 1 std::string Toolbox::GetNowIsoString() { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); return boost::posix_time::to_iso_string(now); } + void Toolbox::GetNowDicom(std::string& date, + std::string& time) + { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + tm tm = boost::posix_time::to_tm(now); + + char s[32]; + sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + date.assign(s); + + // TODO milliseconds + sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0); + time.assign(s); + } +#endif + + std::string Toolbox::StripSpaces(const std::string& source) { size_t first = 0; @@ -801,6 +979,7 @@ } +#if BOOST_HAS_REGEX == 1 std::string Toolbox::WildcardToRegularExpression(const std::string& source) { // TODO - Speed up this with a regular expression @@ -828,6 +1007,7 @@ return result; } +#endif @@ -856,40 +1036,20 @@ } - void Toolbox::DecodeDataUriScheme(std::string& mime, - std::string& content, - const std::string& source) - { - boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)", - boost::regex::icase /* case insensitive search */); - - boost::cmatch what; - if (regex_match(source.c_str(), what, pattern)) - { - mime = what[1]; - content = what[2]; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void Toolbox::CreateDirectory(const std::string& path) + void Toolbox::MakeDirectory(const std::string& path) { if (boost::filesystem::exists(path)) { if (!boost::filesystem::is_directory(path)) { - throw OrthancException("Cannot create the directory over an existing file: " + path); + throw OrthancException(ErrorCode_DirectoryOverFile); } } else { if (!boost::filesystem::create_directories(path)) { - throw OrthancException("Unable to create the directory: " + path); + throw OrthancException(ErrorCode_MakeDirectory); } } } @@ -1050,7 +1210,10 @@ if (pid == -1) { // Error in fork() +#if ORTHANC_ENABLE_LOGGING == 1 LOG(ERROR) << "Cannot fork a child process"; +#endif + throw OrthancException(ErrorCode_SystemCommand); } else if (pid == 0) @@ -1070,7 +1233,10 @@ if (status != 0) { +#if ORTHANC_ENABLE_LOGGING == 1 LOG(ERROR) << "System command failed with status code " << status; +#endif + throw OrthancException(ErrorCode_SystemCommand); } } @@ -1108,5 +1274,89 @@ return true; } + + + void Toolbox::CopyJsonWithoutComments(Json::Value& target, + const Json::Value& source) + { + switch (source.type()) + { + case Json::nullValue: + target = Json::nullValue; + break; + + case Json::intValue: + target = source.asInt64(); + break; + + case Json::uintValue: + target = source.asUInt64(); + break; + + case Json::realValue: + target = source.asDouble(); + break; + + case Json::stringValue: + target = source.asString(); + break; + + case Json::booleanValue: + target = source.asBool(); + break; + + case Json::arrayValue: + { + target = Json::arrayValue; + for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) + { + Json::Value& item = target.append(Json::nullValue); + CopyJsonWithoutComments(item, source[i]); + } + + break; + } + + case Json::objectValue: + { + target = Json::objectValue; + Json::Value::Members members = source.getMemberNames(); + for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) + { + const std::string item = members[i]; + CopyJsonWithoutComments(target[item], source[item]); + } + + break; + } + + default: + break; + } + } + + + bool Toolbox::StartsWith(const std::string& str, + const std::string& prefix) + { + if (str.size() < prefix.size()) + { + return false; + } + else + { + return str.compare(0, prefix.size(), prefix) == 0; + } + } + + + int Toolbox::GetProcessId() + { +#if defined(_WIN32) + return static_cast<int>(_getpid()); +#else + return static_cast<int>(getpid()); +#endif + } }
--- a/Core/Toolbox.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Core/Toolbox.h Wed Sep 30 13:23:31 2015 +0200 @@ -69,6 +69,10 @@ void WriteFile(const std::string& content, const std::string& path); + void WriteFile(const void* content, + size_t size, + const std::string& path); + void USleep(uint64_t microSeconds); void RemoveFile(const std::string& path); @@ -90,53 +94,72 @@ uint64_t GetFileSize(const std::string& path); +#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1 void ComputeMD5(std::string& result, const std::string& data); void ComputeMD5(std::string& result, const void* data, size_t length); +#endif void ComputeSHA1(std::string& result, const std::string& data); - bool IsSHA1(const std::string& str); + bool IsSHA1(const char* str, + size_t size); + bool IsSHA1(const std::string& s); + +#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 void DecodeBase64(std::string& result, const std::string& data); void EncodeBase64(std::string& result, const std::string& data); +# if BOOST_HAS_REGEX == 1 + void DecodeDataUriScheme(std::string& mime, + std::string& content, + const std::string& source); +# endif +#endif + std::string GetPathToExecutable(); std::string GetDirectoryOfExecutable(); std::string ConvertToUtf8(const std::string& source, - const Encoding sourceEncoding); + Encoding sourceEncoding); + + std::string ConvertFromUtf8(const std::string& source, + Encoding targetEncoding); std::string ConvertToAscii(const std::string& source); std::string StripSpaces(const std::string& source); +#if BOOST_HAS_DATE_TIME == 1 std::string GetNowIsoString(); + void GetNowDicom(std::string& date, + std::string& time); +#endif + // In-place percent-decoding for URL void UrlDecode(std::string& s); Endianness DetectEndianness(); +#if BOOST_HAS_REGEX == 1 std::string WildcardToRegularExpression(const std::string& s); +#endif void TokenizeString(std::vector<std::string>& result, const std::string& source, char separator); - void DecodeDataUriScheme(std::string& mime, - std::string& content, - const std::string& source); - - void CreateDirectory(const std::string& path); + void MakeDirectory(const std::string& path); bool IsExistingFile(const std::string& path); @@ -151,5 +174,13 @@ const std::vector<std::string>& arguments); bool IsInteger(const std::string& str); + + void CopyJsonWithoutComments(Json::Value& target, + const Json::Value& source); + + bool StartsWith(const std::string& str, + const std::string& prefix); + + int GetProcessId(); } }
--- a/INSTALL Wed Feb 11 10:40:08 2015 +0100 +++ b/INSTALL Wed Sep 30 13:23:31 2015 +0200 @@ -32,12 +32,24 @@ To build Orthanc, you must: 1) Download the source code (either using Mercurial, or through the - released versions). For the examples below, we assume the source + official releases). For the examples below, we assume the source directory is "~/Orthanc". 2) Create a build directory. For the examples below, we assume the build directory is "~/OrthancBuild". +3) Depending on your platform, follow the build instructions below. + + +WARNING 1: If you do not create a fresh "~/OrthancBuild" directory +after upgrading the source code (i.e. if you reuse the build directory +that was used to build a different version of Orthanc), the build +might fail because of changes in the compilation/linking flags. Always +prefer to force a re-build in a new directory. + +WARNING 2: If cmake complains about not being able to uncompress +third-party dependencies, delete the "~/Orthanc/ThirdPartyDownloads/" +folder, then restart cmake. Native Linux Compilation
--- a/LinuxCompilation.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/LinuxCompilation.txt Wed Sep 30 13:23:31 2015 +0200 @@ -74,50 +74,12 @@ Orthanc users. -SUPPORTED - Debian Squeeze (6.x) --------------------------------- - -# sudo apt-get install build-essential unzip cmake mercurial \ - uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ - libgoogle-glog-dev libpng-dev libgtest-dev \ - libsqlite3-dev libssl-dev zlib1g-dev - -# cmake -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_BOOST=OFF \ - -DUSE_SYSTEM_DCMTK=OFF \ - -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_SYSTEM_PUGIXML=OFF \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ - ~/Orthanc - - -SUPPORTED - Debian Wheezy (7.x) -------------------------------- - -# sudo apt-get install build-essential unzip cmake mercurial \ - uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ - libgtest-dev libpng-dev libsqlite3-dev \ - libssl-dev zlib1g-dev libdcmtk2-dev \ - libboost-all-dev libwrap0-dev libjsoncpp-dev - -# cmake -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_GOOGLE_LOG=OFF \ - -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DUSE_SYSTEM_PUGIXML=OFF \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ - ~/Orthanc - - SUPPORTED - Debian Jessie/Sid ----------------------------- # 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 \ + libgoogle-glog-dev libgtest-dev libpng-dev libjpeg-dev \ libsqlite3-dev libssl-dev zlib1g-dev libdcmtk2-dev \ libboost-all-dev libwrap0-dev libjsoncpp-dev libpugixml-dev @@ -131,15 +93,16 @@ http://anonscm.debian.org/viewvc/debian-med/trunk/packages/orthanc/trunk/debian/ -SUPPORTED - Ubuntu 12.04 LTS ----------------------------- +SUPPORTED - Ubuntu 12.04.5 LTS +------------------------------ # sudo apt-get install build-essential unzip cmake mercurial \ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ - libgtest-dev libpng-dev libsqlite3-dev libssl-dev \ - zlib1g-dev libdcmtk2-dev libboost-all-dev libwrap0-dev + libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \ + zlib1g-dev libdcmtk2-dev libboost1.48-all-dev libwrap0-dev \ + libcharls-dev -# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ +# cmake "-DDCMTK_LIBRARIES=boost_locale;CharLS;dcmjpls;wrap;oflog" \ -DALLOW_DOWNLOADS=ON \ -DUSE_SYSTEM_MONGOOSE=OFF \ -DUSE_SYSTEM_JSONCPP=OFF \ @@ -149,67 +112,36 @@ ~/Orthanc -SUPPORTED - Ubuntu 12.10 ------------------------- + +SUPPORTED - Ubuntu 14.04 LTS +---------------------------- # 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 libcharls-dev - -With JPEG: + libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \ + zlib1g-dev libdcmtk2-dev libboost-all-dev libwrap0-dev \ + libcharls-dev libjsoncpp-dev libpugixml-dev -# cmake "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" \ - -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_SYSTEM_JSONCPP=OFF \ - -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DUSE_SYSTEM_PUGIXML=OFF \ - ~/Orthanc - - -Without JPEG: - -# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ - -DALLOW_DOWNLOADS=ON \ - -DUSE_SYSTEM_MONGOOSE=OFF \ - -DUSE_SYSTEM_JSONCPP=OFF \ +# cmake -DALLOW_DOWNLOADS=ON \ -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ - -DUSE_SYSTEM_PUGIXML=OFF \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ - ~/Orthanc - - -SUPPORTED - Ubuntu 13.10 ------------------------- - -# 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 \ - -DUSE_SYSTEM_PUGIXML=OFF \ - -DENABLE_JPEG=OFF \ - -DENABLE_JPEG_LOSSLESS=OFF \ - ~/Orthanc + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DDCMTK_LIBRARIES=dcmjpls \ + ~/Orthanc -SUPPORTED - Fedora 19/20 +SUPPORTED - Fedora 20-22 ------------------------ -# sudo yum install make automake gcc gcc-c++ python cmake \ +# sudo yum install unzip make automake gcc gcc-c++ python cmake \ boost-devel curl-devel dcmtk-devel glog-devel \ - gtest-devel libpng-devel libsqlite3x-devel libuuid-devel \ + gtest-devel libpng-devel libsqlite3x-devel libuuid-devel jpeg-devel \ mongoose-devel openssl-devel jsoncpp-devel lua-devel pugixml-devel +You will also have to install "gflags-devel" on Fedora 21&22: + +# sudo yum install gflags-devel + # cmake "-DDCMTK_LIBRARIES=CharLS" \ -DSYSTEM_MONGOOSE_USE_CALLBACKS=OFF \ ~/Orthanc @@ -219,6 +151,37 @@ +SUPPORTED - FreeBSD 10.1 +------------------------ + +# pkg install jsoncpp pugixml lua51 curl googletest dcmtk cmake jpeg \ + e2fsprogs-libuuid glog boost-libs sqlite3 python libiconv + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DDCMTK_LIBRARIES="dcmdsig;charls;dcmjpls" \ + ~/Orthanc + + + +SUPPORTED - CentOS 6 +-------------------- + +# yum install unzip make automake gcc gcc-c++ python cmake curl-devel \ + libpng-devel sqlite-devel libuuid-devel openssl-devel \ + lua-devel mercurial patch tar + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_PUGIXML=OFF \ + -DUSE_SYSTEM_SQLITE=OFF \ + -DUSE_SYSTEM_BOOST=OFF \ + -DUSE_SYSTEM_DCMTK=OFF \ + -DUSE_SYSTEM_GOOGLE_TEST=OFF \ + -DUSE_SYSTEM_LIBJPEG=OFF \ + ~/Orthanc + Other Linux distributions?
--- a/NEWS Wed Feb 11 10:40:08 2015 +0100 +++ b/NEWS Wed Sep 30 13:23:31 2015 +0200 @@ -1,6 +1,160 @@ Pending changes in the mainline =============================== +* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive") +* "/tools/create-dicom": Support of binary tags encoded using data URI scheme + +Plugins +------- + +* New function "OrthancPluginRegisterErrorCode()" to declare custom error codes +* New function "OrthancPluginRegisterDictionaryTag()" to declare DICOM tags + +Lua +--- + +* Optional argument "keepStrings" in "DumpJson()" + +Maintenance +----------- + +* "/system" URI gives information about the plugins used for storage area and DB back-end +* Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers +* "/tools/create-dicom" can create tags with unknown VR + + +Version 0.9.4 (2015/09/16) +========================== + +* Preview of PDF files encapsulated in DICOM from Orthanc Explorer +* Creation of DICOM files with encapsulated PDF through "/tools/create-dicom" +* "limit" and "since" arguments while retrieving DICOM resources in the REST API +* Support of "deflate" and "gzip" content-types in HTTP requests +* Options to validate peers against CA certificates in HTTPS requests +* New configuration option: "HttpTimeout" to set the default timeout for HTTP requests + +Lua +--- + +* More information about the origin request in the "OnStoredInstance()" and + "ReceivedInstanceFilter()" callbacks. WARNING: This can result in + incompatibilities wrt. previous versions of Orthanc. +* New function "GetOrthancConfiguration()" to get the Orthanc configuration + +Plugins +------- + +* New functions to compress/uncompress images using PNG and JPEG +* New functions to issue HTTP requests from plugins +* New function "OrthancPluginBufferCompression()" to (un)compress memory buffers +* New function "OrthancPluginReadFile()" to read files from the filesystem +* New function "OrthancPluginWriteFile()" to write files to the filesystem +* New function "OrthancPluginGetErrorDescription()" to convert error codes to strings +* New function "OrthancPluginSendHttpStatus()" to send HTTP status with a body +* New function "OrthancPluginRegisterRestCallbackNoLock()" for high-performance plugins +* Plugins have access to explicit error codes +* Improvements to the sample "ServeFolders" plugin +* Primitives to upgrade the database version in plugins + +Maintenance +----------- + +* Many code refactorings +* Improved error codes (no more custom descriptions in exceptions) +* If error while calling the REST API, the answer body contains description of the error + (this feature can be disabled with the "HttpDescribeErrors" option) +* Upgrade to curl 7.44.0 for static and Windows builds +* Upgrade to libcurl 1.0.2d for static and Windows builds +* Depends on libjpeg 9a +* Bypass zlib uncompression if "StorageCompression" is enabled and HTTP client supports deflate + + +Version 0.9.3 (2015/08/07) +========================== + +* C-Echo testing can be triggered from Orthanc Explorer (in the query/retrieve page) +* Removal of the dependency upon Google Log, Orthanc now uses its internal logger + (use -DENABLE_GOOGLE_LOG=ON to re-enable Google Log) +* Upgrade to JsonCpp 0.10.5 for static and Windows builds + + +Version 0.9.2 (2015/08/02) +========================== + +* Upgrade to Boost 1.58.0 for static and Windows builds +* Source code repository moved from Google Code to BitBucket +* Inject version information into Windows binaries +* Fix access to binary data in HTTP/REST requests by Lua scripts +* Fix potential deadlock in the callbacks of plugins + + +Version 0.9.1 (2015/07/02) +========================== + +General +------- + +* The configuration can be splitted into several files stored inside the same folder +* Custom setting of the local AET during C-Store SCU (both in Lua and in the REST API) +* Many code refactorings + +Lua +--- + +* Access to the REST API of Orthanc (RestApiGet, RestApiPost, RestApiPut, RestApiDelete) +* Functions to convert between Lua values and JSON strings: "ParseJson" and "DumpJson" +* New events: "OnStablePatient", "OnStableStudy", "OnStableSeries", "Initialize", "Finalize" + +Plugins +------- + +* Plugins can retrieve the configuration file directly as a JSON string +* Plugins can send answers as multipart messages + +Fixes +----- + +* Fix compatibility issues for C-Find SCU to Siemens Syngo.Via modalities SCP +* Fix issue 15 (Lua scripts making HTTP requests) +* Fix issue 35 (Characters in PatientID string are not protected for C-Find) +* Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges) + + +Version 0.9.0 (2015/06/03) +========================== + +Major +----- + +* DICOM Query/Retrieve available from Orthanc Explorer +* C-Move SCU and C-Find SCU are accessible through the REST API +* "?expand" flag for URIs "/patients", "/studies" and "/series" +* "/tools/find" URI to search for DICOM resources from REST +* Support of FreeBSD +* The "Orthanc Client" SDK is now a separate project + +Minor +----- + +* Speed-up in Orthanc Explorer for large amount of images +* Speed-up of the C-Find SCP server of Orthanc +* Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts +* Option "CaseSensitivePN" to enable case-insensitive C-Find SCP + +Fixes +----- + +* Prevent freeze on C-FIND if no DICOM tag is to be returned +* Fix slow C-Store SCP on recent versions of Linux, if + USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009) +* Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) +* Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding +* Plugins now receive duplicated GET arguments in their REST callbacks + + +Version 0.8.6 (2015/02/12) +========================== + Major ----- @@ -15,6 +169,7 @@ * More flexible "/modify" and "/anonymize" for single instance * Access to called AET and remote AET from Lua scripts ("OnStoredInstance") * Option "DicomAssociationCloseDelay" to set delay before closing DICOM association +* ZIP archives now display the accession number of the studies Plugins ------- @@ -32,6 +187,7 @@ * Code refactorings * Fix issue 25 (AET with underscore not allowed) * Fix replacement and insertion of private DICOM tags +* Fix anonymization generating non-portable DICOM files Version 0.8.5 (2014/11/04)
--- a/OrthancCppClient/Instance.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,286 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/PrecompiledHeaders.h" -#include "Instance.h" - -#include "OrthancConnection.h" - -#include <boost/lexical_cast.hpp> - -namespace OrthancClient -{ - void Instance::DownloadImage() - { - if (reader_.get() == NULL) - { - const char* suffix; - switch (mode_) - { - case Orthanc::ImageExtractionMode_Preview: - suffix = "preview"; - break; - - case Orthanc::ImageExtractionMode_UInt8: - suffix = "image-uint8"; - break; - - case Orthanc::ImageExtractionMode_UInt16: - suffix = "image-uint16"; - break; - - case Orthanc::ImageExtractionMode_Int16: - suffix = "image-int16"; - break; - - default: - throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); - } - - Orthanc::HttpClient client(connection_.GetHttpClient()); - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/" + suffix); - std::string png; - - if (!client.Apply(png)) - { - throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); - } - - reader_.reset(new Orthanc::PngReader); - reader_->ReadFromMemory(png); - } - } - - void Instance::DownloadDicom() - { - if (dicom_.get() == NULL) - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/file"); - - dicom_.reset(new std::string); - - if (!client.Apply(*dicom_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - } - - Instance::Instance(const OrthancConnection& connection, - const char* id) : - connection_(connection), - id_(id), - mode_(Orthanc::ImageExtractionMode_Int16) - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/instances/" + id_ + "/simplified-tags"); - Json::Value v; - if (!client.Apply(tags_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - const char* Instance::GetTagAsString(const char* tag) const - { - if (tags_.isMember(tag)) - { - return tags_[tag].asCString(); - } - else - { - throw OrthancClientException(Orthanc::ErrorCode_InexistentItem); - } - } - - float Instance::GetTagAsFloat(const char* tag) const - { - std::string value = GetTagAsString(tag); - - try - { - return boost::lexical_cast<float>(value); - } - catch (boost::bad_lexical_cast) - { - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - } - - int Instance::GetTagAsInt(const char* tag) const - { - std::string value = GetTagAsString(tag); - - try - { - return boost::lexical_cast<int>(value); - } - catch (boost::bad_lexical_cast) - { - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - } - - unsigned int Instance::GetWidth() - { - DownloadImage(); - return reader_->GetWidth(); - } - - unsigned int Instance::GetHeight() - { - DownloadImage(); - return reader_->GetHeight(); - } - - unsigned int Instance::GetPitch() - { - DownloadImage(); - return reader_->GetPitch(); - } - - Orthanc::PixelFormat Instance::GetPixelFormat() - { - DownloadImage(); - return reader_->GetFormat(); - } - - const void* Instance::GetBuffer() - { - DownloadImage(); - return reader_->GetConstBuffer(); - } - - const void* Instance::GetBuffer(unsigned int y) - { - DownloadImage(); - return reader_->GetConstRow(y); - } - - void Instance::DiscardImage() - { - reader_.reset(); - } - - void Instance::DiscardDicom() - { - dicom_.reset(); - } - - - void Instance::SetImageExtractionMode(Orthanc::ImageExtractionMode mode) - { - if (mode_ == mode) - { - return; - } - - DiscardImage(); - mode_ = mode; - } - - - void Instance::SplitVectorOfFloats(std::vector<float>& target, - const char* tag) - { - const std::string value = GetTagAsString(tag); - - target.clear(); - - try - { - std::string tmp; - for (size_t i = 0; i < value.size(); i++) - { - if (value[i] == '\\') - { - target.push_back(boost::lexical_cast<float>(tmp)); - tmp.clear(); - } - else - { - tmp.push_back(value[i]); - } - } - - target.push_back(boost::lexical_cast<float>(tmp)); - } - catch (boost::bad_lexical_cast) - { - // Unable to parse the Image Orientation Patient. - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - const uint64_t Instance::GetDicomSize() - { - DownloadDicom(); - assert(dicom_.get() != NULL); - return dicom_->size(); - } - - const void* Instance::GetDicom() - { - DownloadDicom(); - assert(dicom_.get() != NULL); - - if (dicom_->size() == 0) - { - return NULL; - } - else - { - return &((*dicom_) [0]); - } - } - - - 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 Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <string> -#include <json/value.h> - -#include "OrthancClientException.h" -#include "../Core/IDynamicObject.h" -#include "../Core/ImageFormats/PngReader.h" - -namespace OrthancClient -{ - class OrthancConnection; - - /** - * {summary}{Connection to an instance stored in %Orthanc.} - * {description}{This class encapsulates a connection to an image instance - * from a remote instance of %Orthanc.} - **/ - class LAAW_API Instance : public Orthanc::IDynamicObject - { - private: - const OrthancConnection& connection_; - std::string id_; - Json::Value tags_; - std::auto_ptr<Orthanc::PngReader> reader_; - Orthanc::ImageExtractionMode mode_; - std::auto_ptr<std::string> dicom_; - std::string content_; - - void DownloadImage(); - - void DownloadDicom(); - - public: - /** - * {summary}{Create a connection to some image instance.} - * {param}{connection The remote instance of %Orthanc.} - * {param}{id The %Orthanc identifier of the image instance.} - **/ - Instance(const OrthancConnection& connection, - const char* id); - - - /** - * {summary}{Get the %Orthanc identifier of this identifier.} - * {returns}{The identifier.} - **/ - const char* GetId() const - { - return id_.c_str(); - } - - - /** - * {summary}{Set the extraction mode for the 2D image corresponding to this instance.} - * {param}{mode The extraction mode.} - **/ - void SetImageExtractionMode(Orthanc::ImageExtractionMode mode); - - /** - * {summary}{Get the extraction mode for the 2D image corresponding to this instance.} - * {returns}{The extraction mode.} - **/ - Orthanc::ImageExtractionMode GetImageExtractionMode() const - { - return mode_; - } - - - /** - * {summary}{Get the string value of some DICOM tag of this instance.} - * {param}{tag The name of the tag of interest.} - * {returns}{The value of the tag.} - **/ - const char* GetTagAsString(const char* tag) const; - - /** - * {summary}{Get the floating point value that is stored in some DICOM tag of this instance.} - * {param}{tag The name of the tag of interest.} - * {returns}{The value of the tag.} - **/ - float GetTagAsFloat(const char* tag) const; - - /** - * {summary}{Get the integer value that is stored in some DICOM tag of this instance.} - * {param}{tag The name of the tag of interest.} - * {returns}{The value of the tag.} - **/ - int32_t GetTagAsInt(const char* tag) const; - - - /** - * {summary}{Get the width of the 2D image.} - * {description}{Get the width of the 2D image that is encoded by this DICOM instance.} - * {returns}{The width.} - **/ - uint32_t GetWidth(); - - /** - * {summary}{Get the height of the 2D image.} - * {description}{Get the height of the 2D image that is encoded by this DICOM instance.} - * {returns}{The height.} - **/ - uint32_t GetHeight(); - - /** - * {summary}{Get the number of bytes between two lines of the image (pitch).} - * {description}{Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image.} - * {returns}{The pitch.} - **/ - uint32_t GetPitch(); - - /** - * {summary}{Get the format of the pixels of the 2D image.} - * {description}{Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image.} - * {returns}{The pixel format.} - **/ - Orthanc::PixelFormat GetPixelFormat(); - - /** - * {summary}{Access the memory buffer in which the raw pixels of the 2D image are stored.} - * {returns}{A pointer to the memory buffer.} - **/ - const void* GetBuffer(); - - /** - * {summary}{Access the memory buffer in which the raw pixels of some line of the 2D image are stored.} - * {param}{y The line of interest.} - * {returns}{A pointer to the memory buffer.} - **/ - const void* GetBuffer(uint32_t y); - - /** - * {summary}{Get the size of the DICOM file corresponding to this instance.} - * {returns}{The file size.} - **/ - const uint64_t GetDicomSize(); - - /** - * {summary}{Get a pointer to the content of the DICOM file corresponding to this instance.} - * {returns}{The DICOM file.} - **/ - const void* GetDicom(); - - /** - * {summary}{Discard the downloaded 2D image, so as to make room in memory.} - **/ - void DiscardImage(); - - /** - * {summary}{Discard the downloaded DICOM file, so as to make room in memory.} - **/ - void DiscardDicom(); - - 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 Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/OrthancException.h" -#include <laaw/laaw.h> - -namespace OrthancClient -{ - class OrthancClientException : public ::Laaw::LaawException - { - public: - OrthancClientException(Orthanc::ErrorCode code) : - LaawException(Orthanc::OrthancException::GetDescription(code)) - { - } - - OrthancClientException(const char* message) : - LaawException(message) - { - } - - OrthancClientException(const std::string& message) : - LaawException(message) - { - } - }; -}
--- a/OrthancCppClient/OrthancConnection.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/PrecompiledHeaders.h" -#include "OrthancConnection.h" - -#include "../Core/Toolbox.h" - -namespace OrthancClient -{ - void OrthancConnection::ReadPatients() - { - client_.SetMethod(Orthanc::HttpMethod_Get); - client_.SetUrl(orthancUrl_ + "/patients"); - - Json::Value v; - if (!client_.Apply(content_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - Orthanc::IDynamicObject* OrthancConnection::GetFillerItem(size_t index) - { - Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); - std::string id = content_[tmp].asString(); - return new Patient(*this, id.c_str()); - } - - Patient& OrthancConnection::GetPatient(unsigned int index) - { - return dynamic_cast<Patient&>(patients_.GetItem(index)); - } - - OrthancConnection::OrthancConnection(const char* orthancUrl) : - orthancUrl_(orthancUrl), patients_(*this) - { - ReadPatients(); - } - - OrthancConnection::OrthancConnection(const char* orthancUrl, - const char* username, - const char* password) : - orthancUrl_(orthancUrl), patients_(*this) - { - client_.SetCredentials(username, password); - ReadPatients(); - } - - - void OrthancConnection::Store(const void* dicom, uint64_t size) - { - if (size == 0) - { - return; - } - - client_.SetMethod(Orthanc::HttpMethod_Post); - client_.SetUrl(orthancUrl_ + "/instances"); - - // Copy the DICOM file in the POST body. TODO - Avoid memory copy - client_.AccessPostData().resize(static_cast<size_t>(size)); - memcpy(&client_.AccessPostData()[0], dicom, static_cast<size_t>(size)); - - Json::Value v; - if (!client_.Apply(v)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - - Reload(); - } - - - void OrthancConnection::StoreFile(const char* filename) - { - std::string content; - Orthanc::Toolbox::ReadFile(content, filename); - - if (content.size() != 0) - { - Store(&content[0], content.size()); - } - } - -}
--- a/OrthancCppClient/OrthancConnection.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/HttpClient.h" - -#include "Patient.h" - -namespace OrthancClient -{ - /** - * {summary}{Connection to an instance of %Orthanc.} - * {description}{This class encapsulates a connection to a remote instance - * of %Orthanc through its REST API.} - **/ - class LAAW_API OrthancConnection : - public boost::noncopyable, - private Orthanc::ArrayFilledByThreads::IFiller - { - private: - Orthanc::HttpClient client_; - std::string orthancUrl_; - Orthanc::ArrayFilledByThreads patients_; - Json::Value content_; - - void ReadPatients(); - - virtual size_t GetFillerSize() - { - return content_.size(); - } - - virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); - - public: - /** - * {summary}{Create a connection to an instance of %Orthanc.} - * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.} - **/ - OrthancConnection(const char* orthancUrl); - - /** - * {summary}{Create a connection to an instance of %Orthanc, with authentication.} - * {param}{orthancUrl URL to which the REST API of %Orthanc is listening.} - * {param}{username The username.} - * {param}{password The password.} - **/ - OrthancConnection(const char* orthancUrl, - const char* username, - const char* password); - - virtual ~OrthancConnection() - { - } - - /** - * {summary}{Returns the number of threads for this connection.} - * {description}{Returns the number of simultaneous connections - * that are used when downloading information from this instance - * of %Orthanc.} - * {returns}{The number of threads.} - **/ - uint32_t GetThreadCount() const - { - return patients_.GetThreadCount(); - } - - /** - * {summary}{Sets the number of threads for this connection.} - * {description}{Sets the number of simultaneous connections - * that are used when downloading information from this instance - * of %Orthanc.} - * {param}{threadCount The number of threads.} - **/ - void SetThreadCount(uint32_t threadCount) - { - patients_.SetThreadCount(threadCount); - } - - /** - * {summary}{Reload the list of the patients.} - * {description}{This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} - **/ - void Reload() - { - ReadPatients(); - patients_.Invalidate(); - } - - LAAW_API_INTERNAL const Orthanc::HttpClient& GetHttpClient() const - { - return client_; - } - - /** - * {summary}{Returns the URL of this instance of %Orthanc.} - * {description}{Returns the URL of the remote %Orthanc instance to which this object is connected.} - * {returns}{The URL.} - **/ - const char* GetOrthancUrl() const - { - return orthancUrl_.c_str(); - } - - /** - * {summary}{Returns the number of patients.} - * {description}{Returns the number of patients that are stored in the remote instance of %Orthanc.} - * {returns}{The number of patients.} - **/ - uint32_t GetPatientCount() - { - return patients_.GetSize(); - } - - /** - * {summary}{Get some patient.} - * {description}{This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive).} - * {param}{index The index of the patient of interest.} - * {returns}{The patient.} - **/ - Patient& GetPatient(uint32_t index); - - /** - * {summary}{Delete some patient.} - * {description}{Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} - * {param}{index The index of the patient of interest.} - * {returns}{The patient.} - **/ - void DeletePatient(uint32_t index) - { - GetPatient(index).Delete(); - Reload(); - } - - /** - * {summary}{Send a DICOM file.} - * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} - * {param}{filename Path to the DICOM file} - **/ - void StoreFile(const char* filename); - - /** - * {summary}{Send a DICOM file that is contained inside a memory buffer.} - * {description}{This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated.} - * {param}{dicom The memory buffer containing the DICOM file.} - * {param}{size The size of the DICOM file.} - **/ - void Store(const void* dicom, uint64_t size); - }; -}
--- a/OrthancCppClient/OrthancCppClient.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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/>. - **/ - - -/** - * The sources of the C++ client library must be put in this file to - * avoid problems with precompiled headers. - **/ - -#include "../Core/ChunkedBuffer.cpp" -#include "../Core/Enumerations.cpp" -#include "../Core/HttpClient.cpp" -#include "../Core/ImageFormats/ImageAccessor.cpp" -#include "../Core/ImageFormats/ImageBuffer.cpp" -#include "../Core/ImageFormats/PngReader.cpp" -#include "../Core/MultiThreading/ArrayFilledByThreads.cpp" -#include "../Core/MultiThreading/SharedMessageQueue.cpp" -#include "../Core/MultiThreading/ThreadedCommandProcessor.cpp" -#include "../Core/OrthancException.cpp" -#include "../Core/Toolbox.cpp" -#include "../OrthancCppClient/Instance.cpp" -#include "../OrthancCppClient/OrthancConnection.cpp" -#include "../OrthancCppClient/Patient.cpp" -#include "../OrthancCppClient/Series.cpp" -#include "../OrthancCppClient/Study.cpp"
--- a/OrthancCppClient/Patient.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/PrecompiledHeaders.h" -#include "Patient.h" - -#include "OrthancConnection.h" - -namespace OrthancClient -{ - void Patient::ReadPatient() - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_); - - Json::Value v; - if (!client.Apply(patient_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - Orthanc::IDynamicObject* Patient::GetFillerItem(size_t index) - { - Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); - std::string id = patient_["Studies"][tmp].asString(); - return new Study(connection_, id.c_str()); - } - - Patient::Patient(const OrthancConnection& connection, - const char* id) : - connection_(connection), - id_(id), - studies_(*this) - { - studies_.SetThreadCount(connection.GetThreadCount()); - ReadPatient(); - } - - const char* Patient::GetMainDicomTag(const char* tag, const char* defaultValue) const - { - if (patient_["MainDicomTags"].isMember(tag)) - { - return patient_["MainDicomTags"][tag].asCString(); - } - else - { - return defaultValue; - } - } - - void Patient::Delete() - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - client.SetMethod(Orthanc::HttpMethod_Delete); - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/patients/" + id_); - - std::string s; - if (!client.Apply(s)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } -}
--- a/OrthancCppClient/Patient.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "Study.h" - -namespace OrthancClient -{ - /** - * {summary}{Connection to a patient stored in %Orthanc.} - * {description}{This class encapsulates a connection to a patient - * from a remote instance of %Orthanc.} - **/ - class LAAW_API Patient : - public Orthanc::IDynamicObject, - private Orthanc::ArrayFilledByThreads::IFiller - { - private: - const OrthancConnection& connection_; - std::string id_; - Json::Value patient_; - Orthanc::ArrayFilledByThreads studies_; - - void ReadPatient(); - - virtual size_t GetFillerSize() - { - return patient_["Studies"].size(); - } - - virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); - - public: - /** - * {summary}{Create a connection to some patient.} - * {param}{connection The remote instance of %Orthanc.} - * {param}{id The %Orthanc identifier of the patient.} - **/ - Patient(const OrthancConnection& connection, - const char* id); - - /** - * {summary}{Reload the studies of this patient.} - * {description}{This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated.} - **/ - void Reload() - { - studies_.Reload(); - } - - /** - * {summary}{Return the number of studies for this patient.} - * {returns}{The number of studies.} - **/ - uint32_t GetStudyCount() - { - return studies_.GetSize(); - } - - /** - * {summary}{Get some study of this patient.} - * {description}{This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive).} - * {param}{index The index of the study of interest.} - * {returns}{The study.} - **/ - Study& GetStudy(uint32_t index) - { - return dynamic_cast<Study&>(studies_.GetItem(index)); - } - - /** - * {summary}{Get the %Orthanc identifier of this patient.} - * {returns}{The identifier.} - **/ - const char* GetId() const - { - return id_.c_str(); - } - - /** - * {summary}{Get the value of one of the main DICOM tags for this patient.} - * {param}{tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate").} - * {param}{defaultValue The default value to be returned if this tag does not exist.} - * {returns}{The value of the tag.} - **/ - const char* GetMainDicomTag(const char* tag, - const char* defaultValue) const; - - LAAW_API_INTERNAL void Delete(); - }; -}
--- a/OrthancCppClient/Series.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,527 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/PrecompiledHeaders.h" -#include "Series.h" - -#include "OrthancConnection.h" - -#include <set> -#include <boost/lexical_cast.hpp> - -namespace OrthancClient -{ - namespace - { - class SliceLocator - { - private: - float normal_[3]; - - public: - SliceLocator(Instance& someSlice) - { - /** - * Compute the slice normal from Image Orientation Patient. - * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice - * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html - * http://www.itk.org/pipermail/insight-users/2003-September/004762.html - **/ - - std::vector<float> cosines; - someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient"); // 0020-0037 - - if (cosines.size() != 6) - { - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - - normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4]; - normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5]; - normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3]; - } - - - /** - * Compute the distance of some slice along the slice normal. - **/ - float ComputeSliceLocation(Instance& instance) const - { - std::vector<float> ipp; - instance.SplitVectorOfFloats(ipp, "ImagePositionPatient"); // 0020-0032 - if (ipp.size() != 3) - { - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - - float dist = 0; - - for (int i = 0; i < 3; i++) - { - dist += normal_[i] * ipp[i]; - } - - return dist; - } - }; - - class ImageDownloadCommand : public Orthanc::ICommand - { - private: - Orthanc::PixelFormat format_; - Orthanc::ImageExtractionMode mode_; - Instance& instance_; - void* target_; - size_t lineStride_; - - public: - ImageDownloadCommand(Instance& instance, - Orthanc::PixelFormat format, - Orthanc::ImageExtractionMode mode, - void* target, - size_t lineStride) : - format_(format), - mode_(mode), - instance_(instance), - target_(target), - lineStride_(lineStride) - { - instance_.SetImageExtractionMode(mode); - } - - virtual bool Execute() - { - using namespace Orthanc; - - unsigned int width = instance_.GetHeight(); - - for (unsigned int y = 0; y < instance_.GetHeight(); y++) - { - uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_; - - if (instance_.GetPixelFormat() == format_) - { - memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth()); - } - else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 && - format_ == PixelFormat_RGB24) - { - const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y)); - for (unsigned int x = 0; x < width; x++, s++, p += 3) - { - p[0] = *s; - p[1] = *s; - p[2] = *s; - } - } - else - { - throw OrthancClientException(ErrorCode_NotImplemented); - } - } - - // Do not keep the image in memory, as we are loading 3D images - instance_.DiscardImage(); - - return true; - } - }; - - - class ProgressToFloatListener : public Orthanc::ThreadedCommandProcessor::IListener - { - private: - float* target_; - - public: - ProgressToFloatListener(float* target) : target_(target) - { - } - - virtual void SignalProgress(unsigned int current, - unsigned int total) - { - if (total == 0) - { - *target_ = 0; - } - else - { - *target_ = static_cast<float>(current) / static_cast<float>(total); - } - } - - virtual void SignalSuccess(unsigned int total) - { - *target_ = 1; - } - - virtual void SignalFailure() - { - *target_ = 0; - } - - virtual void SignalCancel() - { - *target_ = 0; - } - }; - - } - - - void Series::Check3DImage() - { - if (!Is3DImage()) - { - throw OrthancClientException(Orthanc::ErrorCode_NotImplemented); - } - } - - bool Series::Is3DImageInternal() - { - try - { - if (GetInstanceCount() == 0) - { - // Empty image, use some default value (should never happen) - voxelSizeX_ = 1; - voxelSizeY_ = 1; - voxelSizeZ_ = 1; - sliceThickness_ = 1; - - return true; - } - - // Choose a reference slice - Instance& reference = GetInstance(0); - - // Check that all the child instances share the same 3D parameters - for (unsigned int i = 0; i < GetInstanceCount(); i++) - { - Instance& i2 = GetInstance(i); - - if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) || - std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) || - std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) || - std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) || - std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing"))) - { - return false; - } - } - - - // Extract X/Y voxel size and slice thickness - std::string s = GetInstance(0).GetTagAsString("PixelSpacing"); // 0028-0030 - size_t pos = s.find('\\'); - assert(pos != std::string::npos); - std::string sy = s.substr(0, pos); - std::string sx = s.substr(pos + 1); - - try - { - voxelSizeX_ = boost::lexical_cast<float>(sx); - voxelSizeY_ = boost::lexical_cast<float>(sy); - } - catch (boost::bad_lexical_cast) - { - throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat); - } - - sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness"); // 0018-0050 - - - // Compute the location of each slice to extract the voxel size along Z - voxelSizeZ_ = std::numeric_limits<float>::infinity(); - - SliceLocator locator(reference); - float referenceSliceLocation = locator.ComputeSliceLocation(reference); - - std::set<float> l; - for (unsigned int i = 0; i < GetInstanceCount(); i++) - { - float location = locator.ComputeSliceLocation(GetInstance(i)); - float distanceToReferenceSlice = fabs(location - referenceSliceLocation); - - l.insert(location); - - if (distanceToReferenceSlice > std::numeric_limits<float>::epsilon() && - distanceToReferenceSlice < voxelSizeZ_) - { - voxelSizeZ_ = distanceToReferenceSlice; - } - } - - - // Make sure that 2 slices do not share the same Z location - return l.size() == GetInstanceCount(); - } - catch (OrthancClientException) - { - return false; - } - } - - void Series::ReadSeries() - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/series/" + id_); - Json::Value v; - if (!client.Apply(series_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - Orthanc::IDynamicObject* Series::GetFillerItem(size_t index) - { - Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); - std::string id = series_["Instances"][tmp].asString(); - return new Instance(connection_, id.c_str()); - } - - Series::Series(const OrthancConnection& connection, - const char* id) : - connection_(connection), - id_(id), - instances_(*this) - { - ReadSeries(); - status_ = Status3DImage_NotTested; - url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_; - - voxelSizeX_ = 0; - voxelSizeY_ = 0; - voxelSizeZ_ = 0; - sliceThickness_ = 0; - - instances_.SetThreadCount(connection.GetThreadCount()); - } - - - bool Series::Is3DImage() - { - if (status_ == Status3DImage_NotTested) - { - status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False; - } - - return status_ == Status3DImage_True; - } - - unsigned int Series::GetInstanceCount() - { - return instances_.GetSize(); - } - - Instance& Series::GetInstance(unsigned int index) - { - return dynamic_cast<Instance&>(instances_.GetItem(index)); - } - - unsigned int Series::GetWidth() - { - Check3DImage(); - - if (GetInstanceCount() == 0) - return 0; - else - return GetInstance(0).GetTagAsInt("Columns"); - } - - unsigned int Series::GetHeight() - { - Check3DImage(); - - if (GetInstanceCount() == 0) - return 0; - else - return GetInstance(0).GetTagAsInt("Rows"); - } - - const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const - { - if (series_["MainDicomTags"].isMember(tag)) - { - return series_["MainDicomTags"][tag].asCString(); - } - else - { - return defaultValue; - } - } - - - - void Series::Load3DImageInternal(void* target, - Orthanc::PixelFormat format, - size_t lineStride, - size_t stackStride, - Orthanc::ThreadedCommandProcessor::IListener* listener) - { - using namespace Orthanc; - - // Choose the extraction mode, depending on the format of the - // target image. - - uint8_t bytesPerPixel; - ImageExtractionMode mode; - - switch (format) - { - case PixelFormat_RGB24: - bytesPerPixel = 3; - mode = ImageExtractionMode_Preview; - break; - - case PixelFormat_Grayscale8: - bytesPerPixel = 1; - mode = ImageExtractionMode_UInt8; // Preview ??? - break; - - case PixelFormat_Grayscale16: - bytesPerPixel = 2; - mode = ImageExtractionMode_UInt16; - break; - - case PixelFormat_SignedGrayscale16: - bytesPerPixel = 2; - mode = ImageExtractionMode_UInt16; - format = PixelFormat_Grayscale16; - break; - - default: - throw OrthancClientException(ErrorCode_NotImplemented); - } - - - // Check that the target image is properly sized - unsigned int sx = GetWidth(); - unsigned int sy = GetHeight(); - - if (lineStride < sx * bytesPerPixel || - stackStride < sx * sy * bytesPerPixel) - { - throw OrthancClientException(ErrorCode_BadRequest); - } - - if (sx == 0 || sy == 0 || GetInstanceCount() == 0) - { - // Empty image, nothing to do - if (listener) - listener->SignalSuccess(0); - return; - } - - - /** - * Order the stacks according to their distance along the slice - * normal (using the "Image Position Patient" tag). This works - * even if the "SliceLocation" tag is absent. - **/ - SliceLocator locator(GetInstance(0)); - - typedef std::map<float, Instance*> Instances; - Instances instances; - for (unsigned int i = 0; i < GetInstanceCount(); i++) - { - float dist = locator.ComputeSliceLocation(GetInstance(i)); - instances[dist] = &GetInstance(i); - } - - if (instances.size() != GetInstanceCount()) - { - // Several instances have the same Z coordinate - throw OrthancClientException(ErrorCode_NotImplemented); - } - - - // Submit the download of each stack as a set of commands - ThreadedCommandProcessor processor(connection_.GetThreadCount()); - - if (listener != NULL) - { - processor.SetListener(*listener); - } - - uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target); - for (Instances::iterator it = instances.begin(); it != instances.end(); ++it) - { - processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride)); - stackTarget += stackStride; - } - - - // Wait for all the stacks to be downloaded - if (!processor.Join()) - { - throw OrthancClientException(ErrorCode_NetworkProtocol); - } - } - - float Series::GetVoxelSizeX() - { - Check3DImage(); // Is3DImageInternal() will compute the voxel sizes - return voxelSizeX_; - } - - float Series::GetVoxelSizeY() - { - Check3DImage(); // Is3DImageInternal() will compute the voxel sizes - return voxelSizeY_; - } - - float Series::GetVoxelSizeZ() - { - Check3DImage(); // Is3DImageInternal() will compute the voxel sizes - return voxelSizeZ_; - } - - float Series::GetSliceThickness() - { - Check3DImage(); // Is3DImageInternal() will compute the voxel sizes - return sliceThickness_; - } - - void Series::Load3DImage(void* target, - Orthanc::PixelFormat format, - int64_t lineStride, - int64_t stackStride, - float* progress) - { - ProgressToFloatListener listener(progress); - Load3DImageInternal(target, format, static_cast<size_t>(lineStride), - static_cast<size_t>(stackStride), &listener); - } -}
--- a/OrthancCppClient/Series.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,239 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "Instance.h" - -#include "../Core/MultiThreading/ArrayFilledByThreads.h" -#include "../Core/MultiThreading/ThreadedCommandProcessor.h" - -namespace OrthancClient -{ - /** - * {summary}{Connection to a series stored in %Orthanc.} - * {description}{This class encapsulates a connection to a series - * from a remote instance of %Orthanc.} - **/ - class LAAW_API Series : - public Orthanc::IDynamicObject, - private Orthanc::ArrayFilledByThreads::IFiller - { - private: - enum Status3DImage - { - Status3DImage_NotTested, - Status3DImage_True, - Status3DImage_False - }; - - const OrthancConnection& connection_; - std::string id_, url_; - Json::Value series_; - Orthanc::ArrayFilledByThreads instances_; - Status3DImage status_; - - float voxelSizeX_; - float voxelSizeY_; - float voxelSizeZ_; - float sliceThickness_; - - void Check3DImage(); - - bool Is3DImageInternal(); - - void ReadSeries(); - - virtual size_t GetFillerSize() - { - return series_["Instances"].size(); - } - - virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); - - void Load3DImageInternal(void* target, - Orthanc::PixelFormat format, - size_t lineStride, - size_t stackStride, - Orthanc::ThreadedCommandProcessor::IListener* listener); - - public: - /** - * {summary}{Create a connection to some series.} - * {param}{connection The remote instance of %Orthanc.} - * {param}{id The %Orthanc identifier of the series.} - **/ - Series(const OrthancConnection& connection, - const char* id); - - /** - * {summary}{Reload the instances of this series.} - * {description}{This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated.} - **/ - void Reload() - { - instances_.Reload(); - } - - /** - * {summary}{Return the number of instances for this series.} - * {returns}{The number of instances.} - **/ - uint32_t GetInstanceCount(); - - /** - * {summary}{Get some instance of this series.} - * {description}{This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive).} - * {param}{index The index of the instance of interest.} - * {returns}{The instance.} - **/ - Instance& GetInstance(uint32_t index); - - /** - * {summary}{Get the %Orthanc identifier of this series.} - * {returns}{The identifier.} - **/ - const char* GetId() const - { - return id_.c_str(); - } - - /** - * {summary}{Returns the URL to this series.} - * {returns}{The URL.} - **/ - const char* GetUrl() const - { - return url_.c_str(); - } - - - /** - * {summary}{Get the value of one of the main DICOM tags for this series.} - * {param}{tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...).} - * {param}{defaultValue The default value to be returned if this tag does not exist.} - * {returns}{The value of the tag.} - **/ - const char* GetMainDicomTag(const char* tag, - const char* defaultValue) const; - - /** - * {summary}{Test whether this series encodes a 3D image that can be downloaded from %Orthanc.} - * {returns}{"true" if and only if this is a 3D image.} - **/ - bool Is3DImage(); - - /** - * {summary}{Get the width of the 3D image.} - * {description}{Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image.} - * {returns}{The width.} - **/ - uint32_t GetWidth(); - - /** - * {summary}{Get the height of the 3D image.} - * {description}{Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image.} - * {returns}{The height.} - **/ - uint32_t GetHeight(); - - /** - * {summary}{Get the physical size of a voxel along the X-axis.} - * {description}{Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image.} - * {returns}{The voxel size.} - **/ - float GetVoxelSizeX(); - - /** - * {summary}{Get the physical size of a voxel along the Y-axis.} - * {description}{Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image.} - * {returns}{The voxel size.} - **/ - float GetVoxelSizeY(); - - /** - * {summary}{Get the physical size of a voxel along the Z-axis.} - * {description}{Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image.} - * {returns}{The voxel size.} - **/ - float GetVoxelSizeZ(); - - /** - * {summary}{Get the slice thickness.} - * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.} - * {returns}{The slice thickness.} - **/ - float GetSliceThickness(); - - LAAW_API_INTERNAL void Load3DImage(void* target, - Orthanc::PixelFormat format, - int64_t lineStride, - int64_t stackStride, - Orthanc::ThreadedCommandProcessor::IListener& listener) - { - Load3DImageInternal(target, format, static_cast<size_t>(lineStride), - static_cast<size_t>(stackStride), &listener); - } - - /** - * {summary}{Load the 3D image into a memory buffer.} - * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image.} - * {param}{target The target memory buffer.} - * {param}{format The memory layout of the voxels.} - * {param}{lineStride The number of bytes between two lines in the target memory buffer.} - * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.} - **/ - void Load3DImage(void* target, - Orthanc::PixelFormat format, - int64_t lineStride, - int64_t stackStride) - { - Load3DImageInternal(target, format, static_cast<size_t>(lineStride), - static_cast<size_t>(stackStride), NULL); - } - - /** - * {summary}{Load the 3D image into a memory buffer.} - * {description}{Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. This method will also update a progress indicator to monitor the loading of the image.} - * {param}{target The target memory buffer.} - * {param}{format The memory layout of the voxels.} - * {param}{lineStride The number of bytes between two lines in the target memory buffer.} - * {param}{stackStride The number of bytes between two 2D slices in the target memory buffer.} - * {param}{progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread.} - **/ - void Load3DImage(void* target, - Orthanc::PixelFormat format, - int64_t lineStride, - int64_t stackStride, - float* progress); - }; -}
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1525 +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/>. - **/ - - -#include <laaw/laaw.h> -#include <string.h> // For strcpy() and strlen() -#include <stdlib.h> // For free() - -static char* LAAW_EXTERNC_CopyString(const char* str) -{ - char* copy = reinterpret_cast<char*>(malloc(strlen(str) + 1)); - strcpy(copy, str); - return copy; -} - -extern "C" -{ - LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c(void** newObject, const char* arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::OrthancConnection(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_f3fd272e4636f6a531aabb72ee01cd5b(void** newObject, const char* arg0, const char* arg1, const char* arg2) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::OrthancConnection(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1), reinterpret_cast< const char* >(arg2)); - - 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_12d3de0a96e9efb11136a9811bb9ed38(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - delete static_cast<OrthancClient::OrthancConnection*>(thisObject); - - 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_557aee7b61817292a0f31269d3c35db7(const void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::OrthancConnection* this_ = static_cast<const OrthancClient::OrthancConnection*>(thisObject); -*result = this_->GetThreadCount(); - - 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_0b8dff0ce67f10954a49b059e348837e(void* thisObject, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -this_->SetThreadCount(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_e05097c153f676e5a5ee54dcfc78256f(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -this_->Reload(); - - 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_e840242bf58d17d3c1d722da09ce88e0(const void* thisObject, const char** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::OrthancConnection* this_ = static_cast<const OrthancClient::OrthancConnection*>(thisObject); -*result = this_->GetOrthancUrl(); - - 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_c9af31433001b5dfc012a552dc6d0050(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -*result = this_->GetPatientCount(); - - 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_3fba4d6b818180a44cd1cae6046334dc(void* thisObject, void** result, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -*result = &this_->GetPatient(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_aeb20dc75b9246188db857317e5e0ce7(void* thisObject, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -this_->DeletePatient(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_62689803d9871e4d9c51a648640b320b(void* thisObject, const char* arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -this_->StoreFile(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_2fb64c9e5a67eccd413b0e913469a421(void* thisObject, const void* arg0, uint64_t arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::OrthancConnection* this_ = static_cast<OrthancClient::OrthancConnection*>(thisObject); -this_->Store(reinterpret_cast< const void* >(arg0), arg1); - - 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_6cf0d7268667f9b0aa4511bacf184919(void** newObject, void* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::Patient(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_7d81cd502ee27e859735d0ea7112b5a1(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - delete static_cast<OrthancClient::Patient*>(thisObject); - - 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_f756172daf04516eec3a566adabb4335(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); -this_->Reload(); - - 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_ddb68763ec902a97d579666a73a20118(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); -*result = this_->GetStudyCount(); - - 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_fba3c68b4be7558dbc65f7ce1ab57d63(void* thisObject, void** result, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Patient* this_ = static_cast<OrthancClient::Patient*>(thisObject); -*result = &this_->GetStudy(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_b4ca99d958f843493e58d1ef967340e1(const void* thisObject, const char** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Patient* this_ = static_cast<const OrthancClient::Patient*>(thisObject); -*result = this_->GetId(); - - 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_78d5cc76d282437b6f93ec3b82c35701(const void* thisObject, const char** result, const char* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Patient* this_ = static_cast<const OrthancClient::Patient*>(thisObject); -*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_193599b9e345384fcdfcd47c29c55342(void** newObject, void* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::Series(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_7c97f17063a357d38c5fab1136ad12a0(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - delete static_cast<OrthancClient::Series*>(thisObject); - - 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_48a2a1a9d68c047e22bfba23014643d2(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -this_->Reload(); - - 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_852bf8296ca21c5fde5ec565cc10721d(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetInstanceCount(); - - 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_efd04574e0779faa83df1f2d8f9888db(void* thisObject, void** result, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = &this_->GetInstance(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_736247ff5e8036dac38163da6f666ed5(const void* thisObject, const char** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); -*result = this_->GetId(); - - 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_d82d2598a7a73f4b6fcc0c09c25b08ca(const void* thisObject, const char** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); -*result = this_->GetUrl(); - - 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_88134b978f9acb2aecdadf54aeab3c64(const void* thisObject, const char** result, const char* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Series* this_ = static_cast<const OrthancClient::Series*>(thisObject); -*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_152cb1b704c053d24b0dab7461ba6ea3(void* thisObject, int32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->Is3DImage(); - - 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_eee03f337ec81d9f1783cd41e5238757(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetWidth(); - - 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_006f08237bd7611636fc721baebfb4c5(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetHeight(); - - 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_b794f5cd3dad7d7b575dd1fd902afdd0(void* thisObject, float* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetVoxelSizeX(); - - 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_8ee2e50dd9df8f66a3c1766090dd03ab(void* thisObject, float* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetVoxelSizeY(); - - 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_046aed35bbe4751691f4c34cc249a61d(void* thisObject, float* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetVoxelSizeZ(); - - 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_2be452e7af5bf7dfd8c5021842674497(void* thisObject, float* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -*result = this_->GetSliceThickness(); - - return NULL; - } - catch (::Laaw::LaawException& e) - { - return LAAW_EXTERNC_CopyString(e.What()); - } - catch (...) - { - return LAAW_EXTERNC_CopyString("..."); - } - } - - LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -this_->Load3DImage(reinterpret_cast< void* >(arg0), static_cast< ::Orthanc::PixelFormat >(arg1), arg2, arg3); - - 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_b2601a161c24ad0a1d3586246f87452c(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3, float* arg4) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject); -this_->Load3DImage(reinterpret_cast< void* >(arg0), static_cast< ::Orthanc::PixelFormat >(arg1), arg2, arg3, reinterpret_cast< float* >(arg4)); - - 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_b01c6003238eb46c8db5dc823d7ca678(void** newObject, void* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::Study(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_0147007fb99bad8cd95a139ec8795376(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - delete static_cast<OrthancClient::Study*>(thisObject); - - 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_e65b20b7e0170b67544cd6664a4639b7(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); -this_->Reload(); - - 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_470e981b0e41f17231ba0ae6f3033321(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); -*result = this_->GetSeriesCount(); - - 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_04cefd138b6ea15ad909858f2a0a8f05(void* thisObject, void** result, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Study* this_ = static_cast<OrthancClient::Study*>(thisObject); -*result = &this_->GetSeries(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_aee5b1f6f0c082f2c3b0986f9f6a18c7(const void* thisObject, const char** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Study* this_ = static_cast<const OrthancClient::Study*>(thisObject); -*result = this_->GetId(); - - 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_93965682bace75491413e1f0b8d5a654(const void* thisObject, const char** result, const char* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Study* this_ = static_cast<const OrthancClient::Study*>(thisObject); -*result = this_->GetMainDicomTag(reinterpret_cast< const char* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_6c5ad02f91b583e29cebd0bd319ce21d(void** newObject, void* arg0, const char* arg1) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - *newObject = new OrthancClient::Instance(*reinterpret_cast< ::OrthancClient::OrthancConnection* >(arg0), reinterpret_cast< const char* >(arg1)); - - 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_4068241c44a9c1367fe0e57be523f207(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - delete static_cast<OrthancClient::Instance*>(thisObject); - - 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_236ee8b403bc99535a8a4695c0cd45cb(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_->GetId(); - - 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_2a437b7aba6bb01e81113835be8f0146(void* thisObject, int32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -this_->SetImageExtractionMode(static_cast< ::Orthanc::ImageExtractionMode >(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_2bcbcb850934ae0bb4c6f0cc940e6cda(const void* thisObject, int32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); -*result = this_->GetImageExtractionMode(); - - 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_8d415c3a78a48e7e61d9fd24e7c79484(const void* thisObject, const char** result, const char* arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); -*result = this_->GetTagAsString(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_70d2f8398bbc63b5f792b69b4ad5fecb(const void* thisObject, float* result, const char* arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); -*result = this_->GetTagAsFloat(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_1729a067d902771517388eedd7346b23(const void* thisObject, int32_t* result, const char* arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - const OrthancClient::Instance* this_ = static_cast<const OrthancClient::Instance*>(thisObject); -*result = this_->GetTagAsInt(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_72e2aeee66cd3abd8ab7e987321c3745(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetWidth(); - - 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_1ea3df5a1ac1a1a687fe7325adddb6f0(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetHeight(); - - 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_99b4f370e4f532d8b763e2cb49db92f8(void* thisObject, uint32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetPitch(); - - 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_c41c742b68617f1c0590577a0a5ebc0c(void* thisObject, int32_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetPixelFormat(); - - 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_142dd2feba0fc1d262bbd0baeb441a8b(void* thisObject, const void** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetBuffer(); - - 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_5f5c9f81a4dff8daa6c359f1d0488fef(void* thisObject, const void** result, uint32_t arg0) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetBuffer(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_9ca979fffd08fa256306d4e68d8b0e91(void* thisObject, uint64_t* result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetDicomSize(); - - 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_6f2d77a26edc91c28d89408dbc3c271e(void* thisObject, const void** result) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -*result = this_->GetDicom(); - - 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_c0f494b80d4ff8b232df7a75baa0700a(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -this_->DiscardImage(); - - 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_d604f44bd5195e082e745e9cbc164f4c(void* thisObject) - { - try - { - #ifdef LAAW_EXTERNC_START_FUNCTION - LAAW_EXTERNC_START_FUNCTION; - #endif - - OrthancClient::Instance* this_ = static_cast<OrthancClient::Instance*>(thisObject); -this_->DiscardDicom(); - - 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_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() - { - return "Native client to the REST API of Orthanc"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCompany() - { - return "University Hospital of Liege"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetProduct() - { - return "OrthancClient"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetCopyright() - { - return "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetVersion() - { - return "0.8"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() - { - return "0.8.0.5"; - } - - LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() - { - return "0.8.5"; - } - - LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str) - { - if (str != NULL) - free(str); - } -}
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1817 +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/>. - **/ - - -/** - * @file - **/ - -#pragma once - -#include <stdexcept> -#include <memory> -#include <string> -#include <string.h> - -#if defined(_WIN32) - -/******************************************************************** - ** This is the Windows-specific section - ********************************************************************/ - -#include <windows.h> - -/* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */ -#ifdef _M_X64 -/* 64 bits target */ -#define LAAW_ORTHANC_CLIENT_CALL_CONV __fastcall -#define LAAW_ORTHANC_CLIENT_CALL_DECORATION(Name, StdCallSuffix) Name -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "OrthancClient_Windows64.dll" -#else -/* 32 bits target */ -#define LAAW_ORTHANC_CLIENT_CALL_CONV __stdcall -#define LAAW_ORTHANC_CLIENT_CALL_DECORATION(Name, StdCallSuffix) "_" Name "@" StdCallSuffix -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "OrthancClient_Windows32.dll" -#endif - -#define LAAW_ORTHANC_CLIENT_HANDLE_TYPE HINSTANCE -#define LAAW_ORTHANC_CLIENT_HANDLE_NULL 0 -#define LAAW_ORTHANC_CLIENT_FUNCTION_TYPE FARPROC -#define LAAW_ORTHANC_CLIENT_LOADER(path) LoadLibraryA(path) -#define LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle, name, decoration) GetProcAddress(handle, LAAW_ORTHANC_CLIENT_CALL_DECORATION(name, decoration)) -#define LAAW_ORTHANC_CLIENT_CLOSER(handle) FreeLibrary(handle) - - -/******************************************************************** - ** This is the Linux-specific section - ********************************************************************/ - -#elif defined (__linux) - -#include <stdlib.h> -#include <dlfcn.h> - -/* cf. http://sourceforge.net/p/predef/wiki/Architectures/ */ -#ifdef __amd64__ -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.8" -#else -#define LAAW_ORTHANC_CLIENT_DEFAULT_PATH "libOrthancClient.so.0.8" -#endif - -#define LAAW_ORTHANC_CLIENT_CALL_CONV -#define LAAW_ORTHANC_CLIENT_HANDLE_TYPE void* -#define LAAW_ORTHANC_CLIENT_HANDLE_NULL NULL -#define LAAW_ORTHANC_CLIENT_FUNCTION_TYPE intptr_t -#define LAAW_ORTHANC_CLIENT_LOADER(path) dlopen(path, RTLD_LAZY) -#define LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle, name, decoration) (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) dlsym(handle, name) -#define LAAW_ORTHANC_CLIENT_CLOSER(handle) dlclose(handle) - - -#else -#error Please support your platform here -#endif - - -/******************************************************************** - ** Definition of the integer types - ********************************************************************/ - -#ifndef LAAW_INT8 // Only define the integer types once - -#if defined(__GNUC__) - -// Under GCC (including MinGW), the stdint.h standard header is used. - -#include <stdint.h> - -#define LAAW_INT8 int8_t -#define LAAW_UINT8 uint8_t -#define LAAW_INT16 int16_t -#define LAAW_UINT16 uint16_t -#define LAAW_INT32 int32_t -#define LAAW_UINT32 uint32_t -#define LAAW_INT64 int64_t -#define LAAW_UINT64 uint64_t - -#elif defined(_MSC_VER) - -// Under Visual Studio, it is required to define the various integer -// types by hand. - -#if (_MSC_VER < 1300) -typedef signed char LAAW_INT8; -typedef signed short LAAW_INT16; -typedef signed int LAAW_INT32; -typedef unsigned char LAAW_UINT8; -typedef unsigned short LAAW_UINT16; -typedef unsigned int LAAW_UINT32; -#else -typedef signed __int8 LAAW_INT8; -typedef signed __int16 LAAW_INT16; -typedef signed __int32 LAAW_INT32; -typedef unsigned __int8 LAAW_UINT8; -typedef unsigned __int16 LAAW_UINT16; -typedef unsigned __int32 LAAW_UINT32; -#endif - -typedef signed __int64 LAAW_INT64; -typedef unsigned __int64 LAAW_UINT64; - -#else -#error "Please support your compiler here" -#endif - -#endif - - - - - -/******************************************************************** - ** This is a shared section between Windows and Linux - ********************************************************************/ - -namespace OrthancClient { -/** - * @brief Exception class that is thrown by the functions of this shared library. - **/ -class OrthancClientException : public std::exception - { - private: - std::string message_; - - public: - /** - * @brief Constructs an exception. - * @param message The error message. - **/ - OrthancClientException(std::string message) : message_(message) - { - } - - ~OrthancClientException() throw() - { - } - - /** - * @brief Get the error message associated with this exception. - * @returns The error message. - **/ - const std::string& What() const throw() - { - return message_; - } -}; -} - - -namespace OrthancClient { namespace Internals { -/** - * This internal class implements a Singleton design pattern that will - * store a reference to the shared library handle, together with a - * pointer to each function in the shared library. - **/ -class Library - { - private: - LAAW_ORTHANC_CLIENT_HANDLE_TYPE handle_; - LAAW_ORTHANC_CLIENT_FUNCTION_TYPE functionsIndex_[63 + 1]; - - - - void Load(const char* sharedLibraryPath) - { - - if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) - { - // Do nothing if the library is already loaded - return; - } - - /* Setup the path to the default shared library if not provided */ - if (sharedLibraryPath == NULL) - { - sharedLibraryPath = LAAW_ORTHANC_CLIENT_DEFAULT_PATH; - } - - /* Load the shared library */ - handle_ = LAAW_ORTHANC_CLIENT_LOADER(sharedLibraryPath); - - - if (handle_ == LAAW_ORTHANC_CLIENT_HANDLE_NULL) - { - throw ::OrthancClient::OrthancClientException("Error loading shared library"); - } - - LoadFunctions(); - } - - inline void LoadFunctions(); - - void FreeString(char* str) - { - typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*); - Function function = (Function) GetFunction(63); - function(str); - } - - Library() - { - handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; - } - - ~Library() - { - Finalize(); - } - - public: - LAAW_ORTHANC_CLIENT_FUNCTION_TYPE GetFunction(unsigned int index) - { - /** - * If the library has not been manually initialized by a call to - * ::OrthancClient::Initialize(), it is loaded from - * the default location (lazy initialization). - **/ - if (handle_ == NULL) - { - Load(NULL); - } - - return functionsIndex_[index]; - } - - void ThrowExceptionIfNeeded(char* message) - { - if (message != NULL) - { - std::string tmp(message); - FreeString(message); - throw ::OrthancClient::OrthancClientException(tmp); - } - } - - static inline Library& GetInstance() - { - /** - * This function defines a "static variable" inside a "static - * inline method" of a class. This ensures that a single - * instance of this variable will be used across all the - * compilation modules of the software. - * http://stackoverflow.com/a/1389403/881731 - **/ - - static Library singleton; - return singleton; - } - - static void Initialize(const char* sharedLibraryPath) - { - GetInstance().Load(sharedLibraryPath); - } - - void Finalize() - { - if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL) - { -#if 0 - /** - * Do not explicitly unload the shared library, as it might - * interfere with the destruction of static objects declared - * inside the library (e.g. this is the case of gflags that is - * internally used by googlelog). - **/ - LAAW_ORTHANC_CLIENT_CLOSER(handle_); -#endif - handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL; - } - } -}; -}} - - -/*! - * \addtogroup Global Global definitions. - * @{ - * @} - */ - - -namespace OrthancClient { -/*! - * \addtogroup Initialization Initialization of the shared library. - * @{ - */ - -/** - * @brief Manually initialize the shared library, using the default library name. - * - * Call this method before using the library to ensure correct - * behaviour in multi-threaded applications. This method is also - * useful to control the time at which the shared library is - * loaded (e.g. for real-time applications). - **/ -inline void Initialize() -{ - ::OrthancClient::Internals::Library::Initialize(NULL); -} - -/** - * @brief Manually initialize the shared library. - * - * Call this method before using the library to ensure correct - * behaviour in multi-threaded applications. This method is also - * useful to control the time at which the shared library is - * loaded (e.g. for real-time applications). - * - * @param sharedLibraryPath The path to the shared library that - * contains the module. - **/ -inline void Initialize(const std::string& sharedLibraryPath) -{ - ::OrthancClient::Internals::Library::Initialize(sharedLibraryPath.c_str()); -} - -/** - * @brief Manually finalize the shared library. - * - * Calling explicitly this function is not mandatory. It is useful to - * force the release of the resources acquired by the shared library, - * or to manually control the order in which the global variables get - * deleted. - **/ -inline void Finalize() -{ - ::OrthancClient::Internals::Library::GetInstance().Finalize(); -} - - -/** - * @} - */ -} - - -namespace OrthancClient { namespace Internals { -inline void Library::LoadFunctions() -{ - typedef const char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (); - Function getVersion = (Function) LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_GetVersion", "0"); - if (getVersion == NULL) - { - throw ::OrthancClient::OrthancClientException("Unable to get the library version"); - } - - /** - * It is assumed that the API does not change when the revision - * number (MAJOR.MINOR.REVISION) changes. - **/ - if (strcmp(getVersion(), "0.8")) - { - throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version"); - } - - functionsIndex_[63] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4"); - functionsIndex_[3] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7", "8"); - functionsIndex_[4] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e", "8"); - functionsIndex_[5] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f", "4"); - functionsIndex_[6] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0", "8"); - functionsIndex_[7] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050", "8"); - functionsIndex_[8] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc", "12"); - functionsIndex_[9] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7", "8"); - functionsIndex_[10] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b", "8"); - functionsIndex_[11] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421", "16"); - functionsIndex_[0] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c", "8"); - functionsIndex_[1] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b", "16"); - functionsIndex_[2] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38", "4"); - functionsIndex_[14] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_f756172daf04516eec3a566adabb4335", "4"); - functionsIndex_[15] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118", "8"); - functionsIndex_[16] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63", "12"); - functionsIndex_[17] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1", "8"); - functionsIndex_[18] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701", "16"); - functionsIndex_[12] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919", "12"); - functionsIndex_[13] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1", "4"); - functionsIndex_[21] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2", "4"); - functionsIndex_[22] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d", "8"); - functionsIndex_[23] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db", "12"); - functionsIndex_[24] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5", "8"); - functionsIndex_[25] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca", "8"); - functionsIndex_[26] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64", "16"); - functionsIndex_[27] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3", "8"); - functionsIndex_[28] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757", "8"); - functionsIndex_[29] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5", "8"); - functionsIndex_[30] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0", "8"); - functionsIndex_[31] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab", "8"); - functionsIndex_[32] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d", "8"); - functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497", "8"); - functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28"); - functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32"); - functionsIndex_[19] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342", "12"); - functionsIndex_[20] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0", "4"); - functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4"); - functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8"); - functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12"); - functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8"); - functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16"); - functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12"); - functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4"); - functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8"); - functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8"); - functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8"); - functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12"); - functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12"); - functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12"); - functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8"); - functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8"); - functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8"); - functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8"); - functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8"); - functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12"); - functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8"); - functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8"); - functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4"); - functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4"); - functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8"); - functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8"); - functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12"); - functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4"); - - /* Check whether the functions were properly loaded */ - for (unsigned int i = 0; i <= 63; i++) - { - if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL) - { - throw ::OrthancClient::OrthancClientException("Unable to load the functions of the shared library"); - } - } -} -}} -namespace OrthancClient -{ - class OrthancConnection; -} - -namespace OrthancClient -{ - class Patient; -} - -namespace OrthancClient -{ - class Series; -} - -namespace OrthancClient -{ - class Study; -} - -namespace OrthancClient -{ - class Instance; -} - -namespace Orthanc -{ - /** - * @brief The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image. - * - * The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image. - * - * @ingroup Global - **/ - enum PixelFormat - { - /** - * @brief Graylevel, signed 16bpp image. - * - * The image is graylevel. Each pixel is signed and stored in two bytes. - * - **/ - PixelFormat_SignedGrayscale16 = 5, - /** - * @brief Color image in RGB24 format. - * - * This format describes a color image. The pixels are stored in 3 consecutive bytes. The memory layout is RGB. - * - **/ - PixelFormat_RGB24 = 1, - /** - * @brief Color image in RGBA32 format. - * - * This format describes a color image. The pixels are stored in 4 consecutive bytes. The memory layout is RGBA. - * - **/ - PixelFormat_RGBA32 = 2, - /** - * @brief Graylevel 8bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in one byte. - * - **/ - PixelFormat_Grayscale8 = 3, - /** - * @brief Graylevel, unsigned 16bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in two bytes. - * - **/ - PixelFormat_Grayscale16 = 4 - }; -} - -namespace Orthanc -{ - /** - * @brief The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image. - * - * The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image. - * - * @ingroup Global - **/ - enum ImageExtractionMode - { - /** - * @brief Truncation to the [-32768, 32767] range. - * - * Truncation to the [-32768, 32767] range. - * - **/ - ImageExtractionMode_Int16 = 4, - /** - * @brief Rescaled to 8bpp. - * - * The minimum value of the image is set to 0, and its maximum value is set to 255. - * - **/ - ImageExtractionMode_Preview = 1, - /** - * @brief Truncation to the [0, 255] range. - * - * Truncation to the [0, 255] range. - * - **/ - ImageExtractionMode_UInt8 = 2, - /** - * @brief Truncation to the [0, 65535] range. - * - * Truncation to the [0, 65535] range. - * - **/ - ImageExtractionMode_UInt16 = 3 - }; -} - -namespace OrthancClient -{ - /** - * @brief Connection to an instance of %Orthanc. - * - * This class encapsulates a connection to a remote instance of %Orthanc through its REST API. - * - **/ - class OrthancConnection - { - friend class ::OrthancClient::Patient; - friend class ::OrthancClient::Series; - friend class ::OrthancClient::Study; - friend class ::OrthancClient::Instance; - private: - bool isReference_; - OrthancConnection& operator= (const OrthancConnection&); // Assignment is forbidden - void* pimpl_; - OrthancConnection(void* pimpl) : isReference_(true), pimpl_(pimpl) {} - public: - /** - * @brief Construct a new reference to this object. - * - * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. - * - * @param other The original object. - **/ - OrthancConnection(const OrthancConnection& other) : isReference_(true), pimpl_(other.pimpl_) { } - inline OrthancConnection(const ::std::string& orthancUrl); - inline OrthancConnection(const ::std::string& orthancUrl, const ::std::string& username, const ::std::string& password); - inline ~OrthancConnection(); - inline LAAW_UINT32 GetThreadCount() const; - inline void SetThreadCount(LAAW_UINT32 threadCount); - inline void Reload(); - inline ::std::string GetOrthancUrl() const; - inline LAAW_UINT32 GetPatientCount(); - inline ::OrthancClient::Patient GetPatient(LAAW_UINT32 index); - inline void DeletePatient(LAAW_UINT32 index); - inline void StoreFile(const ::std::string& filename); - inline void Store(const void* dicom, LAAW_UINT64 size); - }; -} - -namespace OrthancClient -{ - /** - * @brief Connection to a patient stored in %Orthanc. - * - * This class encapsulates a connection to a patient from a remote instance of %Orthanc. - * - **/ - class Patient - { - friend class ::OrthancClient::OrthancConnection; - friend class ::OrthancClient::Series; - friend class ::OrthancClient::Study; - friend class ::OrthancClient::Instance; - private: - bool isReference_; - Patient& operator= (const Patient&); // Assignment is forbidden - void* pimpl_; - Patient(void* pimpl) : isReference_(true), pimpl_(pimpl) {} - public: - /** - * @brief Construct a new reference to this object. - * - * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. - * - * @param other The original object. - **/ - Patient(const Patient& other) : isReference_(true), pimpl_(other.pimpl_) { } - inline Patient(::OrthancClient::OrthancConnection& connection, const ::std::string& id); - inline ~Patient(); - inline void Reload(); - inline LAAW_UINT32 GetStudyCount(); - inline ::OrthancClient::Study GetStudy(LAAW_UINT32 index); - inline ::std::string GetId() const; - inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; - }; -} - -namespace OrthancClient -{ - /** - * @brief Connection to a series stored in %Orthanc. - * - * This class encapsulates a connection to a series from a remote instance of %Orthanc. - * - **/ - class Series - { - friend class ::OrthancClient::OrthancConnection; - friend class ::OrthancClient::Patient; - friend class ::OrthancClient::Study; - friend class ::OrthancClient::Instance; - private: - bool isReference_; - Series& operator= (const Series&); // Assignment is forbidden - void* pimpl_; - Series(void* pimpl) : isReference_(true), pimpl_(pimpl) {} - public: - /** - * @brief Construct a new reference to this object. - * - * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. - * - * @param other The original object. - **/ - Series(const Series& other) : isReference_(true), pimpl_(other.pimpl_) { } - inline Series(::OrthancClient::OrthancConnection& connection, const ::std::string& id); - inline ~Series(); - inline void Reload(); - inline LAAW_UINT32 GetInstanceCount(); - inline ::OrthancClient::Instance GetInstance(LAAW_UINT32 index); - inline ::std::string GetId() const; - inline ::std::string GetUrl() const; - inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; - inline bool Is3DImage(); - inline LAAW_UINT32 GetWidth(); - inline LAAW_UINT32 GetHeight(); - inline float GetVoxelSizeX(); - inline float GetVoxelSizeY(); - inline float GetVoxelSizeZ(); - inline float GetSliceThickness(); - inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride); - inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]); - }; -} - -namespace OrthancClient -{ - /** - * @brief Connection to a study stored in %Orthanc. - * - * This class encapsulates a connection to a study from a remote instance of %Orthanc. - * - **/ - class Study - { - friend class ::OrthancClient::OrthancConnection; - friend class ::OrthancClient::Patient; - friend class ::OrthancClient::Series; - friend class ::OrthancClient::Instance; - private: - bool isReference_; - Study& operator= (const Study&); // Assignment is forbidden - void* pimpl_; - Study(void* pimpl) : isReference_(true), pimpl_(pimpl) {} - public: - /** - * @brief Construct a new reference to this object. - * - * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. - * - * @param other The original object. - **/ - Study(const Study& other) : isReference_(true), pimpl_(other.pimpl_) { } - inline Study(::OrthancClient::OrthancConnection& connection, const ::std::string& id); - inline ~Study(); - inline void Reload(); - inline LAAW_UINT32 GetSeriesCount(); - inline ::OrthancClient::Series GetSeries(LAAW_UINT32 index); - inline ::std::string GetId() const; - inline ::std::string GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const; - }; -} - -namespace OrthancClient -{ - /** - * @brief Connection to an instance stored in %Orthanc. - * - * This class encapsulates a connection to an image instance from a remote instance of %Orthanc. - * - **/ - class Instance - { - friend class ::OrthancClient::OrthancConnection; - friend class ::OrthancClient::Patient; - friend class ::OrthancClient::Series; - friend class ::OrthancClient::Study; - private: - bool isReference_; - Instance& operator= (const Instance&); // Assignment is forbidden - void* pimpl_; - Instance(void* pimpl) : isReference_(true), pimpl_(pimpl) {} - public: - /** - * @brief Construct a new reference to this object. - * - * Construct a new reference to this object. Pay attention to the fact that when the referenced object is deleted, the content of this object will be invalid. - * - * @param other The original object. - **/ - Instance(const Instance& other) : isReference_(true), pimpl_(other.pimpl_) { } - inline Instance(::OrthancClient::OrthancConnection& connection, const ::std::string& id); - inline ~Instance(); - inline ::std::string GetId() const; - inline void SetImageExtractionMode(::Orthanc::ImageExtractionMode mode); - inline ::Orthanc::ImageExtractionMode GetImageExtractionMode() const; - inline ::std::string GetTagAsString(const ::std::string& tag) const; - inline float GetTagAsFloat(const ::std::string& tag) const; - inline LAAW_INT32 GetTagAsInt(const ::std::string& tag) const; - inline LAAW_UINT32 GetWidth(); - inline LAAW_UINT32 GetHeight(); - inline LAAW_UINT32 GetPitch(); - inline ::Orthanc::PixelFormat GetPixelFormat(); - inline const void* GetBuffer(); - inline const void* GetBuffer(LAAW_UINT32 y); - inline LAAW_UINT64 GetDicomSize(); - inline const void* GetDicom(); - inline void DiscardImage(); - inline void DiscardDicom(); - inline void LoadTagContent(const ::std::string& path); - inline ::std::string GetLoadedTagContent() const; - }; -} - -namespace OrthancClient -{ - /** - * @brief Create a connection to an instance of %Orthanc. - * - * Create a connection to an instance of %Orthanc. - * - * @param orthancUrl URL to which the REST API of %Orthanc is listening. - **/ - inline OrthancConnection::OrthancConnection(const ::std::string& orthancUrl) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(0); - char* error = function(&pimpl_, orthancUrl.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Create a connection to an instance of %Orthanc, with authentication. - * - * Create a connection to an instance of %Orthanc, with authentication. - * - * @param orthancUrl URL to which the REST API of %Orthanc is listening. - * @param username The username. - * @param password The password. - **/ - inline OrthancConnection::OrthancConnection(const ::std::string& orthancUrl, const ::std::string& username, const ::std::string& password) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, const char*, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(1); - char* error = function(&pimpl_, orthancUrl.c_str(), username.c_str(), password.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Destructs the object. - * - * Destructs the object. - * - **/ - inline OrthancConnection::~OrthancConnection() - { - if (isReference_) return; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(2); - char* error = function(pimpl_); - error = error; // Remove warning about unused variable - } - /** - * @brief Returns the number of threads for this connection. - * - * Returns the number of simultaneous connections that are used when downloading information from this instance of %Orthanc. - * - * @return The number of threads. - **/ - inline LAAW_UINT32 OrthancConnection::GetThreadCount() const - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(3); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Sets the number of threads for this connection. - * - * Sets the number of simultaneous connections that are used when downloading information from this instance of %Orthanc. - * - * @param threadCount The number of threads. - **/ - inline void OrthancConnection::SetThreadCount(LAAW_UINT32 threadCount) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(4); - char* error = function(pimpl_, threadCount); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Reload the list of the patients. - * - * This method will reload the list of the patients from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. - * - **/ - inline void OrthancConnection::Reload() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(5); - char* error = function(pimpl_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Returns the URL of this instance of %Orthanc. - * - * Returns the URL of the remote %Orthanc instance to which this object is connected. - * - * @return The URL. - **/ - inline ::std::string OrthancConnection::GetOrthancUrl() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(6); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Returns the number of patients. - * - * Returns the number of patients that are stored in the remote instance of %Orthanc. - * - * @return The number of patients. - **/ - inline LAAW_UINT32 OrthancConnection::GetPatientCount() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(7); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get some patient. - * - * This method will return an object that contains information about some patient. The patients are indexed by a number between 0 (inclusive) and the result of GetPatientCount() (exclusive). - * - * @param index The index of the patient of interest. - * @return The patient. - **/ - inline ::OrthancClient::Patient OrthancConnection::GetPatient(LAAW_UINT32 index) - { - void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(8); - char* error = function(pimpl_, &result_, index); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return ::OrthancClient::Patient(result_); - } - /** - * @brief Delete some patient. - * - * Delete some patient from the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. - * - * @param index The index of the patient of interest. - * @return The patient. - **/ - inline void OrthancConnection::DeletePatient(LAAW_UINT32 index) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(9); - char* error = function(pimpl_, index); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Send a DICOM file. - * - * This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. - * - * @param filename Path to the DICOM file - **/ - inline void OrthancConnection::StoreFile(const ::std::string& filename) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(10); - char* error = function(pimpl_, filename.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Send a DICOM file that is contained inside a memory buffer. - * - * This method will store a DICOM file in the remote instance of %Orthanc. Pay attention to the fact that the patients that have been previously returned by GetPatient() will be invalidated. - * - * @param dicom The memory buffer containing the DICOM file. - * @param size The size of the DICOM file. - **/ - inline void OrthancConnection::Store(const void* dicom, LAAW_UINT64 size) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void*, LAAW_UINT64); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(11); - char* error = function(pimpl_, dicom, size); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } -} - -namespace OrthancClient -{ - /** - * @brief Create a connection to some patient. - * - * Create a connection to some patient. - * - * @param connection The remote instance of %Orthanc. - * @param id The %Orthanc identifier of the patient. - **/ - inline Patient::Patient(::OrthancClient::OrthancConnection& connection, const ::std::string& id) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(12); - char* error = function(&pimpl_, connection.pimpl_, id.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Destructs the object. - * - * Destructs the object. - * - **/ - inline Patient::~Patient() - { - if (isReference_) return; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(13); - char* error = function(pimpl_); - error = error; // Remove warning about unused variable - } - /** - * @brief Reload the studies of this patient. - * - * This method will reload the list of the studies of this patient. Pay attention to the fact that the studies that have been previously returned by GetStudy() will be invalidated. - * - **/ - inline void Patient::Reload() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(14); - char* error = function(pimpl_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Return the number of studies for this patient. - * - * Return the number of studies for this patient. - * - * @return The number of studies. - **/ - inline LAAW_UINT32 Patient::GetStudyCount() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(15); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get some study of this patient. - * - * This method will return an object that contains information about some study. The studies are indexed by a number between 0 (inclusive) and the result of GetStudyCount() (exclusive). - * - * @param index The index of the study of interest. - * @return The study. - **/ - inline ::OrthancClient::Study Patient::GetStudy(LAAW_UINT32 index) - { - void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(16); - char* error = function(pimpl_, &result_, index); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return ::OrthancClient::Study(result_); - } - /** - * @brief Get the %Orthanc identifier of this patient. - * - * Get the %Orthanc identifier of this patient. - * - * @return The identifier. - **/ - inline ::std::string Patient::GetId() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(17); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Get the value of one of the main DICOM tags for this patient. - * - * Get the value of one of the main DICOM tags for this patient. - * - * @param tag The name of the tag of interest ("PatientName", "PatientID", "PatientSex" or "PatientBirthDate"). - * @param defaultValue The default value to be returned if this tag does not exist. - * @return The value of the tag. - **/ - inline ::std::string Patient::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(18); - char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } -} - -namespace OrthancClient -{ - /** - * @brief Create a connection to some series. - * - * Create a connection to some series. - * - * @param connection The remote instance of %Orthanc. - * @param id The %Orthanc identifier of the series. - **/ - inline Series::Series(::OrthancClient::OrthancConnection& connection, const ::std::string& id) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(19); - char* error = function(&pimpl_, connection.pimpl_, id.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Destructs the object. - * - * Destructs the object. - * - **/ - inline Series::~Series() - { - if (isReference_) return; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(20); - char* error = function(pimpl_); - error = error; // Remove warning about unused variable - } - /** - * @brief Reload the instances of this series. - * - * This method will reload the list of the instances of this series. Pay attention to the fact that the instances that have been previously returned by GetInstance() will be invalidated. - * - **/ - inline void Series::Reload() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(21); - char* error = function(pimpl_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Return the number of instances for this series. - * - * Return the number of instances for this series. - * - * @return The number of instances. - **/ - inline LAAW_UINT32 Series::GetInstanceCount() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(22); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get some instance of this series. - * - * This method will return an object that contains information about some instance. The instances are indexed by a number between 0 (inclusive) and the result of GetInstanceCount() (exclusive). - * - * @param index The index of the instance of interest. - * @return The instance. - **/ - inline ::OrthancClient::Instance Series::GetInstance(LAAW_UINT32 index) - { - void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(23); - char* error = function(pimpl_, &result_, index); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return ::OrthancClient::Instance(result_); - } - /** - * @brief Get the %Orthanc identifier of this series. - * - * Get the %Orthanc identifier of this series. - * - * @return The identifier. - **/ - inline ::std::string Series::GetId() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(24); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Returns the URL to this series. - * - * Returns the URL to this series. - * - * @return The URL. - **/ - inline ::std::string Series::GetUrl() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(25); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Get the value of one of the main DICOM tags for this series. - * - * Get the value of one of the main DICOM tags for this series. - * - * @param tag The name of the tag of interest ("Modality", "Manufacturer", "SeriesDate", "SeriesDescription", "SeriesInstanceUID"...). - * @param defaultValue The default value to be returned if this tag does not exist. - * @return The value of the tag. - **/ - inline ::std::string Series::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(26); - char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Test whether this series encodes a 3D image that can be downloaded from %Orthanc. - * - * Test whether this series encodes a 3D image that can be downloaded from %Orthanc. - * - * @return "true" if and only if this is a 3D image. - **/ - inline bool Series::Is3DImage() - { - LAAW_INT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(27); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_ != 0; - } - /** - * @brief Get the width of the 3D image. - * - * Get the width of the 3D image (i.e. along the X-axis). This call is only valid if this series corresponds to a 3D image. - * - * @return The width. - **/ - inline LAAW_UINT32 Series::GetWidth() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(28); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the height of the 3D image. - * - * Get the height of the 3D image (i.e. along the Y-axis). This call is only valid if this series corresponds to a 3D image. - * - * @return The height. - **/ - inline LAAW_UINT32 Series::GetHeight() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(29); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the physical size of a voxel along the X-axis. - * - * Get the physical size of a voxel along the X-axis. This call is only valid if this series corresponds to a 3D image. - * - * @return The voxel size. - **/ - inline float Series::GetVoxelSizeX() - { - float result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(30); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the physical size of a voxel along the Y-axis. - * - * Get the physical size of a voxel along the Y-axis. This call is only valid if this series corresponds to a 3D image. - * - * @return The voxel size. - **/ - inline float Series::GetVoxelSizeY() - { - float result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(31); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the physical size of a voxel along the Z-axis. - * - * Get the physical size of a voxel along the Z-axis. This call is only valid if this series corresponds to a 3D image. - * - * @return The voxel size. - **/ - inline float Series::GetVoxelSizeZ() - { - float result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(32); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the slice thickness. - * - * Get the slice thickness. This call is only valid if this series corresponds to a 3D image. - * - * @return The slice thickness. - **/ - inline float Series::GetSliceThickness() - { - float result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Load the 3D image into a memory buffer. - * - * Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. - * - * @param target The target memory buffer. - * @param format The memory layout of the voxels. - * @param lineStride The number of bytes between two lines in the target memory buffer. - * @param stackStride The number of bytes between two 2D slices in the target memory buffer. - **/ - inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34); - char* error = function(pimpl_, target, format, lineStride, stackStride); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Load the 3D image into a memory buffer. - * - * Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image. This method will also update a progress indicator to monitor the loading of the image. - * - * @param target The target memory buffer. - * @param format The memory layout of the voxels. - * @param lineStride The number of bytes between two lines in the target memory buffer. - * @param stackStride The number of bytes between two 2D slices in the target memory buffer. - * @param progress A pointer to a floating-point number that is continuously updated by the download threads to reflect the percentage of completion (between 0 and 1). This value can be read from a separate thread. - **/ - inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64, float*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35); - char* error = function(pimpl_, target, format, lineStride, stackStride, progress); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } -} - -namespace OrthancClient -{ - /** - * @brief Create a connection to some study. - * - * Create a connection to some study. - * - * @param connection The remote instance of %Orthanc. - * @param id The %Orthanc identifier of the study. - **/ - inline Study::Study(::OrthancClient::OrthancConnection& connection, const ::std::string& id) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36); - char* error = function(&pimpl_, connection.pimpl_, id.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Destructs the object. - * - * Destructs the object. - * - **/ - inline Study::~Study() - { - if (isReference_) return; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37); - char* error = function(pimpl_); - error = error; // Remove warning about unused variable - } - /** - * @brief Reload the series of this study. - * - * This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated. - * - **/ - inline void Study::Reload() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38); - char* error = function(pimpl_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Return the number of series for this study. - * - * Return the number of series for this study. - * - * @return The number of series. - **/ - inline LAAW_UINT32 Study::GetSeriesCount() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get some series of this study. - * - * This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive). - * - * @param index The index of the series of interest. - * @return The series. - **/ - inline ::OrthancClient::Series Study::GetSeries(LAAW_UINT32 index) - { - void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40); - char* error = function(pimpl_, &result_, index); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return ::OrthancClient::Series(result_); - } - /** - * @brief Get the %Orthanc identifier of this study. - * - * Get the %Orthanc identifier of this study. - * - * @return The identifier. - **/ - inline ::std::string Study::GetId() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Get the value of one of the main DICOM tags for this study. - * - * Get the value of one of the main DICOM tags for this study. - * - * @param tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime"). - * @param defaultValue The default value to be returned if this tag does not exist. - * @return The value of the tag. - **/ - inline ::std::string Study::GetMainDicomTag(const ::std::string& tag, const ::std::string& defaultValue) const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42); - char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } -} - -namespace OrthancClient -{ - /** - * @brief Create a connection to some image instance. - * - * Create a connection to some image instance. - * - * @param connection The remote instance of %Orthanc. - * @param id The %Orthanc identifier of the image instance. - **/ - inline Instance::Instance(::OrthancClient::OrthancConnection& connection, const ::std::string& id) - { - isReference_ = false; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43); - char* error = function(&pimpl_, connection.pimpl_, id.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Destructs the object. - * - * Destructs the object. - * - **/ - inline Instance::~Instance() - { - if (isReference_) return; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44); - char* error = function(pimpl_); - error = error; // Remove warning about unused variable - } - /** - * @brief Get the %Orthanc identifier of this identifier. - * - * Get the %Orthanc identifier of this identifier. - * - * @return The identifier. - **/ - inline ::std::string Instance::GetId() const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Set the extraction mode for the 2D image corresponding to this instance. - * - * Set the extraction mode for the 2D image corresponding to this instance. - * - * @param mode The extraction mode. - **/ - inline void Instance::SetImageExtractionMode(::Orthanc::ImageExtractionMode mode) - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46); - char* error = function(pimpl_, mode); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Get the extraction mode for the 2D image corresponding to this instance. - * - * Get the extraction mode for the 2D image corresponding to this instance. - * - * @return The extraction mode. - **/ - inline ::Orthanc::ImageExtractionMode Instance::GetImageExtractionMode() const - { - LAAW_INT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return static_cast< ::Orthanc::ImageExtractionMode >(result_); - } - /** - * @brief Get the string value of some DICOM tag of this instance. - * - * Get the string value of some DICOM tag of this instance. - * - * @param tag The name of the tag of interest. - * @return The value of the tag. - **/ - inline ::std::string Instance::GetTagAsString(const ::std::string& tag) const - { - const char* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48); - char* error = function(pimpl_, &result_, tag.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } - /** - * @brief Get the floating point value that is stored in some DICOM tag of this instance. - * - * Get the floating point value that is stored in some DICOM tag of this instance. - * - * @param tag The name of the tag of interest. - * @return The value of the tag. - **/ - inline float Instance::GetTagAsFloat(const ::std::string& tag) const - { - float result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, float*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49); - char* error = function(pimpl_, &result_, tag.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the integer value that is stored in some DICOM tag of this instance. - * - * Get the integer value that is stored in some DICOM tag of this instance. - * - * @param tag The name of the tag of interest. - * @return The value of the tag. - **/ - inline LAAW_INT32 Instance::GetTagAsInt(const ::std::string& tag) const - { - LAAW_INT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*, const char*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50); - char* error = function(pimpl_, &result_, tag.c_str()); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the width of the 2D image. - * - * Get the width of the 2D image that is encoded by this DICOM instance. - * - * @return The width. - **/ - inline LAAW_UINT32 Instance::GetWidth() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the height of the 2D image. - * - * Get the height of the 2D image that is encoded by this DICOM instance. - * - * @return The height. - **/ - inline LAAW_UINT32 Instance::GetHeight() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the number of bytes between two lines of the image (pitch). - * - * Get the number of bytes between two lines of the image in the memory buffer returned by GetBuffer(). This value depends on the extraction mode for the image. - * - * @return The pitch. - **/ - inline LAAW_UINT32 Instance::GetPitch() - { - LAAW_UINT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get the format of the pixels of the 2D image. - * - * Return the memory layout that is used for the 2D image that is encoded by this DICOM instance. This value depends on the extraction mode for the image. - * - * @return The pixel format. - **/ - inline ::Orthanc::PixelFormat Instance::GetPixelFormat() - { - LAAW_INT32 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return static_cast< ::Orthanc::PixelFormat >(result_); - } - /** - * @brief Access the memory buffer in which the raw pixels of the 2D image are stored. - * - * Access the memory buffer in which the raw pixels of the 2D image are stored. - * - * @return A pointer to the memory buffer. - **/ - inline const void* Instance::GetBuffer() - { - const void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return reinterpret_cast< const void* >(result_); - } - /** - * @brief Access the memory buffer in which the raw pixels of some line of the 2D image are stored. - * - * Access the memory buffer in which the raw pixels of some line of the 2D image are stored. - * - * @param y The line of interest. - * @return A pointer to the memory buffer. - **/ - inline const void* Instance::GetBuffer(LAAW_UINT32 y) - { - const void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**, LAAW_UINT32); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56); - char* error = function(pimpl_, &result_, y); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return reinterpret_cast< const void* >(result_); - } - /** - * @brief Get the size of the DICOM file corresponding to this instance. - * - * Get the size of the DICOM file corresponding to this instance. - * - * @return The file size. - **/ - inline LAAW_UINT64 Instance::GetDicomSize() - { - LAAW_UINT64 result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT64*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return result_; - } - /** - * @brief Get a pointer to the content of the DICOM file corresponding to this instance. - * - * Get a pointer to the content of the DICOM file corresponding to this instance. - * - * @return The DICOM file. - **/ - inline const void* Instance::GetDicom() - { - const void* result_; - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return reinterpret_cast< const void* >(result_); - } - /** - * @brief Discard the downloaded 2D image, so as to make room in memory. - * - * Discard the downloaded 2D image, so as to make room in memory. - * - **/ - inline void Instance::DiscardImage() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59); - char* error = function(pimpl_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - } - /** - * @brief Discard the downloaded DICOM file, so as to make room in memory. - * - * Discard the downloaded DICOM file, so as to make room in memory. - * - **/ - inline void Instance::DiscardDicom() - { - typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*); - Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60); - 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(61); - 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(62); - char* error = function(pimpl_, &result_); - ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error); - return std::string(result_); - } -} -
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -LIBRARY some.dll -EXPORTS - _LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7@8 = LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7@8 - _LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e@8 = LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e@8 - _LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f@4 = LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f@4 - _LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0@8 = LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0@8 - _LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050@8 = LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050@8 - _LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc@12 = LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc@12 - _LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7@8 = LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7@8 - _LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b@8 = LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b@8 - _LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421@16 = LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421@16 - _LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c@8 = LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c@8 - _LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b@16 = LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b@16 - _LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38@4 = LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38@4 - _LAAW_EXTERNC_f756172daf04516eec3a566adabb4335@4 = LAAW_EXTERNC_f756172daf04516eec3a566adabb4335@4 - _LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118@8 = LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118@8 - _LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63@12 = LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63@12 - _LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1@8 = LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1@8 - _LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701@16 = LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701@16 - _LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919@12 = LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919@12 - _LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1@4 = LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1@4 - _LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2@4 = LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2@4 - _LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d@8 = LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d@8 - _LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db@12 = LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db@12 - _LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5@8 = LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5@8 - _LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca@8 = LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca@8 - _LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64@16 = LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64@16 - _LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3@8 = LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3@8 - _LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757@8 = LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757@8 - _LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5@8 = LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5@8 - _LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 = LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 - _LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 = LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 - _LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 = LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 - _LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 = LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 - _LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 = LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 - _LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 = LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 - _LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 = LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 - _LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0@4 = LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0@4 - _LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7@4 = LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7@4 - _LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321@8 = LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321@8 - _LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05@12 = LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05@12 - _LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7@8 = LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7@8 - _LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654@16 = LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654@16 - _LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678@12 = LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678@12 - _LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376@4 = LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376@4 - _LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb@8 = LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb@8 - _LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146@8 = LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146@8 - _LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda@8 = LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda@8 - _LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484@12 = LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484@12 - _LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb@12 = LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb@12 - _LAAW_EXTERNC_1729a067d902771517388eedd7346b23@12 = LAAW_EXTERNC_1729a067d902771517388eedd7346b23@12 - _LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745@8 = LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745@8 - _LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0@8 = LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0@8 - _LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8@8 = LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8@8 - _LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c@8 = LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c@8 - _LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b@8 = LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b@8 - _LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef@12 = LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef@12 - _LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91@8 = LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91@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 - _LAAW_EXTERNC_GetCompany@0 = LAAW_EXTERNC_GetCompany@0 - _LAAW_EXTERNC_GetProduct@0 = LAAW_EXTERNC_GetProduct@0 - _LAAW_EXTERNC_GetCopyright@0 = LAAW_EXTERNC_GetCopyright@0 - _LAAW_EXTERNC_GetVersion@0 = LAAW_EXTERNC_GetVersion@0 - _LAAW_EXTERNC_GetFileVersion@0 = LAAW_EXTERNC_GetFileVersion@0 - _LAAW_EXTERNC_GetFullVersion@0 = LAAW_EXTERNC_GetFullVersion@0 - _LAAW_EXTERNC_FreeString@4 = LAAW_EXTERNC_FreeString@4
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#include <winver.h> - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,8,0,5 - PRODUCTVERSION 0,8,0,0 - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "Comments", "Release 0.8.5" - VALUE "CompanyName", "University Hospital of Liege" - VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.8.0.5" - VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege" - VALUE "LegalTrademarks", "Licensing information is available on http://www.orthanc-server.com/" - VALUE "OriginalFilename", "OrthancClient_Windows32.dll" - VALUE "ProductName", "OrthancClient" - VALUE "ProductVersion", "0.8" - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 // U.S. English - END - END
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -LIBRARY some.dll -EXPORTS - LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7 - LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e - LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f - LAAW_EXTERNC_e840242bf58d17d3c1d722da09ce88e0 - LAAW_EXTERNC_c9af31433001b5dfc012a552dc6d0050 - LAAW_EXTERNC_3fba4d6b818180a44cd1cae6046334dc - LAAW_EXTERNC_aeb20dc75b9246188db857317e5e0ce7 - LAAW_EXTERNC_62689803d9871e4d9c51a648640b320b - LAAW_EXTERNC_2fb64c9e5a67eccd413b0e913469a421 - LAAW_EXTERNC_1f1acb322ea4d0aad65172824607673c - LAAW_EXTERNC_f3fd272e4636f6a531aabb72ee01cd5b - LAAW_EXTERNC_12d3de0a96e9efb11136a9811bb9ed38 - LAAW_EXTERNC_f756172daf04516eec3a566adabb4335 - LAAW_EXTERNC_ddb68763ec902a97d579666a73a20118 - LAAW_EXTERNC_fba3c68b4be7558dbc65f7ce1ab57d63 - LAAW_EXTERNC_b4ca99d958f843493e58d1ef967340e1 - LAAW_EXTERNC_78d5cc76d282437b6f93ec3b82c35701 - LAAW_EXTERNC_6cf0d7268667f9b0aa4511bacf184919 - LAAW_EXTERNC_7d81cd502ee27e859735d0ea7112b5a1 - LAAW_EXTERNC_48a2a1a9d68c047e22bfba23014643d2 - LAAW_EXTERNC_852bf8296ca21c5fde5ec565cc10721d - LAAW_EXTERNC_efd04574e0779faa83df1f2d8f9888db - LAAW_EXTERNC_736247ff5e8036dac38163da6f666ed5 - LAAW_EXTERNC_d82d2598a7a73f4b6fcc0c09c25b08ca - LAAW_EXTERNC_88134b978f9acb2aecdadf54aeab3c64 - LAAW_EXTERNC_152cb1b704c053d24b0dab7461ba6ea3 - LAAW_EXTERNC_eee03f337ec81d9f1783cd41e5238757 - LAAW_EXTERNC_006f08237bd7611636fc721baebfb4c5 - LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0 - LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab - LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d - LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497 - LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5 - LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c - LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342 - LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0 - LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7 - LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321 - LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05 - LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7 - LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654 - LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678 - LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376 - LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb - LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146 - LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda - LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484 - LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb - LAAW_EXTERNC_1729a067d902771517388eedd7346b23 - LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745 - LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0 - LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8 - LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c - LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b - LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef - LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91 - LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e - LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a - LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c - LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd - LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb - LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d - LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207 - LAAW_EXTERNC_GetDescription - LAAW_EXTERNC_GetCompany - LAAW_EXTERNC_GetProduct - LAAW_EXTERNC_GetCopyright - LAAW_EXTERNC_GetVersion - LAAW_EXTERNC_GetFileVersion - LAAW_EXTERNC_GetFullVersion - LAAW_EXTERNC_FreeString
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#include <winver.h> - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,8,0,5 - PRODUCTVERSION 0,8,0,0 - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "Comments", "Release 0.8.5" - VALUE "CompanyName", "University Hospital of Liege" - VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.8.0.5" - VALUE "InternalName", "OrthancClient" - VALUE "LegalCopyright", "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege" - VALUE "LegalTrademarks", "Licensing information is available on http://www.orthanc-server.com/" - VALUE "OriginalFilename", "OrthancClient_Windows64.dll" - VALUE "ProductName", "OrthancClient" - VALUE "ProductVersion", "0.8" - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 // U.S. English - END - END
--- a/OrthancCppClient/SharedLibrary/ConfigurationCpp.json Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -{ - "InternalsNamespace" : [ "OrthancClient", "Internals" ], - "PublicNamespace" : [ "OrthancClient" ], - "ExceptionClassName" : "OrthancClientException" -}
--- a/OrthancCppClient/SharedLibrary/Generate.sh Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e - -mkdir -p AUTOGENERATED -LAAW_ROOT=~/Subversion/Jomago/Src/Labo/Laaw - -${LAAW_ROOT}/Parser/Build/LaawParser.exe AUTOGENERATED/CodeModelRaw.json ../OrthancConnection.h -I`pwd`/../../s/jsoncpp-src-0.6.0-rc2/include -fms-extensions -python ${LAAW_ROOT}/Generators/CodeModelPostProcessing.py AUTOGENERATED/CodeModel.json AUTOGENERATED/CodeModelRaw.json Product.json -python ${LAAW_ROOT}/Generators/GenerateWrapperCpp.py AUTOGENERATED/OrthancCppClient.h AUTOGENERATED/CodeModel.json Product.json ConfigurationCpp.json -python ${LAAW_ROOT}/Generators/GenerateExternC.py AUTOGENERATED/ExternC.cpp AUTOGENERATED/CodeModel.json Product.json -python ${LAAW_ROOT}/Generators/GenerateWindows32Def.py AUTOGENERATED/Windows32.def AUTOGENERATED/CodeModel.json -python ${LAAW_ROOT}/Generators/GenerateWindows64Def.py AUTOGENERATED/Windows64.def AUTOGENERATED/CodeModel.json -python ${LAAW_ROOT}/Generators/ApplyProductSubstitutions.py AUTOGENERATED/Windows32.rc ${LAAW_ROOT}/Resources/DllResources.rc.mustache Product.json PLATFORM_SUFFIX "_Windows32" -python ${LAAW_ROOT}/Generators/ApplyProductSubstitutions.py AUTOGENERATED/Windows64.rc ${LAAW_ROOT}/Resources/DllResources.rc.mustache Product.json PLATFORM_SUFFIX "_Windows64"
--- a/OrthancCppClient/SharedLibrary/Laaw/VersionScript.map Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -# This is a version-script - -{ -global: - LAAW_EXTERNC_*; - -local: - *; -};
--- a/OrthancCppClient/SharedLibrary/Laaw/laaw/laaw-exports.h Wed Feb 11 10:40:08 2015 +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/laaw.h Wed Feb 11 10:40:08 2015 +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(); - } - }; -}
--- a/OrthancCppClient/SharedLibrary/Product.json Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{ - "Product" : "OrthancClient", - "Description" : "Native client to the REST API of Orthanc", - "Company" : "University Hospital of Liege", - "Copyright" : "(c) 2012-2015, Sebastien Jodogne, University Hospital of Liege", - "Legal" : "Licensing information is available on http://www.orthanc-server.com/", - "Version" : "0.8.5" -}
--- a/OrthancCppClient/SharedLibrary/SharedLibrary.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../../Core/HttpClient.h" -#include "../OrthancConnection.h" - - -class SharedLibrarySingleton -{ -public: - SharedLibrarySingleton() - { - Orthanc::HttpClient::GlobalInitialize(); - } - - ~SharedLibrarySingleton() - { - Orthanc::HttpClient::GlobalFinalize(); - } -}; - - -static SharedLibrarySingleton singleton_; - - -#include "AUTOGENERATED/ExternC.cpp"
--- a/OrthancCppClient/Study.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "../Core/PrecompiledHeaders.h" -#include "Study.h" - -#include "OrthancConnection.h" - -namespace OrthancClient -{ - void Study::ReadStudy() - { - Orthanc::HttpClient client(connection_.GetHttpClient()); - client.SetUrl(std::string(connection_.GetOrthancUrl()) + "/studies/" + id_); - - Json::Value v; - if (!client.Apply(study_)) - { - throw OrthancClientException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - Orthanc::IDynamicObject* Study::GetFillerItem(size_t index) - { - Json::Value::ArrayIndex tmp = static_cast<Json::Value::ArrayIndex>(index); - std::string id = study_["Series"][tmp].asString(); - return new Series(connection_, id.c_str()); - } - - Study::Study(const OrthancConnection& connection, - const char* id) : - connection_(connection), - id_(id), - series_(*this) - { - series_.SetThreadCount(connection.GetThreadCount()); - ReadStudy(); - } - - const char* Study::GetMainDicomTag(const char* tag, const char* defaultValue) const - { - if (study_["MainDicomTags"].isMember(tag)) - { - return study_["MainDicomTags"][tag].asCString(); - } - else - { - return defaultValue; - } - } -}
--- a/OrthancCppClient/Study.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "Series.h" - -namespace OrthancClient -{ - /** - * {summary}{Connection to a study stored in %Orthanc.} - * {description}{This class encapsulates a connection to a study - * from a remote instance of %Orthanc.} - **/ - class LAAW_API Study : - public Orthanc::IDynamicObject, - private Orthanc::ArrayFilledByThreads::IFiller - { - private: - const OrthancConnection& connection_; - std::string id_; - Json::Value study_; - Orthanc::ArrayFilledByThreads series_; - - void ReadStudy(); - - virtual size_t GetFillerSize() - { - return study_["Series"].size(); - } - - virtual Orthanc::IDynamicObject* GetFillerItem(size_t index); - - public: - /** - * {summary}{Create a connection to some study.} - * {param}{connection The remote instance of %Orthanc.} - * {param}{id The %Orthanc identifier of the study.} - **/ - Study(const OrthancConnection& connection, - const char* id); - - /** - * {summary}{Reload the series of this study.} - * {description}{This method will reload the list of the series of this study. Pay attention to the fact that the series that have been previously returned by GetSeries() will be invalidated.} - **/ - void Reload() - { - series_.Reload(); - } - - /** - * {summary}{Return the number of series for this study.} - * {returns}{The number of series.} - **/ - uint32_t GetSeriesCount() - { - return series_.GetSize(); - } - - /** - * {summary}{Get some series of this study.} - * {description}{This method will return an object that contains information about some series. The series are indexed by a number between 0 (inclusive) and the result of GetSeriesCount() (exclusive).} - * {param}{index The index of the series of interest.} - * {returns}{The series.} - **/ - Series& GetSeries(uint32_t index) - { - return dynamic_cast<Series&>(series_.GetItem(index)); - } - - /** - * {summary}{Get the %Orthanc identifier of this study.} - * {returns}{The identifier.} - **/ - const char* GetId() const - { - return id_.c_str(); - } - - /** - * {summary}{Get the value of one of the main DICOM tags for this study.} - * {param}{tag The name of the tag of interest ("StudyDate", "StudyDescription", "StudyInstanceUID" or "StudyTime").} - * {param}{defaultValue The default value to be returned if this tag does not exist.} - * {returns}{The value of the tag.} - **/ - const char* GetMainDicomTag(const char* tag, - const char* defaultValue) const; - }; -}
--- a/OrthancExplorer/explorer.html Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancExplorer/explorer.html Wed Sep 30 13:23:31 2015 +0200 @@ -6,15 +6,15 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Orthanc Explorer</title> - <link rel="stylesheet" href="libs/jquery.mobile-1.1.0.min.css" /> + <link rel="stylesheet" href="libs/jquery.mobile.min.css" /> <link rel="stylesheet" href="libs/jqtree.css" /> <link rel="stylesheet" href="libs/jquery.mobile.simpledialog.min.css" /> <link rel="stylesheet" href="libs/jquery-file-upload/css/style.css" /> <link rel="stylesheet" href="libs/jquery-file-upload/css/jquery.fileupload-ui.css" /> <link rel="stylesheet" href="libs/slimbox2/slimbox2.css" /> - <script src="libs/jquery-1.7.2.min.js"></script> - <script src="libs/jquery.mobile-1.1.0.min.js"></script> + <script src="libs/jquery.min.js"></script> + <script src="libs/jquery.mobile.min.js"></script> <script src="libs/jqm.page.params.js"></script> <script src="libs/tree.jquery.js"></script> <script src="libs/date.js"></script> @@ -30,6 +30,7 @@ <link rel="stylesheet" href="explorer.css" /> <script src="file-upload.js"></script> <script src="explorer.js"></script> + <script src="query-retrieve.js"></script> <script src="../plugins/explorer.js"></script> </head> <body> @@ -37,7 +38,10 @@ <div data-role="header" > <h1><span class="orthanc-name"></span>Find a patient</h1> <a href="#plugins" data-icon="grid" class="ui-btn-left" data-direction="reverse">Plugins</a> - <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + </div> </div> <div data-role="content"> <ul id="all-patients" data-role="listview" data-inset="true" data-filter="true"> @@ -75,7 +79,10 @@ <div data-role="header" > <h1><span class="orthanc-name"></span>Patient</h1> <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> - <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + </div> </div> <div data-role="content"> <div class="ui-grid-a"> @@ -129,7 +136,10 @@ Study </h1> <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> - <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + </div> </div> <div data-role="content"> <div class="ui-grid-a"> @@ -178,7 +188,10 @@ </h1> <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> - <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + </div> </div> <div data-role="content"> <div class="ui-grid-a"> @@ -228,7 +241,10 @@ Instance </h1> <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> - <a href="#upload" data-icon="gear" class="ui-btn-right">Upload DICOM</a> + <div data-type="horizontal" data-role="controlgroup" class="ui-btn-right"> + <a href="#upload" data-icon="gear" data-role="button">Upload</a> + <a href="#query-retrieve" data-icon="search" data-role="button">Query/Retrieve</a> + </div> </div> <div data-role="content"> <div class="ui-grid-a"> @@ -284,15 +300,111 @@ </div> </div> + <div data-role="page" id="query-retrieve" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (1/3)</h1> + <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + </div> + <div data-role="content"> + <form data-ajax="false"> + <div data-role="fieldcontain"> + <label for="qr-server">DICOM server:</label> + <select name="qr-server" id="qr-server"> + </select> + </div> + + <div data-role="fieldcontain" id="qr-fields"> + <fieldset data-role="controlgroup"> + <legend>Field of interest:</legend> + <input type="radio" name="qr-field" id="qr-patient-id" value="PatientID" checked="checked" /> + <label for="qr-patient-id">Patient ID</label> + <input type="radio" name="qr-field" id="qr-patient-name" value="PatientName" /> + <label for="qr-patient-name">Patient Name</label> + <input type="radio" name="qr-field" id="qr-accession-number" value="AccessionNumber" /> + <label for="qr-accession-number">Accession Number</label> + <input type="radio" name="qr-field" id="qr-study-description" value="StudyDescription" /> + <label for="qr-study-description">Study Description</label> + </fieldset> + </div> + + <div data-role="fieldcontain"> + <label for="qr-value">Value for this field:</label> + <input type="text" name="qr-value" id="qr-value" value="*" /> + </div> + + <div data-role="fieldcontain"> + <label for="qr-date">Study date:</label> + <select name="qr-date" id="qr-date"> + </select> + </div> + + <div data-role="fieldcontain" id="qr-modalities"> + <div data-role="fieldcontain"> + <fieldset data-role="controlgroup" data-type="horizontal"> + <legend>Modalities:</legend> + <input type="checkbox" name="CR" id="qr-cr" class="custom" /> <label for="qr-cr">CR</label> + <input type="checkbox" name="CT" id="qr-ct" class="custom" /> <label for="qr-ct">CT</label> + <input type="checkbox" name="MR" id="qr-mr" class="custom" /> <label for="qr-mr">MR</label> + <input type="checkbox" name="NM" id="qr-nm" class="custom" /> <label for="qr-nm">NM</label> + <input type="checkbox" name="PT" id="qr-pt" class="custom" /> <label for="qr-pt">PT</label> + <input type="checkbox" name="US" id="qr-us" class="custom" /> <label for="qr-us">US</label> + <input type="checkbox" name="XA" id="qr-xa" class="custom" /> <label for="qr-xa">XA</label> + </fieldset> + </div> + </div> + + <fieldset class="ui-grid-a"> + <div class="ui-block-a"> + <button id="qr-echo" data-theme="a">Test Echo</button> + </div> + <div class="ui-block-b"> + <button id="qr-submit" type="submit" data-theme="b">Search studies</button> + </div> + </fieldset> + </form> + </div> + </div> + + + <div data-role="page" id="query-retrieve-2" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (2/3)</h1> + <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a> + </div> + <div data-role="content"> + <ul data-role="listview" data-inset="true" data-filter="true" data-split-icon="arrow-d" data-split-theme="b"> + </ul> + </div> + </div> + + + <div data-role="page" id="query-retrieve-3" > + <div data-role="header" > + <h1><span class="orthanc-name"></span>DICOM Query/Retrieve (3/3)</h1> + <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a> + <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a> + </div> + <div data-role="content"> + <ul data-role="listview" data-inset="true" data-filter="true" data-split-icon="arrow-d" data-split-theme="b"> + </ul> + </div> + </div> + <div id="peer-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to Orthanc peer...</b></p> - <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> + <p><img src="libs/images/ajax-loader.gif" alt="" /></p> </div> <div id="dicom-store" style="display:none;" class="ui-body-c"> <p align="center"><b>Sending to DICOM modality...</b></p> - <p><img src="libs/images/ajax-loader2.gif" alt="" /></p> + <p><img src="libs/images/ajax-loader.gif" alt="" /></p> + </div> + + <div id="info-retrieve" style="display:none;" class="ui-body-c"> + <p align="center"><b>Retrieving images from DICOM modality...</b></p> + <p><img src="libs/images/ajax-loader.gif" alt="" /></p> </div> <div id="dialog" style="display:none" >
--- a/OrthancExplorer/explorer.js Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancExplorer/explorer.js Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,19 @@ }; +function Refresh() +{ + if (currentPage == 'patient') + RefreshPatient(); + else if (currentPage == 'study') + RefreshStudy(); + else if (currentPage == 'series') + RefreshSeries(); + else if (currentPage == 'instance') + RefreshInstance(); +} + + $(document).ready(function() { var $tree = $('#dicom-tree'); $tree.tree({ @@ -45,6 +58,16 @@ $tree.tree('openNode', event.node, true); } ); + + currentPage = $.mobile.pageData.active; + currentUuid = $.mobile.pageData.uuid; + if (!(typeof currentPage === 'undefined') && + !(typeof currentUuid === 'undefined') && + currentPage.length > 0 && + currentUuid.length > 0) + { + Refresh(); + } }); @@ -142,11 +165,10 @@ -function GetSingleResource(type, uuid, callback) +function GetResource(uri, callback) { - var resource = null; $.ajax({ - url: '../' + type + '/' + uuid, + url: '..' + uri, dataType: 'json', async: false, cache: false, @@ -157,42 +179,6 @@ } -function GetMultipleResources(type, uuids, callback) -{ - if (uuids == null) - { - $.ajax({ - url: '../' + type, - dataType: 'json', - async: false, - cache: false, - success: function(s) { - uuids = s; - } - }); - } - - var resources = []; - var ajaxRequests = uuids.map(function(uuid) { - return $.ajax({ - url: '../' + type + '/' + uuid, - dataType: 'json', - async: true, - cache: false, - success: function(s) { - resources.push(s); - } - }); - }); - - // Wait for all the AJAX requests to end - $.when.apply($, ajaxRequests).then(function() { - callback(resources); - }); -} - - - function CompleteFormatting(s, link, isReverse) { if (link != null) @@ -344,18 +330,18 @@ $('#find-patients').live('pagebeforeshow', function() { - GetMultipleResources('patients', null, function(patients) { - var target = $('#all-patients'); - $('li', target).remove(); + GetResource('/patients?expand', function(patients) { + var target = $('#all-patients'); + $('li', target).remove(); - SortOnDicomTag(patients, 'PatientName', false, false); + SortOnDicomTag(patients, 'PatientName', false, false); - for (var i = 0; i < patients.length; i++) { - var p = FormatPatient(patients[i], '#patient?uuid=' + patients[i].ID); - target.append(p); - } + for (var i = 0; i < patients.length; i++) { + var p = FormatPatient(patients[i], '#patient?uuid=' + patients[i].ID); + target.append(p); + } - target.listview('refresh'); + target.listview('refresh'); }); }); @@ -381,8 +367,8 @@ function RefreshPatient() { if ($.mobile.pageData) { - GetSingleResource('patients', $.mobile.pageData.uuid, function(patient) { - GetMultipleResources('studies', patient.Studies, function(studies) { + GetResource('/patients/' + $.mobile.pageData.uuid, function(patient) { + GetResource('/patients/' + $.mobile.pageData.uuid + '/studies', function(studies) { SortOnDicomTag(studies, 'StudyDate', false, true); $('#patient-info li').remove(); @@ -433,9 +419,9 @@ function RefreshStudy() { if ($.mobile.pageData) { - GetSingleResource('studies', $.mobile.pageData.uuid, function(study) { - GetSingleResource('patients', study.ParentPatient, function(patient) { - GetMultipleResources('series', study.Series, function(series) { + GetResource('/studies/' + $.mobile.pageData.uuid, function(study) { + GetResource('/patients/' + study.ParentPatient, function(patient) { + GetResource('/studies/' + $.mobile.pageData.uuid + '/series', function(series) { SortOnDicomTag(series, 'SeriesDate', false, true); $('#study .patient-link').attr('href', '#patient?uuid=' + patient.ID); @@ -465,7 +451,7 @@ currentPage = 'study'; currentUuid = $.mobile.pageData.uuid; }); - }); + }); }); } } @@ -474,10 +460,10 @@ function RefreshSeries() { if ($.mobile.pageData) { - GetSingleResource('series', $.mobile.pageData.uuid, function(series) { - GetSingleResource('studies', series.ParentStudy, function(study) { - GetSingleResource('patients', study.ParentPatient, function(patient) { - GetMultipleResources('instances', series.Instances, function(instances) { + GetResource('/series/' + $.mobile.pageData.uuid, function(series) { + GetResource('/studies/' + series.ParentStudy, function(study) { + GetResource('/patients/' + study.ParentPatient, function(patient) { + GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) { Sort(instances, function(x) { return x.IndexInSeries; }, true, false); $('#series .patient-link').attr('href', '#patient?uuid=' + patient.ID); @@ -568,10 +554,10 @@ function RefreshInstance() { if ($.mobile.pageData) { - GetSingleResource('instances', $.mobile.pageData.uuid, function(instance) { - GetSingleResource('series', instance.ParentSeries, function(series) { - GetSingleResource('studies', series.ParentStudy, function(study) { - GetSingleResource('patients', study.ParentPatient, function(patient) { + GetResource('/instances/' + $.mobile.pageData.uuid, function(instance) { + GetResource('/series/' + instance.ParentSeries, function(series) { + GetResource('/studies/' + series.ParentStudy, function(study) { + GetResource('/patients/' + study.ParentPatient, function(patient) { $('#instance .patient-link').attr('href', '#patient?uuid=' + patient.ID); $('#instance .study-link').attr('href', '#study?uuid=' + study.ID); @@ -589,13 +575,8 @@ .append(FormatInstance(instance)) .listview('refresh'); - $.ajax({ - url: '../instances/' + instance.ID + '/tags', - cache: false, - dataType: 'json', - success: function(s) { - $('#dicom-tree').tree('loadData', ConvertForTree(s)); - } + GetResource('/instances/' + instance.ID + '/tags', function(s) { + $('#dicom-tree').tree('loadData', ConvertForTree(s)); }); SetupAnonymizedOrModifiedFrom('#instance-anonymized-from', instance, 'instance', 'AnonymizedFrom'); @@ -628,14 +609,7 @@ if ('uuid' in $.mobile.pageData && currentPage == $.mobile.pageData.active && currentUuid != $.mobile.pageData.uuid) { - if (currentPage == 'patient') - RefreshPatient(); - else if (currentPage == 'study') - RefreshStudy(); - else if (currentPage == 'series') - RefreshSeries(); - else if (currentPage == 'instance') - RefreshInstance(); + Refresh(); } }); }); @@ -728,41 +702,52 @@ $('#instance-preview').live('click', function(e) { if ($.mobile.pageData) { - GetSingleResource('instances', $.mobile.pageData.uuid + '/frames', function(frames) { - if (frames.length == 1) - { - // Viewing a single-frame image - jQuery.slimbox('../instances/' + $.mobile.pageData.uuid + '/preview', '', { - overlayFadeDuration : 1, - resizeDuration : 1, - imageFadeDuration : 1 - }); - } - else - { - // Viewing a multi-frame image + var pdf = '../instances/' + $.mobile.pageData.uuid + '/pdf'; + $.ajax({ + url: pdf, + cache: false, + success: function(s) { + window.location.assign(pdf); + }, + error: function() { + GetResource('/instances/' + $.mobile.pageData.uuid + '/frames', function(frames) { + if (frames.length == 1) + { + // Viewing a single-frame image + jQuery.slimbox('../instances/' + $.mobile.pageData.uuid + '/preview', '', { + overlayFadeDuration : 1, + resizeDuration : 1, + imageFadeDuration : 1 + }); + } + else + { + // Viewing a multi-frame image - var images = []; - for (var i = 0; i < frames.length; i++) { - images.push([ '../instances/' + $.mobile.pageData.uuid + '/frames/' + i + '/preview' ]); - } + var images = []; + for (var i = 0; i < frames.length; i++) { + images.push([ '../instances/' + $.mobile.pageData.uuid + '/frames/' + i + '/preview' ]); + } - jQuery.slimbox(images, 0, { - overlayFadeDuration : 1, - resizeDuration : 1, - imageFadeDuration : 1, - loop : true + jQuery.slimbox(images, 0, { + overlayFadeDuration : 1, + resizeDuration : 1, + imageFadeDuration : 1, + loop : true + }); + } }); } }); - } }); + + $('#series-preview').live('click', function(e) { if ($.mobile.pageData) { - GetSingleResource('series', $.mobile.pageData.uuid, function(series) { - GetMultipleResources('instances', series.Instances, function(instances) { + GetResource('/series/' + $.mobile.pageData.uuid, function(series) { + GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) { Sort(instances, function(x) { return x.IndexInSeries; }, true, false); var images = []; @@ -777,7 +762,7 @@ imageFadeDuration : 1, loop : true }); - }) + }); }); } }); @@ -786,7 +771,6 @@ - function ChooseDicomModality(callback) { var clickedModality = '';
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancExplorer/libs/images/notes.txt Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,5 @@ +* The "ajax-loader.gif" was generated by the http://www.ajaxload.info/ + Web site. "The Generated gifs can be used, modified and distributed + under the terms of the Do What The Fuck You Want To Public License." + +* The images "icons*" come from jQuery Mobile.
--- a/OrthancExplorer/libs/jquery-1.7.2.min.js Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -/*! jQuery v1.7.2 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( -a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancExplorer/libs/jquery.min.js Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file
--- a/OrthancExplorer/libs/jquery.mobile-1.1.0.min.css Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f9f9f9,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus{outline:0}.ui-focus,.ui-btn:focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus{outline-width:1px;outline-style:dotted}.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;box-shadow:0 1px 1px -1px #fff;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.5em}.ui-page-footer-fixed{padding-bottom:3em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0;max-width:100%}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-mini{margin:.25em 5px}.ui-btn-inner{padding:.6em 20px;min-width:.75em;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1;width:100%}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left .ui-btn-inner .ui-icon,.ui-btn-icon-right .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=.0001);font-size:1px;border:0;text-indent:-9999px}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:0 0 .5em;zoom:1}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -6px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.ui-header .ui-field-contain-left,.ui-header .ui-field-contain-right{position:absolute;top:0;width:25%}.ui-header .ui-field-contain-left{left:1em}.ui-header .ui-field-contain-right{right:1em}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1;margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:97%;outline:0}.ui-header input.ui-input-text,.ui-footer input.ui-input-text{margin-left:1.25%;padding:.4em 1%;width:95.5%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em;width:97%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:48px}.ui-li-divider .ui-li-count,.ui-li-static .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file
--- a/OrthancExplorer/libs/jquery.mobile-1.1.0.min.js Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -(function(D,s,k){typeof define==="function"&&define.amd?define(["jquery"],function(a){k(a,D,s);return a.mobile}):k(D.jQuery,D,s)})(this,document,function(D,s,k){(function(a,c,b,e){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function d(b){for(var d={},f,g;b;){f=a.data(b,t);for(g in f)if(f[g])d[g]=d.hasVirtualBinding=true;b=b.parentNode}return d}function g(){y&&(clearTimeout(y),y=0);y=setTimeout(function(){F=y=0;C.length=0;z=false;G=true},a.vmouse.resetTimerDuration)} -function h(b,d,g){var c,h;if(!(h=g&&g[b])){if(g=!g)a:{for(g=d.target;g;){if((h=a.data(g,t))&&(!b||h[b]))break a;g=g.parentNode}g=null}h=g}if(h){c=d;var g=c.type,z,j;c=a.Event(c);c.type=b;h=c.originalEvent;z=a.event.props;g.search(/^(mouse|click)/)>-1&&(z=w);if(h)for(j=z.length;j;)b=z[--j],c[b]=h[b];if(g.search(/mouse(down|up)|click/)>-1&&!c.which)c.which=1;if(g.search(/^touch/)!==-1&&(b=f(h),g=b.touches,b=b.changedTouches,g=g&&g.length?g[0]:b&&b.length?b[0]:e))for(h=0,len=u.length;h<len;h++)b=u[h], -c[b]=g[b];a(d.target).trigger(c)}return c}function j(b){var d=a.data(b.target,x);if(!z&&(!F||F!==d))if(d=h("v"+b.type,b))d.isDefaultPrevented()&&b.preventDefault(),d.isPropagationStopped()&&b.stopPropagation(),d.isImmediatePropagationStopped()&&b.stopImmediatePropagation()}function o(b){var g=f(b).touches,c;if(g&&g.length===1&&(c=b.target,g=d(c),g.hasVirtualBinding))F=L++,a.data(c,x,F),y&&(clearTimeout(y),y=0),A=G=false,c=f(b).touches[0],s=c.pageX,E=c.pageY,h("vmouseover",b,g),h("vmousedown",b,g)} -function m(a){G||(A||h("vmousecancel",a,d(a.target)),A=true,g())}function p(b){if(!G){var c=f(b).touches[0],e=A,z=a.vmouse.moveDistanceThreshold;A=A||Math.abs(c.pageX-s)>z||Math.abs(c.pageY-E)>z;flags=d(b.target);A&&!e&&h("vmousecancel",b,flags);h("vmousemove",b,flags);g()}}function l(a){if(!G){G=true;var b=d(a.target),c;h("vmouseup",a,b);if(!A&&(c=h("vclick",a,b))&&c.isDefaultPrevented())c=f(a).changedTouches[0],C.push({touchID:F,x:c.clientX,y:c.clientY}),z=true;h("vmouseout",a,b);A=false;g()}}function r(b){var b= -a.data(b,t),d;if(b)for(d in b)if(b[d])return true;return false}function n(){}function k(b){var d=b.substr(1);return{setup:function(){r(this)||a.data(this,t,{});a.data(this,t)[b]=true;v[b]=(v[b]||0)+1;v[b]===1&&H.bind(d,j);a(this).bind(d,n);if(K)v.touchstart=(v.touchstart||0)+1,v.touchstart===1&&H.bind("touchstart",o).bind("touchend",l).bind("touchmove",p).bind("scroll",m)},teardown:function(){--v[b];v[b]||H.unbind(d,j);K&&(--v.touchstart,v.touchstart||H.unbind("touchstart",o).unbind("touchmove",p).unbind("touchend", -l).unbind("scroll",m));var f=a(this),g=a.data(this,t);g&&(g[b]=false);f.unbind(d,n);r(this)||f.removeData(t)}}}var t="virtualMouseBindings",x="virtualTouchID",c="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),u="clientX clientY pageX pageY screenX screenY".split(" "),w=a.event.props.concat(a.event.mouseHooks?a.event.mouseHooks.props:[]),v={},y=0,s=0,E=0,A=false,C=[],z=false,G=false,K="addEventListener"in b,H=a(b),L=1,F=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10, -resetTimerDuration:1500};for(var I=0;I<c.length;I++)a.event.special[c[I]]=k(c[I]);K&&b.addEventListener("click",function(b){var d=C.length,f=b.target,g,c,e,h,z;if(d){g=b.clientX;c=b.clientY;threshold=a.vmouse.clickDistanceThreshold;for(e=f;e;){for(h=0;h<d;h++)if(z=C[h],e===f&&Math.abs(z.x-g)<threshold&&Math.abs(z.y-c)<threshold||a.data(e,x)===z.touchID){b.preventDefault();b.stopPropagation();return}e=e.parentNode}}},true)})(jQuery,s,k);(function(a,c,b){function e(a){a=a||location.href;return"#"+a.replace(/^[^#]*#?(.*)$/, -"$1")}var f="hashchange",d=k,g,h=a.event.special,j=d.documentMode,o="on"+f in c&&(j===b||j>7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;h[f]=a.extend(h[f],{setup:function(){if(o)return false;a(g.start)},teardown:function(){if(o)return false;a(g.stop)}});g=function(){function g(){var b=e(),d=t(r);if(b!==r)k(r=b,d),a(c).trigger(f);else if(d!==r)location.href=location.href.replace(/#.*/,"")+d;j=setTimeout(g,a.fn[f].delay)}var h={},j,r=e(),n=function(a){return a},k= -n,t=n;h.start=function(){j||g()};h.stop=function(){j&&clearTimeout(j);j=b};a.browser.msie&&!o&&function(){var b,c;h.start=function(){if(!b)c=(c=a.fn[f].src)&&c+e(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){c||k(e());g()}).attr("src",c||"javascript:0").insertAfter("body")[0].contentWindow,d.onpropertychange=function(){try{if(event.propertyName==="title")b.document.title=d.title}catch(a){}}};h.stop=n;t=function(){return e(b.location.href)};k=function(g,c){var e=b.document, -h=a.fn[f].domain;if(g!==c)e.title=d.title,e.open(),h&&e.write('<script>document.domain="'+h+'"<\/script>'),e.close(),b.location.hash=g}}();return h}()})(jQuery,this);(function(a,c){if(a.cleanData){var b=a.cleanData;a.cleanData=function(f){for(var d=0,g;(g=f[d])!=null;d++)a(g).triggerHandler("remove");b(f)}}else{var e=a.fn.remove;a.fn.remove=function(b,d){return this.each(function(){d||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return e.call(a(this), -b,d)})}}a.widget=function(b,d,g){var c=b.split(".")[0],e,b=b.split(".")[1];e=c+"-"+b;if(!g)g=d,d=a.Widget;a.expr[":"][e]=function(d){return!!a.data(d,b)};a[c]=a[c]||{};a[c][b]=function(a,b){arguments.length&&this._createWidget(a,b)};d=new d;d.options=a.extend(true,{},d.options);a[c][b].prototype=a.extend(true,d,{namespace:c,widgetName:b,widgetEventPrefix:a[c][b].prototype.widgetEventPrefix||b,widgetBaseClass:e},g);a.widget.bridge(b,a[c][b])};a.widget.bridge=function(b,d){a.fn[b]=function(g){var e= -typeof g==="string",j=Array.prototype.slice.call(arguments,1),o=this,g=!e&&j.length?a.extend.apply(null,[true,g].concat(j)):g;if(e&&g.charAt(0)==="_")return o;e?this.each(function(){var d=a.data(this,b);if(!d)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+g+"'";if(!a.isFunction(d[g]))throw"no such method '"+g+"' for "+b+" widget instance";var e=d[g].apply(d,j);if(e!==d&&e!==c)return o=e,false}):this.each(function(){var c=a.data(this,b);c?c.option(g||{})._init(): -a.data(this,b,new d(g,this))});return o}};a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(b,d){a.data(d,this.widgetName,this);this.element=a(d);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var g=this;this.element.bind("remove."+this.widgetName,function(){g.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b= -{};a.metadata&&(b=a.metadata.get(element)[this.widgetName]);return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,d){var g=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(d===c)return this.options[b]; -g={};g[b]=d}this._setOptions(g);return this},_setOptions:function(b){var d=this;a.each(b,function(a,b){d._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b;a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,d,g){var c=this.options[b],d=a.Event(d); -d.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();g=g||{};if(d.originalEvent)for(var b=a.event.props.length,e;b;)e=a.event.props[--b],d[e]=d.originalEvent[e];this.element.trigger(d,g);return!(a.isFunction(c)&&c.call(this.element[0],d,g)===false||d.isDefaultPrevented())}}})(jQuery);(function(a,c){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments);this._trigger("init")},_getCreateOptions:function(){var b=this.element, -e={};a.each(this.options,function(a){var d=b.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));d!==c&&(e[a]=d)});return e},enhanceWithin:function(b,c){this.enhance(a(this.options.initSelector,a(b)),c)},enhance:function(b,c){var f,d=a(b),d=a.mobile.enhanceable(d);c&&d.length&&(f=(f=a.mobile.closestPageData(d))&&f.keepNativeSelector()||"",d=d.not(f));d[this.widgetName]()},raise:function(a){throw"Widget ["+this.widgetName+"]: "+a;}})})(jQuery);(function(a,c){var b={};a.mobile=a.extend({}, -{version:"1.1.0",ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",focusClass:"ui-focus",ajaxEnabled:true,hashListeningEnabled:true,linkBindingEnabled:true,defaultPageTransition:"fade",maxTransitionWidth:false,minScrollBack:250,touchOverflowEnabled:false,defaultDialogTransition:"pop",loadingMessage:"loading",pageLoadErrorMessage:"Error Loading Page",loadingMessageTextVisible:false,loadingMessageTheme:"a",pageLoadErrorMessageTheme:"e",autoInitializePage:true, -pushStateEnabled:true,ignoreContentEnabled:false,orientationChangeEnabled:true,buttonMarkup:{hoverDelay:200},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(b){if(a.type(b)!== -"number")b=a.mobile.defaultHomeScroll;a.event.special.scrollstart.enabled=false;setTimeout(function(){c.scrollTo(0,b);a(k).trigger("silentscroll",{x:0,y:b})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},nsNormalizeDict:b,nsNormalize:function(d){return!d?void 0:b[d]||(b[d]=a.camelCase(a.mobile.ns+d))},getInheritedTheme:function(a,b){for(var c=a[0],e="",f=/ui-(bar|body|overlay)-([a-z])\b/,m,p;c;){m=c.className||"";if((p=f.exec(m))&&(e=p[2]))break;c=c.parentNode}return e|| -b||"a"},closestPageData:function(a){return a.closest(':jqmData(role="page"), :jqmData(role="dialog")').data("page")},enhanceable:function(a){return this.haveParents(a,"enhance")},hijackable:function(a){return this.haveParents(a,"ajax")},haveParents:function(b,c){if(!a.mobile.ignoreContentEnabled)return b;for(var e=b.length,f=a(),o,m,p,l=0;l<e;l++){m=b.eq(l);p=false;for(o=b[l];o;){if((o.getAttribute?o.getAttribute("data-"+a.mobile.ns+c):"")==="false"){p=true;break}o=o.parentNode}p||(f=f.add(m))}return f}}, -a.mobile);a.fn.jqmData=function(b,c){var f;typeof b!="undefined"&&(b&&(b=a.mobile.nsNormalize(b)),f=this.data.apply(this,arguments.length<2?[b]:[b,c]));return f};a.jqmData=function(b,c,f){var e;typeof c!="undefined"&&(e=a.data(b,c?a.mobile.nsNormalize(c):c,f));return e};a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))};a.jqmRemoveData=function(b,c){return a.removeData(b,a.mobile.nsNormalize(c))};a.fn.removeWithDependents=function(){a.removeWithDependents(this)};a.removeWithDependents= -function(b){b=a(b);(b.jqmData("dependents")||a()).remove();b.remove()};a.fn.addDependents=function(b){a.addDependents(a(this),b)};a.addDependents=function(b,c){var f=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(f,c))};a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()};a.fn.jqmEnhanceable=function(){return a.mobile.enhanceable(this)};a.fn.jqmHijackable=function(){return a.mobile.hijackable(this)};var e=a.find,f=/:jqmData\(([^)]*)\)/g;a.find=function(b, -c,h,j){b=b.replace(f,"[data-"+(a.mobile.ns||"")+"$1]");return e.call(this,b,c,h,j)};a.extend(a.find,e);a.find.matches=function(b,c){return a.find(b,null,null,c)};a.find.matchesSelector=function(b,c){return a.find(c,null,null,[b]).length>0}})(jQuery,this);(function(a){a(s);var c=a("html");a.mobile.media=function(){var b={},e=a("<div id='jquery-mediatest'>"),f=a("<body>").append(e);return function(a){if(!(a in b)){var g=k.createElement("style"),h="@media "+a+" { #jquery-mediatest { position:absolute; } }"; -g.type="text/css";g.styleSheet?g.styleSheet.cssText=h:g.appendChild(k.createTextNode(h));c.prepend(f).prepend(g);b[a]=e.css("position")==="absolute";f.add(g).remove()}return b[a]}}()})(jQuery);(function(a,c){function b(a){var b=a.charAt(0).toUpperCase()+a.substr(1),a=(a+" "+g.join(b+" ")+b).split(" "),f;for(f in a)if(d[a[f]]!==c)return true}function e(a,b,c){var d=k.createElement("div"),c=c?[c]:g,f;for(i=0;i<c.length;i++){var e=c[i],h="-"+e.charAt(0).toLowerCase()+e.substr(1)+"-"+a+": "+b+";",e=e.charAt(0).toUpperCase()+ -e.substr(1)+(a.charAt(0).toUpperCase()+a.substr(1));d.setAttribute("style",h);d.style[e]&&(f=true)}return!!f}var f=a("<body>").prependTo("html"),d=f[0].style,g=["Webkit","Moz","O"],h="palmGetResource"in s,j=s.operamini&&{}.toString.call(s.operamini)==="[object OperaMini]",o=s.blackberry;a.extend(a.mobile,{browser:{}});a.mobile.browser.ie=function(){for(var a=3,b=k.createElement("div"),c=b.all||[];b.innerHTML="<\!--[if gt IE "+ ++a+"]><br><![endif]--\>",c[0];);return a>4?a:!a}();a.extend(a.support, -{orientation:"orientation"in s&&"onorientationchange"in s,touch:"ontouchend"in k,cssTransitions:"WebKitTransitionEvent"in s||e("transition","height 100ms linear"),pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!b("content"),touchOverflow:!!b("overflowScrolling"),cssTransform3d:e("perspective","10px","moz")||a.mobile.media("(-"+g.join("-transform-3d),(-")+"-transform-3d),(transform-3d)"),boxShadow:!!b("boxShadow")&&!o,scrollTop:("pageXOffset"in -s||"scrollTop"in k.documentElement||"scrollTop"in f[0])&&!h&&!j,dynamicBaseTag:function(){var b=location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),d=null,e="",g;c.length?e=c.attr("href"):c=d=a("<base>",{href:b}).appendTo("head");g=a("<a href='testurl' />").prependTo(f)[0].href;c[0].href=e||location.pathname;d&&d.remove();return g.indexOf(b)===0}()});f.remove();h=function(){var a=s.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")> --1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();a.mobile.gradeA=function(){return a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=7};a.mobile.ajaxBlacklist=s.blackberry&&!s.WebKitPoint||j||h;h&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")});a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);(function(a,c,b){function e(b,c,d){var f=d.type;d.type=c;a.event.handle.call(b, -d);d.type=f}a.each("touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=function(a){return a?this.bind(c,a):this.trigger(c)};a.attrFn[c]=true});var f=a.support.touch,d=f?"touchstart":"mousedown",g=f?"touchend":"mouseup",h=f?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function b(a,f){d=f;e(c,d?"scrollstart":"scrollstop",a)}var c=this,d,f;a(c).bind("touchmove scroll", -function(c){a.event.special.scrollstart.enabled&&(d||b(c,true),clearTimeout(f),f=setTimeout(function(){b(c,false)},50))})}};a.event.special.tap={setup:function(){var b=this,c=a(b);c.bind("vmousedown",function(d){function f(){clearTimeout(q)}function g(){f();c.unbind("vclick",h).unbind("vmouseup",f);a(k).unbind("vmousecancel",g)}function h(a){g();n==a.target&&e(b,"tap",a)}if(d.which&&d.which!==1)return false;var n=d.target,q;c.bind("vmouseup",f).bind("vclick",h);a(k).bind("vmousecancel",g);q=setTimeout(function(){e(b, -"taphold",a.Event("taphold",{target:n}))},750)})}};a.event.special.swipe={scrollSupressionThreshold:10,durationThreshold:1E3,horizontalDistanceThreshold:30,verticalDistanceThreshold:75,setup:function(){var c=a(this);c.bind(d,function(d){function f(b){if(l){var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;k={time:(new Date).getTime(),coords:[c.pageX,c.pageY]};Math.abs(l.coords[0]-k.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&b.preventDefault()}}var e=d.originalEvent.touches? -d.originalEvent.touches[0]:d,l={time:(new Date).getTime(),coords:[e.pageX,e.pageY],origin:a(d.target)},k;c.bind(h,f).one(g,function(){c.unbind(h,f);l&&k&&k.time-l.time<a.event.special.swipe.durationThreshold&&Math.abs(l.coords[0]-k.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(l.coords[1]-k.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&l.origin.trigger("swipe").trigger(l.coords[0]>k.coords[0]?"swipeleft":"swiperight");l=k=b})})}};(function(a,b){function c(){var a= -f();a!==e&&(e=a,d.trigger("orientationchange"))}var d=a(b),f,e,g,h,t={0:true,180:true};if(a.support.orientation&&(g=b.innerWidth||a(b).width(),h=b.innerHeight||a(b).height(),g=g>h&&g-h>50,h=t[b.orientation],g&&h||!g&&!h))t={"-90":true,90:true};a.event.special.orientationchange={setup:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;e=f();d.bind("throttledresize",c)},teardown:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;d.unbind("throttledresize", -c)},add:function(a){var b=a.handler;a.handler=function(a){a.orientation=f();return b.apply(this,arguments)}}};a.event.special.orientationchange.orientation=f=function(){var c=true,c=k.documentElement;return(c=a.support.orientation?t[b.orientation]:c&&c.clientWidth/c.clientHeight<1.1)?"portrait":"landscape"}})(jQuery,c);(function(){a.event.special.throttledresize={setup:function(){a(this).bind("resize",b)},teardown:function(){a(this).unbind("resize",b)}};var b=function(){f=(new Date).getTime();g=f- -c;g>=250?(c=f,a(this).trigger("throttledresize")):(d&&clearTimeout(d),d=setTimeout(b,250-g))},c=0,d,f,g})();a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})})(jQuery,this);(function(a){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:false,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){var a=this;if(a._trigger("beforecreate")=== -false)return false;a.element.attr("tabindex","0").addClass("ui-page ui-body-"+a.options.theme).bind("pagebeforehide",function(){a.removeContainerBackground()}).bind("pagebeforeshow",function(){a.setContainerBackground()})},removeContainerBackground:function(){a.mobile.pageContainer.removeClass("ui-overlay-"+a.mobile.getInheritedTheme(this.element.parent()))},setContainerBackground:function(c){this.options.theme&&a.mobile.pageContainer.addClass("ui-overlay-"+(c||this.options.theme))},keepNativeSelector:function(){var c= -this.options;return c.keepNative&&a.trim(c.keepNative)&&c.keepNative!==c.keepNativeDefault?[c.keepNative,c.keepNativeDefault].join(", "):c.keepNativeDefault}})})(jQuery);(function(a,c,b){var e=function(d){d===b&&(d=true);return function(b,f,e,o){var k=new a.Deferred,p=f?" reverse":"",l=a.mobile.urlHistory.getActive().lastScroll||a.mobile.defaultHomeScroll,r=a.mobile.getScreenHeight(),n=a.mobile.maxTransitionWidth!==false&&a(c).width()>a.mobile.maxTransitionWidth,q=!a.support.cssTransitions||n||!b|| -b==="none",t=function(){a.mobile.pageContainer.toggleClass("ui-mobile-viewport-transitioning viewport-"+b)},x=function(){a.event.special.scrollstart.enabled=false;c.scrollTo(0,l);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},u=function(){o.removeClass(a.mobile.activePageClass+" out in reverse "+b).height("")},n=function(){o&&d&&u();e.addClass(a.mobile.activePageClass);a.mobile.focusPage(e);e.height(r+l);x();q||e.animationComplete(w);e.addClass(b+" in"+p);q&&w()},w=function(){d|| -o&&u();e.removeClass("out in reverse "+b).height("");t();a(c).scrollTop()!==l&&x();k.resolve(b,f,e,o,true)};t();o&&!q?(d?o.animationComplete(n):n(),o.height(r+a(c).scrollTop()).addClass(b+" out"+p)):n();return k.promise()}},f=e(),e=e(false);a.mobile.defaultTransitionHandler=f;a.mobile.transitionHandlers={"default":a.mobile.defaultTransitionHandler,sequential:f,simultaneous:e};a.mobile.transitionFallbacks={}})(jQuery,this);(function(a,c){function b(b){r&&(!r.closest(".ui-page-active").length||b)&& -r.removeClass(a.mobile.activeBtnClass);r=null}function e(){t=false;q.length>0&&a.mobile.changePage.apply(null,q.pop())}function f(b,c,d,f){c&&c.data("page")._trigger("beforehide",null,{nextPage:b});b.data("page")._trigger("beforeshow",null,{prevPage:c||a("")});a.mobile.hidePageLoadingMsg();d&&!a.support.cssTransform3d&&a.mobile.transitionFallbacks[d]&&(d=a.mobile.transitionFallbacks[d]);d=(a.mobile.transitionHandlers[d||"default"]||a.mobile.defaultTransitionHandler)(d,f,b,c);d.done(function(){c&& -c.data("page")._trigger("hide",null,{nextPage:b});b.data("page")._trigger("show",null,{prevPage:c||a("")})});return d}function d(){return s.innerHeight||a(s).height()}function g(){var b=a("."+a.mobile.activePageClass),c=parseFloat(b.css("padding-top")),f=parseFloat(b.css("padding-bottom"));b.css("min-height",d()-c-f)}function h(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c);b.page()}function j(a){for(;a;){if(typeof a.nodeName==="string"&&a.nodeName.toLowerCase()=="a")break;a=a.parentNode}return a}function o(b){var b= -a(b).closest(".ui-page").jqmData("url"),c=v.hrefNoHash;if(!b||!l.isPath(b))b=c;return l.makeUrlAbsolute(b,c)}var m=a(s);a("html");var p=a("head"),l={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,parseUrl:function(b){if(a.type(b)==="object")return b;b=l.urlParseRE.exec(b||"")||[];return{href:b[0]||"",hrefNoHash:b[1]||"",hrefNoSearch:b[2]||"",domain:b[3]||"", -protocol:b[4]||"",doubleSlash:b[5]||"",authority:b[6]||"",username:b[8]||"",password:b[9]||"",host:b[10]||"",hostname:b[11]||"",port:b[12]||"",pathname:b[13]||"",directory:b[14]||"",filename:b[15]||"",search:b[16]||"",hash:b[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;for(var a=a||"",c=(b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"")?b.split("/"):[],d=a.split("/"),f=0;f<d.length;f++){var e=d[f];switch(e){case ".":break;case "..":c.length&&c.pop();break;default:c.push(e)}}return"/"+ -c.join("/")},isSameDomain:function(a,b){return l.parseUrl(a).domain===l.parseUrl(b).domain},isRelativeUrl:function(a){return l.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return l.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!l.isRelativeUrl(a))return a;var c=l.parseUrl(a),d=l.parseUrl(b),f=c.protocol||d.protocol,e=c.protocol?c.doubleSlash:c.doubleSlash||d.doubleSlash,g=c.authority||d.authority,h=c.pathname!=="",j=l.makePathAbsolute(c.pathname||d.filename,d.pathname);return f+ -e+g+j+(c.search||!h&&d.search||"")+c.hash},addSearchParams:function(b,c){var d=l.parseUrl(b),f=typeof c==="object"?a.param(c):c,e=d.search||"?";return d.hrefNoSearch+e+(e.charAt(e.length-1)!=="?"?"&":"")+f+(d.hash||"")},convertUrlToDataUrl:function(a){var b=l.parseUrl(a);if(l.isEmbeddedPage(b))return b.hash.split(x)[0].replace(/^#/,"");else if(l.isSameDomain(b,v))return b.hrefNoHash.replace(v.domain,"");return a},get:function(a){if(a===c)a=location.hash;return l.stripHash(a).replace(/[^\/]*\.[^\/*]+$/, -"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(x)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(v.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return l.stripHash(a.replace(/\?.*$/,"").replace(x,""))},isExternal:function(a){a=l.parseUrl(a);return a.protocol&&a.domain!==w.domain?true:false},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var b= -l.parseUrl(l.makeUrlAbsolute(b,v)),d=a.mobile.firstPage,d=d&&d[0]?d[0].id:c;return(b.hrefNoHash===w.hrefNoHash||y&&b.hrefNoHash===v.hrefNoHash)&&(!b.hash||b.hash==="#"||d&&b.hash.replace(/^#/,"")===d)},isEmbeddedPage:function(a){a=l.parseUrl(a);return a.protocol!==""?a.hash&&(a.hrefNoHash===w.hrefNoHash||y&&a.hrefNoHash===v.hrefNoHash):/^#/.test(a.href)}},r=null,n={stack:[],activeIndex:0,getActive:function(){return n.stack[n.activeIndex]},getPrev:function(){return n.stack[n.activeIndex-1]},getNext:function(){return n.stack[n.activeIndex+ -1]},addNew:function(a,b,c,d,f){n.getNext()&&n.clearForward();n.stack.push({url:a,transition:b,title:c,pageUrl:d,role:f});n.activeIndex=n.stack.length-1},clearForward:function(){n.stack=n.stack.slice(0,n.activeIndex+1)},directHashChange:function(b){var d,f,e;this.getActive();a.each(n.stack,function(a,c){b.currentUrl===c.url&&(d=a<n.activeIndex,f=!d,e=a)});this.activeIndex=e!==c?e:this.activeIndex;d?(b.either||b.isBack)(true):f&&(b.either||b.isForward)(false)},ignoreNextHashChange:false},q=[],t=false, -x="&ui-state=dialog",u=p.children("base"),w=l.parseUrl(location.href),v=u.length?l.parseUrl(l.makeUrlAbsolute(u.attr("href"),w.href)):w,y=w.hrefNoHash!==v.hrefNoHash,B=a.support.dynamicBaseTag?{element:u.length?u:a("<base>",{href:v.hrefNoHash}).prependTo(p),set:function(a){B.element.attr("href",l.makeUrlAbsolute(a,v))},reset:function(){B.element.attr("href",v.hrefNoHash)}}:c;a.mobile.focusPage=function(a){var b=a.find("[autofocus]"),c=a.find(".ui-title:eq(0)");b.length?b.focus():c.length?c.focus(): -a.focus()};var E=true,A,C;A=function(){if(E){var b=a.mobile.urlHistory.getActive();if(b){var c=m.scrollTop();b.lastScroll=c<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:c}}};C=function(){setTimeout(A,100)};m.bind(a.support.pushState?"popstate":"hashchange",function(){E=false});m.one(a.support.pushState?"popstate":"hashchange",function(){E=true});m.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){E=true;m.unbind("scrollstop",C);m.bind("scrollstop",C)})}); -m.bind("scrollstop",C);a.mobile.getScreenHeight=d;a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd animationend",b):(setTimeout(b,0),a(this))};a.mobile.path=l;a.mobile.base=B;a.mobile.urlHistory=n;a.mobile.dialogHashKey=x;a.mobile.allowCrossDomainPages=false;a.mobile.getDocumentUrl=function(b){return b?a.extend({},w):w.href};a.mobile.getDocumentBase=function(b){return b?a.extend({},v):v.href};a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&& -b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c);c.isDefaultPrevented()||b.removeWithDependents()})};a.mobile.loadPage=function(b,d){var f=a.Deferred(),e=a.extend({},a.mobile.loadPage.defaults,d),g=null,j=null,k=l.makeUrlAbsolute(b,a.mobile.activePage&&o(a.mobile.activePage)||v.hrefNoHash);if(e.data&&e.type==="get")k=l.addSearchParams(k,e.data),e.data=c;if(e.data&&e.type==="post")e.reloadPage=true;var u=l.getFilePath(k), -n=l.convertUrlToDataUrl(k);e.pageContainer=e.pageContainer||a.mobile.pageContainer;g=e.pageContainer.children(":jqmData(url='"+n+"')");g.length===0&&n&&!l.isPath(n)&&(g=e.pageContainer.children("#"+n).attr("data-"+a.mobile.ns+"url",n));if(g.length===0)if(a.mobile.firstPage&&l.isFirstPageUrl(u))a.mobile.firstPage.parent().length&&(g=a(a.mobile.firstPage));else if(l.isEmbeddedPage(u))return f.reject(k,d),f.promise();B&&B.reset();if(g.length){if(!e.reloadPage)return h(g,e.role),f.resolve(k,d,g),f.promise(); -j=g}var m=e.pageContainer,x=new a.Event("pagebeforeload"),p={url:b,absUrl:k,dataUrl:n,deferred:f,options:e};m.trigger(x,p);if(x.isDefaultPrevented())return f.promise();if(e.showLoadMsg)var r=setTimeout(function(){a.mobile.showPageLoadingMsg()},e.loadMsgDelay);!a.mobile.allowCrossDomainPages&&!l.isSameDomain(w,k)?f.reject(k,d):a.ajax({url:u,type:e.type,data:e.data,dataType:"html",success:function(c,o,m){var x=a("<div></div>"),v=c.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,w=RegExp("\\bdata-"+a.mobile.ns+ -"url=[\"']?([^\"'>]*)[\"']?");RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)").test(c)&&RegExp.$1&&w.test(RegExp.$1)&&RegExp.$1&&(b=u=l.getFilePath(RegExp.$1));B&&B.set(u);x.get(0).innerHTML=c;g=x.find(":jqmData(role='page'), :jqmData(role='dialog')").first();g.length||(g=a("<div data-"+a.mobile.ns+"role='page'>"+c.split(/<\/?body[^>]*>/gmi)[1]+"</div>"));v&&!g.jqmData("title")&&(~v.indexOf("&")&&(v=a("<div>"+v+"</div>").text()),g.jqmData("title",v));if(!a.support.dynamicBaseTag){var q= -l.get(u);g.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b),c=c.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(c)||a(this).attr(b,q+c)})}g.attr("data-"+a.mobile.ns+"url",l.convertUrlToDataUrl(u)).attr("data-"+a.mobile.ns+"external-page",true).appendTo(e.pageContainer);g.one("pagecreate",a.mobile._bindPageRemove);h(g,e.role);k.indexOf("&"+ -a.mobile.subPageUrlKey)>-1&&(g=e.pageContainer.children(":jqmData(url='"+n+"')"));e.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg());p.xhr=m;p.textStatus=o;p.page=g;e.pageContainer.trigger("pageload",p);f.resolve(k,d,g,j)},error:function(b,c,g){B&&B.set(l.get());p.xhr=b;p.textStatus=c;p.errorThrown=g;b=new a.Event("pageloadfailed");e.pageContainer.trigger(b,p);b.isDefaultPrevented()||(e.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg(),a.mobile.showPageLoadingMsg(a.mobile.pageLoadErrorMessageTheme, -a.mobile.pageLoadErrorMessage,true),setTimeout(a.mobile.hidePageLoadingMsg,1500)),f.reject(k,d))}});return f.promise()};a.mobile.loadPage.defaults={type:"get",data:c,reloadPage:false,role:c,showLoadMsg:false,pageContainer:c,loadMsgDelay:50};a.mobile.changePage=function(d,g){if(t)q.unshift(arguments);else{var j=a.extend({},a.mobile.changePage.defaults,g);j.pageContainer=j.pageContainer||a.mobile.pageContainer;j.fromPage=j.fromPage||a.mobile.activePage;var u=j.pageContainer,o=new a.Event("pagebeforechange"), -m={toPage:d,options:j};u.trigger(o,m);if(!o.isDefaultPrevented())if(d=m.toPage,t=true,typeof d=="string")a.mobile.loadPage(d,j).done(function(b,c,d,e){t=false;c.duplicateCachedPage=e;a.mobile.changePage(d,c)}).fail(function(){t=false;b(true);e();j.pageContainer.trigger("pagechangefailed",m)});else{if(d[0]===a.mobile.firstPage[0]&&!j.dataUrl)j.dataUrl=w.hrefNoHash;var o=j.fromPage,p=j.dataUrl&&l.convertUrlToDataUrl(j.dataUrl)||d.jqmData("url"),v=p;l.getFilePath(p);var r=n.getActive(),s=n.activeIndex=== -0,y=0,B=k.title,A=j.role==="dialog"||d.jqmData("role")==="dialog";if(o&&o[0]===d[0]&&!j.allowSamePageTransition)t=false,u.trigger("pagechange",m);else{h(d,j.role);j.fromHashChange&&n.directHashChange({currentUrl:p,isBack:function(){y=-1},isForward:function(){y=1}});try{k.activeElement&&k.activeElement.nodeName.toLowerCase()!="body"?a(k.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(E){}A&&r&&(p=(r.url||"")+x);if(j.changeHash!==false&&p)n.ignoreNextHashChange=true, -l.set(p);var C=!r?B:d.jqmData("title")||d.children(":jqmData(role='header')").find(".ui-title").getEncodedText();C&&B==k.title&&(B=C);d.jqmData("title")||d.jqmData("title",B);j.transition=j.transition||(y&&!s?r.transition:c)||(A?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition);y||n.addNew(p,j.transition,B,v,j.role);k.title=n.getActive().title;a.mobile.activePage=d;j.reverse=j.reverse||y<0;f(d,o,j.transition,j.reverse).done(function(c,f,g,h,l){b();j.duplicateCachedPage&&j.duplicateCachedPage.remove(); -l||a.mobile.focusPage(d);e();u.trigger("pagechange",m)})}}}};a.mobile.changePage.defaults={transition:c,reverse:false,changeHash:true,fromHashChange:false,role:c,duplicateCachedPage:c,pageContainer:c,showLoadMsg:true,dataUrl:c,fromPage:c,allowSamePageTransition:false};a.mobile._registerInternalEvents=function(){a(k).delegate("form","submit",function(b){var c=a(this);if(a.mobile.ajaxEnabled&&!c.is(":jqmData(ajax='false')")&&c.jqmHijackable().length){var d=c.attr("method"),e=c.attr("target"),f=c.attr("action"); -if(!f&&(f=o(c),f===v.hrefNoHash))f=w.hrefNoSearch;f=l.makeUrlAbsolute(f,o(c));!l.isExternal(f)&&!e&&(a.mobile.changePage(f,{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),direction:c.jqmData("direction"),reloadPage:true}),b.preventDefault())}});a(k).bind("vclick",function(c){if(!(c.which>1)&&a.mobile.linkBindingEnabled&&(c=j(c.target),a(c).jqmHijackable().length&&c&&l.parseUrl(c.getAttribute("href")||"#").hash!=="#"))b(true),r=a(c).closest(".ui-btn").not(".ui-disabled"), -r.addClass(a.mobile.activeBtnClass),a("."+a.mobile.activePageClass+" .ui-btn").not(c).blur(),a(c).jqmData("href",a(c).attr("href")).attr("href","#")});a(k).bind("click",function(d){if(a.mobile.linkBindingEnabled){var f=j(d.target),e=a(f),g;if(f&&!(d.which>1)&&e.jqmHijackable().length){g=function(){s.setTimeout(function(){b(true)},200)};e.jqmData("href")&&e.attr("href",e.jqmData("href"));if(e.is(":jqmData(rel='back')"))return s.history.back(),false;var h=o(e),f=l.makeUrlAbsolute(e.attr("href")||"#", -h);if(!a.mobile.ajaxEnabled&&!l.isEmbeddedPage(f))g();else{if(f.search("#")!=-1)if(f=f.replace(/[^#]*#/,""))f=l.isPath(f)?l.makeUrlAbsolute(f,h):l.makeUrlAbsolute("#"+f,w.hrefNoHash);else{d.preventDefault();return}var h=e.is("[rel='external']")||e.is(":jqmData(ajax='false')")||e.is("[target]"),k=a.mobile.allowCrossDomainPages&&w.protocol==="file:"&&f.search(/^https?:/)!=-1;h||l.isExternal(f)&&!k?g():(g=e.jqmData("transition"),h=(h=e.jqmData("direction"))&&h==="reverse"||e.jqmData("back"),e=e.attr("data-"+ -a.mobile.ns+"rel")||c,a.mobile.changePage(f,{transition:g,reverse:h,role:e}),d.preventDefault())}}}});a(k).delegate(".ui-page","pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),d=c.attr("href");d&&a.inArray(d,b)===-1&&(b.push(d),a.mobile.loadPage(d,{role:c.attr("data-"+a.mobile.ns+"rel")}))})});a.mobile._handleHashChange=function(b){var d=l.stripHash(b),f={transition:a.mobile.urlHistory.stack.length===0?"none":c,changeHash:false,fromHashChange:true}; -if(!a.mobile.hashListeningEnabled||n.ignoreNextHashChange)n.ignoreNextHashChange=false;else{if(n.stack.length>1&&d.indexOf(x)>-1)if(a.mobile.activePage.is(".ui-dialog"))n.directHashChange({currentUrl:d,either:function(b){var c=a.mobile.urlHistory.getActive();d=c.pageUrl;a.extend(f,{role:c.role,transition:c.transition,reverse:b})}});else{n.directHashChange({currentUrl:d,isBack:function(){s.history.back()},isForward:function(){s.history.forward()}});return}d?(d=typeof d==="string"&&!l.isPath(d)?l.makeUrlAbsolute("#"+ -d,v):d,a.mobile.changePage(d,f)):a.mobile.changePage(a.mobile.firstPage,f)}};m.bind("hashchange",function(){a.mobile._handleHashChange(location.hash)});a(k).bind("pageshow",g);a(s).bind("throttledresize",g)}})(jQuery);(function(a,c){var b={},e=a(c),f=a.mobile.path.parseUrl(location.href);a.extend(b,{initialFilePath:f.pathname+f.search,initialHref:f.hrefNoHash,state:function(){return{hash:location.hash||"#"+b.initialFilePath,title:k.title,initialHref:b.initialHref}},resetUIKeys:function(b){var c="&"+ -a.mobile.subPageUrlKey,f=b.indexOf(a.mobile.dialogHashKey);f>-1?b=b.slice(0,f)+"#"+b.slice(f):b.indexOf(c)>-1&&(b=b.split(c).join("#"+c));return b},hashValueAfterReset:function(c){c=b.resetUIKeys(c);return a.mobile.path.parseUrl(c).hash},nextHashChangePrevented:function(c){a.mobile.urlHistory.ignoreNextHashChange=c;b.onHashChangeDisabled=c},onHashChange:function(){if(!b.onHashChangeDisabled){var c,f;c=location.hash;var e=a.mobile.path.isPath(c),j=e?location.href:a.mobile.getDocumentUrl();c=e?c.replace("#", -""):c;f=b.state();c=a.mobile.path.makeUrlAbsolute(c,j);e&&(c=b.resetUIKeys(c));history.replaceState(f,k.title,c)}},onPopState:function(c){var c=c.originalEvent.state,f,h;if(c){f=b.hashValueAfterReset(a.mobile.urlHistory.getActive().url);h=b.hashValueAfterReset(c.hash.replace("#",""));if(f=f!==h)e.one("hashchange.pushstate",function(){b.nextHashChangePrevented(false)});b.nextHashChangePrevented(false);a.mobile._handleHashChange(c.hash);f&&b.nextHashChangePrevented(true)}},init:function(){e.bind("hashchange", -b.onHashChange);e.bind("popstate",b.onPopState);location.hash===""&&history.replaceState(b.state(),k.title,location.href)}});a(function(){a.mobile.pushStateEnabled&&a.support.pushState&&b.init()})})(jQuery,this);jQuery.mobile.transitionFallbacks.pop="fade";(function(a){a.mobile.transitionHandlers.slide=a.mobile.transitionHandlers.simultaneous;a.mobile.transitionFallbacks.slide="fade"})(jQuery,this);jQuery.mobile.transitionFallbacks.slidedown="fade";jQuery.mobile.transitionFallbacks.slideup="fade"; -jQuery.mobile.transitionFallbacks.flip="fade";jQuery.mobile.transitionFallbacks.flow="fade";jQuery.mobile.transitionFallbacks.turn="fade";(function(a){a.mobile.page.prototype.options.degradeInputs={color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:"text",tel:false,time:false,url:false,week:false};a(k).bind("pagecreate create",function(c){var b=a.mobile.closestPageData(a(c.target)),e;if(b)e=b.options,a(c.target).find("input").not(b.keepNativeSelector()).each(function(){var b= -a(this),c=this.getAttribute("type"),g=e.degradeInputs[c]||"text";if(e.degradeInputs[c]){var h=a("<div>").html(b.clone()).html(),j=h.indexOf(" type=")>-1;b.replaceWith(h.replace(j?/\s+type=["']?\w+['"]?/:/\/?>/,' type="'+g+'" data-'+a.mobile.ns+'type="'+c+'"'+(j?"":">")))}})})})(jQuery);(function(a,c){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,c=this.element,f=a("<a href='#' data-"+a.mobile.ns+ -"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>"),d=a("<div/>",{role:"dialog","class":"ui-dialog-contain ui-corner-all ui-overlay-shadow"});c.addClass("ui-dialog ui-overlay-"+this.options.overlayTheme);c.wrapInner(d).children().find(":jqmData(role='header')").prepend(f).end().children(":first-child").addClass("ui-corner-top").end().children(":last-child").addClass("ui-corner-bottom");f.bind("click",function(){b.close()});c.bind("vclick submit",function(b){var b= -a(b.target).closest(b.type==="vclick"?"a":"form"),c;b.length&&!b.jqmData("transition")&&(c=a.mobile.urlHistory.getActive()||{},b.attr("data-"+a.mobile.ns+"transition",c.transition||a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(){a(this).find("."+a.mobile.activeBtnClass).removeClass(a.mobile.activeBtnClass)}).bind("pagebeforeshow",function(){b.options.overlayTheme&&b.element.page("removeContainerBackground").page("setContainerBackground", -b.options.overlayTheme)})},close:function(){c.history.back()}});a(k).delegate(a.mobile.dialog.prototype.options.initSelector,"pagecreate",function(){a.mobile.dialog.prototype.enhance(this)})})(jQuery,this);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")};a(k).bind("pagecreate create",function(c){a(":jqmData(role='fieldcontain')",c.target).jqmEnhanceable().fieldcontain()})})(jQuery);(function(a){a.fn.grid=function(c){return this.each(function(){var b= -a(this),e=a.extend({grid:null},c),f=b.children(),d={solo:1,a:2,b:3,c:4,d:5},e=e.grid;if(!e)if(f.length<=5)for(var g in d)d[g]===f.length&&(e=g);else e="a";d=d[e];b.addClass("ui-grid-"+e);f.filter(":nth-child("+d+"n+1)").addClass("ui-block-a");d>1&&f.filter(":nth-child("+d+"n+2)").addClass("ui-block-b");d>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");d>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");d>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);(function(a){a(k).bind("pagecreate create", -function(c){a(":jqmData(role='nojs')",c.target).addClass("ui-nojs")})})(jQuery);(function(a,c){function b(a){for(var b;a;){if((b=typeof a.className==="string"&&a.className+" ")&&b.indexOf("ui-btn ")>-1&&b.indexOf("ui-disabled ")<0)break;a=a.parentNode}return a}a.fn.buttonMarkup=function(b){for(var b=b&&a.type(b)=="object"?b:{},d=0;d<this.length;d++){var g=this.eq(d),h=g[0],j=a.extend({},a.fn.buttonMarkup.defaults,{icon:b.icon!==c?b.icon:g.jqmData("icon"),iconpos:b.iconpos!==c?b.iconpos:g.jqmData("iconpos"), -theme:b.theme!==c?b.theme:g.jqmData("theme")||a.mobile.getInheritedTheme(g,"c"),inline:b.inline!==c?b.inline:g.jqmData("inline"),shadow:b.shadow!==c?b.shadow:g.jqmData("shadow"),corners:b.corners!==c?b.corners:g.jqmData("corners"),iconshadow:b.iconshadow!==c?b.iconshadow:g.jqmData("iconshadow"),mini:b.mini!==c?b.mini:g.jqmData("mini")},b),o="ui-btn-inner",m,p,l,r,n,q;a.each(j,function(b,c){h.setAttribute("data-"+a.mobile.ns+b,c);g.jqmData(b,c)});(q=a.data(h.tagName==="INPUT"||h.tagName==="BUTTON"? -h.parentNode:h,"buttonElements"))?(h=q.outer,g=a(h),l=q.inner,r=q.text,a(q.icon).remove(),q.icon=null):(l=k.createElement(j.wrapperEls),r=k.createElement(j.wrapperEls));n=j.icon?k.createElement("span"):null;e&&!q&&e();if(!j.theme)j.theme=a.mobile.getInheritedTheme(g,"c");m="ui-btn ui-btn-up-"+j.theme;m+=j.inline?" ui-btn-inline":"";m+=j.shadow?" ui-shadow":"";m+=j.corners?" ui-btn-corner-all":"";j.mini!==c&&(m+=j.mini?" ui-mini":" ui-fullsize");j.inline!==c&&(m+=j.inline===false?" ui-btn-block":" ui-btn-inline"); -if(j.icon)j.icon="ui-icon-"+j.icon,j.iconpos=j.iconpos||"left",p="ui-icon "+j.icon,j.iconshadow&&(p+=" ui-icon-shadow");j.iconpos&&(m+=" ui-btn-icon-"+j.iconpos,j.iconpos=="notext"&&!g.attr("title")&&g.attr("title",g.getEncodedText()));o+=j.corners?" ui-btn-corner-all":"";j.iconpos&&j.iconpos==="notext"&&!g.attr("title")&&g.attr("title",g.getEncodedText());q&&g.removeClass(q.bcls||"");g.removeClass("ui-link").addClass(m);l.className=o;r.className="ui-btn-text";q||l.appendChild(r);if(n&&(n.className= -p,!q||!q.icon))n.appendChild(k.createTextNode("\u00a0")),l.appendChild(n);for(;h.firstChild&&!q;)r.appendChild(h.firstChild);q||h.appendChild(l);q={bcls:m,outer:h,inner:l,text:r,icon:n};a.data(h,"buttonElements",q);a.data(l,"buttonElements",q);a.data(r,"buttonElements",q);n&&a.data(n,"buttonElements",q)}return this};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,wrapperEls:"span"};var e=function(){var c=a.mobile.buttonMarkup.hoverDelay,d,g;a(k).bind({"vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart":function(e){var j, -k=a(b(e.target)),e=e.type;if(k.length)if(j=k.attr("data-"+a.mobile.ns+"theme"),e==="vmousedown")a.support.touch?d=setTimeout(function(){k.removeClass("ui-btn-up-"+j).addClass("ui-btn-down-"+j)},c):k.removeClass("ui-btn-up-"+j).addClass("ui-btn-down-"+j);else if(e==="vmousecancel"||e==="vmouseup")k.removeClass("ui-btn-down-"+j).addClass("ui-btn-up-"+j);else if(e==="vmouseover"||e==="focus")a.support.touch?g=setTimeout(function(){k.removeClass("ui-btn-up-"+j).addClass("ui-btn-hover-"+j)},c):k.removeClass("ui-btn-up-"+ -j).addClass("ui-btn-hover-"+j);else if(e==="vmouseout"||e==="blur"||e==="scrollstart")k.removeClass("ui-btn-hover-"+j+" ui-btn-down-"+j).addClass("ui-btn-up-"+j),d&&clearTimeout(d),g&&clearTimeout(g)},"focusin focus":function(c){a(b(c.target)).addClass(a.mobile.focusClass)},"focusout blur":function(c){a(b(c.target)).removeClass(a.mobile.focusClass)}});e=null};a(k).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", -b.target).not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})})(jQuery);(function(a){a.mobile.page.prototype.options.backBtnText="Back";a.mobile.page.prototype.options.addBackBtn=false;a.mobile.page.prototype.options.backBtnTheme=null;a.mobile.page.prototype.options.headerTheme="a";a.mobile.page.prototype.options.footerTheme="a";a.mobile.page.prototype.options.contentTheme=null;a(k).delegate(":jqmData(role='page'), :jqmData(role='dialog')","pagecreate",function(){var c=a(this), -b=c.data("page").options,e=c.jqmData("role"),f=b.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",this).jqmEnhanceable().each(function(){var d=a(this),g=d.jqmData("role"),h=d.jqmData("theme"),j=h||b.contentTheme||e==="dialog"&&f,k;d.addClass("ui-"+g);if(g==="header"||g==="footer"){var m=h||(g==="header"?b.headerTheme:b.footerTheme)||f;d.addClass("ui-bar-"+m).attr("role",g==="header"?"banner":"contentinfo");g==="header"&&(h=d.children("a"),k=h.hasClass("ui-btn-left"), -j=h.hasClass("ui-btn-right"),k=k||h.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length,j||h.eq(1).addClass("ui-btn-right"));b.addBackBtn&&g==="header"&&a(".ui-page").length>1&&c.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!k&&a("<a href='#' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+b.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",b.backBtnTheme||m).prependTo(d);d.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({role:"heading", -"aria-level":"1"})}else g==="content"&&(j&&d.addClass("ui-body-"+j),d.attr("role","main"))})})})(jQuery);(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:true,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,iconTheme:"d",mini:false,initSelector:":jqmData(role='collapsible')"},_create:function(){var c=this.element,b=this.options,e=c.addClass("ui-collapsible"),f=c.children(b.heading).first(), -d=e.wrapInner("<div class='ui-collapsible-content'></div>").find(".ui-collapsible-content"),g=c.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set");f.is("legend")&&(f=a("<div role='heading'>"+f.html()+"</div>").insertBefore(f),f.next().remove());if(g.length){if(!b.theme)b.theme=g.jqmData("theme")||a.mobile.getInheritedTheme(g,"c");if(!b.contentTheme)b.contentTheme=g.jqmData("content-theme");if(!b.iconPos)b.iconPos=g.jqmData("iconpos");if(!b.mini)b.mini=g.jqmData("mini")}d.addClass(b.contentTheme? -"ui-body-"+b.contentTheme:"");f.insertBefore(d).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:false,corners:false,iconpos:c.jqmData("iconpos")||b.iconPos||"left",icon:"plus",mini:b.mini,theme:b.theme}).add(".ui-btn-inner",c).addClass("ui-corner-top ui-corner-bottom");e.bind("expand collapse",function(c){if(!c.isDefaultPrevented()){c.preventDefault(); -var j=a(this),c=c.type==="collapse",k=b.contentTheme;f.toggleClass("ui-collapsible-heading-collapsed",c).find(".ui-collapsible-heading-status").text(c?b.expandCueText:b.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-minus",!c).toggleClass("ui-icon-plus",c);j.toggleClass("ui-collapsible-collapsed",c);d.toggleClass("ui-collapsible-content-collapsed",c).attr("aria-hidden",c);if(k&&(!g.length||e.jqmData("collapsible-last")))f.find("a").first().add(f.find(".ui-btn-inner")).toggleClass("ui-corner-bottom", -c),d.toggleClass("ui-corner-bottom",!c);d.trigger("updatelayout")}}).trigger(b.collapsed?"collapse":"expand");f.bind("click",function(a){var b=f.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";e.trigger(b);a.preventDefault()})}});a(k).bind("pagecreate create",function(c){a.mobile.collapsible.prototype.enhanceWithin(c.target)})})(jQuery);(function(a,c){a.widget("mobile.collapsibleset",a.mobile.widget,{options:{initSelector:":jqmData(role='collapsible-set')"},_create:function(){var b=this.element.addClass("ui-collapsible-set"), -e=this.options;if(!e.theme)e.theme=a.mobile.getInheritedTheme(b,"c");if(!e.contentTheme)e.contentTheme=b.jqmData("content-theme");if(!e.corners)e.corners=b.jqmData("corners")===c?true:false;b.jqmData("collapsiblebound")||b.jqmData("collapsiblebound",true).bind("expand collapse",function(b){var c=b.type==="collapse",b=a(b.target).closest(".ui-collapsible"),e=b.data("collapsible");e.options.contentTheme&&b.jqmData("collapsible-last")&&(b.find(e.options.heading).first().find("a").first().add(".ui-btn-inner").toggleClass("ui-corner-bottom", -c),b.find(".ui-collapsible-content").toggleClass("ui-corner-bottom",!c))}).bind("expand",function(b){a(b.target).closest(".ui-collapsible").siblings(".ui-collapsible").trigger("collapse")})},_init:function(){this.refresh()},refresh:function(){var b=this.options,c=this.element.children(":jqmData(role='collapsible')");a.mobile.collapsible.prototype.enhance(c.not(".ui-collapsible"));c.each(function(){a(this).find(a.mobile.collapsible.prototype.options.heading).find("a").first().add(".ui-btn-inner").removeClass("ui-corner-top ui-corner-bottom")}); -c.first().find("a").first().addClass(b.corners?"ui-corner-top":"").find(".ui-btn-inner").addClass("ui-corner-top");c.last().jqmData("collapsible-last",true).find("a").first().addClass(b.corners?"ui-corner-bottom":"").find(".ui-btn-inner").addClass("ui-corner-bottom")}});a(k).bind("pagecreate create",function(b){a.mobile.collapsibleset.prototype.enhanceWithin(b.target)})})(jQuery);(function(a,c){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"}, -_create:function(){var b=this.element,e=b.find("a"),f=e.filter(":jqmData(icon)").length?this.options.iconpos:c;b.addClass("ui-navbar").attr("role","navigation").find("ul").jqmEnhanceable().grid({grid:this.options.grid});f||b.addClass("ui-navbar-noicons");e.buttonMarkup({corners:false,shadow:false,inline:true,iconpos:f});b.delegate("a","vclick",function(b){a(b.target).hasClass("ui-disabled")||(e.removeClass(a.mobile.activeBtnClass),a(this).addClass(a.mobile.activeBtnClass))});b.closest(".ui-page").bind("pagebeforeshow", -function(){e.filter(".ui-state-persist").addClass(a.mobile.activeBtnClass)})}});a(k).bind("pagecreate create",function(b){a.mobile.navbar.prototype.enhanceWithin(b.target)})})(jQuery);(function(a){var c={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",mini:false,inset:false,initSelector:":jqmData(role='listview')"},_create:function(){var a="";a+=this.options.inset?" ui-listview-inset ui-corner-all ui-shadow ": -"";a+=this.element.jqmData("mini")||this.options.mini===true?" ui-mini":"";this.element.addClass(function(c,f){return f+" ui-listview "+a});this.refresh(true)},_removeCorners:function(a,c){a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));c==="top"?a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl"):c==="bottom"?a.removeClass("ui-corner-bottom ui-corner-br ui-corner-bl"):a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl ui-corner-bottom ui-corner-br ui-corner-bl")},_refreshCorners:function(a){var c, -f;this.options.inset&&(c=this.element.children("li"),f=a?c.not(".ui-screen-hidden"):c.filter(":visible"),this._removeCorners(c),c=f.first().addClass("ui-corner-top"),c.add(c.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),f=f.last().addClass("ui-corner-bottom"),f.add(f.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl")); -a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,c,f,d){var g={};for(g[f]=g[d]=true;a;){if(g[a.nodeName])return a;a=a[c]}return null},_getChildrenByTagName:function(b,c,f){var d=[],g={};g[c]=g[f]=true;for(b=b.firstChild;b;)g[b.nodeName]&&d.push(b),b=b.nextSibling;return a(d)},_addThumbClasses:function(b){var c,f,d=b.length;for(c=0;c<d;c++)f=a(this._findFirstElementByTagName(b[c].firstChild,"nextSibling","img","IMG")),f.length&&(f.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(f[0].parentNode, -"parentNode","li","LI")).addClass(f.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page");this._createSubPages();var c=this.options,f=this.element,d=f.jqmData("dividertheme")||c.dividerTheme,g=f.jqmData("splittheme"),h=f.jqmData("spliticon"),j=this._getChildrenByTagName(f[0],"li","LI"),o=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1,m={},p,l,r,n,q,t,x;o&&f.find(".ui-li-dec").remove();if(!c.theme)c.theme=a.mobile.getInheritedTheme(this.element, -"c");for(var u=0,w=j.length;u<w;u++){p=j.eq(u);l="ui-li";if(b||!p.hasClass("ui-li"))r=p.jqmData("theme")||c.theme,n=this._getChildrenByTagName(p[0],"a","A"),n.length?(t=p.jqmData("icon"),p.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:n.length>1||t===false?false:t||"arrow-r",theme:r}),t!=false&&n.length==1&&p.addClass("ui-li-has-arrow"),n.first().removeClass("ui-link").addClass("ui-link-inherit"),n.length>1&&(l+=" ui-li-has-alt",n=n.last(),q=g||n.jqmData("theme")|| -c.splitTheme,x=n.jqmData("icon"),n.appendTo(p).attr("title",n.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,corners:false,theme:r,icon:false,iconpos:false}).find(".ui-btn-inner").append(a(k.createElement("span")).buttonMarkup({shadow:true,corners:true,theme:q,iconpos:"notext",icon:x||t||h||c.splitIcon})))):p.jqmData("role")==="list-divider"?(l+=" ui-li-divider ui-bar-"+d,p.attr("role","heading"),o&&(o=1)):l+=" ui-li-static ui-body-"+r;o&&l.indexOf("ui-li-divider")< -0&&(r=p.is(".ui-li-static:first")?p:p.find(".ui-link-inherit"),r.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+o++ +". </span>"));m[l]||(m[l]=[]);m[l].push(p[0])}for(l in m)a(m[l]).addClass(l).children(".ui-btn-inner").addClass(l);f.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+ -(f.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all");this._addThumbClasses(j);this._addThumbClasses(f.find(".ui-link-inherit"));this._refreshCorners(b)},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,e=b.closest(".ui-page"),f=e.jqmData("url"),d=f||e[0][a.expando],g=b.attr("id"),h=this.options,j="data-"+a.mobile.ns,k=this,m=e.find(":jqmData(role='footer')").jqmData("id"),p;typeof c[d]==="undefined"&&(c[d]=-1);g=g|| -++c[d];a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=a(this),e=d.attr("id")||g+"-"+c,c=d.parent(),k=a(d.prevAll().toArray().reverse()),k=k.length?k:a("<span>"+a.trim(c.contents()[0].nodeValue)+"</span>"),o=k.first().getEncodedText(),e=(f||"")+"&"+a.mobile.subPageUrlKey+"="+e,x=d.jqmData("theme")||h.theme,u=d.jqmData("counttheme")||b.jqmData("counttheme")||h.countTheme;p=true;d.detach().wrap("<div "+j+"role='page' "+j+"url='"+e+"' "+j+"theme='"+x+"' "+j+"count-theme='"+u+"'><div "+ -j+"role='content'></div></div>").parent().before("<div "+j+"role='header' "+j+"theme='"+h.headerTheme+"'><div class='ui-title'>"+o+"</div></div>").after(m?a("<div "+j+"role='footer' "+j+"id='"+m+"'>"):"").parent().appendTo(a.mobile.pageContainer).page();d=c.find("a:first");d.length||(d=a("<a/>").html(k||o).prependTo(c.empty()));d.attr("href","#"+e)}).listview();p&&e.is(":jqmData(external-page='true')")&&e.data("page").options.domCache===false&&e.unbind("pagehide.remove").bind("pagehide.remove",function(b, -c){var d=c.nextPage;c.nextPage&&(d=d.jqmData("url"),d.indexOf(f+"&"+a.mobile.subPageUrlKey)!==0&&(k.childPages().remove(),e.remove()))})},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}});a(k).bind("pagecreate create",function(b){a.mobile.listview.prototype.enhanceWithin(b.target)})})(jQuery);(function(a,c){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"}, -_create:function(){var b=this,e=this.element,f=a(e).closest("label"),d=f.length?f:a(e).closest("form,fieldset,:jqmData(role='page'),:jqmData(role='dialog')").find("label").filter("[for='"+e[0].id+"']"),g=e[0].type,f=e.jqmData("mini")||e.closest("form,fieldset").jqmData("mini"),h=g+"-on",j=g+"-off",o=e.parents(":jqmData(type='horizontal')").length?c:j,m=e.jqmData("iconpos")||e.closest("form,fieldset").jqmData("iconpos");if(!(g!=="checkbox"&&g!=="radio")){a.extend(this,{label:d,inputtype:g,checkedClass:"ui-"+ -h+(o?"":" "+a.mobile.activeBtnClass),uncheckedClass:"ui-"+j,checkedicon:"ui-icon-"+h,uncheckedicon:"ui-icon-"+j});if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");d.buttonMarkup({theme:this.options.theme,icon:o,shadow:false,mini:f,iconpos:m});f=k.createElement("div");f.className="ui-"+g;e.add(d).wrapAll(f);d.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(e.is(":disabled"))a.preventDefault();else return b._cacheVals(), -e.prop("checked",g==="radio"&&true||!e.prop("checked")),e.triggerHandler("click"),b._getInputSet().not(e).prop("checked",false),b._updateAll(),false}});e.bind({vmousedown:function(){b._cacheVals()},vclick:function(){var c=a(this);c.is(":checked")?(c.prop("checked",true),b._getInputSet().not(c).prop("checked",false)):c.prop("checked",false);b._updateAll()},focus:function(){d.addClass(a.mobile.focusClass)},blur:function(){d.removeClass(a.mobile.focusClass)}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){a(this).jqmData("cacheVal", -this.checked)})},_getInputSet:function(){return this.inputtype==="checkbox"?this.element:this.element.closest("form,fieldset,:jqmData(role='page')").find("input[name='"+this.element[0].name+"'][type='"+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var c=a(this);(this.checked||b.inputtype==="checkbox")&&c.trigger("change")}).checkboxradio("refresh")},refresh:function(){var a=this.element[0],c=this.label,f=c.find(".ui-icon");a.checked?(c.addClass(this.checkedClass).removeClass(this.uncheckedClass), -f.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(c.removeClass(this.checkedClass).addClass(this.uncheckedClass),f.removeClass(this.checkedicon).addClass(this.uncheckedicon));a.disabled?this.disable():this.enable()},disable:function(){this.element.prop("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",false).parent().removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target, -true)})})(jQuery);(function(a,c){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:false,corners:true,shadow:true,iconshadow:true,initSelector:"button, [type='button'], [type='submit'], [type='reset'], [type='image']",mini:false},_create:function(){var b=this.element,e,f=this.options,d;d="";var g;if(b[0].tagName==="A")!b.hasClass("ui-btn")&&b.buttonMarkup();else{if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");~b[0].className.indexOf("ui-btn-left")&& -(d="ui-btn-left");~b[0].className.indexOf("ui-btn-right")&&(d="ui-btn-right");e=this.button=a("<div></div>").text(b.text()||b.val()).insertBefore(b).buttonMarkup({theme:f.theme,icon:f.icon,iconpos:f.iconpos,inline:f.inline,corners:f.corners,shadow:f.shadow,iconshadow:f.iconshadow,mini:f.mini}).addClass(d).append(b.addClass("ui-btn-hidden"));f=b.attr("type");d=b.attr("name");f!=="button"&&f!=="reset"&&d&&b.bind("vclick",function(){g===c&&(g=a("<input>",{type:"hidden",name:b.attr("name"),value:b.attr("value")}).insertBefore(b), -a(k).one("submit",function(){g.remove();g=c}))});b.bind({focus:function(){e.addClass(a.mobile.focusClass)},blur:function(){e.removeClass(a.mobile.focusClass)}});this.refresh()}},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)},refresh:function(){var b= -this.element;b.prop("disabled")?this.disable():this.enable();a(this.button.data("buttonElements").text).text(b.text()||b.val())}});a(k).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target,true)})})(jQuery);(function(a){a.fn.controlgroup=function(c){function b(a,b){a.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(b[0]).end().last().addClass(b[1]).addClass("ui-controlgroup-last")}return this.each(function(){var e=a(this),f=a.extend({direction:e.jqmData("type")|| -"vertical",shadow:false,excludeInvisible:true,mini:e.jqmData("mini")},c),d=e.children("legend"),g=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];e.find("input").first().attr("type");d.length&&(e.wrapInner("<div class='ui-controlgroup-controls'></div>"),a("<div role='heading' class='ui-controlgroup-label'>"+d.html()+"</div>").insertBefore(e.children(0)),d.remove());e.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);b(e.find(".ui-btn"+ -(f.excludeInvisible?":visible":"")).not(".ui-slider-handle"),g);b(e.find(".ui-btn-inner"),g);f.shadow&&e.addClass("ui-shadow");f.mini&&e.addClass("ui-mini")})}})(jQuery);(function(a){a(k).bind("pagecreate create",function(c){a(c.target).find("a").jqmEnhanceable().not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})})(jQuery);(function(a){var c=a("meta[name=viewport]"),b=c.attr("content"),e=b+",maximum-scale=1, user-scalable=no",f=b+",maximum-scale=10, user-scalable=yes", -d=/(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test(b);a.mobile.zoom=a.extend({},{enabled:!d,locked:false,disable:function(b){if(!d&&!a.mobile.zoom.locked)c.attr("content",e),a.mobile.zoom.enabled=false,a.mobile.zoom.locked=b||false},enable:function(b){if(!d&&(!a.mobile.zoom.locked||b===true))c.attr("content",f),a.mobile.zoom.enabled=true,a.mobile.zoom.locked=false},restore:function(){if(!d)c.attr("content",b),a.mobile.zoom.enabled=true}})})(jQuery);(function(a){a.widget("mobile.textinput", -a.mobile.widget,{options:{theme:null,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])", -clearSearchButtonText:"clear text"},_create:function(){var c=this.element,b=this.options,e=b.theme||a.mobile.getInheritedTheme(this.element,"c"),f=" ui-body-"+e,d=c.jqmData("mini")==true,g=d?" ui-mini":"",h,j;a("label[for='"+c.attr("id")+"']").addClass("ui-input-text");h=c.addClass("ui-input-text ui-body-"+e);typeof c[0].autocorrect!=="undefined"&&!a.support.touchOverflow&&(c[0].setAttribute("autocorrect","off"),c[0].setAttribute("autocomplete","off"));c.is("[type='search'],:jqmData(type='search')")? -(h=c.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+f+g+"'></div>").parent(),j=a("<a href='#' class='ui-input-clear' title='"+b.clearSearchButtonText+"'>"+b.clearSearchButtonText+"</a>").bind("click",function(a){c.val("").focus().trigger("change");j.addClass("ui-input-clear-hidden");a.preventDefault()}).appendTo(h).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true,mini:d}),e=function(){setTimeout(function(){j.toggleClass("ui-input-clear-hidden", -!c.val())},0)},e(),c.bind("paste cut keyup focus change blur",e)):c.addClass("ui-corner-all ui-shadow-inset"+f+g);c.focus(function(){h.addClass(a.mobile.focusClass)}).blur(function(){h.removeClass(a.mobile.focusClass)}).bind("focus",function(){b.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("blur",function(){b.preventFocusZoom&&a.mobile.zoom.enable(true)});if(c.is("textarea")){var o=function(){var a=c[0].scrollHeight;c[0].clientHeight<a&&c.height(a+15)},m;c.keyup(function(){clearTimeout(m); -m=setTimeout(o,100)});a(k).one("pagechange",o);a.trim(c.val())&&a(s).load(o)}},disable:function(){(this.element.attr("disabled",true).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(c){a.mobile.textinput.prototype.enhanceWithin(c.target, -true)})})(jQuery);(function(a){a.mobile.listview.prototype.options.filter=false;a.mobile.listview.prototype.options.filterPlaceholder="Filter items...";a.mobile.listview.prototype.options.filterTheme="c";a.mobile.listview.prototype.options.filterCallback=function(a,b){return a.toLowerCase().indexOf(b)===-1};a(k).delegate(":jqmData(role='listview')","listviewcreate",function(){var c=a(this),b=c.data("listview");if(b.options.filter){var e=a("<form>",{"class":"ui-listview-filter ui-bar-"+b.options.filterTheme, -role:"search"});a("<input>",{placeholder:b.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval","").bind("keyup change",function(){var e=a(this),d=this.value.toLowerCase(),g=null,g=e.jqmData("lastval")+"",h=false,j="";e.jqmData("lastval",d);g=d.length<g.length||d.indexOf(g)!==0?c.children():c.children(":not(.ui-screen-hidden)");if(d){for(var k=g.length-1;k>=0;k--)e=a(g[k]),j=e.jqmData("filtertext")||e.text(),e.is("li:jqmData(role=list-divider)")?(e.toggleClass("ui-filter-hidequeue", -!h),h=false):b.options.filterCallback(j,d)?e.toggleClass("ui-filter-hidequeue",true):h=true;g.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",false);g.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",true).toggleClass("ui-filter-hidequeue",false)}else g.toggleClass("ui-screen-hidden",false);b._refreshCorners()}).appendTo(e).textinput();b.options.inset&&e.addClass("ui-listview-filter-inset");e.bind("submit",function(){return false}).insertBefore(c)}})})(jQuery);(function(a, -c){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",mini:false},_create:function(){var b=this,e=this.element,f=a.mobile.getInheritedTheme(e,"c"),d=this.options.theme||f,f=this.options.trackTheme||f,g=e[0].nodeName.toLowerCase(),h=g=="select"?"ui-slider-switch":"",j=e.attr("id"),o=j+"-label",j=a("[for='"+j+"']").attr("id",o),m=function(){return g=="input"?parseFloat(e.val()): -e[0].selectedIndex},p=g=="input"?parseFloat(e.attr("min")):0,l=g=="input"?parseFloat(e.attr("max")):e.find("option").length-1,r=s.parseFloat(e.attr("step")||1),n=this.options.inline||e.jqmData("inline")==true?" ui-slider-inline":"",q=this.options.mini||e.jqmData("mini")?" ui-slider-mini":"",t=k.createElement("a"),x=a(t),u=k.createElement("div"),w=a(u),v=e.jqmData("highlight")&&g!="select"?function(){var b=k.createElement("div");b.className="ui-slider-bg ui-btn-active ui-btn-corner-all";return a(b).prependTo(w)}(): -false;t.setAttribute("href","#");u.setAttribute("role","application");u.className=["ui-slider ",h," ui-btn-down-",f," ui-btn-corner-all",n,q].join("");t.className="ui-slider-handle";u.appendChild(t);x.buttonMarkup({corners:true,theme:d,shadow:true}).attr({role:"slider","aria-valuemin":p,"aria-valuemax":l,"aria-valuenow":m(),"aria-valuetext":m(),title:m(),"aria-labelledby":o});a.extend(this,{slider:w,handle:x,valuebg:v,dragging:false,beforeStart:null,userModified:false,mouseMoved:false});if(g=="select"){d= -k.createElement("div");d.className="ui-slider-inneroffset";h=0;for(o=u.childNodes.length;h<o;h++)d.appendChild(u.childNodes[h]);u.appendChild(d);x.addClass("ui-slider-handle-snapping");u=e.find("option");d=0;for(h=u.length;d<h;d++)o=!d?"b":"a",n=!d?" ui-btn-down-"+f:" "+a.mobile.activeBtnClass,k.createElement("div"),q=k.createElement("span"),q.className=["ui-slider-label ui-slider-label-",o,n," ui-btn-corner-all"].join(""),q.setAttribute("role","img"),q.appendChild(k.createTextNode(u[d].innerHTML)), -a(q).prependTo(w);b._labels=a(".ui-slider-label",w)}j.addClass("ui-slider");e.addClass(g==="input"?"ui-slider-input":"ui-slider-switch").change(function(){b.mouseMoved||b.refresh(m(),true)}).keyup(function(){b.refresh(m(),true,true)}).blur(function(){b.refresh(m(),true)});a(k).bind("vmousemove",function(a){if(b.dragging)return b.mouseMoved=true,g==="select"&&x.removeClass("ui-slider-handle-snapping"),b.refresh(a),b.userModified=b.beforeStart!==e[0].selectedIndex,false});w.bind("vmousedown",function(a){b.dragging= -true;b.userModified=false;b.mouseMoved=false;if(g==="select")b.beforeStart=e[0].selectedIndex;b.refresh(a);return false}).bind("vclick",false);w.add(k).bind("vmouseup",function(){if(b.dragging)return b.dragging=false,g==="select"&&(x.addClass("ui-slider-handle-snapping"),b.mouseMoved?b.userModified?b.refresh(b.beforeStart==0?1:0):b.refresh(b.beforeStart):b.refresh(b.beforeStart==0?1:0)),b.mouseMoved=false});w.insertAfter(e);g=="select"&&this.handle.bind({focus:function(){w.addClass(a.mobile.focusClass)}, -blur:function(){w.removeClass(a.mobile.focusClass)}});this.handle.bind({vmousedown:function(){a(this).focus()},vclick:false,keydown:function(c){var d=m();if(!b.options.disabled){switch(c.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:if(c.preventDefault(),!b._keySliding)b._keySliding=true,a(this).addClass("ui-state-active")}switch(c.keyCode){case a.mobile.keyCode.HOME:b.refresh(p); -break;case a.mobile.keyCode.END:b.refresh(l);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:b.refresh(d+r);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.refresh(d-r)}}},keyup:function(){if(b._keySliding)b._keySliding=false,a(this).removeClass("ui-state-active")}});this.refresh(c,c,true)},refresh:function(b,c,f){(this.options.disabled||this.element.attr("disabled"))&&this.disable();var d=this.element,g=d[0].nodeName.toLowerCase(), -h=g==="input"?parseFloat(d.attr("min")):0,j=g==="input"?parseFloat(d.attr("max")):d.find("option").length-1,k=g==="input"&&parseFloat(d.attr("step"))>0?parseFloat(d.attr("step")):1;if(typeof b==="object"){if(!this.dragging||b.pageX<this.slider.offset().left-8||b.pageX>this.slider.offset().left+this.slider.width()+8)return;b=Math.round((b.pageX-this.slider.offset().left)/this.slider.width()*100)}else b==null&&(b=g==="input"?parseFloat(d.val()||0):d[0].selectedIndex),b=(parseFloat(b)-h)/(j-h)*100;if(!isNaN(b)){b< -0&&(b=0);b>100&&(b=100);var m=b/100*(j-h)+h,p=(m-h)%k;m-=p;Math.abs(p)*2>=k&&(m+=p>0?k:-k);m=parseFloat(m.toFixed(5));m<h&&(m=h);m>j&&(m=j);this.handle.css("left",b+"%");this.handle.attr({"aria-valuenow":g==="input"?m:d.find("option").eq(m).attr("value"),"aria-valuetext":g==="input"?m:d.find("option").eq(m).getEncodedText(),title:g==="input"?m:d.find("option").eq(m).getEncodedText()});this.valuebg&&this.valuebg.css("width",b+"%");if(this._labels){var h=this.handle.width()/this.slider.width()*100, -l=b&&h+(100-h)*b/100,r=b===100?0:Math.min(h+100-l,100);this._labels.each(function(){var b=a(this).is(".ui-slider-label-a");a(this).width((b?l:r)+"%")})}if(!f)f=false,g==="input"?(f=d.val()!==m,d.val(m)):(f=d[0].selectedIndex!==m,d[0].selectedIndex=m),!c&&f&&d.trigger("change")}},enable:function(){this.element.attr("disabled",false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled", -true);return this._setOption("disabled",true)}});a(k).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target,true)})})(jQuery);(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:false,corners:true,shadow:true,iconshadow:true,overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:true,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")> --1,initSelector:"select:not(:jqmData(role='slider'))",mini:false},_button:function(){return a("<div/>")},_setDisabled:function(a){this.element.attr("disabled",a);this.button.attr("aria-disabled",a);return this._setOption("disabled",a)},_focusButton:function(){var a=this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){var c="";~this.element[0].className.indexOf("ui-btn-left")&&(c=" ui-btn-left");~this.element[0].className.indexOf("ui-btn-right")&& -(c=" ui-btn-right");this.select=this.element.wrap("<div class='ui-select"+c+"'>");this.selectID=this.select.attr("id");this.label=a("label[for='"+this.selectID+"']").addClass("ui-select");this.isMultiple=this.select[0].multiple;if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.select,"c")},_create:function(){this._preExtension();this._trigger("beforeCreate");this.button=this._button();var c=this,b=this.options,e=this.button.text(a(this.select[0].options.item(this.select[0].selectedIndex== --1?0:this.select[0].selectedIndex)).text()).insertBefore(this.select).buttonMarkup({theme:b.theme,icon:b.icon,iconpos:b.iconpos,inline:b.inline,corners:b.corners,shadow:b.shadow,iconshadow:b.iconshadow,mini:b.mini});b.nativeMenu&&s.opera&&s.opera.version&&this.select.addClass("ui-select-nativeonly");if(this.isMultiple)this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(e.addClass("ui-li-has-count"));(b.disabled||this.element.attr("disabled"))&&this.disable(); -this.select.change(function(){c.refresh()});this.build()},build:function(){var c=this;this.select.appendTo(c.button).bind("vmousedown",function(){c.button.addClass(a.mobile.activeBtnClass)}).bind("focus",function(){c.button.addClass(a.mobile.focusClass)}).bind("blur",function(){c.button.removeClass(a.mobile.focusClass)}).bind("focus vmouseover",function(){c.button.trigger("vmouseover")}).bind("vmousemove",function(){c.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){c.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur", -function(){c.button.removeClass("ui-btn-down-"+c.options.theme)});c.button.bind("vmousedown",function(){c.options.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("mouseup",function(){c.options.preventFocusZoom&&a.mobile.zoom.enable(true)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var c=this,b=this.selected();this.button.find(".ui-btn-text").text(function(){return!c.isMultiple? -b.text():b.length?b.map(function(){return a(this).text()}).get().join(", "):c.placeholder})},setButtonCount:function(){var a=this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText();this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(true);this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(false);this.button.removeClass("ui-disabled")}});a(k).bind("pagecreate create", -function(c){a.mobile.selectmenu.prototype.enhanceWithin(c.target,true)})})(jQuery);(function(a){var c=function(b){var c=b.selectID,f=b.label,d=b.select.closest(".ui-page"),g=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(d),h=b._selectOptions(),j=b.isMultiple=b.select[0].multiple,o=c+"-button",m=c+"-menu",p=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+b.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+b.options.overlayTheme+"'><div data-"+a.mobile.ns+ -"role='header'><div class='ui-title'>"+f.getEncodedText()+"</div></div><div data-"+a.mobile.ns+"role='content'></div></div>"),l=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-"+b.options.overlayTheme+" "+a.mobile.defaultDialogTransition}).insertAfter(g),r=a("<ul>",{"class":"ui-selectmenu-list",id:m,role:"listbox","aria-labelledby":o}).attr("data-"+a.mobile.ns+"theme",b.options.theme).appendTo(l),n=a("<div>",{"class":"ui-header ui-bar-"+b.options.theme}).prependTo(l), -q=a("<h1>",{"class":"ui-title"}).appendTo(n),t;b.isMultiple&&(t=a("<a>",{text:b.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(n).buttonMarkup());a.extend(b,{select:b.select,selectID:c,buttonId:o,menuId:m,thisPage:d,menuPage:p,label:f,screen:g,selectOptions:h,isMultiple:j,theme:b.options.theme,listbox:l,list:r,header:n,headerTitle:q,headerClose:t,menuPageContent:void 0,menuPageClose:void 0,placeholder:"", -build:function(){var c=this;c.refresh();c.select.attr("tabindex","-1").focus(function(){a(this).blur();c.button.focus()});c.button.bind("vclick keydown",function(b){if(b.type=="vclick"||b.keyCode&&(b.keyCode===a.mobile.keyCode.ENTER||b.keyCode===a.mobile.keyCode.SPACE))c.open(),b.preventDefault()});c.list.attr("role","listbox").bind("focusin",function(b){a(b.target).attr("tabindex","0").trigger("vmouseover")}).bind("focusout",function(b){a(b.target).attr("tabindex","-1").trigger("vmouseout")}).delegate("li:not(.ui-disabled, .ui-li-divider)", -"click",function(b){var d=c.select[0].selectedIndex,e=c.list.find("li:not(.ui-li-divider)").index(this),f=c._selectOptions().eq(e)[0];f.selected=c.isMultiple?!f.selected:true;c.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",f.selected).toggleClass("ui-icon-checkbox-off",!f.selected);(c.isMultiple||d!==e)&&c.select.trigger("change");c.isMultiple||c.close();b.preventDefault()}).keydown(function(c){var d=a(c.target),e=d.closest("li");switch(c.keyCode){case 38:return c=e.prev().not(".ui-selectmenu-placeholder"), -c.is(".ui-li-divider")&&(c=c.prev()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 40:return c=e.next(),c.is(".ui-li-divider")&&(c=c.next()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 13:case 32:return d.trigger("click"),false}});c.menuPage.bind("pagehide",function(){c.list.appendTo(c.listbox);c._focusButton();a.mobile._bindPageRemove.call(c.thisPage)}); -c.screen.bind("vclick",function(){c.close()});c.isMultiple&&c.headerClose.click(function(){if(c.menuType=="overlay")return c.close(),false});c.thisPage.addDependents(this.menuPage)},_isRebuildRequired:function(){var a=this.list.find("li");return this._selectOptions().text()!==a.text()},refresh:function(b){var c=this;this._selectOptions();this.selected();var d=this.selectedIndices();(b||this._isRebuildRequired())&&c._buildList();c.setButtonText();c.setButtonCount();c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected", -false).each(function(b){a.inArray(b,d)>-1&&(b=a(this),b.attr("aria-selected",true),c.isMultiple?b.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):b.is(".ui-selectmenu-placeholder")?b.next().addClass(a.mobile.activeBtnClass):b.addClass(a.mobile.activeBtnClass))})},close:function(){if(!this.options.disabled&&this.isOpen)this.menuType=="page"?s.history.back():(this.screen.addClass("ui-screen-hidden"),this.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in"), -this.list.appendTo(this.listbox),this._focusButton()),this.isOpen=false},open:function(){function b(){c.list.find("."+a.mobile.activeBtnClass+" a").focus()}if(!this.options.disabled){var c=this,d=a(s),e=c.list.parent(),f=e.outerHeight(),e=e.outerWidth();a(".ui-page-active");var g=d.scrollTop(),j=c.button.offset().top,h=d.height(),d=d.width();c.button.addClass(a.mobile.activeBtnClass);setTimeout(function(){c.button.removeClass(a.mobile.activeBtnClass)},300);if(f>h-80||!a.support.scrollTop){c.menuPage.appendTo(a.mobile.pageContainer).page(); -c.menuPageContent=p.find(".ui-content");c.menuPageClose=p.find(".ui-header a");c.thisPage.unbind("pagehide.remove");if(g==0&&j>h)c.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",j)});c.menuPage.one("pageshow",function(){b();c.isOpen=true});c.menuType="page";c.menuPageContent.append(c.list);c.menuPage.find("div .ui-title").text(c.label.text());a.mobile.changePage(c.menuPage,{transition:a.mobile.defaultDialogTransition})}else{c.menuType="overlay";c.screen.height(a(k).height()).removeClass("ui-screen-hidden"); -var l=j-g,m=g+h-j,n=f/2,o=parseFloat(c.list.parent().css("max-width")),f=l>f/2&&m>f/2?j+c.button.outerHeight()/2-n:l>m?g+h-f-30:g+30;e<o?g=(d-e)/2:(g=c.button.offset().left+c.button.outerWidth()/2-e/2,g<30?g=30:g+e>d&&(g=d-e-30));c.listbox.append(c.list).removeClass("ui-selectmenu-hidden").css({top:f,left:g}).addClass("in");b();c.isOpen=true}}},_buildList:function(){var b=this.options,c=this.placeholder,d=true,e=this.isMultiple?"checkbox-off":"false";this.list.empty().filter(".ui-listview").listview("destroy"); -var f=this.select.find("option"),g=f.length,j=this.select[0],h="data-"+a.mobile.ns,l=h+"option-index",m=h+"icon";h+="role";for(var n=k.createDocumentFragment(),o,p=0;p<g;p++){var r=f[p],q=a(r),s=r.parentNode,t=q.text(),D=k.createElement("a"),J=[];D.setAttribute("href","#");D.appendChild(k.createTextNode(t));s!==j&&s.nodeName.toLowerCase()==="optgroup"&&(s=s.getAttribute("label"),s!=o&&(o=k.createElement("li"),o.setAttribute(h,"list-divider"),o.setAttribute("role","option"),o.setAttribute("tabindex", -"-1"),o.appendChild(k.createTextNode(s)),n.appendChild(o),o=s));if(d&&(!r.getAttribute("value")||t.length==0||q.jqmData("placeholder")))if(d=false,b.hidePlaceholderMenuItems&&J.push("ui-selectmenu-placeholder"),!c)c=this.placeholder=t;q=k.createElement("li");r.disabled&&(J.push("ui-disabled"),q.setAttribute("aria-disabled",true));q.setAttribute(l,p);q.setAttribute(m,e);q.className=J.join(" ");q.setAttribute("role","option");D.setAttribute("tabindex","-1");q.appendChild(D);n.appendChild(q)}this.list[0].appendChild(n); -!this.isMultiple&&!c.length?this.header.hide():this.headerTitle.text(this.placeholder);this.list.listview()},_button:function(){return a("<a>",{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a(k).bind("selectmenubeforecreate",function(b){b=a(b.target).data("selectmenu");b.options.nativeMenu||c(b)})})(jQuery);(function(a){a.widget("mobile.fixedtoolbar",a.mobile.widget,{options:{visibleOnPageShow:true,disablePageZoom:true,transition:"slide",fullscreen:false, -tapToggle:true,tapToggleBlacklist:"a, input, select, textarea, .ui-header-fixed, .ui-footer-fixed",hideDuringFocus:"input, textarea, select",updatePagePadding:true,trackPersistentToolbars:true,supportBlacklist:function(){var a=s,b=navigator.userAgent,e=navigator.platform,f=b.match(/AppleWebKit\/([0-9]+)/),f=!!f&&f[1],d=b.match(/Fennec\/([0-9]+)/),d=!!d&&d[1],g=b.match(/Opera Mobi\/([0-9]+)/),h=!!g&&g[1];return(e.indexOf("iPhone")>-1||e.indexOf("iPad")>-1||e.indexOf("iPod")>-1)&&f&&f<534||a.operamini&& -{}.toString.call(a.operamini)==="[object OperaMini]"||g&&h<7458||b.indexOf("Android")>-1&&f&&f<533||d&&d<6||"palmGetResource"in s&&f&&f<534||b.indexOf("MeeGo")>-1&&b.indexOf("NokiaBrowser/8.5.0")>-1?true:false},initSelector:":jqmData(position='fixed')"},_create:function(){var a=this.options,b=this.element,e=b.is(":jqmData(role='header')")?"header":"footer",f=b.closest(".ui-page");a.supportBlacklist()?this.destroy():(b.addClass("ui-"+e+"-fixed"),a.fullscreen?(b.addClass("ui-"+e+"-fullscreen"),f.addClass("ui-page-"+ -e+"-fullscreen")):f.addClass("ui-page-"+e+"-fixed"),this._addTransitionClass(),this._bindPageEvents(),this._bindToggleHandlers())},_addTransitionClass:function(){var a=this.options.transition;a&&a!=="none"&&(a==="slide"&&(a=this.element.is(".ui-header")?"slidedown":"slideup"),this.element.addClass(a))},_bindPageEvents:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("pagebeforeshow",function(){b.disablePageZoom&&a.mobile.zoom.disable(true);b.visibleOnPageShow||c.hide(true)}).bind("webkitAnimationStart animationstart updatelayout", -function(){b.updatePagePadding&&c.updatePagePadding()}).bind("pageshow",function(){c.updatePagePadding();b.updatePagePadding&&a(s).bind("throttledresize."+c.widgetName,function(){c.updatePagePadding()})}).bind("pagebeforehide",function(e,f){b.disablePageZoom&&a.mobile.zoom.enable(true);b.updatePagePadding&&a(s).unbind("throttledresize."+c.widgetName);if(b.trackPersistentToolbars){var d=a(".ui-footer-fixed:jqmData(id)",this),g=a(".ui-header-fixed:jqmData(id)",this),h=d.length&&f.nextPage&&a(".ui-footer-fixed:jqmData(id='"+ -d.jqmData("id")+"')",f.nextPage),j=g.length&&f.nextPage&&a(".ui-header-fixed:jqmData(id='"+g.jqmData("id")+"')",f.nextPage),h=h||a();if(h.length||j.length)h.add(j).appendTo(a.mobile.pageContainer),f.nextPage.one("pageshow",function(){h.add(j).appendTo(this)})}})},_visible:true,updatePagePadding:function(){var a=this.element,b=a.is(".ui-header");this.options.fullscreen||a.closest(".ui-page").css("padding-"+(b?"top":"bottom"),a.outerHeight())},_useTransition:function(c){var b=this.element,e=a(s).scrollTop(), -f=b.height(),d=b.closest(".ui-page").height(),g=a.mobile.getScreenHeight(),b=b.is(":jqmData(role='header')")?"header":"footer";return!c&&(this.options.transition&&this.options.transition!=="none"&&(b==="header"&&!this.options.fullscreen&&e>f||b==="footer"&&!this.options.fullscreen&&e+g<d-f)||this.options.fullscreen)},show:function(a){var b=this.element;this._useTransition(a)?b.removeClass("out ui-fixed-hidden").addClass("in"):b.removeClass("ui-fixed-hidden");this._visible=true},hide:function(a){var b= -this.element,e="out"+(this.options.transition==="slide"?" reverse":"");this._useTransition(a)?b.addClass(e).removeClass("in").animationComplete(function(){b.addClass("ui-fixed-hidden").removeClass(e)}):b.addClass("ui-fixed-hidden").removeClass(e);this._visible=false},toggle:function(){this[this._visible?"hide":"show"]()},_bindToggleHandlers:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("vclick",function(e){b.tapToggle&&!a(e.target).closest(b.tapToggleBlacklist).length&&c.toggle()}).bind("focusin focusout", -function(e){if(screen.width<500&&a(e.target).is(b.hideDuringFocus)&&!a(e.target).closest(".ui-header-fixed, .ui-footer-fixed").length)c[e.type==="focusin"&&c._visible?"hide":"show"]()})},destroy:function(){this.element.removeClass("ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden");this.element.closest(".ui-page").removeClass("ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen")}});a(k).bind("pagecreate create", -function(c){a(c.target).jqmData("fullscreen")&&a(a.mobile.fixedtoolbar.prototype.options.initSelector,c.target).not(":jqmData(fullscreen)").jqmData("fullscreen",true);a.mobile.fixedtoolbar.prototype.enhanceWithin(c.target)})})(jQuery);(function(a,c){if(/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1){var b=a.mobile.zoom,e,f,d,g,h;a(c).bind("orientationchange.iosorientationfix",b.enable).bind("devicemotion.iosorientationfix",function(a){e=a.originalEvent; -h=e.accelerationIncludingGravity;f=Math.abs(h.x);d=Math.abs(h.y);g=Math.abs(h.z);!c.orientation&&(f>7||(g>6&&d<8||g<8&&d>6)&&f>5)?b.enabled&&b.disable():b.enabled||b.enable()})}})(jQuery,this);(function(a,c){function b(){var b=a("."+a.mobile.activeBtnClass).first();h.css({top:a.support.scrollTop&&g.scrollTop()+g.height()/2||b.length&&b.offset().top||100})}function e(){var c=h.offset(),d=g.scrollTop(),f=a.mobile.getScreenHeight();if(c.top<d||c.top-d>f)h.addClass("ui-loader-fakefix"),b(),g.unbind("scroll", -e).bind("scroll",b)}function f(){d.removeClass("ui-mobile-rendering")}var d=a("html");a("head");var g=a(c);a(c.document).trigger("mobileinit");if(a.mobile.gradeA()){if(a.mobile.ajaxBlacklist)a.mobile.ajaxEnabled=false;d.addClass("ui-mobile ui-mobile-rendering");setTimeout(f,5E3);var h=a("<div class='ui-loader'><span class='ui-icon ui-icon-loading'></span><h1></h1></div>");a.extend(a.mobile,{showPageLoadingMsg:function(b,c,f){d.addClass("ui-loading");if(a.mobile.loadingMessage){var k=f||a.mobile.loadingMessageTextVisible; -b=b||a.mobile.loadingMessageTheme;h.attr("class","ui-loader ui-corner-all ui-body-"+(b||"a")+" ui-loader-"+(k?"verbose":"default")+(f?" ui-loader-textonly":"")).find("h1").text(c||a.mobile.loadingMessage).end().appendTo(a.mobile.pageContainer);e();g.bind("scroll",e)}},hidePageLoadingMsg:function(){d.removeClass("ui-loading");a.mobile.loadingMessage&&h.removeClass("ui-loader-fakefix");a(c).unbind("scroll",b);a(c).unbind("scroll",e)},initializePage:function(){var b=a(":jqmData(role='page'), :jqmData(role='dialog')"); -b.length||(b=a("body").wrapInner("<div data-"+a.mobile.ns+"role='page'></div>").children(0));b.each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)});a.mobile.firstPage=b.first();a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport");g.trigger("pagecontainercreate");a.mobile.showPageLoadingMsg();f();!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage, -{transition:"none",reverse:true,changeHash:false,fromHashChange:true}):g.trigger("hashchange",[true])}});a.mobile._registerInternalEvents();a(function(){c.scrollTo(0,1);a.mobile.defaultHomeScroll=!a.support.scrollTop||a(c).scrollTop()===1?0:1;a.fn.controlgroup&&a(k).bind("pagecreate create",function(b){a(":jqmData(role='controlgroup')",b.target).jqmEnhanceable().controlgroup({excludeInvisible:false})});a.mobile.autoInitializePage&&a.mobile.initializePage();g.load(a.mobile.silentScroll)})}})(jQuery, -this)});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancExplorer/libs/jquery.mobile.min.css Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,2 @@ +/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ +.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f9f9f9,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus{outline:0}.ui-focus,.ui-btn:focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus{outline-width:1px;outline-style:dotted}.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;box-shadow:0 1px 1px -1px #fff;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.5em}.ui-page-footer-fixed{padding-bottom:3em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0;max-width:100%}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-mini{margin:.25em 5px}.ui-btn-inner{padding:.6em 20px;min-width:.75em;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1;width:100%}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left .ui-btn-inner .ui-icon,.ui-btn-icon-right .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=.0001);font-size:1px;border:0;text-indent:-9999px}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:0 0 .5em;zoom:1}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -6px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.ui-header .ui-field-contain-left,.ui-header .ui-field-contain-right{position:absolute;top:0;width:25%}.ui-header .ui-field-contain-left{left:1em}.ui-header .ui-field-contain-right{right:1em}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1;margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:97%;outline:0}.ui-header input.ui-input-text,.ui-footer input.ui-input-text{margin-left:1.25%;padding:.4em 1%;width:95.5%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em;width:97%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:48px}.ui-li-divider .ui-li-count,.ui-li-static .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancExplorer/libs/jquery.mobile.min.js Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,177 @@ +/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ +(function(D,s,k){typeof define==="function"&&define.amd?define(["jquery"],function(a){k(a,D,s);return a.mobile}):k(D.jQuery,D,s)})(this,document,function(D,s,k){(function(a,c,b,e){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function d(b){for(var d={},f,g;b;){f=a.data(b,t);for(g in f)if(f[g])d[g]=d.hasVirtualBinding=true;b=b.parentNode}return d}function g(){y&&(clearTimeout(y),y=0);y=setTimeout(function(){F=y=0;C.length=0;z=false;G=true},a.vmouse.resetTimerDuration)} +function h(b,d,g){var c,h;if(!(h=g&&g[b])){if(g=!g)a:{for(g=d.target;g;){if((h=a.data(g,t))&&(!b||h[b]))break a;g=g.parentNode}g=null}h=g}if(h){c=d;var g=c.type,z,j;c=a.Event(c);c.type=b;h=c.originalEvent;z=a.event.props;g.search(/^(mouse|click)/)>-1&&(z=w);if(h)for(j=z.length;j;)b=z[--j],c[b]=h[b];if(g.search(/mouse(down|up)|click/)>-1&&!c.which)c.which=1;if(g.search(/^touch/)!==-1&&(b=f(h),g=b.touches,b=b.changedTouches,g=g&&g.length?g[0]:b&&b.length?b[0]:e))for(h=0,len=u.length;h<len;h++)b=u[h], +c[b]=g[b];a(d.target).trigger(c)}return c}function j(b){var d=a.data(b.target,x);if(!z&&(!F||F!==d))if(d=h("v"+b.type,b))d.isDefaultPrevented()&&b.preventDefault(),d.isPropagationStopped()&&b.stopPropagation(),d.isImmediatePropagationStopped()&&b.stopImmediatePropagation()}function o(b){var g=f(b).touches,c;if(g&&g.length===1&&(c=b.target,g=d(c),g.hasVirtualBinding))F=L++,a.data(c,x,F),y&&(clearTimeout(y),y=0),A=G=false,c=f(b).touches[0],s=c.pageX,E=c.pageY,h("vmouseover",b,g),h("vmousedown",b,g)} +function m(a){G||(A||h("vmousecancel",a,d(a.target)),A=true,g())}function p(b){if(!G){var c=f(b).touches[0],e=A,z=a.vmouse.moveDistanceThreshold;A=A||Math.abs(c.pageX-s)>z||Math.abs(c.pageY-E)>z;flags=d(b.target);A&&!e&&h("vmousecancel",b,flags);h("vmousemove",b,flags);g()}}function l(a){if(!G){G=true;var b=d(a.target),c;h("vmouseup",a,b);if(!A&&(c=h("vclick",a,b))&&c.isDefaultPrevented())c=f(a).changedTouches[0],C.push({touchID:F,x:c.clientX,y:c.clientY}),z=true;h("vmouseout",a,b);A=false;g()}}function r(b){var b= +a.data(b,t),d;if(b)for(d in b)if(b[d])return true;return false}function n(){}function k(b){var d=b.substr(1);return{setup:function(){r(this)||a.data(this,t,{});a.data(this,t)[b]=true;v[b]=(v[b]||0)+1;v[b]===1&&H.bind(d,j);a(this).bind(d,n);if(K)v.touchstart=(v.touchstart||0)+1,v.touchstart===1&&H.bind("touchstart",o).bind("touchend",l).bind("touchmove",p).bind("scroll",m)},teardown:function(){--v[b];v[b]||H.unbind(d,j);K&&(--v.touchstart,v.touchstart||H.unbind("touchstart",o).unbind("touchmove",p).unbind("touchend", +l).unbind("scroll",m));var f=a(this),g=a.data(this,t);g&&(g[b]=false);f.unbind(d,n);r(this)||f.removeData(t)}}}var t="virtualMouseBindings",x="virtualTouchID",c="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),u="clientX clientY pageX pageY screenX screenY".split(" "),w=a.event.props.concat(a.event.mouseHooks?a.event.mouseHooks.props:[]),v={},y=0,s=0,E=0,A=false,C=[],z=false,G=false,K="addEventListener"in b,H=a(b),L=1,F=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10, +resetTimerDuration:1500};for(var I=0;I<c.length;I++)a.event.special[c[I]]=k(c[I]);K&&b.addEventListener("click",function(b){var d=C.length,f=b.target,g,c,e,h,z;if(d){g=b.clientX;c=b.clientY;threshold=a.vmouse.clickDistanceThreshold;for(e=f;e;){for(h=0;h<d;h++)if(z=C[h],e===f&&Math.abs(z.x-g)<threshold&&Math.abs(z.y-c)<threshold||a.data(e,x)===z.touchID){b.preventDefault();b.stopPropagation();return}e=e.parentNode}}},true)})(jQuery,s,k);(function(a,c,b){function e(a){a=a||location.href;return"#"+a.replace(/^[^#]*#?(.*)$/, +"$1")}var f="hashchange",d=k,g,h=a.event.special,j=d.documentMode,o="on"+f in c&&(j===b||j>7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;h[f]=a.extend(h[f],{setup:function(){if(o)return false;a(g.start)},teardown:function(){if(o)return false;a(g.stop)}});g=function(){function g(){var b=e(),d=t(r);if(b!==r)k(r=b,d),a(c).trigger(f);else if(d!==r)location.href=location.href.replace(/#.*/,"")+d;j=setTimeout(g,a.fn[f].delay)}var h={},j,r=e(),n=function(a){return a},k= +n,t=n;h.start=function(){j||g()};h.stop=function(){j&&clearTimeout(j);j=b};a.browser.msie&&!o&&function(){var b,c;h.start=function(){if(!b)c=(c=a.fn[f].src)&&c+e(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){c||k(e());g()}).attr("src",c||"javascript:0").insertAfter("body")[0].contentWindow,d.onpropertychange=function(){try{if(event.propertyName==="title")b.document.title=d.title}catch(a){}}};h.stop=n;t=function(){return e(b.location.href)};k=function(g,c){var e=b.document, +h=a.fn[f].domain;if(g!==c)e.title=d.title,e.open(),h&&e.write('<script>document.domain="'+h+'"<\/script>'),e.close(),b.location.hash=g}}();return h}()})(jQuery,this);(function(a,c){if(a.cleanData){var b=a.cleanData;a.cleanData=function(f){for(var d=0,g;(g=f[d])!=null;d++)a(g).triggerHandler("remove");b(f)}}else{var e=a.fn.remove;a.fn.remove=function(b,d){return this.each(function(){d||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return e.call(a(this), +b,d)})}}a.widget=function(b,d,g){var c=b.split(".")[0],e,b=b.split(".")[1];e=c+"-"+b;if(!g)g=d,d=a.Widget;a.expr[":"][e]=function(d){return!!a.data(d,b)};a[c]=a[c]||{};a[c][b]=function(a,b){arguments.length&&this._createWidget(a,b)};d=new d;d.options=a.extend(true,{},d.options);a[c][b].prototype=a.extend(true,d,{namespace:c,widgetName:b,widgetEventPrefix:a[c][b].prototype.widgetEventPrefix||b,widgetBaseClass:e},g);a.widget.bridge(b,a[c][b])};a.widget.bridge=function(b,d){a.fn[b]=function(g){var e= +typeof g==="string",j=Array.prototype.slice.call(arguments,1),o=this,g=!e&&j.length?a.extend.apply(null,[true,g].concat(j)):g;if(e&&g.charAt(0)==="_")return o;e?this.each(function(){var d=a.data(this,b);if(!d)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+g+"'";if(!a.isFunction(d[g]))throw"no such method '"+g+"' for "+b+" widget instance";var e=d[g].apply(d,j);if(e!==d&&e!==c)return o=e,false}):this.each(function(){var c=a.data(this,b);c?c.option(g||{})._init(): +a.data(this,b,new d(g,this))});return o}};a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(b,d){a.data(d,this.widgetName,this);this.element=a(d);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var g=this;this.element.bind("remove."+this.widgetName,function(){g.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b= +{};a.metadata&&(b=a.metadata.get(element)[this.widgetName]);return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,d){var g=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(d===c)return this.options[b]; +g={};g[b]=d}this._setOptions(g);return this},_setOptions:function(b){var d=this;a.each(b,function(a,b){d._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b;a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,d,g){var c=this.options[b],d=a.Event(d); +d.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();g=g||{};if(d.originalEvent)for(var b=a.event.props.length,e;b;)e=a.event.props[--b],d[e]=d.originalEvent[e];this.element.trigger(d,g);return!(a.isFunction(c)&&c.call(this.element[0],d,g)===false||d.isDefaultPrevented())}}})(jQuery);(function(a,c){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments);this._trigger("init")},_getCreateOptions:function(){var b=this.element, +e={};a.each(this.options,function(a){var d=b.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));d!==c&&(e[a]=d)});return e},enhanceWithin:function(b,c){this.enhance(a(this.options.initSelector,a(b)),c)},enhance:function(b,c){var f,d=a(b),d=a.mobile.enhanceable(d);c&&d.length&&(f=(f=a.mobile.closestPageData(d))&&f.keepNativeSelector()||"",d=d.not(f));d[this.widgetName]()},raise:function(a){throw"Widget ["+this.widgetName+"]: "+a;}})})(jQuery);(function(a,c){var b={};a.mobile=a.extend({}, +{version:"1.1.0",ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",focusClass:"ui-focus",ajaxEnabled:true,hashListeningEnabled:true,linkBindingEnabled:true,defaultPageTransition:"fade",maxTransitionWidth:false,minScrollBack:250,touchOverflowEnabled:false,defaultDialogTransition:"pop",loadingMessage:"loading",pageLoadErrorMessage:"Error Loading Page",loadingMessageTextVisible:false,loadingMessageTheme:"a",pageLoadErrorMessageTheme:"e",autoInitializePage:true, +pushStateEnabled:true,ignoreContentEnabled:false,orientationChangeEnabled:true,buttonMarkup:{hoverDelay:200},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(b){if(a.type(b)!== +"number")b=a.mobile.defaultHomeScroll;a.event.special.scrollstart.enabled=false;setTimeout(function(){c.scrollTo(0,b);a(k).trigger("silentscroll",{x:0,y:b})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},nsNormalizeDict:b,nsNormalize:function(d){return!d?void 0:b[d]||(b[d]=a.camelCase(a.mobile.ns+d))},getInheritedTheme:function(a,b){for(var c=a[0],e="",f=/ui-(bar|body|overlay)-([a-z])\b/,m,p;c;){m=c.className||"";if((p=f.exec(m))&&(e=p[2]))break;c=c.parentNode}return e|| +b||"a"},closestPageData:function(a){return a.closest(':jqmData(role="page"), :jqmData(role="dialog")').data("page")},enhanceable:function(a){return this.haveParents(a,"enhance")},hijackable:function(a){return this.haveParents(a,"ajax")},haveParents:function(b,c){if(!a.mobile.ignoreContentEnabled)return b;for(var e=b.length,f=a(),o,m,p,l=0;l<e;l++){m=b.eq(l);p=false;for(o=b[l];o;){if((o.getAttribute?o.getAttribute("data-"+a.mobile.ns+c):"")==="false"){p=true;break}o=o.parentNode}p||(f=f.add(m))}return f}}, +a.mobile);a.fn.jqmData=function(b,c){var f;typeof b!="undefined"&&(b&&(b=a.mobile.nsNormalize(b)),f=this.data.apply(this,arguments.length<2?[b]:[b,c]));return f};a.jqmData=function(b,c,f){var e;typeof c!="undefined"&&(e=a.data(b,c?a.mobile.nsNormalize(c):c,f));return e};a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))};a.jqmRemoveData=function(b,c){return a.removeData(b,a.mobile.nsNormalize(c))};a.fn.removeWithDependents=function(){a.removeWithDependents(this)};a.removeWithDependents= +function(b){b=a(b);(b.jqmData("dependents")||a()).remove();b.remove()};a.fn.addDependents=function(b){a.addDependents(a(this),b)};a.addDependents=function(b,c){var f=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(f,c))};a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()};a.fn.jqmEnhanceable=function(){return a.mobile.enhanceable(this)};a.fn.jqmHijackable=function(){return a.mobile.hijackable(this)};var e=a.find,f=/:jqmData\(([^)]*)\)/g;a.find=function(b, +c,h,j){b=b.replace(f,"[data-"+(a.mobile.ns||"")+"$1]");return e.call(this,b,c,h,j)};a.extend(a.find,e);a.find.matches=function(b,c){return a.find(b,null,null,c)};a.find.matchesSelector=function(b,c){return a.find(c,null,null,[b]).length>0}})(jQuery,this);(function(a){a(s);var c=a("html");a.mobile.media=function(){var b={},e=a("<div id='jquery-mediatest'>"),f=a("<body>").append(e);return function(a){if(!(a in b)){var g=k.createElement("style"),h="@media "+a+" { #jquery-mediatest { position:absolute; } }"; +g.type="text/css";g.styleSheet?g.styleSheet.cssText=h:g.appendChild(k.createTextNode(h));c.prepend(f).prepend(g);b[a]=e.css("position")==="absolute";f.add(g).remove()}return b[a]}}()})(jQuery);(function(a,c){function b(a){var b=a.charAt(0).toUpperCase()+a.substr(1),a=(a+" "+g.join(b+" ")+b).split(" "),f;for(f in a)if(d[a[f]]!==c)return true}function e(a,b,c){var d=k.createElement("div"),c=c?[c]:g,f;for(i=0;i<c.length;i++){var e=c[i],h="-"+e.charAt(0).toLowerCase()+e.substr(1)+"-"+a+": "+b+";",e=e.charAt(0).toUpperCase()+ +e.substr(1)+(a.charAt(0).toUpperCase()+a.substr(1));d.setAttribute("style",h);d.style[e]&&(f=true)}return!!f}var f=a("<body>").prependTo("html"),d=f[0].style,g=["Webkit","Moz","O"],h="palmGetResource"in s,j=s.operamini&&{}.toString.call(s.operamini)==="[object OperaMini]",o=s.blackberry;a.extend(a.mobile,{browser:{}});a.mobile.browser.ie=function(){for(var a=3,b=k.createElement("div"),c=b.all||[];b.innerHTML="<\!--[if gt IE "+ ++a+"]><br><![endif]--\>",c[0];);return a>4?a:!a}();a.extend(a.support, +{orientation:"orientation"in s&&"onorientationchange"in s,touch:"ontouchend"in k,cssTransitions:"WebKitTransitionEvent"in s||e("transition","height 100ms linear"),pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!b("content"),touchOverflow:!!b("overflowScrolling"),cssTransform3d:e("perspective","10px","moz")||a.mobile.media("(-"+g.join("-transform-3d),(-")+"-transform-3d),(transform-3d)"),boxShadow:!!b("boxShadow")&&!o,scrollTop:("pageXOffset"in +s||"scrollTop"in k.documentElement||"scrollTop"in f[0])&&!h&&!j,dynamicBaseTag:function(){var b=location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),d=null,e="",g;c.length?e=c.attr("href"):c=d=a("<base>",{href:b}).appendTo("head");g=a("<a href='testurl' />").prependTo(f)[0].href;c[0].href=e||location.pathname;d&&d.remove();return g.indexOf(b)===0}()});f.remove();h=function(){var a=s.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")> +-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();a.mobile.gradeA=function(){return a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=7};a.mobile.ajaxBlacklist=s.blackberry&&!s.WebKitPoint||j||h;h&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")});a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);(function(a,c,b){function e(b,c,d){var f=d.type;d.type=c;a.event.handle.call(b, +d);d.type=f}a.each("touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=function(a){return a?this.bind(c,a):this.trigger(c)};a.attrFn[c]=true});var f=a.support.touch,d=f?"touchstart":"mousedown",g=f?"touchend":"mouseup",h=f?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function b(a,f){d=f;e(c,d?"scrollstart":"scrollstop",a)}var c=this,d,f;a(c).bind("touchmove scroll", +function(c){a.event.special.scrollstart.enabled&&(d||b(c,true),clearTimeout(f),f=setTimeout(function(){b(c,false)},50))})}};a.event.special.tap={setup:function(){var b=this,c=a(b);c.bind("vmousedown",function(d){function f(){clearTimeout(q)}function g(){f();c.unbind("vclick",h).unbind("vmouseup",f);a(k).unbind("vmousecancel",g)}function h(a){g();n==a.target&&e(b,"tap",a)}if(d.which&&d.which!==1)return false;var n=d.target,q;c.bind("vmouseup",f).bind("vclick",h);a(k).bind("vmousecancel",g);q=setTimeout(function(){e(b, +"taphold",a.Event("taphold",{target:n}))},750)})}};a.event.special.swipe={scrollSupressionThreshold:10,durationThreshold:1E3,horizontalDistanceThreshold:30,verticalDistanceThreshold:75,setup:function(){var c=a(this);c.bind(d,function(d){function f(b){if(l){var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;k={time:(new Date).getTime(),coords:[c.pageX,c.pageY]};Math.abs(l.coords[0]-k.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&b.preventDefault()}}var e=d.originalEvent.touches? +d.originalEvent.touches[0]:d,l={time:(new Date).getTime(),coords:[e.pageX,e.pageY],origin:a(d.target)},k;c.bind(h,f).one(g,function(){c.unbind(h,f);l&&k&&k.time-l.time<a.event.special.swipe.durationThreshold&&Math.abs(l.coords[0]-k.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(l.coords[1]-k.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&l.origin.trigger("swipe").trigger(l.coords[0]>k.coords[0]?"swipeleft":"swiperight");l=k=b})})}};(function(a,b){function c(){var a= +f();a!==e&&(e=a,d.trigger("orientationchange"))}var d=a(b),f,e,g,h,t={0:true,180:true};if(a.support.orientation&&(g=b.innerWidth||a(b).width(),h=b.innerHeight||a(b).height(),g=g>h&&g-h>50,h=t[b.orientation],g&&h||!g&&!h))t={"-90":true,90:true};a.event.special.orientationchange={setup:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;e=f();d.bind("throttledresize",c)},teardown:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;d.unbind("throttledresize", +c)},add:function(a){var b=a.handler;a.handler=function(a){a.orientation=f();return b.apply(this,arguments)}}};a.event.special.orientationchange.orientation=f=function(){var c=true,c=k.documentElement;return(c=a.support.orientation?t[b.orientation]:c&&c.clientWidth/c.clientHeight<1.1)?"portrait":"landscape"}})(jQuery,c);(function(){a.event.special.throttledresize={setup:function(){a(this).bind("resize",b)},teardown:function(){a(this).unbind("resize",b)}};var b=function(){f=(new Date).getTime();g=f- +c;g>=250?(c=f,a(this).trigger("throttledresize")):(d&&clearTimeout(d),d=setTimeout(b,250-g))},c=0,d,f,g})();a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})})(jQuery,this);(function(a){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:false,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){var a=this;if(a._trigger("beforecreate")=== +false)return false;a.element.attr("tabindex","0").addClass("ui-page ui-body-"+a.options.theme).bind("pagebeforehide",function(){a.removeContainerBackground()}).bind("pagebeforeshow",function(){a.setContainerBackground()})},removeContainerBackground:function(){a.mobile.pageContainer.removeClass("ui-overlay-"+a.mobile.getInheritedTheme(this.element.parent()))},setContainerBackground:function(c){this.options.theme&&a.mobile.pageContainer.addClass("ui-overlay-"+(c||this.options.theme))},keepNativeSelector:function(){var c= +this.options;return c.keepNative&&a.trim(c.keepNative)&&c.keepNative!==c.keepNativeDefault?[c.keepNative,c.keepNativeDefault].join(", "):c.keepNativeDefault}})})(jQuery);(function(a,c,b){var e=function(d){d===b&&(d=true);return function(b,f,e,o){var k=new a.Deferred,p=f?" reverse":"",l=a.mobile.urlHistory.getActive().lastScroll||a.mobile.defaultHomeScroll,r=a.mobile.getScreenHeight(),n=a.mobile.maxTransitionWidth!==false&&a(c).width()>a.mobile.maxTransitionWidth,q=!a.support.cssTransitions||n||!b|| +b==="none",t=function(){a.mobile.pageContainer.toggleClass("ui-mobile-viewport-transitioning viewport-"+b)},x=function(){a.event.special.scrollstart.enabled=false;c.scrollTo(0,l);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},u=function(){o.removeClass(a.mobile.activePageClass+" out in reverse "+b).height("")},n=function(){o&&d&&u();e.addClass(a.mobile.activePageClass);a.mobile.focusPage(e);e.height(r+l);x();q||e.animationComplete(w);e.addClass(b+" in"+p);q&&w()},w=function(){d|| +o&&u();e.removeClass("out in reverse "+b).height("");t();a(c).scrollTop()!==l&&x();k.resolve(b,f,e,o,true)};t();o&&!q?(d?o.animationComplete(n):n(),o.height(r+a(c).scrollTop()).addClass(b+" out"+p)):n();return k.promise()}},f=e(),e=e(false);a.mobile.defaultTransitionHandler=f;a.mobile.transitionHandlers={"default":a.mobile.defaultTransitionHandler,sequential:f,simultaneous:e};a.mobile.transitionFallbacks={}})(jQuery,this);(function(a,c){function b(b){r&&(!r.closest(".ui-page-active").length||b)&& +r.removeClass(a.mobile.activeBtnClass);r=null}function e(){t=false;q.length>0&&a.mobile.changePage.apply(null,q.pop())}function f(b,c,d,f){c&&c.data("page")._trigger("beforehide",null,{nextPage:b});b.data("page")._trigger("beforeshow",null,{prevPage:c||a("")});a.mobile.hidePageLoadingMsg();d&&!a.support.cssTransform3d&&a.mobile.transitionFallbacks[d]&&(d=a.mobile.transitionFallbacks[d]);d=(a.mobile.transitionHandlers[d||"default"]||a.mobile.defaultTransitionHandler)(d,f,b,c);d.done(function(){c&& +c.data("page")._trigger("hide",null,{nextPage:b});b.data("page")._trigger("show",null,{prevPage:c||a("")})});return d}function d(){return s.innerHeight||a(s).height()}function g(){var b=a("."+a.mobile.activePageClass),c=parseFloat(b.css("padding-top")),f=parseFloat(b.css("padding-bottom"));b.css("min-height",d()-c-f)}function h(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c);b.page()}function j(a){for(;a;){if(typeof a.nodeName==="string"&&a.nodeName.toLowerCase()=="a")break;a=a.parentNode}return a}function o(b){var b= +a(b).closest(".ui-page").jqmData("url"),c=v.hrefNoHash;if(!b||!l.isPath(b))b=c;return l.makeUrlAbsolute(b,c)}var m=a(s);a("html");var p=a("head"),l={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,parseUrl:function(b){if(a.type(b)==="object")return b;b=l.urlParseRE.exec(b||"")||[];return{href:b[0]||"",hrefNoHash:b[1]||"",hrefNoSearch:b[2]||"",domain:b[3]||"", +protocol:b[4]||"",doubleSlash:b[5]||"",authority:b[6]||"",username:b[8]||"",password:b[9]||"",host:b[10]||"",hostname:b[11]||"",port:b[12]||"",pathname:b[13]||"",directory:b[14]||"",filename:b[15]||"",search:b[16]||"",hash:b[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;for(var a=a||"",c=(b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"")?b.split("/"):[],d=a.split("/"),f=0;f<d.length;f++){var e=d[f];switch(e){case ".":break;case "..":c.length&&c.pop();break;default:c.push(e)}}return"/"+ +c.join("/")},isSameDomain:function(a,b){return l.parseUrl(a).domain===l.parseUrl(b).domain},isRelativeUrl:function(a){return l.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return l.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!l.isRelativeUrl(a))return a;var c=l.parseUrl(a),d=l.parseUrl(b),f=c.protocol||d.protocol,e=c.protocol?c.doubleSlash:c.doubleSlash||d.doubleSlash,g=c.authority||d.authority,h=c.pathname!=="",j=l.makePathAbsolute(c.pathname||d.filename,d.pathname);return f+ +e+g+j+(c.search||!h&&d.search||"")+c.hash},addSearchParams:function(b,c){var d=l.parseUrl(b),f=typeof c==="object"?a.param(c):c,e=d.search||"?";return d.hrefNoSearch+e+(e.charAt(e.length-1)!=="?"?"&":"")+f+(d.hash||"")},convertUrlToDataUrl:function(a){var b=l.parseUrl(a);if(l.isEmbeddedPage(b))return b.hash.split(x)[0].replace(/^#/,"");else if(l.isSameDomain(b,v))return b.hrefNoHash.replace(v.domain,"");return a},get:function(a){if(a===c)a=location.hash;return l.stripHash(a).replace(/[^\/]*\.[^\/*]+$/, +"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(x)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(v.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return l.stripHash(a.replace(/\?.*$/,"").replace(x,""))},isExternal:function(a){a=l.parseUrl(a);return a.protocol&&a.domain!==w.domain?true:false},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var b= +l.parseUrl(l.makeUrlAbsolute(b,v)),d=a.mobile.firstPage,d=d&&d[0]?d[0].id:c;return(b.hrefNoHash===w.hrefNoHash||y&&b.hrefNoHash===v.hrefNoHash)&&(!b.hash||b.hash==="#"||d&&b.hash.replace(/^#/,"")===d)},isEmbeddedPage:function(a){a=l.parseUrl(a);return a.protocol!==""?a.hash&&(a.hrefNoHash===w.hrefNoHash||y&&a.hrefNoHash===v.hrefNoHash):/^#/.test(a.href)}},r=null,n={stack:[],activeIndex:0,getActive:function(){return n.stack[n.activeIndex]},getPrev:function(){return n.stack[n.activeIndex-1]},getNext:function(){return n.stack[n.activeIndex+ +1]},addNew:function(a,b,c,d,f){n.getNext()&&n.clearForward();n.stack.push({url:a,transition:b,title:c,pageUrl:d,role:f});n.activeIndex=n.stack.length-1},clearForward:function(){n.stack=n.stack.slice(0,n.activeIndex+1)},directHashChange:function(b){var d,f,e;this.getActive();a.each(n.stack,function(a,c){b.currentUrl===c.url&&(d=a<n.activeIndex,f=!d,e=a)});this.activeIndex=e!==c?e:this.activeIndex;d?(b.either||b.isBack)(true):f&&(b.either||b.isForward)(false)},ignoreNextHashChange:false},q=[],t=false, +x="&ui-state=dialog",u=p.children("base"),w=l.parseUrl(location.href),v=u.length?l.parseUrl(l.makeUrlAbsolute(u.attr("href"),w.href)):w,y=w.hrefNoHash!==v.hrefNoHash,B=a.support.dynamicBaseTag?{element:u.length?u:a("<base>",{href:v.hrefNoHash}).prependTo(p),set:function(a){B.element.attr("href",l.makeUrlAbsolute(a,v))},reset:function(){B.element.attr("href",v.hrefNoHash)}}:c;a.mobile.focusPage=function(a){var b=a.find("[autofocus]"),c=a.find(".ui-title:eq(0)");b.length?b.focus():c.length?c.focus(): +a.focus()};var E=true,A,C;A=function(){if(E){var b=a.mobile.urlHistory.getActive();if(b){var c=m.scrollTop();b.lastScroll=c<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:c}}};C=function(){setTimeout(A,100)};m.bind(a.support.pushState?"popstate":"hashchange",function(){E=false});m.one(a.support.pushState?"popstate":"hashchange",function(){E=true});m.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){E=true;m.unbind("scrollstop",C);m.bind("scrollstop",C)})}); +m.bind("scrollstop",C);a.mobile.getScreenHeight=d;a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd animationend",b):(setTimeout(b,0),a(this))};a.mobile.path=l;a.mobile.base=B;a.mobile.urlHistory=n;a.mobile.dialogHashKey=x;a.mobile.allowCrossDomainPages=false;a.mobile.getDocumentUrl=function(b){return b?a.extend({},w):w.href};a.mobile.getDocumentBase=function(b){return b?a.extend({},v):v.href};a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&& +b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c);c.isDefaultPrevented()||b.removeWithDependents()})};a.mobile.loadPage=function(b,d){var f=a.Deferred(),e=a.extend({},a.mobile.loadPage.defaults,d),g=null,j=null,k=l.makeUrlAbsolute(b,a.mobile.activePage&&o(a.mobile.activePage)||v.hrefNoHash);if(e.data&&e.type==="get")k=l.addSearchParams(k,e.data),e.data=c;if(e.data&&e.type==="post")e.reloadPage=true;var u=l.getFilePath(k), +n=l.convertUrlToDataUrl(k);e.pageContainer=e.pageContainer||a.mobile.pageContainer;g=e.pageContainer.children(":jqmData(url='"+n+"')");g.length===0&&n&&!l.isPath(n)&&(g=e.pageContainer.children("#"+n).attr("data-"+a.mobile.ns+"url",n));if(g.length===0)if(a.mobile.firstPage&&l.isFirstPageUrl(u))a.mobile.firstPage.parent().length&&(g=a(a.mobile.firstPage));else if(l.isEmbeddedPage(u))return f.reject(k,d),f.promise();B&&B.reset();if(g.length){if(!e.reloadPage)return h(g,e.role),f.resolve(k,d,g),f.promise(); +j=g}var m=e.pageContainer,x=new a.Event("pagebeforeload"),p={url:b,absUrl:k,dataUrl:n,deferred:f,options:e};m.trigger(x,p);if(x.isDefaultPrevented())return f.promise();if(e.showLoadMsg)var r=setTimeout(function(){a.mobile.showPageLoadingMsg()},e.loadMsgDelay);!a.mobile.allowCrossDomainPages&&!l.isSameDomain(w,k)?f.reject(k,d):a.ajax({url:u,type:e.type,data:e.data,dataType:"html",success:function(c,o,m){var x=a("<div></div>"),v=c.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,w=RegExp("\\bdata-"+a.mobile.ns+ +"url=[\"']?([^\"'>]*)[\"']?");RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)").test(c)&&RegExp.$1&&w.test(RegExp.$1)&&RegExp.$1&&(b=u=l.getFilePath(RegExp.$1));B&&B.set(u);x.get(0).innerHTML=c;g=x.find(":jqmData(role='page'), :jqmData(role='dialog')").first();g.length||(g=a("<div data-"+a.mobile.ns+"role='page'>"+c.split(/<\/?body[^>]*>/gmi)[1]+"</div>"));v&&!g.jqmData("title")&&(~v.indexOf("&")&&(v=a("<div>"+v+"</div>").text()),g.jqmData("title",v));if(!a.support.dynamicBaseTag){var q= +l.get(u);g.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b),c=c.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(c)||a(this).attr(b,q+c)})}g.attr("data-"+a.mobile.ns+"url",l.convertUrlToDataUrl(u)).attr("data-"+a.mobile.ns+"external-page",true).appendTo(e.pageContainer);g.one("pagecreate",a.mobile._bindPageRemove);h(g,e.role);k.indexOf("&"+ +a.mobile.subPageUrlKey)>-1&&(g=e.pageContainer.children(":jqmData(url='"+n+"')"));e.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg());p.xhr=m;p.textStatus=o;p.page=g;e.pageContainer.trigger("pageload",p);f.resolve(k,d,g,j)},error:function(b,c,g){B&&B.set(l.get());p.xhr=b;p.textStatus=c;p.errorThrown=g;b=new a.Event("pageloadfailed");e.pageContainer.trigger(b,p);b.isDefaultPrevented()||(e.showLoadMsg&&(clearTimeout(r),a.mobile.hidePageLoadingMsg(),a.mobile.showPageLoadingMsg(a.mobile.pageLoadErrorMessageTheme, +a.mobile.pageLoadErrorMessage,true),setTimeout(a.mobile.hidePageLoadingMsg,1500)),f.reject(k,d))}});return f.promise()};a.mobile.loadPage.defaults={type:"get",data:c,reloadPage:false,role:c,showLoadMsg:false,pageContainer:c,loadMsgDelay:50};a.mobile.changePage=function(d,g){if(t)q.unshift(arguments);else{var j=a.extend({},a.mobile.changePage.defaults,g);j.pageContainer=j.pageContainer||a.mobile.pageContainer;j.fromPage=j.fromPage||a.mobile.activePage;var u=j.pageContainer,o=new a.Event("pagebeforechange"), +m={toPage:d,options:j};u.trigger(o,m);if(!o.isDefaultPrevented())if(d=m.toPage,t=true,typeof d=="string")a.mobile.loadPage(d,j).done(function(b,c,d,e){t=false;c.duplicateCachedPage=e;a.mobile.changePage(d,c)}).fail(function(){t=false;b(true);e();j.pageContainer.trigger("pagechangefailed",m)});else{if(d[0]===a.mobile.firstPage[0]&&!j.dataUrl)j.dataUrl=w.hrefNoHash;var o=j.fromPage,p=j.dataUrl&&l.convertUrlToDataUrl(j.dataUrl)||d.jqmData("url"),v=p;l.getFilePath(p);var r=n.getActive(),s=n.activeIndex=== +0,y=0,B=k.title,A=j.role==="dialog"||d.jqmData("role")==="dialog";if(o&&o[0]===d[0]&&!j.allowSamePageTransition)t=false,u.trigger("pagechange",m);else{h(d,j.role);j.fromHashChange&&n.directHashChange({currentUrl:p,isBack:function(){y=-1},isForward:function(){y=1}});try{k.activeElement&&k.activeElement.nodeName.toLowerCase()!="body"?a(k.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(E){}A&&r&&(p=(r.url||"")+x);if(j.changeHash!==false&&p)n.ignoreNextHashChange=true, +l.set(p);var C=!r?B:d.jqmData("title")||d.children(":jqmData(role='header')").find(".ui-title").getEncodedText();C&&B==k.title&&(B=C);d.jqmData("title")||d.jqmData("title",B);j.transition=j.transition||(y&&!s?r.transition:c)||(A?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition);y||n.addNew(p,j.transition,B,v,j.role);k.title=n.getActive().title;a.mobile.activePage=d;j.reverse=j.reverse||y<0;f(d,o,j.transition,j.reverse).done(function(c,f,g,h,l){b();j.duplicateCachedPage&&j.duplicateCachedPage.remove(); +l||a.mobile.focusPage(d);e();u.trigger("pagechange",m)})}}}};a.mobile.changePage.defaults={transition:c,reverse:false,changeHash:true,fromHashChange:false,role:c,duplicateCachedPage:c,pageContainer:c,showLoadMsg:true,dataUrl:c,fromPage:c,allowSamePageTransition:false};a.mobile._registerInternalEvents=function(){a(k).delegate("form","submit",function(b){var c=a(this);if(a.mobile.ajaxEnabled&&!c.is(":jqmData(ajax='false')")&&c.jqmHijackable().length){var d=c.attr("method"),e=c.attr("target"),f=c.attr("action"); +if(!f&&(f=o(c),f===v.hrefNoHash))f=w.hrefNoSearch;f=l.makeUrlAbsolute(f,o(c));!l.isExternal(f)&&!e&&(a.mobile.changePage(f,{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),direction:c.jqmData("direction"),reloadPage:true}),b.preventDefault())}});a(k).bind("vclick",function(c){if(!(c.which>1)&&a.mobile.linkBindingEnabled&&(c=j(c.target),a(c).jqmHijackable().length&&c&&l.parseUrl(c.getAttribute("href")||"#").hash!=="#"))b(true),r=a(c).closest(".ui-btn").not(".ui-disabled"), +r.addClass(a.mobile.activeBtnClass),a("."+a.mobile.activePageClass+" .ui-btn").not(c).blur(),a(c).jqmData("href",a(c).attr("href")).attr("href","#")});a(k).bind("click",function(d){if(a.mobile.linkBindingEnabled){var f=j(d.target),e=a(f),g;if(f&&!(d.which>1)&&e.jqmHijackable().length){g=function(){s.setTimeout(function(){b(true)},200)};e.jqmData("href")&&e.attr("href",e.jqmData("href"));if(e.is(":jqmData(rel='back')"))return s.history.back(),false;var h=o(e),f=l.makeUrlAbsolute(e.attr("href")||"#", +h);if(!a.mobile.ajaxEnabled&&!l.isEmbeddedPage(f))g();else{if(f.search("#")!=-1)if(f=f.replace(/[^#]*#/,""))f=l.isPath(f)?l.makeUrlAbsolute(f,h):l.makeUrlAbsolute("#"+f,w.hrefNoHash);else{d.preventDefault();return}var h=e.is("[rel='external']")||e.is(":jqmData(ajax='false')")||e.is("[target]"),k=a.mobile.allowCrossDomainPages&&w.protocol==="file:"&&f.search(/^https?:/)!=-1;h||l.isExternal(f)&&!k?g():(g=e.jqmData("transition"),h=(h=e.jqmData("direction"))&&h==="reverse"||e.jqmData("back"),e=e.attr("data-"+ +a.mobile.ns+"rel")||c,a.mobile.changePage(f,{transition:g,reverse:h,role:e}),d.preventDefault())}}}});a(k).delegate(".ui-page","pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),d=c.attr("href");d&&a.inArray(d,b)===-1&&(b.push(d),a.mobile.loadPage(d,{role:c.attr("data-"+a.mobile.ns+"rel")}))})});a.mobile._handleHashChange=function(b){var d=l.stripHash(b),f={transition:a.mobile.urlHistory.stack.length===0?"none":c,changeHash:false,fromHashChange:true}; +if(!a.mobile.hashListeningEnabled||n.ignoreNextHashChange)n.ignoreNextHashChange=false;else{if(n.stack.length>1&&d.indexOf(x)>-1)if(a.mobile.activePage.is(".ui-dialog"))n.directHashChange({currentUrl:d,either:function(b){var c=a.mobile.urlHistory.getActive();d=c.pageUrl;a.extend(f,{role:c.role,transition:c.transition,reverse:b})}});else{n.directHashChange({currentUrl:d,isBack:function(){s.history.back()},isForward:function(){s.history.forward()}});return}d?(d=typeof d==="string"&&!l.isPath(d)?l.makeUrlAbsolute("#"+ +d,v):d,a.mobile.changePage(d,f)):a.mobile.changePage(a.mobile.firstPage,f)}};m.bind("hashchange",function(){a.mobile._handleHashChange(location.hash)});a(k).bind("pageshow",g);a(s).bind("throttledresize",g)}})(jQuery);(function(a,c){var b={},e=a(c),f=a.mobile.path.parseUrl(location.href);a.extend(b,{initialFilePath:f.pathname+f.search,initialHref:f.hrefNoHash,state:function(){return{hash:location.hash||"#"+b.initialFilePath,title:k.title,initialHref:b.initialHref}},resetUIKeys:function(b){var c="&"+ +a.mobile.subPageUrlKey,f=b.indexOf(a.mobile.dialogHashKey);f>-1?b=b.slice(0,f)+"#"+b.slice(f):b.indexOf(c)>-1&&(b=b.split(c).join("#"+c));return b},hashValueAfterReset:function(c){c=b.resetUIKeys(c);return a.mobile.path.parseUrl(c).hash},nextHashChangePrevented:function(c){a.mobile.urlHistory.ignoreNextHashChange=c;b.onHashChangeDisabled=c},onHashChange:function(){if(!b.onHashChangeDisabled){var c,f;c=location.hash;var e=a.mobile.path.isPath(c),j=e?location.href:a.mobile.getDocumentUrl();c=e?c.replace("#", +""):c;f=b.state();c=a.mobile.path.makeUrlAbsolute(c,j);e&&(c=b.resetUIKeys(c));history.replaceState(f,k.title,c)}},onPopState:function(c){var c=c.originalEvent.state,f,h;if(c){f=b.hashValueAfterReset(a.mobile.urlHistory.getActive().url);h=b.hashValueAfterReset(c.hash.replace("#",""));if(f=f!==h)e.one("hashchange.pushstate",function(){b.nextHashChangePrevented(false)});b.nextHashChangePrevented(false);a.mobile._handleHashChange(c.hash);f&&b.nextHashChangePrevented(true)}},init:function(){e.bind("hashchange", +b.onHashChange);e.bind("popstate",b.onPopState);location.hash===""&&history.replaceState(b.state(),k.title,location.href)}});a(function(){a.mobile.pushStateEnabled&&a.support.pushState&&b.init()})})(jQuery,this);jQuery.mobile.transitionFallbacks.pop="fade";(function(a){a.mobile.transitionHandlers.slide=a.mobile.transitionHandlers.simultaneous;a.mobile.transitionFallbacks.slide="fade"})(jQuery,this);jQuery.mobile.transitionFallbacks.slidedown="fade";jQuery.mobile.transitionFallbacks.slideup="fade"; +jQuery.mobile.transitionFallbacks.flip="fade";jQuery.mobile.transitionFallbacks.flow="fade";jQuery.mobile.transitionFallbacks.turn="fade";(function(a){a.mobile.page.prototype.options.degradeInputs={color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:"text",tel:false,time:false,url:false,week:false};a(k).bind("pagecreate create",function(c){var b=a.mobile.closestPageData(a(c.target)),e;if(b)e=b.options,a(c.target).find("input").not(b.keepNativeSelector()).each(function(){var b= +a(this),c=this.getAttribute("type"),g=e.degradeInputs[c]||"text";if(e.degradeInputs[c]){var h=a("<div>").html(b.clone()).html(),j=h.indexOf(" type=")>-1;b.replaceWith(h.replace(j?/\s+type=["']?\w+['"]?/:/\/?>/,' type="'+g+'" data-'+a.mobile.ns+'type="'+c+'"'+(j?"":">")))}})})})(jQuery);(function(a,c){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,c=this.element,f=a("<a href='#' data-"+a.mobile.ns+ +"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>"),d=a("<div/>",{role:"dialog","class":"ui-dialog-contain ui-corner-all ui-overlay-shadow"});c.addClass("ui-dialog ui-overlay-"+this.options.overlayTheme);c.wrapInner(d).children().find(":jqmData(role='header')").prepend(f).end().children(":first-child").addClass("ui-corner-top").end().children(":last-child").addClass("ui-corner-bottom");f.bind("click",function(){b.close()});c.bind("vclick submit",function(b){var b= +a(b.target).closest(b.type==="vclick"?"a":"form"),c;b.length&&!b.jqmData("transition")&&(c=a.mobile.urlHistory.getActive()||{},b.attr("data-"+a.mobile.ns+"transition",c.transition||a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(){a(this).find("."+a.mobile.activeBtnClass).removeClass(a.mobile.activeBtnClass)}).bind("pagebeforeshow",function(){b.options.overlayTheme&&b.element.page("removeContainerBackground").page("setContainerBackground", +b.options.overlayTheme)})},close:function(){c.history.back()}});a(k).delegate(a.mobile.dialog.prototype.options.initSelector,"pagecreate",function(){a.mobile.dialog.prototype.enhance(this)})})(jQuery,this);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")};a(k).bind("pagecreate create",function(c){a(":jqmData(role='fieldcontain')",c.target).jqmEnhanceable().fieldcontain()})})(jQuery);(function(a){a.fn.grid=function(c){return this.each(function(){var b= +a(this),e=a.extend({grid:null},c),f=b.children(),d={solo:1,a:2,b:3,c:4,d:5},e=e.grid;if(!e)if(f.length<=5)for(var g in d)d[g]===f.length&&(e=g);else e="a";d=d[e];b.addClass("ui-grid-"+e);f.filter(":nth-child("+d+"n+1)").addClass("ui-block-a");d>1&&f.filter(":nth-child("+d+"n+2)").addClass("ui-block-b");d>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");d>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");d>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);(function(a){a(k).bind("pagecreate create", +function(c){a(":jqmData(role='nojs')",c.target).addClass("ui-nojs")})})(jQuery);(function(a,c){function b(a){for(var b;a;){if((b=typeof a.className==="string"&&a.className+" ")&&b.indexOf("ui-btn ")>-1&&b.indexOf("ui-disabled ")<0)break;a=a.parentNode}return a}a.fn.buttonMarkup=function(b){for(var b=b&&a.type(b)=="object"?b:{},d=0;d<this.length;d++){var g=this.eq(d),h=g[0],j=a.extend({},a.fn.buttonMarkup.defaults,{icon:b.icon!==c?b.icon:g.jqmData("icon"),iconpos:b.iconpos!==c?b.iconpos:g.jqmData("iconpos"), +theme:b.theme!==c?b.theme:g.jqmData("theme")||a.mobile.getInheritedTheme(g,"c"),inline:b.inline!==c?b.inline:g.jqmData("inline"),shadow:b.shadow!==c?b.shadow:g.jqmData("shadow"),corners:b.corners!==c?b.corners:g.jqmData("corners"),iconshadow:b.iconshadow!==c?b.iconshadow:g.jqmData("iconshadow"),mini:b.mini!==c?b.mini:g.jqmData("mini")},b),o="ui-btn-inner",m,p,l,r,n,q;a.each(j,function(b,c){h.setAttribute("data-"+a.mobile.ns+b,c);g.jqmData(b,c)});(q=a.data(h.tagName==="INPUT"||h.tagName==="BUTTON"? +h.parentNode:h,"buttonElements"))?(h=q.outer,g=a(h),l=q.inner,r=q.text,a(q.icon).remove(),q.icon=null):(l=k.createElement(j.wrapperEls),r=k.createElement(j.wrapperEls));n=j.icon?k.createElement("span"):null;e&&!q&&e();if(!j.theme)j.theme=a.mobile.getInheritedTheme(g,"c");m="ui-btn ui-btn-up-"+j.theme;m+=j.inline?" ui-btn-inline":"";m+=j.shadow?" ui-shadow":"";m+=j.corners?" ui-btn-corner-all":"";j.mini!==c&&(m+=j.mini?" ui-mini":" ui-fullsize");j.inline!==c&&(m+=j.inline===false?" ui-btn-block":" ui-btn-inline"); +if(j.icon)j.icon="ui-icon-"+j.icon,j.iconpos=j.iconpos||"left",p="ui-icon "+j.icon,j.iconshadow&&(p+=" ui-icon-shadow");j.iconpos&&(m+=" ui-btn-icon-"+j.iconpos,j.iconpos=="notext"&&!g.attr("title")&&g.attr("title",g.getEncodedText()));o+=j.corners?" ui-btn-corner-all":"";j.iconpos&&j.iconpos==="notext"&&!g.attr("title")&&g.attr("title",g.getEncodedText());q&&g.removeClass(q.bcls||"");g.removeClass("ui-link").addClass(m);l.className=o;r.className="ui-btn-text";q||l.appendChild(r);if(n&&(n.className= +p,!q||!q.icon))n.appendChild(k.createTextNode("\u00a0")),l.appendChild(n);for(;h.firstChild&&!q;)r.appendChild(h.firstChild);q||h.appendChild(l);q={bcls:m,outer:h,inner:l,text:r,icon:n};a.data(h,"buttonElements",q);a.data(l,"buttonElements",q);a.data(r,"buttonElements",q);n&&a.data(n,"buttonElements",q)}return this};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,wrapperEls:"span"};var e=function(){var c=a.mobile.buttonMarkup.hoverDelay,d,g;a(k).bind({"vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart":function(e){var j, +k=a(b(e.target)),e=e.type;if(k.length)if(j=k.attr("data-"+a.mobile.ns+"theme"),e==="vmousedown")a.support.touch?d=setTimeout(function(){k.removeClass("ui-btn-up-"+j).addClass("ui-btn-down-"+j)},c):k.removeClass("ui-btn-up-"+j).addClass("ui-btn-down-"+j);else if(e==="vmousecancel"||e==="vmouseup")k.removeClass("ui-btn-down-"+j).addClass("ui-btn-up-"+j);else if(e==="vmouseover"||e==="focus")a.support.touch?g=setTimeout(function(){k.removeClass("ui-btn-up-"+j).addClass("ui-btn-hover-"+j)},c):k.removeClass("ui-btn-up-"+ +j).addClass("ui-btn-hover-"+j);else if(e==="vmouseout"||e==="blur"||e==="scrollstart")k.removeClass("ui-btn-hover-"+j+" ui-btn-down-"+j).addClass("ui-btn-up-"+j),d&&clearTimeout(d),g&&clearTimeout(g)},"focusin focus":function(c){a(b(c.target)).addClass(a.mobile.focusClass)},"focusout blur":function(c){a(b(c.target)).removeClass(a.mobile.focusClass)}});e=null};a(k).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", +b.target).not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})})(jQuery);(function(a){a.mobile.page.prototype.options.backBtnText="Back";a.mobile.page.prototype.options.addBackBtn=false;a.mobile.page.prototype.options.backBtnTheme=null;a.mobile.page.prototype.options.headerTheme="a";a.mobile.page.prototype.options.footerTheme="a";a.mobile.page.prototype.options.contentTheme=null;a(k).delegate(":jqmData(role='page'), :jqmData(role='dialog')","pagecreate",function(){var c=a(this), +b=c.data("page").options,e=c.jqmData("role"),f=b.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",this).jqmEnhanceable().each(function(){var d=a(this),g=d.jqmData("role"),h=d.jqmData("theme"),j=h||b.contentTheme||e==="dialog"&&f,k;d.addClass("ui-"+g);if(g==="header"||g==="footer"){var m=h||(g==="header"?b.headerTheme:b.footerTheme)||f;d.addClass("ui-bar-"+m).attr("role",g==="header"?"banner":"contentinfo");g==="header"&&(h=d.children("a"),k=h.hasClass("ui-btn-left"), +j=h.hasClass("ui-btn-right"),k=k||h.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length,j||h.eq(1).addClass("ui-btn-right"));b.addBackBtn&&g==="header"&&a(".ui-page").length>1&&c.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!k&&a("<a href='#' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+b.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",b.backBtnTheme||m).prependTo(d);d.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({role:"heading", +"aria-level":"1"})}else g==="content"&&(j&&d.addClass("ui-body-"+j),d.attr("role","main"))})})})(jQuery);(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:true,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,iconTheme:"d",mini:false,initSelector:":jqmData(role='collapsible')"},_create:function(){var c=this.element,b=this.options,e=c.addClass("ui-collapsible"),f=c.children(b.heading).first(), +d=e.wrapInner("<div class='ui-collapsible-content'></div>").find(".ui-collapsible-content"),g=c.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set");f.is("legend")&&(f=a("<div role='heading'>"+f.html()+"</div>").insertBefore(f),f.next().remove());if(g.length){if(!b.theme)b.theme=g.jqmData("theme")||a.mobile.getInheritedTheme(g,"c");if(!b.contentTheme)b.contentTheme=g.jqmData("content-theme");if(!b.iconPos)b.iconPos=g.jqmData("iconpos");if(!b.mini)b.mini=g.jqmData("mini")}d.addClass(b.contentTheme? +"ui-body-"+b.contentTheme:"");f.insertBefore(d).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:false,corners:false,iconpos:c.jqmData("iconpos")||b.iconPos||"left",icon:"plus",mini:b.mini,theme:b.theme}).add(".ui-btn-inner",c).addClass("ui-corner-top ui-corner-bottom");e.bind("expand collapse",function(c){if(!c.isDefaultPrevented()){c.preventDefault(); +var j=a(this),c=c.type==="collapse",k=b.contentTheme;f.toggleClass("ui-collapsible-heading-collapsed",c).find(".ui-collapsible-heading-status").text(c?b.expandCueText:b.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-minus",!c).toggleClass("ui-icon-plus",c);j.toggleClass("ui-collapsible-collapsed",c);d.toggleClass("ui-collapsible-content-collapsed",c).attr("aria-hidden",c);if(k&&(!g.length||e.jqmData("collapsible-last")))f.find("a").first().add(f.find(".ui-btn-inner")).toggleClass("ui-corner-bottom", +c),d.toggleClass("ui-corner-bottom",!c);d.trigger("updatelayout")}}).trigger(b.collapsed?"collapse":"expand");f.bind("click",function(a){var b=f.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";e.trigger(b);a.preventDefault()})}});a(k).bind("pagecreate create",function(c){a.mobile.collapsible.prototype.enhanceWithin(c.target)})})(jQuery);(function(a,c){a.widget("mobile.collapsibleset",a.mobile.widget,{options:{initSelector:":jqmData(role='collapsible-set')"},_create:function(){var b=this.element.addClass("ui-collapsible-set"), +e=this.options;if(!e.theme)e.theme=a.mobile.getInheritedTheme(b,"c");if(!e.contentTheme)e.contentTheme=b.jqmData("content-theme");if(!e.corners)e.corners=b.jqmData("corners")===c?true:false;b.jqmData("collapsiblebound")||b.jqmData("collapsiblebound",true).bind("expand collapse",function(b){var c=b.type==="collapse",b=a(b.target).closest(".ui-collapsible"),e=b.data("collapsible");e.options.contentTheme&&b.jqmData("collapsible-last")&&(b.find(e.options.heading).first().find("a").first().add(".ui-btn-inner").toggleClass("ui-corner-bottom", +c),b.find(".ui-collapsible-content").toggleClass("ui-corner-bottom",!c))}).bind("expand",function(b){a(b.target).closest(".ui-collapsible").siblings(".ui-collapsible").trigger("collapse")})},_init:function(){this.refresh()},refresh:function(){var b=this.options,c=this.element.children(":jqmData(role='collapsible')");a.mobile.collapsible.prototype.enhance(c.not(".ui-collapsible"));c.each(function(){a(this).find(a.mobile.collapsible.prototype.options.heading).find("a").first().add(".ui-btn-inner").removeClass("ui-corner-top ui-corner-bottom")}); +c.first().find("a").first().addClass(b.corners?"ui-corner-top":"").find(".ui-btn-inner").addClass("ui-corner-top");c.last().jqmData("collapsible-last",true).find("a").first().addClass(b.corners?"ui-corner-bottom":"").find(".ui-btn-inner").addClass("ui-corner-bottom")}});a(k).bind("pagecreate create",function(b){a.mobile.collapsibleset.prototype.enhanceWithin(b.target)})})(jQuery);(function(a,c){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"}, +_create:function(){var b=this.element,e=b.find("a"),f=e.filter(":jqmData(icon)").length?this.options.iconpos:c;b.addClass("ui-navbar").attr("role","navigation").find("ul").jqmEnhanceable().grid({grid:this.options.grid});f||b.addClass("ui-navbar-noicons");e.buttonMarkup({corners:false,shadow:false,inline:true,iconpos:f});b.delegate("a","vclick",function(b){a(b.target).hasClass("ui-disabled")||(e.removeClass(a.mobile.activeBtnClass),a(this).addClass(a.mobile.activeBtnClass))});b.closest(".ui-page").bind("pagebeforeshow", +function(){e.filter(".ui-state-persist").addClass(a.mobile.activeBtnClass)})}});a(k).bind("pagecreate create",function(b){a.mobile.navbar.prototype.enhanceWithin(b.target)})})(jQuery);(function(a){var c={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",mini:false,inset:false,initSelector:":jqmData(role='listview')"},_create:function(){var a="";a+=this.options.inset?" ui-listview-inset ui-corner-all ui-shadow ": +"";a+=this.element.jqmData("mini")||this.options.mini===true?" ui-mini":"";this.element.addClass(function(c,f){return f+" ui-listview "+a});this.refresh(true)},_removeCorners:function(a,c){a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));c==="top"?a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl"):c==="bottom"?a.removeClass("ui-corner-bottom ui-corner-br ui-corner-bl"):a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl ui-corner-bottom ui-corner-br ui-corner-bl")},_refreshCorners:function(a){var c, +f;this.options.inset&&(c=this.element.children("li"),f=a?c.not(".ui-screen-hidden"):c.filter(":visible"),this._removeCorners(c),c=f.first().addClass("ui-corner-top"),c.add(c.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),f=f.last().addClass("ui-corner-bottom"),f.add(f.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl")); +a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,c,f,d){var g={};for(g[f]=g[d]=true;a;){if(g[a.nodeName])return a;a=a[c]}return null},_getChildrenByTagName:function(b,c,f){var d=[],g={};g[c]=g[f]=true;for(b=b.firstChild;b;)g[b.nodeName]&&d.push(b),b=b.nextSibling;return a(d)},_addThumbClasses:function(b){var c,f,d=b.length;for(c=0;c<d;c++)f=a(this._findFirstElementByTagName(b[c].firstChild,"nextSibling","img","IMG")),f.length&&(f.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(f[0].parentNode, +"parentNode","li","LI")).addClass(f.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page");this._createSubPages();var c=this.options,f=this.element,d=f.jqmData("dividertheme")||c.dividerTheme,g=f.jqmData("splittheme"),h=f.jqmData("spliticon"),j=this._getChildrenByTagName(f[0],"li","LI"),o=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1,m={},p,l,r,n,q,t,x;o&&f.find(".ui-li-dec").remove();if(!c.theme)c.theme=a.mobile.getInheritedTheme(this.element, +"c");for(var u=0,w=j.length;u<w;u++){p=j.eq(u);l="ui-li";if(b||!p.hasClass("ui-li"))r=p.jqmData("theme")||c.theme,n=this._getChildrenByTagName(p[0],"a","A"),n.length?(t=p.jqmData("icon"),p.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:n.length>1||t===false?false:t||"arrow-r",theme:r}),t!=false&&n.length==1&&p.addClass("ui-li-has-arrow"),n.first().removeClass("ui-link").addClass("ui-link-inherit"),n.length>1&&(l+=" ui-li-has-alt",n=n.last(),q=g||n.jqmData("theme")|| +c.splitTheme,x=n.jqmData("icon"),n.appendTo(p).attr("title",n.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,corners:false,theme:r,icon:false,iconpos:false}).find(".ui-btn-inner").append(a(k.createElement("span")).buttonMarkup({shadow:true,corners:true,theme:q,iconpos:"notext",icon:x||t||h||c.splitIcon})))):p.jqmData("role")==="list-divider"?(l+=" ui-li-divider ui-bar-"+d,p.attr("role","heading"),o&&(o=1)):l+=" ui-li-static ui-body-"+r;o&&l.indexOf("ui-li-divider")< +0&&(r=p.is(".ui-li-static:first")?p:p.find(".ui-link-inherit"),r.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+o++ +". </span>"));m[l]||(m[l]=[]);m[l].push(p[0])}for(l in m)a(m[l]).addClass(l).children(".ui-btn-inner").addClass(l);f.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+ +(f.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all");this._addThumbClasses(j);this._addThumbClasses(f.find(".ui-link-inherit"));this._refreshCorners(b)},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,e=b.closest(".ui-page"),f=e.jqmData("url"),d=f||e[0][a.expando],g=b.attr("id"),h=this.options,j="data-"+a.mobile.ns,k=this,m=e.find(":jqmData(role='footer')").jqmData("id"),p;typeof c[d]==="undefined"&&(c[d]=-1);g=g|| +++c[d];a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=a(this),e=d.attr("id")||g+"-"+c,c=d.parent(),k=a(d.prevAll().toArray().reverse()),k=k.length?k:a("<span>"+a.trim(c.contents()[0].nodeValue)+"</span>"),o=k.first().getEncodedText(),e=(f||"")+"&"+a.mobile.subPageUrlKey+"="+e,x=d.jqmData("theme")||h.theme,u=d.jqmData("counttheme")||b.jqmData("counttheme")||h.countTheme;p=true;d.detach().wrap("<div "+j+"role='page' "+j+"url='"+e+"' "+j+"theme='"+x+"' "+j+"count-theme='"+u+"'><div "+ +j+"role='content'></div></div>").parent().before("<div "+j+"role='header' "+j+"theme='"+h.headerTheme+"'><div class='ui-title'>"+o+"</div></div>").after(m?a("<div "+j+"role='footer' "+j+"id='"+m+"'>"):"").parent().appendTo(a.mobile.pageContainer).page();d=c.find("a:first");d.length||(d=a("<a/>").html(k||o).prependTo(c.empty()));d.attr("href","#"+e)}).listview();p&&e.is(":jqmData(external-page='true')")&&e.data("page").options.domCache===false&&e.unbind("pagehide.remove").bind("pagehide.remove",function(b, +c){var d=c.nextPage;c.nextPage&&(d=d.jqmData("url"),d.indexOf(f+"&"+a.mobile.subPageUrlKey)!==0&&(k.childPages().remove(),e.remove()))})},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}});a(k).bind("pagecreate create",function(b){a.mobile.listview.prototype.enhanceWithin(b.target)})})(jQuery);(function(a,c){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"}, +_create:function(){var b=this,e=this.element,f=a(e).closest("label"),d=f.length?f:a(e).closest("form,fieldset,:jqmData(role='page'),:jqmData(role='dialog')").find("label").filter("[for='"+e[0].id+"']"),g=e[0].type,f=e.jqmData("mini")||e.closest("form,fieldset").jqmData("mini"),h=g+"-on",j=g+"-off",o=e.parents(":jqmData(type='horizontal')").length?c:j,m=e.jqmData("iconpos")||e.closest("form,fieldset").jqmData("iconpos");if(!(g!=="checkbox"&&g!=="radio")){a.extend(this,{label:d,inputtype:g,checkedClass:"ui-"+ +h+(o?"":" "+a.mobile.activeBtnClass),uncheckedClass:"ui-"+j,checkedicon:"ui-icon-"+h,uncheckedicon:"ui-icon-"+j});if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");d.buttonMarkup({theme:this.options.theme,icon:o,shadow:false,mini:f,iconpos:m});f=k.createElement("div");f.className="ui-"+g;e.add(d).wrapAll(f);d.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(e.is(":disabled"))a.preventDefault();else return b._cacheVals(), +e.prop("checked",g==="radio"&&true||!e.prop("checked")),e.triggerHandler("click"),b._getInputSet().not(e).prop("checked",false),b._updateAll(),false}});e.bind({vmousedown:function(){b._cacheVals()},vclick:function(){var c=a(this);c.is(":checked")?(c.prop("checked",true),b._getInputSet().not(c).prop("checked",false)):c.prop("checked",false);b._updateAll()},focus:function(){d.addClass(a.mobile.focusClass)},blur:function(){d.removeClass(a.mobile.focusClass)}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){a(this).jqmData("cacheVal", +this.checked)})},_getInputSet:function(){return this.inputtype==="checkbox"?this.element:this.element.closest("form,fieldset,:jqmData(role='page')").find("input[name='"+this.element[0].name+"'][type='"+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var c=a(this);(this.checked||b.inputtype==="checkbox")&&c.trigger("change")}).checkboxradio("refresh")},refresh:function(){var a=this.element[0],c=this.label,f=c.find(".ui-icon");a.checked?(c.addClass(this.checkedClass).removeClass(this.uncheckedClass), +f.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(c.removeClass(this.checkedClass).addClass(this.uncheckedClass),f.removeClass(this.checkedicon).addClass(this.uncheckedicon));a.disabled?this.disable():this.enable()},disable:function(){this.element.prop("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",false).parent().removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target, +true)})})(jQuery);(function(a,c){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:false,corners:true,shadow:true,iconshadow:true,initSelector:"button, [type='button'], [type='submit'], [type='reset'], [type='image']",mini:false},_create:function(){var b=this.element,e,f=this.options,d;d="";var g;if(b[0].tagName==="A")!b.hasClass("ui-btn")&&b.buttonMarkup();else{if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.element,"c");~b[0].className.indexOf("ui-btn-left")&& +(d="ui-btn-left");~b[0].className.indexOf("ui-btn-right")&&(d="ui-btn-right");e=this.button=a("<div></div>").text(b.text()||b.val()).insertBefore(b).buttonMarkup({theme:f.theme,icon:f.icon,iconpos:f.iconpos,inline:f.inline,corners:f.corners,shadow:f.shadow,iconshadow:f.iconshadow,mini:f.mini}).addClass(d).append(b.addClass("ui-btn-hidden"));f=b.attr("type");d=b.attr("name");f!=="button"&&f!=="reset"&&d&&b.bind("vclick",function(){g===c&&(g=a("<input>",{type:"hidden",name:b.attr("name"),value:b.attr("value")}).insertBefore(b), +a(k).one("submit",function(){g.remove();g=c}))});b.bind({focus:function(){e.addClass(a.mobile.focusClass)},blur:function(){e.removeClass(a.mobile.focusClass)}});this.refresh()}},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)},refresh:function(){var b= +this.element;b.prop("disabled")?this.disable():this.enable();a(this.button.data("buttonElements").text).text(b.text()||b.val())}});a(k).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target,true)})})(jQuery);(function(a){a.fn.controlgroup=function(c){function b(a,b){a.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(b[0]).end().last().addClass(b[1]).addClass("ui-controlgroup-last")}return this.each(function(){var e=a(this),f=a.extend({direction:e.jqmData("type")|| +"vertical",shadow:false,excludeInvisible:true,mini:e.jqmData("mini")},c),d=e.children("legend"),g=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];e.find("input").first().attr("type");d.length&&(e.wrapInner("<div class='ui-controlgroup-controls'></div>"),a("<div role='heading' class='ui-controlgroup-label'>"+d.html()+"</div>").insertBefore(e.children(0)),d.remove());e.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);b(e.find(".ui-btn"+ +(f.excludeInvisible?":visible":"")).not(".ui-slider-handle"),g);b(e.find(".ui-btn-inner"),g);f.shadow&&e.addClass("ui-shadow");f.mini&&e.addClass("ui-mini")})}})(jQuery);(function(a){a(k).bind("pagecreate create",function(c){a(c.target).find("a").jqmEnhanceable().not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})})(jQuery);(function(a){var c=a("meta[name=viewport]"),b=c.attr("content"),e=b+",maximum-scale=1, user-scalable=no",f=b+",maximum-scale=10, user-scalable=yes", +d=/(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test(b);a.mobile.zoom=a.extend({},{enabled:!d,locked:false,disable:function(b){if(!d&&!a.mobile.zoom.locked)c.attr("content",e),a.mobile.zoom.enabled=false,a.mobile.zoom.locked=b||false},enable:function(b){if(!d&&(!a.mobile.zoom.locked||b===true))c.attr("content",f),a.mobile.zoom.enabled=true,a.mobile.zoom.locked=false},restore:function(){if(!d)c.attr("content",b),a.mobile.zoom.enabled=true}})})(jQuery);(function(a){a.widget("mobile.textinput", +a.mobile.widget,{options:{theme:null,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])", +clearSearchButtonText:"clear text"},_create:function(){var c=this.element,b=this.options,e=b.theme||a.mobile.getInheritedTheme(this.element,"c"),f=" ui-body-"+e,d=c.jqmData("mini")==true,g=d?" ui-mini":"",h,j;a("label[for='"+c.attr("id")+"']").addClass("ui-input-text");h=c.addClass("ui-input-text ui-body-"+e);typeof c[0].autocorrect!=="undefined"&&!a.support.touchOverflow&&(c[0].setAttribute("autocorrect","off"),c[0].setAttribute("autocomplete","off"));c.is("[type='search'],:jqmData(type='search')")? +(h=c.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+f+g+"'></div>").parent(),j=a("<a href='#' class='ui-input-clear' title='"+b.clearSearchButtonText+"'>"+b.clearSearchButtonText+"</a>").bind("click",function(a){c.val("").focus().trigger("change");j.addClass("ui-input-clear-hidden");a.preventDefault()}).appendTo(h).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true,mini:d}),e=function(){setTimeout(function(){j.toggleClass("ui-input-clear-hidden", +!c.val())},0)},e(),c.bind("paste cut keyup focus change blur",e)):c.addClass("ui-corner-all ui-shadow-inset"+f+g);c.focus(function(){h.addClass(a.mobile.focusClass)}).blur(function(){h.removeClass(a.mobile.focusClass)}).bind("focus",function(){b.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("blur",function(){b.preventFocusZoom&&a.mobile.zoom.enable(true)});if(c.is("textarea")){var o=function(){var a=c[0].scrollHeight;c[0].clientHeight<a&&c.height(a+15)},m;c.keyup(function(){clearTimeout(m); +m=setTimeout(o,100)});a(k).one("pagechange",o);a.trim(c.val())&&a(s).load(o)}},disable:function(){(this.element.attr("disabled",true).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).removeClass("ui-disabled")}});a(k).bind("pagecreate create",function(c){a.mobile.textinput.prototype.enhanceWithin(c.target, +true)})})(jQuery);(function(a){a.mobile.listview.prototype.options.filter=false;a.mobile.listview.prototype.options.filterPlaceholder="Filter items...";a.mobile.listview.prototype.options.filterTheme="c";a.mobile.listview.prototype.options.filterCallback=function(a,b){return a.toLowerCase().indexOf(b)===-1};a(k).delegate(":jqmData(role='listview')","listviewcreate",function(){var c=a(this),b=c.data("listview");if(b.options.filter){var e=a("<form>",{"class":"ui-listview-filter ui-bar-"+b.options.filterTheme, +role:"search"});a("<input>",{placeholder:b.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval","").bind("keyup change",function(){var e=a(this),d=this.value.toLowerCase(),g=null,g=e.jqmData("lastval")+"",h=false,j="";e.jqmData("lastval",d);g=d.length<g.length||d.indexOf(g)!==0?c.children():c.children(":not(.ui-screen-hidden)");if(d){for(var k=g.length-1;k>=0;k--)e=a(g[k]),j=e.jqmData("filtertext")||e.text(),e.is("li:jqmData(role=list-divider)")?(e.toggleClass("ui-filter-hidequeue", +!h),h=false):b.options.filterCallback(j,d)?e.toggleClass("ui-filter-hidequeue",true):h=true;g.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",false);g.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",true).toggleClass("ui-filter-hidequeue",false)}else g.toggleClass("ui-screen-hidden",false);b._refreshCorners()}).appendTo(e).textinput();b.options.inset&&e.addClass("ui-listview-filter-inset");e.bind("submit",function(){return false}).insertBefore(c)}})})(jQuery);(function(a, +c){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",mini:false},_create:function(){var b=this,e=this.element,f=a.mobile.getInheritedTheme(e,"c"),d=this.options.theme||f,f=this.options.trackTheme||f,g=e[0].nodeName.toLowerCase(),h=g=="select"?"ui-slider-switch":"",j=e.attr("id"),o=j+"-label",j=a("[for='"+j+"']").attr("id",o),m=function(){return g=="input"?parseFloat(e.val()): +e[0].selectedIndex},p=g=="input"?parseFloat(e.attr("min")):0,l=g=="input"?parseFloat(e.attr("max")):e.find("option").length-1,r=s.parseFloat(e.attr("step")||1),n=this.options.inline||e.jqmData("inline")==true?" ui-slider-inline":"",q=this.options.mini||e.jqmData("mini")?" ui-slider-mini":"",t=k.createElement("a"),x=a(t),u=k.createElement("div"),w=a(u),v=e.jqmData("highlight")&&g!="select"?function(){var b=k.createElement("div");b.className="ui-slider-bg ui-btn-active ui-btn-corner-all";return a(b).prependTo(w)}(): +false;t.setAttribute("href","#");u.setAttribute("role","application");u.className=["ui-slider ",h," ui-btn-down-",f," ui-btn-corner-all",n,q].join("");t.className="ui-slider-handle";u.appendChild(t);x.buttonMarkup({corners:true,theme:d,shadow:true}).attr({role:"slider","aria-valuemin":p,"aria-valuemax":l,"aria-valuenow":m(),"aria-valuetext":m(),title:m(),"aria-labelledby":o});a.extend(this,{slider:w,handle:x,valuebg:v,dragging:false,beforeStart:null,userModified:false,mouseMoved:false});if(g=="select"){d= +k.createElement("div");d.className="ui-slider-inneroffset";h=0;for(o=u.childNodes.length;h<o;h++)d.appendChild(u.childNodes[h]);u.appendChild(d);x.addClass("ui-slider-handle-snapping");u=e.find("option");d=0;for(h=u.length;d<h;d++)o=!d?"b":"a",n=!d?" ui-btn-down-"+f:" "+a.mobile.activeBtnClass,k.createElement("div"),q=k.createElement("span"),q.className=["ui-slider-label ui-slider-label-",o,n," ui-btn-corner-all"].join(""),q.setAttribute("role","img"),q.appendChild(k.createTextNode(u[d].innerHTML)), +a(q).prependTo(w);b._labels=a(".ui-slider-label",w)}j.addClass("ui-slider");e.addClass(g==="input"?"ui-slider-input":"ui-slider-switch").change(function(){b.mouseMoved||b.refresh(m(),true)}).keyup(function(){b.refresh(m(),true,true)}).blur(function(){b.refresh(m(),true)});a(k).bind("vmousemove",function(a){if(b.dragging)return b.mouseMoved=true,g==="select"&&x.removeClass("ui-slider-handle-snapping"),b.refresh(a),b.userModified=b.beforeStart!==e[0].selectedIndex,false});w.bind("vmousedown",function(a){b.dragging= +true;b.userModified=false;b.mouseMoved=false;if(g==="select")b.beforeStart=e[0].selectedIndex;b.refresh(a);return false}).bind("vclick",false);w.add(k).bind("vmouseup",function(){if(b.dragging)return b.dragging=false,g==="select"&&(x.addClass("ui-slider-handle-snapping"),b.mouseMoved?b.userModified?b.refresh(b.beforeStart==0?1:0):b.refresh(b.beforeStart):b.refresh(b.beforeStart==0?1:0)),b.mouseMoved=false});w.insertAfter(e);g=="select"&&this.handle.bind({focus:function(){w.addClass(a.mobile.focusClass)}, +blur:function(){w.removeClass(a.mobile.focusClass)}});this.handle.bind({vmousedown:function(){a(this).focus()},vclick:false,keydown:function(c){var d=m();if(!b.options.disabled){switch(c.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:if(c.preventDefault(),!b._keySliding)b._keySliding=true,a(this).addClass("ui-state-active")}switch(c.keyCode){case a.mobile.keyCode.HOME:b.refresh(p); +break;case a.mobile.keyCode.END:b.refresh(l);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:b.refresh(d+r);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.refresh(d-r)}}},keyup:function(){if(b._keySliding)b._keySliding=false,a(this).removeClass("ui-state-active")}});this.refresh(c,c,true)},refresh:function(b,c,f){(this.options.disabled||this.element.attr("disabled"))&&this.disable();var d=this.element,g=d[0].nodeName.toLowerCase(), +h=g==="input"?parseFloat(d.attr("min")):0,j=g==="input"?parseFloat(d.attr("max")):d.find("option").length-1,k=g==="input"&&parseFloat(d.attr("step"))>0?parseFloat(d.attr("step")):1;if(typeof b==="object"){if(!this.dragging||b.pageX<this.slider.offset().left-8||b.pageX>this.slider.offset().left+this.slider.width()+8)return;b=Math.round((b.pageX-this.slider.offset().left)/this.slider.width()*100)}else b==null&&(b=g==="input"?parseFloat(d.val()||0):d[0].selectedIndex),b=(parseFloat(b)-h)/(j-h)*100;if(!isNaN(b)){b< +0&&(b=0);b>100&&(b=100);var m=b/100*(j-h)+h,p=(m-h)%k;m-=p;Math.abs(p)*2>=k&&(m+=p>0?k:-k);m=parseFloat(m.toFixed(5));m<h&&(m=h);m>j&&(m=j);this.handle.css("left",b+"%");this.handle.attr({"aria-valuenow":g==="input"?m:d.find("option").eq(m).attr("value"),"aria-valuetext":g==="input"?m:d.find("option").eq(m).getEncodedText(),title:g==="input"?m:d.find("option").eq(m).getEncodedText()});this.valuebg&&this.valuebg.css("width",b+"%");if(this._labels){var h=this.handle.width()/this.slider.width()*100, +l=b&&h+(100-h)*b/100,r=b===100?0:Math.min(h+100-l,100);this._labels.each(function(){var b=a(this).is(".ui-slider-label-a");a(this).width((b?l:r)+"%")})}if(!f)f=false,g==="input"?(f=d.val()!==m,d.val(m)):(f=d[0].selectedIndex!==m,d[0].selectedIndex=m),!c&&f&&d.trigger("change")}},enable:function(){this.element.attr("disabled",false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled", +true);return this._setOption("disabled",true)}});a(k).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target,true)})})(jQuery);(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:false,corners:true,shadow:true,iconshadow:true,overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:true,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")> +-1,initSelector:"select:not(:jqmData(role='slider'))",mini:false},_button:function(){return a("<div/>")},_setDisabled:function(a){this.element.attr("disabled",a);this.button.attr("aria-disabled",a);return this._setOption("disabled",a)},_focusButton:function(){var a=this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){var c="";~this.element[0].className.indexOf("ui-btn-left")&&(c=" ui-btn-left");~this.element[0].className.indexOf("ui-btn-right")&& +(c=" ui-btn-right");this.select=this.element.wrap("<div class='ui-select"+c+"'>");this.selectID=this.select.attr("id");this.label=a("label[for='"+this.selectID+"']").addClass("ui-select");this.isMultiple=this.select[0].multiple;if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.select,"c")},_create:function(){this._preExtension();this._trigger("beforeCreate");this.button=this._button();var c=this,b=this.options,e=this.button.text(a(this.select[0].options.item(this.select[0].selectedIndex== +-1?0:this.select[0].selectedIndex)).text()).insertBefore(this.select).buttonMarkup({theme:b.theme,icon:b.icon,iconpos:b.iconpos,inline:b.inline,corners:b.corners,shadow:b.shadow,iconshadow:b.iconshadow,mini:b.mini});b.nativeMenu&&s.opera&&s.opera.version&&this.select.addClass("ui-select-nativeonly");if(this.isMultiple)this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(e.addClass("ui-li-has-count"));(b.disabled||this.element.attr("disabled"))&&this.disable(); +this.select.change(function(){c.refresh()});this.build()},build:function(){var c=this;this.select.appendTo(c.button).bind("vmousedown",function(){c.button.addClass(a.mobile.activeBtnClass)}).bind("focus",function(){c.button.addClass(a.mobile.focusClass)}).bind("blur",function(){c.button.removeClass(a.mobile.focusClass)}).bind("focus vmouseover",function(){c.button.trigger("vmouseover")}).bind("vmousemove",function(){c.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){c.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur", +function(){c.button.removeClass("ui-btn-down-"+c.options.theme)});c.button.bind("vmousedown",function(){c.options.preventFocusZoom&&a.mobile.zoom.disable(true)}).bind("mouseup",function(){c.options.preventFocusZoom&&a.mobile.zoom.enable(true)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var c=this,b=this.selected();this.button.find(".ui-btn-text").text(function(){return!c.isMultiple? +b.text():b.length?b.map(function(){return a(this).text()}).get().join(", "):c.placeholder})},setButtonCount:function(){var a=this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText();this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(true);this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(false);this.button.removeClass("ui-disabled")}});a(k).bind("pagecreate create", +function(c){a.mobile.selectmenu.prototype.enhanceWithin(c.target,true)})})(jQuery);(function(a){var c=function(b){var c=b.selectID,f=b.label,d=b.select.closest(".ui-page"),g=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(d),h=b._selectOptions(),j=b.isMultiple=b.select[0].multiple,o=c+"-button",m=c+"-menu",p=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+b.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+b.options.overlayTheme+"'><div data-"+a.mobile.ns+ +"role='header'><div class='ui-title'>"+f.getEncodedText()+"</div></div><div data-"+a.mobile.ns+"role='content'></div></div>"),l=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-"+b.options.overlayTheme+" "+a.mobile.defaultDialogTransition}).insertAfter(g),r=a("<ul>",{"class":"ui-selectmenu-list",id:m,role:"listbox","aria-labelledby":o}).attr("data-"+a.mobile.ns+"theme",b.options.theme).appendTo(l),n=a("<div>",{"class":"ui-header ui-bar-"+b.options.theme}).prependTo(l), +q=a("<h1>",{"class":"ui-title"}).appendTo(n),t;b.isMultiple&&(t=a("<a>",{text:b.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(n).buttonMarkup());a.extend(b,{select:b.select,selectID:c,buttonId:o,menuId:m,thisPage:d,menuPage:p,label:f,screen:g,selectOptions:h,isMultiple:j,theme:b.options.theme,listbox:l,list:r,header:n,headerTitle:q,headerClose:t,menuPageContent:void 0,menuPageClose:void 0,placeholder:"", +build:function(){var c=this;c.refresh();c.select.attr("tabindex","-1").focus(function(){a(this).blur();c.button.focus()});c.button.bind("vclick keydown",function(b){if(b.type=="vclick"||b.keyCode&&(b.keyCode===a.mobile.keyCode.ENTER||b.keyCode===a.mobile.keyCode.SPACE))c.open(),b.preventDefault()});c.list.attr("role","listbox").bind("focusin",function(b){a(b.target).attr("tabindex","0").trigger("vmouseover")}).bind("focusout",function(b){a(b.target).attr("tabindex","-1").trigger("vmouseout")}).delegate("li:not(.ui-disabled, .ui-li-divider)", +"click",function(b){var d=c.select[0].selectedIndex,e=c.list.find("li:not(.ui-li-divider)").index(this),f=c._selectOptions().eq(e)[0];f.selected=c.isMultiple?!f.selected:true;c.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",f.selected).toggleClass("ui-icon-checkbox-off",!f.selected);(c.isMultiple||d!==e)&&c.select.trigger("change");c.isMultiple||c.close();b.preventDefault()}).keydown(function(c){var d=a(c.target),e=d.closest("li");switch(c.keyCode){case 38:return c=e.prev().not(".ui-selectmenu-placeholder"), +c.is(".ui-li-divider")&&(c=c.prev()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 40:return c=e.next(),c.is(".ui-li-divider")&&(c=c.next()),c.length&&(d.blur().attr("tabindex","-1"),c.addClass("ui-btn-down-"+b.options.theme).find("a").first().focus()),false;case 13:case 32:return d.trigger("click"),false}});c.menuPage.bind("pagehide",function(){c.list.appendTo(c.listbox);c._focusButton();a.mobile._bindPageRemove.call(c.thisPage)}); +c.screen.bind("vclick",function(){c.close()});c.isMultiple&&c.headerClose.click(function(){if(c.menuType=="overlay")return c.close(),false});c.thisPage.addDependents(this.menuPage)},_isRebuildRequired:function(){var a=this.list.find("li");return this._selectOptions().text()!==a.text()},refresh:function(b){var c=this;this._selectOptions();this.selected();var d=this.selectedIndices();(b||this._isRebuildRequired())&&c._buildList();c.setButtonText();c.setButtonCount();c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected", +false).each(function(b){a.inArray(b,d)>-1&&(b=a(this),b.attr("aria-selected",true),c.isMultiple?b.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):b.is(".ui-selectmenu-placeholder")?b.next().addClass(a.mobile.activeBtnClass):b.addClass(a.mobile.activeBtnClass))})},close:function(){if(!this.options.disabled&&this.isOpen)this.menuType=="page"?s.history.back():(this.screen.addClass("ui-screen-hidden"),this.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in"), +this.list.appendTo(this.listbox),this._focusButton()),this.isOpen=false},open:function(){function b(){c.list.find("."+a.mobile.activeBtnClass+" a").focus()}if(!this.options.disabled){var c=this,d=a(s),e=c.list.parent(),f=e.outerHeight(),e=e.outerWidth();a(".ui-page-active");var g=d.scrollTop(),j=c.button.offset().top,h=d.height(),d=d.width();c.button.addClass(a.mobile.activeBtnClass);setTimeout(function(){c.button.removeClass(a.mobile.activeBtnClass)},300);if(f>h-80||!a.support.scrollTop){c.menuPage.appendTo(a.mobile.pageContainer).page(); +c.menuPageContent=p.find(".ui-content");c.menuPageClose=p.find(".ui-header a");c.thisPage.unbind("pagehide.remove");if(g==0&&j>h)c.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",j)});c.menuPage.one("pageshow",function(){b();c.isOpen=true});c.menuType="page";c.menuPageContent.append(c.list);c.menuPage.find("div .ui-title").text(c.label.text());a.mobile.changePage(c.menuPage,{transition:a.mobile.defaultDialogTransition})}else{c.menuType="overlay";c.screen.height(a(k).height()).removeClass("ui-screen-hidden"); +var l=j-g,m=g+h-j,n=f/2,o=parseFloat(c.list.parent().css("max-width")),f=l>f/2&&m>f/2?j+c.button.outerHeight()/2-n:l>m?g+h-f-30:g+30;e<o?g=(d-e)/2:(g=c.button.offset().left+c.button.outerWidth()/2-e/2,g<30?g=30:g+e>d&&(g=d-e-30));c.listbox.append(c.list).removeClass("ui-selectmenu-hidden").css({top:f,left:g}).addClass("in");b();c.isOpen=true}}},_buildList:function(){var b=this.options,c=this.placeholder,d=true,e=this.isMultiple?"checkbox-off":"false";this.list.empty().filter(".ui-listview").listview("destroy"); +var f=this.select.find("option"),g=f.length,j=this.select[0],h="data-"+a.mobile.ns,l=h+"option-index",m=h+"icon";h+="role";for(var n=k.createDocumentFragment(),o,p=0;p<g;p++){var r=f[p],q=a(r),s=r.parentNode,t=q.text(),D=k.createElement("a"),J=[];D.setAttribute("href","#");D.appendChild(k.createTextNode(t));s!==j&&s.nodeName.toLowerCase()==="optgroup"&&(s=s.getAttribute("label"),s!=o&&(o=k.createElement("li"),o.setAttribute(h,"list-divider"),o.setAttribute("role","option"),o.setAttribute("tabindex", +"-1"),o.appendChild(k.createTextNode(s)),n.appendChild(o),o=s));if(d&&(!r.getAttribute("value")||t.length==0||q.jqmData("placeholder")))if(d=false,b.hidePlaceholderMenuItems&&J.push("ui-selectmenu-placeholder"),!c)c=this.placeholder=t;q=k.createElement("li");r.disabled&&(J.push("ui-disabled"),q.setAttribute("aria-disabled",true));q.setAttribute(l,p);q.setAttribute(m,e);q.className=J.join(" ");q.setAttribute("role","option");D.setAttribute("tabindex","-1");q.appendChild(D);n.appendChild(q)}this.list[0].appendChild(n); +!this.isMultiple&&!c.length?this.header.hide():this.headerTitle.text(this.placeholder);this.list.listview()},_button:function(){return a("<a>",{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a(k).bind("selectmenubeforecreate",function(b){b=a(b.target).data("selectmenu");b.options.nativeMenu||c(b)})})(jQuery);(function(a){a.widget("mobile.fixedtoolbar",a.mobile.widget,{options:{visibleOnPageShow:true,disablePageZoom:true,transition:"slide",fullscreen:false, +tapToggle:true,tapToggleBlacklist:"a, input, select, textarea, .ui-header-fixed, .ui-footer-fixed",hideDuringFocus:"input, textarea, select",updatePagePadding:true,trackPersistentToolbars:true,supportBlacklist:function(){var a=s,b=navigator.userAgent,e=navigator.platform,f=b.match(/AppleWebKit\/([0-9]+)/),f=!!f&&f[1],d=b.match(/Fennec\/([0-9]+)/),d=!!d&&d[1],g=b.match(/Opera Mobi\/([0-9]+)/),h=!!g&&g[1];return(e.indexOf("iPhone")>-1||e.indexOf("iPad")>-1||e.indexOf("iPod")>-1)&&f&&f<534||a.operamini&& +{}.toString.call(a.operamini)==="[object OperaMini]"||g&&h<7458||b.indexOf("Android")>-1&&f&&f<533||d&&d<6||"palmGetResource"in s&&f&&f<534||b.indexOf("MeeGo")>-1&&b.indexOf("NokiaBrowser/8.5.0")>-1?true:false},initSelector:":jqmData(position='fixed')"},_create:function(){var a=this.options,b=this.element,e=b.is(":jqmData(role='header')")?"header":"footer",f=b.closest(".ui-page");a.supportBlacklist()?this.destroy():(b.addClass("ui-"+e+"-fixed"),a.fullscreen?(b.addClass("ui-"+e+"-fullscreen"),f.addClass("ui-page-"+ +e+"-fullscreen")):f.addClass("ui-page-"+e+"-fixed"),this._addTransitionClass(),this._bindPageEvents(),this._bindToggleHandlers())},_addTransitionClass:function(){var a=this.options.transition;a&&a!=="none"&&(a==="slide"&&(a=this.element.is(".ui-header")?"slidedown":"slideup"),this.element.addClass(a))},_bindPageEvents:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("pagebeforeshow",function(){b.disablePageZoom&&a.mobile.zoom.disable(true);b.visibleOnPageShow||c.hide(true)}).bind("webkitAnimationStart animationstart updatelayout", +function(){b.updatePagePadding&&c.updatePagePadding()}).bind("pageshow",function(){c.updatePagePadding();b.updatePagePadding&&a(s).bind("throttledresize."+c.widgetName,function(){c.updatePagePadding()})}).bind("pagebeforehide",function(e,f){b.disablePageZoom&&a.mobile.zoom.enable(true);b.updatePagePadding&&a(s).unbind("throttledresize."+c.widgetName);if(b.trackPersistentToolbars){var d=a(".ui-footer-fixed:jqmData(id)",this),g=a(".ui-header-fixed:jqmData(id)",this),h=d.length&&f.nextPage&&a(".ui-footer-fixed:jqmData(id='"+ +d.jqmData("id")+"')",f.nextPage),j=g.length&&f.nextPage&&a(".ui-header-fixed:jqmData(id='"+g.jqmData("id")+"')",f.nextPage),h=h||a();if(h.length||j.length)h.add(j).appendTo(a.mobile.pageContainer),f.nextPage.one("pageshow",function(){h.add(j).appendTo(this)})}})},_visible:true,updatePagePadding:function(){var a=this.element,b=a.is(".ui-header");this.options.fullscreen||a.closest(".ui-page").css("padding-"+(b?"top":"bottom"),a.outerHeight())},_useTransition:function(c){var b=this.element,e=a(s).scrollTop(), +f=b.height(),d=b.closest(".ui-page").height(),g=a.mobile.getScreenHeight(),b=b.is(":jqmData(role='header')")?"header":"footer";return!c&&(this.options.transition&&this.options.transition!=="none"&&(b==="header"&&!this.options.fullscreen&&e>f||b==="footer"&&!this.options.fullscreen&&e+g<d-f)||this.options.fullscreen)},show:function(a){var b=this.element;this._useTransition(a)?b.removeClass("out ui-fixed-hidden").addClass("in"):b.removeClass("ui-fixed-hidden");this._visible=true},hide:function(a){var b= +this.element,e="out"+(this.options.transition==="slide"?" reverse":"");this._useTransition(a)?b.addClass(e).removeClass("in").animationComplete(function(){b.addClass("ui-fixed-hidden").removeClass(e)}):b.addClass("ui-fixed-hidden").removeClass(e);this._visible=false},toggle:function(){this[this._visible?"hide":"show"]()},_bindToggleHandlers:function(){var c=this,b=c.options;c.element.closest(".ui-page").bind("vclick",function(e){b.tapToggle&&!a(e.target).closest(b.tapToggleBlacklist).length&&c.toggle()}).bind("focusin focusout", +function(e){if(screen.width<500&&a(e.target).is(b.hideDuringFocus)&&!a(e.target).closest(".ui-header-fixed, .ui-footer-fixed").length)c[e.type==="focusin"&&c._visible?"hide":"show"]()})},destroy:function(){this.element.removeClass("ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden");this.element.closest(".ui-page").removeClass("ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen")}});a(k).bind("pagecreate create", +function(c){a(c.target).jqmData("fullscreen")&&a(a.mobile.fixedtoolbar.prototype.options.initSelector,c.target).not(":jqmData(fullscreen)").jqmData("fullscreen",true);a.mobile.fixedtoolbar.prototype.enhanceWithin(c.target)})})(jQuery);(function(a,c){if(/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1){var b=a.mobile.zoom,e,f,d,g,h;a(c).bind("orientationchange.iosorientationfix",b.enable).bind("devicemotion.iosorientationfix",function(a){e=a.originalEvent; +h=e.accelerationIncludingGravity;f=Math.abs(h.x);d=Math.abs(h.y);g=Math.abs(h.z);!c.orientation&&(f>7||(g>6&&d<8||g<8&&d>6)&&f>5)?b.enabled&&b.disable():b.enabled||b.enable()})}})(jQuery,this);(function(a,c){function b(){var b=a("."+a.mobile.activeBtnClass).first();h.css({top:a.support.scrollTop&&g.scrollTop()+g.height()/2||b.length&&b.offset().top||100})}function e(){var c=h.offset(),d=g.scrollTop(),f=a.mobile.getScreenHeight();if(c.top<d||c.top-d>f)h.addClass("ui-loader-fakefix"),b(),g.unbind("scroll", +e).bind("scroll",b)}function f(){d.removeClass("ui-mobile-rendering")}var d=a("html");a("head");var g=a(c);a(c.document).trigger("mobileinit");if(a.mobile.gradeA()){if(a.mobile.ajaxBlacklist)a.mobile.ajaxEnabled=false;d.addClass("ui-mobile ui-mobile-rendering");setTimeout(f,5E3);var h=a("<div class='ui-loader'><span class='ui-icon ui-icon-loading'></span><h1></h1></div>");a.extend(a.mobile,{showPageLoadingMsg:function(b,c,f){d.addClass("ui-loading");if(a.mobile.loadingMessage){var k=f||a.mobile.loadingMessageTextVisible; +b=b||a.mobile.loadingMessageTheme;h.attr("class","ui-loader ui-corner-all ui-body-"+(b||"a")+" ui-loader-"+(k?"verbose":"default")+(f?" ui-loader-textonly":"")).find("h1").text(c||a.mobile.loadingMessage).end().appendTo(a.mobile.pageContainer);e();g.bind("scroll",e)}},hidePageLoadingMsg:function(){d.removeClass("ui-loading");a.mobile.loadingMessage&&h.removeClass("ui-loader-fakefix");a(c).unbind("scroll",b);a(c).unbind("scroll",e)},initializePage:function(){var b=a(":jqmData(role='page'), :jqmData(role='dialog')"); +b.length||(b=a("body").wrapInner("<div data-"+a.mobile.ns+"role='page'></div>").children(0));b.each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)});a.mobile.firstPage=b.first();a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport");g.trigger("pagecontainercreate");a.mobile.showPageLoadingMsg();f();!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage, +{transition:"none",reverse:true,changeHash:false,fromHashChange:true}):g.trigger("hashchange",[true])}});a.mobile._registerInternalEvents();a(function(){c.scrollTo(0,1);a.mobile.defaultHomeScroll=!a.support.scrollTop||a(c).scrollTop()===1?0:1;a.fn.controlgroup&&a(k).bind("pagecreate create",function(b){a(":jqmData(role='controlgroup')",b.target).jqmEnhanceable().controlgroup({excludeInvisible:false})});a.mobile.autoInitializePage&&a.mobile.initializePage();g.load(a.mobile.silentScroll)})}})(jQuery, +this)});
--- a/OrthancExplorer/libs/jquery.mobile.structure-1.1.0.min.css Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;box-shadow:0 1px 1px -1px #fff;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.5em}.ui-page-footer-fixed{padding-bottom:3em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0;max-width:100%}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-mini{margin:.25em 5px}.ui-btn-inner{padding:.6em 20px;min-width:.75em;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1;width:100%}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left .ui-btn-inner .ui-icon,.ui-btn-icon-right .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=.0001);font-size:1px;border:0;text-indent:-9999px}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:0 0 .5em;zoom:1}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -6px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.ui-header .ui-field-contain-left,.ui-header .ui-field-contain-right{position:absolute;top:0;width:25%}.ui-header .ui-field-contain-left{left:1em}.ui-header .ui-field-contain-right{right:1em}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1;margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:97%;outline:0}.ui-header input.ui-input-text,.ui-footer input.ui-input-text{margin-left:1.25%;padding:.4em 1%;width:95.5%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em;width:97%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:48px}.ui-li-divider .ui-li-count,.ui-li-static .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file
--- a/OrthancExplorer/libs/jquery.mobile.theme-1.1.0.min.css Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f9f9f9,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus{outline:0}.ui-focus,.ui-btn:focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus{outline-width:1px;outline-style:dotted} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancExplorer/query-retrieve.js Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,294 @@ +function JavascriptDateToDicom(date) +{ + var s = date.toISOString(); + return s.substring(0, 4) + s.substring(5, 7) + s.substring(8, 10); +} + +function GenerateDicomDate(days) +{ + var today = new Date(); + var other = new Date(today); + other.setDate(today.getDate() + days); + return JavascriptDateToDicom(other); +} + + +$('#query-retrieve').live('pagebeforeshow', function() { + $.ajax({ + url: '../modalities', + dataType: 'json', + async: false, + cache: false, + success: function(modalities) { + var target = $('#qr-server'); + $('option', target).remove(); + + for (var i = 0; i < modalities.length; i++) { + var option = $('<option>').text(modalities[i]); + target.append(option); + } + + target.selectmenu('refresh'); + } + }); + + var target = $('#qr-date'); + $('option', target).remove(); + target.append($('<option>').attr('value', '*').text('Any date')); + target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today')); + target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday')); + target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days')); + target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months')); + target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year')); + target.selectmenu('refresh'); +}); + + +$('#qr-echo').live('click', function() { + var server = $('#qr-server').val(); + var message = 'Error: The C-Echo has failed!'; + + $.ajax({ + url: '../modalities/' + server + '/echo', + type: 'POST', + cache: false, + async: false, + success: function() { + message = 'The C-Echo has succeeded!'; + } + }); + + $('<div>').simpledialog2({ + mode: 'button', + headerText: 'Echo result', + headerClose: true, + buttonPrompt: message, + animate: false, + buttons : { + 'OK': { click: function () { } } + } + }); + + return false; +}); + + +$('#qr-submit').live('click', function() { + var query = { + 'Level' : 'Study', + 'Query' : { + 'AccessionNumber' : '*', + 'PatientBirthDate' : '*', + 'PatientID' : '*', + 'PatientName' : '*', + 'PatientSex' : '*', + 'SpecificCharacterSet' : 'ISO_IR 192', // UTF-8 + 'StudyDate' : $('#qr-date').val(), + 'StudyDescription' : '*' + } + }; + + var field = $('#qr-fields input:checked').val(); + query['Query'][field] = $('#qr-value').val().toUpperCase(); + + var modalities = ''; + $('#qr-modalities input:checked').each(function() { + var s = $(this).attr('name'); + if (modalities == '*') + modalities = s; + else + modalities += '\\' + s; + }); + + if (modalities.length > 0) { + query['Query']['ModalitiesInStudy'] = modalities; + } + + + var server = $('#qr-server').val(); + $.ajax({ + url: '../modalities/' + server + '/query', + type: 'POST', + data: JSON.stringify(query), + dataType: 'json', + async: false, + error: function() { + alert('Error during query (C-Find)'); + }, + success: function(result) { + window.location.assign('explorer.html#query-retrieve-2?server=' + server + '&uuid=' + result['ID']); + } + }); + + return false; +}); + + + +function Retrieve(url) +{ + $.ajax({ + url: '../system', + dataType: 'json', + async: false, + success: function(system) { + $('<div>').simpledialog2({ + mode: 'button', + headerText: 'Target', + headerClose: true, + buttonPrompt: 'Enter Application Entity Title (AET):', + buttonInput: true, + buttonInputDefault: system['DicomAet'], + buttons : { + 'OK': { + click: function () { + var aet = $.mobile.sdLastInput; + if (aet.length == 0) + aet = system['DicomAet']; + + $.ajax({ + url: url, + type: 'POST', + async: true, // Necessary to block UI + dataType: 'text', + data: aet, + beforeSend: function() { + $.blockUI({ message: $('#info-retrieve') }); + }, + complete: function(s) { + $.unblockUI(); + }, + error: function() { + alert('Error during retrieve'); + } + }); + } + } + } + }); + } + }); +} + + + + +$('#query-retrieve-2').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var uri = '../queries/' + $.mobile.pageData.uuid + '/answers'; + + $.ajax({ + url: uri, + dataType: 'json', + async: false, + success: function(answers) { + var target = $('#query-retrieve-2 ul'); + $('li', target).remove(); + + for (var i = 0; i < answers.length; i++) { + $.ajax({ + url: uri + '/' + answers[i] + '/content?simplify', + dataType: 'json', + async: false, + success: function(study) { + var series = '#query-retrieve-3?server=' + $.mobile.pageData.server + '&uuid=' + study['StudyInstanceUID']; + var info = $('<a>').attr('href', series).html( + ('<h3>{0} - {1}</h3>' + + '<p>Accession number: <b>{2}</b></p>' + + '<p>Birth date: <b>{3}</b></p>' + + '<p>Patient sex: <b>{4}</b></p>' + + '<p>Study description: <b>{5}</b></p>' + + '<p>Study date: <b>{6}</b></p>').format( + study['PatientID'], + study['PatientName'], + study['AccessionNumber'], + FormatDicomDate(study['PatientBirthDate']), + study['PatientSex'], + study['StudyDescription'], + FormatDicomDate(study['StudyDate']))); + + var studyUri = uri + '/' + answers[i] + '/retrieve'; + var retrieve = $('<a>').text('Retrieve').click(function() { + Retrieve(studyUri); + }); + + target.append($('<li>').append(info).append(retrieve)); + } + }); + } + + target.listview('refresh'); + } + }); + } +}); + + +$('#query-retrieve-3').live('pagebeforeshow', function() { + if ($.mobile.pageData) { + var query = { + 'Level' : 'Series', + 'Query' : { + 'Modality' : '*', + 'ProtocolName' : '*', + 'SeriesDescription' : '*', + 'SeriesInstanceUID' : '*', + 'StudyInstanceUID' : $.mobile.pageData.uuid + } + }; + + $.ajax({ + url: '../modalities/' + $.mobile.pageData.server + '/query', + type: 'POST', + data: JSON.stringify(query), + dataType: 'json', + async: false, + error: function() { + alert('Error during query (C-Find)'); + }, + success: function(answer) { + var uri = '../queries/' + answer['ID'] + '/answers'; + + $.ajax({ + url: uri, + dataType: 'json', + async: false, + success: function(answers) { + + var target = $('#query-retrieve-3 ul'); + $('li', target).remove(); + + for (var i = 0; i < answers.length; i++) { + $.ajax({ + url: uri + '/' + answers[i] + '/content?simplify', + dataType: 'json', + async: false, + success: function(series) { + var info = $('<a>').html( + ('<h3>{0}</h3>' + + '<p>Modality: <b>{1}</b></p>' + + '<p>Protocol name: <b>{2}</b></p>' + ).format( + series['SeriesDescription'], + series['Modality'], + series['ProtocolName'] + )); + + var seriesUri = uri + '/' + answers[i] + '/retrieve'; + var retrieve = $('<a>').text('Retrieve').click(function() { + Retrieve(seriesUri); + }); + + target.append($('<li>').append(info).append(retrieve)); + } + }); + } + + target.listview('refresh'); + } + }); + } + }); + } +});
--- a/OrthancServer/DatabaseWrapper.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DatabaseWrapper.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,10 +34,10 @@ #include "DatabaseWrapper.h" #include "../Core/DicomFormat/DicomArray.h" +#include "../Core/Logging.h" #include "../Core/Uuid.h" #include "EmbeddedResources.h" -#include <glog/logging.h> #include <stdio.h> #include <boost/lexical_cast.hpp> @@ -49,10 +49,10 @@ class SignalFileDeleted : public SQLite::IScalarFunction { private: - IServerIndexListener& listener_; + IDatabaseListener& listener_; public: - SignalFileDeleted(IServerIndexListener& listener) : + SignalFileDeleted(IDatabaseListener& listener) : listener_(listener) { } @@ -96,10 +96,10 @@ class SignalResourceDeleted : public SQLite::IScalarFunction { private: - IServerIndexListener& listener_; + IDatabaseListener& listener_; public: - SignalResourceDeleted(IServerIndexListener& listener) : + SignalResourceDeleted(IDatabaseListener& listener) : listener_(listener) { } @@ -742,14 +742,27 @@ } } - static void UpgradeDatabase(SQLite::Connection& db, - EmbeddedResources::FileResourceId script) + void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) { - std::string upgrade; - EmbeddedResources::GetFileResource(upgrade, script); - db.BeginTransaction(); - db.Execute(upgrade); - db.CommitTransaction(); + if (limit == 0) + { + target.clear(); + return; + } + + SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?"); + s.BindInt(0, resourceType); + s.BindInt64(1, limit); + s.BindInt64(2, since); + + target.clear(); + while (s.Step()) + { + target.push_back(s.ColumnString(0)); + } } @@ -784,52 +797,26 @@ } // Check the version of the database - std::string version; - if (!LookupGlobalProperty(version, GlobalProperty_DatabaseSchemaVersion)) + std::string tmp; + if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion)) { - version = "Unknown"; + tmp = "Unknown"; } bool ok = false; try { - LOG(INFO) << "Version of the Orthanc database: " << version; - unsigned int v = boost::lexical_cast<unsigned int>(version); - - /** - * History of the database versions: - * - Orthanc before Orthanc 0.3.0 (inclusive) had no version - * - Version 2: only Orthanc 0.3.1 - * - Version 3: from Orthanc 0.3.2 to Orthanc 0.7.2 (inclusive) - * - Version 4: from Orthanc 0.7.3 to Orthanc 0.8.4 (inclusive) - * - Version 5: from Orthanc 0.8.5 (inclusive) - **/ - - // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema - ok = (v == 3 || v == 4 || v == 5); - - if (v == 3) - { - LOG(WARNING) << "Upgrading database version from 3 to 4"; - UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); - v = 4; - } - - if (v == 4) - { - LOG(WARNING) << "Upgrading database version from 4 to 5"; - UpgradeDatabase(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); - v = 5; - } + LOG(INFO) << "Version of the Orthanc database: " << tmp; + version_ = boost::lexical_cast<unsigned int>(tmp); + ok = true; } catch (boost::bad_lexical_cast&) { - ok = false; } if (!ok) { - LOG(ERROR) << "Incompatible version of the Orthanc database: " << version; + LOG(ERROR) << "Incompatible version of the Orthanc database: " << tmp; throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); } @@ -837,7 +824,51 @@ db_.Register(signalRemainingAncestor_); } - void DatabaseWrapper::SetListener(IServerIndexListener& listener) + + static void ExecuteUpgradeScript(SQLite::Connection& db, + EmbeddedResources::FileResourceId script) + { + std::string upgrade; + EmbeddedResources::GetFileResource(upgrade, script); + db.BeginTransaction(); + db.Execute(upgrade); + db.CommitTransaction(); + } + + + void DatabaseWrapper::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + if (targetVersion != 5) + { + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); + } + + // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema + if (version_ != 3 && + version_ != 4 && + version_ != 5) + { + throw OrthancException(ErrorCode_IncompatibleDatabaseVersion); + } + + if (version_ == 3) + { + LOG(WARNING) << "Upgrading database version from 3 to 4"; + ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4); + version_ = 4; + } + + if (version_ == 4) + { + LOG(WARNING) << "Upgrading database version from 4 to 5"; + ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5); + version_ = 5; + } + } + + + void DatabaseWrapper::SetListener(IDatabaseListener& listener) { listener_ = &listener; db_.Register(new Internals::SignalFileDeleted(listener));
--- a/OrthancServer/DatabaseWrapper.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DatabaseWrapper.h Wed Sep 30 13:23:31 2015 +0200 @@ -52,9 +52,10 @@ class DatabaseWrapper : public IDatabaseWrapper { private: - IServerIndexListener* listener_; + IDatabaseListener* listener_; SQLite::Connection db_; Internals::SignalRemainingAncestor* signalRemainingAncestor_; + unsigned int version_; void Open(); @@ -75,7 +76,7 @@ DatabaseWrapper(); - virtual void SetListener(IServerIndexListener& listener); + virtual void SetListener(IDatabaseListener& listener); virtual void SetGlobalProperty(GlobalProperty property, const std::string& value); @@ -170,6 +171,11 @@ virtual void GetAllPublicIds(std::list<std::string>& target, ResourceType resourceType); + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit); + virtual bool SelectPatientToRecycle(int64_t& internalId); virtual bool SelectPatientToRecycle(int64_t& internalId, @@ -217,6 +223,13 @@ virtual void GetAllMetadata(std::map<MetadataType, std::string>& target, int64_t id); + virtual unsigned int GetDatabaseVersion() + { + return version_; + } + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea);
--- a/OrthancServer/DicomDirWriter.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomDirWriter.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -103,6 +103,7 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" +#include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "../Core/Uuid.h" @@ -118,7 +119,6 @@ #include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ #include <memory> -#include <glog/logging.h> namespace Orthanc {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomFindQuery.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,389 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersServer.h" +#include "DicomFindQuery.h" + +#include "FromDcmtkBridge.h" + +#include <boost/regex.hpp> + + +namespace Orthanc +{ + class DicomFindQuery::ValueConstraint : public DicomFindQuery::IConstraint + { + private: + bool isCaseSensitive_; + std::string expected_; + + public: + ValueConstraint(const std::string& value, + bool caseSensitive) : + isCaseSensitive_(caseSensitive), + expected_(value) + { + } + + const std::string& GetValue() const + { + return expected_; + } + + virtual bool IsExactConstraint() const + { + return isCaseSensitive_; + } + + virtual bool Apply(const std::string& value) const + { + if (isCaseSensitive_) + { + return expected_ == value; + } + else + { + std::string v, c; + Toolbox::ToLowerCase(v, value); + Toolbox::ToLowerCase(c, expected_); + return v == c; + } + } + }; + + + class DicomFindQuery::ListConstraint : public DicomFindQuery::IConstraint + { + private: + std::set<std::string> values_; + + public: + ListConstraint(const std::string& values) + { + std::vector<std::string> items; + Toolbox::TokenizeString(items, values, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + std::string lower; + Toolbox::ToLowerCase(lower, items[i]); + values_.insert(lower); + } + } + + virtual bool Apply(const std::string& value) const + { + std::string tmp; + Toolbox::ToLowerCase(tmp, value); + return values_.find(tmp) != values_.end(); + } + }; + + + class DicomFindQuery::RangeConstraint : public DicomFindQuery::IConstraint + { + private: + std::string lower_; + std::string upper_; + + public: + RangeConstraint(const std::string& range) + { + size_t separator = range.find('-'); + Toolbox::ToLowerCase(lower_, range.substr(0, separator)); + Toolbox::ToLowerCase(upper_, range.substr(separator + 1)); + } + + virtual bool Apply(const std::string& value) const + { + std::string v; + Toolbox::ToLowerCase(v, value); + + if (lower_.size() == 0 && + upper_.size() == 0) + { + return false; + } + + if (lower_.size() == 0) + { + return v <= upper_; + } + + if (upper_.size() == 0) + { + return v >= lower_; + } + + return (v >= lower_ && v <= upper_); + } + }; + + + class DicomFindQuery::WildcardConstraint : public DicomFindQuery::IConstraint + { + private: + boost::regex pattern_; + + public: + WildcardConstraint(const std::string& wildcard, + bool caseSensitive) + { + std::string re = Toolbox::WildcardToRegularExpression(wildcard); + + if (caseSensitive) + { + pattern_ = boost::regex(re); + } + else + { + pattern_ = boost::regex(re, boost::regex::icase /* case insensitive search */); + } + } + + virtual bool Apply(const std::string& value) const + { + return boost::regex_match(value, pattern_); + } + }; + + + void DicomFindQuery::PrepareMainDicomTags(ResourceType level) + { + std::set<DicomTag> tags; + DicomMap::GetMainDicomTags(tags, level); + + for (std::set<DicomTag>::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + mainDicomTags_[*it] = level; + } + } + + + DicomFindQuery::DicomFindQuery() : + level_(ResourceType_Patient), + filterJson_(false) + { + PrepareMainDicomTags(ResourceType_Patient); + PrepareMainDicomTags(ResourceType_Study); + PrepareMainDicomTags(ResourceType_Series); + PrepareMainDicomTags(ResourceType_Instance); + } + + + DicomFindQuery::~DicomFindQuery() + { + for (Constraints::iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + delete it->second; + } + } + + + + + void DicomFindQuery::AssignConstraint(const DicomTag& tag, + IConstraint* constraint) + { + Constraints::iterator it = constraints_.find(tag); + + if (it != constraints_.end()) + { + constraints_.erase(it); + } + + constraints_[tag] = constraint; + + MainDicomTags::const_iterator tmp = mainDicomTags_.find(tag); + if (tmp == mainDicomTags_.end()) + { + // The query depends upon a DICOM tag that is not a main tag + // from the point of view of Orthanc, we need to decode the + // JSON file on the disk. + filterJson_ = true; + } + else + { + filteredLevels_.insert(tmp->second); + } + } + + + void DicomFindQuery::SetConstraint(const DicomTag& tag, + const std::string& constraint, + bool caseSensitivePN) + { + ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag); + + bool sensitive = true; + if (vr == ValueRepresentation_PatientName) + { + sensitive = caseSensitivePN; + } + + // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained + // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html + + if ((vr == ValueRepresentation_Date || + vr == ValueRepresentation_DateTime || + vr == ValueRepresentation_Time) && + constraint.find('-') != std::string::npos) + { + /** + * Range matching is only defined for TM, DA and DT value + * representations. This code fixes issues 35 and 37. + * + * Reference: "Range matching is not defined for types of + * Attributes other than dates and times", DICOM PS 3.4, + * C.2.2.2.5 ("Range Matching"). + **/ + AssignConstraint(tag, new RangeConstraint(constraint)); + } + else if (constraint.find('\\') != std::string::npos) + { + AssignConstraint(tag, new ListConstraint(constraint)); + } + else if (constraint.find('*') != std::string::npos || + constraint.find('?') != std::string::npos) + { + AssignConstraint(tag, new WildcardConstraint(constraint, sensitive)); + } + else + { + /** + * 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 + * + * "Except for Attributes with a PN Value Representation, only + * entities with values which match exactly the value specified in the + * request shall match. This matching is case-sensitive, i.e., + * sensitive to the exact encoding of the key attribute value in + * character sets where a letter may have multiple encodings (e.g., + * based on its case, its position in a word, or whether it is + * accented) + * + * For Attributes with a PN Value Representation (e.g., Patient Name + * (0010,0010)), an application may perform literal matching that is + * either case-sensitive, or that is insensitive to some or all + * aspects of case, position, accent, or other character encoding + * variants." + * + * (0008,0018) UI SOPInstanceUID => Case-sensitive + * (0008,0050) SH AccessionNumber => Case-sensitive + * (0010,0020) LO PatientID => Case-sensitive + * (0020,000D) UI StudyInstanceUID => Case-sensitive + * (0020,000E) UI SeriesInstanceUID => Case-sensitive + **/ + + AssignConstraint(tag, new ValueConstraint(constraint, sensitive)); + } + } + + + bool DicomFindQuery::RestrictIdentifier(std::string& value, + DicomTag identifier) const + { + Constraints::const_iterator it = constraints_.find(identifier); + if (it == constraints_.end() || + !it->second->IsExactConstraint()) + { + return false; + } + else + { + value = dynamic_cast<ValueConstraint*>(it->second)->GetValue(); + return true; + } + } + + bool DicomFindQuery::HasMainDicomTagsFilter(ResourceType level) const + { + return filteredLevels_.find(level) != filteredLevels_.end(); + } + + bool DicomFindQuery::FilterMainDicomTags(const std::string& resourceId, + ResourceType level, + const DicomMap& mainTags) const + { + std::set<DicomTag> tags; + mainTags.GetTags(tags); + + for (std::set<DicomTag>::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + Constraints::const_iterator constraint = constraints_.find(*it); + if (constraint != constraints_.end() && + !constraint->second->Apply(mainTags.GetValue(*it).AsString())) + { + return false; + } + } + + return true; + } + + bool DicomFindQuery::HasInstanceFilter() const + { + return filterJson_; + } + + bool DicomFindQuery::FilterInstance(const std::string& instanceId, + const Json::Value& content) const + { + for (Constraints::const_iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + std::string tag = it->first.Format(); + std::string value; + if (content.isMember(tag)) + { + value = content.get(tag, Json::arrayValue).get("Value", "").asString(); + } + + if (!it->second->Apply(value)) + { + return false; + } + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomFindQuery.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,111 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ResourceFinder.h" + +namespace Orthanc +{ + class DicomFindQuery : public ResourceFinder::IQuery + { + private: + class IConstraint : public boost::noncopyable + { + public: + virtual ~IConstraint() + { + } + + virtual bool IsExactConstraint() const + { + return false; + } + + virtual bool Apply(const std::string& value) const = 0; + }; + + + class ValueConstraint; + class RangeConstraint; + class ListConstraint; + class WildcardConstraint; + + typedef std::map<DicomTag, IConstraint*> Constraints; + typedef std::map<DicomTag, ResourceType> MainDicomTags; + + MainDicomTags mainDicomTags_; + ResourceType level_; + bool filterJson_; + Constraints constraints_; + std::set<ResourceType> filteredLevels_; + + void AssignConstraint(const DicomTag& tag, + IConstraint* constraint); + + void PrepareMainDicomTags(ResourceType level); + + + public: + DicomFindQuery(); + + virtual ~DicomFindQuery(); + + void SetLevel(ResourceType level) + { + level_ = level; + } + + virtual ResourceType GetLevel() const + { + return level_; + } + + void SetConstraint(const DicomTag& tag, + const std::string& constraint, + bool caseSensitivePN); + + virtual bool RestrictIdentifier(std::string& value, + DicomTag identifier) const; + + virtual bool HasMainDicomTagsFilter(ResourceType level) const; + + virtual bool FilterMainDicomTags(const std::string& resourceId, + ResourceType level, + const DicomMap& mainTags) const; + + virtual bool HasInstanceFilter() const; + + virtual bool FilterInstance(const std::string& instanceId, + const Json::Value& content) const; + }; +}
--- a/OrthancServer/DicomInstanceToStore.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomInstanceToStore.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,12 +30,13 @@ **/ +#include "PrecompiledHeadersServer.h" #include "DicomInstanceToStore.h" #include "FromDcmtkBridge.h" +#include "../Core/Logging.h" #include <dcmtk/dcmdata/dcfilefo.h> -#include <glog/logging.h> namespace Orthanc @@ -171,4 +172,99 @@ return json_.GetConstContent(); } + + + + void DicomInstanceToStore::GetOriginInformation(Json::Value& result) const + { + result = Json::objectValue; + result["RequestOrigin"] = EnumerationToString(origin_); + + switch (origin_) + { + case RequestOrigin_Unknown: + { + // None of the methods "SetDicomProtocolOrigin()", "SetHttpOrigin()", + // "SetLuaOrigin()" or "SetPluginsOrigin()" was called! + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + case RequestOrigin_DicomProtocol: + { + result["RemoteIp"] = remoteIp_; + result["RemoteAet"] = dicomRemoteAet_; + result["CalledAet"] = dicomCalledAet_; + break; + } + + case RequestOrigin_Http: + { + result["RemoteIp"] = remoteIp_; + result["Username"] = httpUsername_; + break; + } + + case RequestOrigin_Lua: + case RequestOrigin_Plugins: + { + // No additional information available for these kinds of requests + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + void DicomInstanceToStore::SetDicomProtocolOrigin(const char* remoteIp, + const char* remoteAet, + const char* calledAet) + { + origin_ = RequestOrigin_DicomProtocol; + remoteIp_ = remoteIp; + dicomRemoteAet_ = remoteAet; + dicomCalledAet_ = calledAet; + } + + void DicomInstanceToStore::SetRestOrigin(const RestApiCall& call) + { + origin_ = call.GetRequestOrigin(); + + if (origin_ == RequestOrigin_Http) + { + remoteIp_ = call.GetRemoteIp(); + httpUsername_ = call.GetUsername(); + } + } + + void DicomInstanceToStore::SetHttpOrigin(const char* remoteIp, + const char* username) + { + origin_ = RequestOrigin_Http; + remoteIp_ = remoteIp; + httpUsername_ = username; + } + + void DicomInstanceToStore::SetLuaOrigin() + { + origin_ = RequestOrigin_Lua; + } + + void DicomInstanceToStore::SetPluginsOrigin() + { + origin_ = RequestOrigin_Plugins; + } + + const char* DicomInstanceToStore::GetRemoteAet() const + { + if (origin_ == RequestOrigin_DicomProtocol) + { + return dicomRemoteAet_.c_str(); + } + else + { + return ""; + } + } }
--- a/OrthancServer/DicomInstanceToStore.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomInstanceToStore.h Wed Sep 30 13:23:31 2015 +0200 @@ -35,6 +35,7 @@ #include "ParsedDicomFile.h" #include "ServerIndex.h" #include "../Core/OrthancException.h" +#include "../Core/RestApi/RestApiCall.h" namespace Orthanc { @@ -143,13 +144,35 @@ SmartContainer<DicomMap> summary_; SmartContainer<Json::Value> json_; - std::string remoteAet_; - std::string calledAet_; + RequestOrigin origin_; + std::string remoteIp_; + std::string dicomRemoteAet_; + std::string dicomCalledAet_; + std::string httpUsername_; ServerIndex::MetadataMap metadata_; void ComputeMissingInformation(); public: + DicomInstanceToStore() : origin_(RequestOrigin_Unknown) + { + } + + void SetDicomProtocolOrigin(const char* remoteIp, + const char* remoteAet, + const char* calledAet); + + void SetRestOrigin(const RestApiCall& call); + + void SetHttpOrigin(const char* remoteIp, + const char* username); + + void SetLuaOrigin(); + + void SetPluginsOrigin(); + + const char* GetRemoteAet() const; + void SetBuffer(const std::string& dicom) { buffer_.SetConstReference(dicom); @@ -170,26 +193,6 @@ json_.SetConstReference(json); } - const std::string& GetRemoteAet() const - { - return remoteAet_; - } - - void SetRemoteAet(const std::string& aet) - { - remoteAet_ = aet; - } - - const std::string& GetCalledAet() const - { - return calledAet_; - } - - void SetCalledAet(const std::string& aet) - { - calledAet_ = aet; - } - void AddMetadata(ResourceType level, MetadataType metadata, const std::string& value); @@ -211,5 +214,7 @@ const DicomMap& GetSummary(); const Json::Value& GetJson(); + + void GetOriginInformation(Json::Value& result) const; }; }
--- a/OrthancServer/DicomModification.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomModification.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,11 +33,11 @@ #include "PrecompiledHeadersServer.h" #include "DicomModification.h" +#include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "FromDcmtkBridge.h" #include <memory> // For std::auto_ptr -#include <glog/logging.h> static const std::string ORTHANC_DEIDENTIFICATION_METHOD = "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1";
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -53,14 +53,15 @@ } } - void DicomFindAnswers::ToJson(Json::Value& target) const + void DicomFindAnswers::ToJson(Json::Value& target, + bool simplify) const { target = Json::arrayValue; for (size_t i = 0; i < GetSize(); i++) { Json::Value answer(Json::objectValue); - FromDcmtkBridge::ToJson(answer, GetAnswer(i)); + FromDcmtkBridge::ToJson(answer, GetAnswer(i), simplify); target.append(answer); } }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h Wed Sep 30 13:23:31 2015 +0200 @@ -69,6 +69,7 @@ return *items_.at(index); } - void ToJson(Json::Value& target) const; + void ToJson(Json::Value& target, + bool simplify) const; }; }
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,6 +33,7 @@ #include "../PrecompiledHeadersServer.h" #include "DicomServer.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" #include "../../Core/Toolbox.h" #include "../../Core/Uuid.h" @@ -41,9 +42,6 @@ #include "EmbeddedResources.h" #include <boost/thread.hpp> -#include <boost/filesystem.hpp> -#include <dcmtk/dcmdata/dcdict.h> -#include <glog/logging.h> #if defined(__linux) #include <cstdlib> @@ -60,99 +58,6 @@ }; -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, - EmbeddedResources::FileResourceId resource) - { - Toolbox::TemporaryFile tmp; - - FILE* fp = fopen(tmp.GetPath().c_str(), "wb"); - fwrite(EmbeddedResources::GetFileResourceBuffer(resource), - EmbeddedResources::GetFileResourceSize(resource), 1, fp); - fclose(fp); - - if (!dictionary.loadDictionary(tmp.GetPath().c_str())) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#else - static void LoadExternalDictionary(DcmDataDictionary& dictionary, - const std::string& directory, - const std::string& filename) - { - boost::filesystem::path p = directory; - p = p / filename; - - LOG(WARNING) << "Loading the external DICOM dictionary " << p; - - if (!dictionary.loadDictionary(p.string().c_str())) - { - throw OrthancException(ErrorCode_InternalError); - } - } - -#endif - - - void DicomServer::InitializeDictionary() - { - /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ - dcmDisableGethostbyaddr.set(OFTrue); - - dcmDataDict.clear(); - DcmDataDictionary& d = dcmDataDict.wrlock(); - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - LOG(WARNING) << "Loading the embedded dictionaries"; - /** - * Do not load DICONDE dictionary, it breaks the other tags. The - * command "strace storescu 2>&1 |grep dic" shows that DICONDE - * dictionary is not loaded by storescu. - **/ - //LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICONDE); - - LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICOM); - LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); - -#elif defined(__linux) || defined(__FreeBSD_kernel__) - std::string path = DCMTK_DICTIONARY_DIR; - - const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); - if (env != NULL) - { - path = std::string(env); - } - - LoadExternalDictionary(d, path, "dicom.dic"); - LoadExternalDictionary(d, path, "private.dic"); - -#else -#error Support your platform here -#endif - - dcmDataDict.unlock(); - - /* make sure data dictionary is loaded */ - if (!dcmDataDict.isDictionaryLoaded()) - { - LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; - throw OrthancException(ErrorCode_InternalError); - } - - { - // Test the dictionary with a simple DICOM tag - DcmTag key(0x0010, 0x1030); // This is PatientWeight - if (key.getEVR() != EVR_DS) - { - LOG(ERROR) << "The DICOM dictionary has not been correctly read"; - throw OrthancException(ErrorCode_InternalError); - } - } - } - - void DicomServer::ServerThread(DicomServer* server) { /* initialize network, i.e. create an instance of T_ASC_Network*. */ @@ -162,7 +67,7 @@ if (cond.bad()) { LOG(ERROR) << "cannot create network: " << cond.text(); - throw OrthancException("Cannot create network"); + throw OrthancException(ErrorCode_DicomPortInUse); } LOG(INFO) << "DICOM server started"; @@ -223,7 +128,11 @@ DicomServer::~DicomServer() { - Stop(); + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } } void DicomServer::SetPortNumber(uint16_t port) @@ -275,12 +184,12 @@ { if (aet.size() == 0) { - throw OrthancException("Too short AET"); + throw OrthancException(ErrorCode_BadApplicationEntityTitle); } if (aet.size() > 16) { - throw OrthancException("AET must be shorter than 16 characters"); + throw OrthancException(ErrorCode_BadApplicationEntityTitle); } for (size_t i = 0; i < aet.size(); i++) @@ -323,7 +232,7 @@ } else { - throw OrthancException("No C-FIND request handler factory"); + throw OrthancException(ErrorCode_NoCFindHandler); } } @@ -346,7 +255,7 @@ } else { - throw OrthancException("No C-MOVE request handler factory"); + throw OrthancException(ErrorCode_NoCMoveHandler); } } @@ -369,7 +278,7 @@ } else { - throw OrthancException("No C-STORE request handler factory"); + throw OrthancException(ErrorCode_NoCStoreHandler); } } @@ -392,7 +301,7 @@ } else { - throw OrthancException("No application entity filter"); + throw OrthancException(ErrorCode_NoApplicationEntityFilter); } } @@ -409,16 +318,23 @@ } } + void DicomServer::Stop() { - continue_ = false; + if (continue_) + { + continue_ = false; - if (pimpl_->thread_.joinable()) - { - pimpl_->thread_.join(); + if (pimpl_->thread_.joinable()) + { + pimpl_->thread_.join(); + } + + bagOfDispatchers_.Finalize(); } } + bool DicomServer::IsMyAETitle(const std::string& aet) const { if (!HasCalledApplicationEntityTitleCheck())
--- a/OrthancServer/DicomProtocol/DicomServer.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomServer.h Wed Sep 30 13:23:31 2015 +0200 @@ -66,8 +66,6 @@ static void ServerThread(DicomServer* server); public: - static void InitializeDictionary(); - DicomServer(); ~DicomServer();
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -81,9 +81,11 @@ #include "../PrecompiledHeadersServer.h" #include "DicomUserConnection.h" +#include "../../Core/DicomFormat/DicomArray.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" +#include "../FromDcmtkBridge.h" #include "../ToDcmtkBridge.h" -#include "../FromDcmtkBridge.h" #include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcistrmf.h> @@ -92,8 +94,6 @@ #include <dcmtk/dcmnet/diutil.h> #include <set> -#include <glog/logging.h> - #ifdef _WIN32 @@ -299,7 +299,7 @@ DIC_UI sopInstance; if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) { - throw OrthancException("DicomUserConnection: Unable to find the SOP class and instance"); + throw OrthancException(ErrorCode_NoSopClassOrInstance); } // Figure out which of the accepted presentation contexts should be used @@ -309,8 +309,7 @@ const char *modalityName = dcmSOPClassUIDToModality(sopClass); if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); if (!modalityName) modalityName = "unknown SOP class"; - throw OrthancException("DicomUserConnection: No presentation context for modality " + - std::string(modalityName)); + throw OrthancException(ErrorCode_NoPresentationContext); } // Prepare the transmission of data @@ -337,6 +336,16 @@ } + namespace + { + struct FindPayload + { + DicomFindAnswers* answers; + std::string level; + }; + } + + static void FindCallback( /* in */ void *callbackData, @@ -346,73 +355,144 @@ DcmDataset *responseIdentifiers /* pending response identifiers */ ) { - DicomFindAnswers& answers = *reinterpret_cast<DicomFindAnswers*>(callbackData); + FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); if (responseIdentifiers != NULL) { DicomMap m; FromDcmtkBridge::Convert(m, *responseIdentifiers); - answers.Add(m); + + if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level); + } + + payload.answers->Add(m); + } + } + + + static void CheckFindQuery(ResourceType level, + const DicomMap& fields) + { + std::set<DicomTag> allowedTags; + + // WARNING: Do not add "break" or reorder items in this switch-case! + switch (level) + { + case ResourceType_Instance: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); + + case ResourceType_Series: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); + + case ResourceType_Study: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); + + case ResourceType_Patient: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (level == ResourceType_Study) + { + allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); + } + + allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); + + DicomArray query(fields); + for (size_t i = 0; i < query.GetSize(); i++) + { + const DicomTag& tag = query.GetElement(i).GetTag(); + if (allowedTags.find(tag) == allowedTags.end()) + { + LOG(ERROR) << "Tag not allowed for this C-Find level: " << tag; + throw OrthancException(ErrorCode_BadRequest); + } } } + + static DcmDataset* ConvertQueryFields(const DicomMap& fields, + ModalityManufacturer manufacturer) + { + switch (manufacturer) + { + case ModalityManufacturer_SyngoVia: + { + std::auto_ptr<DicomMap> fix(fields.Clone()); + + // This issue for Syngo.Via and its solution was reported by + // Emsy Chan by private mail on June 17th, 2015. + std::set<DicomTag> tags; + fix->GetTags(tags); + + for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + if (FromDcmtkBridge::GetValueRepresentation(*it) == ValueRepresentation_Date) + { + // Replace a "*" query by an empty query ("") for "date" + // value representations. Necessary to search over dates + // in Syngo.Via. + const DicomValue* value = fix->TestAndGetValue(*it); + + if (value != NULL && + value->AsString() == "*") + { + fix->SetValue(*it, ""); + } + } + } + + return ToDcmtkBridge::Convert(*fix); + } + + default: + return ToDcmtkBridge::Convert(fields); + } + } + + void DicomUserConnection::Find(DicomFindAnswers& result, - FindRootModel model, + ResourceType level, const DicomMap& fields) { + CheckFindQuery(level, fields); + CheckIsOpen(); + FindPayload payload; + payload.answers = &result; + + std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_)); + const char* sopClass; - std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); - switch (model) + switch (level) { - case FindRootModel_Patient: + case ResourceType_Patient: + payload.level = "PATIENT"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - - // Patient ID - if (!fields.HasTag(0x0010, 0x0020)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); - break; - case FindRootModel_Study: + case ResourceType_Study: + payload.level = "STUDY"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - break; - case FindRootModel_Series: + case ResourceType_Series: + payload.level = "SERIES"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); - - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - break; - case FindRootModel_Instance: + case ResourceType_Instance: + payload.level = "INSTANCE"; if (manufacturer_ == ModalityManufacturer_ClearCanvas || manufacturer_ == ModalityManufacturer_Dcm4Chee) { @@ -427,7 +507,27 @@ } sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Add the expected tags for this query level. + // WARNING: Do not reorder or add "break" in this switch-case! + switch (level) + { + case ResourceType_Instance: + // SOP Instance UID + if (!fields.HasTag(0x0008, 0x0018)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); + + case ResourceType_Series: + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); + + case ResourceType_Study: // Accession number if (!fields.HasTag(0x0008, 0x0050)) DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), ""); @@ -436,13 +536,10 @@ if (!fields.HasTag(0x0020, 0x000d)) DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), ""); - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), ""); - - // SOP Instance UID - if (!fields.HasTag(0x0008, 0x0018)) - DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), ""); + case ResourceType_Patient: + // Patient ID + if (!fields.HasTag(0x0010, 0x0020)) + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), ""); break; @@ -454,7 +551,7 @@ int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); if (presID == 0) { - throw OrthancException("DicomUserConnection: The C-FIND command is not supported by the remote AET"); + throw OrthancException(ErrorCode_DicomFindUnavailable); } T_DIMSE_C_FindRQ request; @@ -467,7 +564,7 @@ T_DIMSE_C_FindRSP response; DcmDataset* statusDetail = NULL; OFCondition cond = DIMSE_findUser(pimpl_->assoc_, presID, &request, dataset.get(), - FindCallback, &result, + FindCallback, &payload, /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, &response, &statusDetail); @@ -481,72 +578,53 @@ } - void DicomUserConnection::FindPatient(DicomFindAnswers& result, - const DicomMap& fields) - { - // Only keep the filters from "fields" that are related to the patient - DicomMap s; - fields.ExtractPatientInformation(s); - Find(result, FindRootModel_Patient, s); - } - - void DicomUserConnection::FindStudy(DicomFindAnswers& result, - const DicomMap& fields) - { - // Only keep the filters from "fields" that are related to the study - DicomMap s; - fields.ExtractStudyInformation(s); - - 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); - } - - void DicomUserConnection::FindSeries(DicomFindAnswers& result, - const DicomMap& fields) - { - // Only keep the filters from "fields" that are related to the series - DicomMap s; - fields.ExtractSeriesInformation(s); - - s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); - s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); - s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); - - Find(result, FindRootModel_Series, s); - } - - void DicomUserConnection::FindInstance(DicomFindAnswers& result, + void DicomUserConnection::MoveInternal(const std::string& targetAet, + ResourceType level, const DicomMap& fields) { - // Only keep the filters from "fields" that are related to the instance - DicomMap s; - fields.ExtractInstanceInformation(s); - - s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); - s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); - s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); - s.CopyTagIfExists(fields, DICOM_TAG_SERIES_INSTANCE_UID); - - Find(result, FindRootModel_Instance, s); - } - - - void DicomUserConnection::Move(const std::string& targetAet, - const DicomMap& fields) - { CheckIsOpen(); + std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_)); + const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields)); + switch (level) + { + case ResourceType_Patient: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); + break; + + case ResourceType_Study: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); + break; + + case ResourceType_Series: + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); + break; + + case ResourceType_Instance: + 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 + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } // Figure out which of the accepted presentation contexts should be used int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); if (presID == 0) { - throw OrthancException("DicomUserConnection: The C-MOVE command is not supported by the remote AET"); + throw OrthancException(ErrorCode_DicomMoveUnavailable); } T_DIMSE_C_MoveRQ request; @@ -640,7 +718,7 @@ } - void DicomUserConnection::Connect(const RemoteModalityParameters& parameters) + void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) { SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); SetRemoteHost(parameters.GetHost()); @@ -697,7 +775,7 @@ { if (host.size() > HOST_NAME_MAX - 10) { - throw OrthancException("Remote host name is too long"); + throw OrthancException(ErrorCode_ParameterOutOfRange); } Close(); @@ -758,7 +836,7 @@ if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) { - throw OrthancException("DicomUserConnection: No Acceptable Presentation Contexts"); + throw OrthancException(ErrorCode_NoPresentationContext); } } @@ -830,33 +908,86 @@ } - void DicomUserConnection::MoveSeries(const std::string& targetAet, - const DicomMap& findResult) + static void TestAndCopyTag(DicomMap& result, + const DicomMap& source, + const DicomTag& tag) + { + if (!source.HasTag(tag)) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result.SetValue(tag, source.GetValue(tag)); + } + } + + + void DicomUserConnection::Move(const std::string& targetAet, + const DicomMap& findResult) { - DicomMap simplified; - simplified.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, findResult.GetValue(DICOM_TAG_STUDY_INSTANCE_UID)); - simplified.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SERIES_INSTANCE_UID)); - Move(targetAet, simplified); + if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + throw OrthancException(ErrorCode_InternalError); + } + + const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).AsString(); + ResourceType level = StringToResourceType(tmp.c_str()); + + DicomMap move; + switch (level) + { + case ResourceType_Patient: + TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); + break; + + case ResourceType_Study: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + break; + + case ResourceType_Instance: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + MoveInternal(targetAet, level, move); + } + + + void DicomUserConnection::MovePatient(const std::string& targetAet, + const std::string& patientId) + { + DicomMap query; + query.SetValue(DICOM_TAG_PATIENT_ID, patientId); + MoveInternal(targetAet, ResourceType_Patient, query); + } + + void DicomUserConnection::MoveStudy(const std::string& targetAet, + const std::string& studyUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); + MoveInternal(targetAet, ResourceType_Study, query); } void DicomUserConnection::MoveSeries(const std::string& targetAet, const std::string& studyUid, const std::string& seriesUid) { - DicomMap map; - map.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); - map.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - Move(targetAet, map); - } - - void DicomUserConnection::MoveInstance(const std::string& targetAet, - const DicomMap& findResult) - { - DicomMap simplified; - simplified.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, findResult.GetValue(DICOM_TAG_STUDY_INSTANCE_UID)); - simplified.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SERIES_INSTANCE_UID)); - simplified.SetValue(DICOM_TAG_SOP_INSTANCE_UID, findResult.GetValue(DICOM_TAG_SOP_INSTANCE_UID)); - Move(targetAet, simplified); + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); + MoveInternal(targetAet, ResourceType_Series, query); } void DicomUserConnection::MoveInstance(const std::string& targetAet, @@ -864,11 +995,11 @@ const std::string& seriesUid, const std::string& instanceUid) { - DicomMap map; - map.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); - map.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); - map.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid); - Move(targetAet, map); + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid); + query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid); + MoveInternal(targetAet, ResourceType_Instance, query); }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Wed Sep 30 13:23:31 2015 +0200 @@ -46,14 +46,6 @@ class DicomUserConnection : public boost::noncopyable { private: - enum FindRootModel - { - FindRootModel_Patient, - FindRootModel_Study, - FindRootModel_Series, - FindRootModel_Instance - }; - struct PImpl; boost::shared_ptr<PImpl> pimpl_; @@ -72,12 +64,9 @@ void SetupPresentationContexts(const std::string& preferredTransferSyntax); - void Find(DicomFindAnswers& result, - FindRootModel model, - const DicomMap& fields); - - void Move(const std::string& targetAet, - const DicomMap& fields); + void MoveInternal(const std::string& targetAet, + ResourceType level, + const DicomMap& fields); void ResetStorageSOPClasses(); @@ -88,7 +77,7 @@ ~DicomUserConnection(); - void Connect(const RemoteModalityParameters& parameters); + void SetRemoteModality(const RemoteModalityParameters& parameters); void SetLocalApplicationEntityTitle(const std::string& aet); @@ -150,29 +139,24 @@ void StoreFile(const std::string& path); - void FindPatient(DicomFindAnswers& result, - const DicomMap& fields); - - void FindStudy(DicomFindAnswers& result, - const DicomMap& fields); + void Find(DicomFindAnswers& result, + ResourceType level, + const DicomMap& fields); - void FindSeries(DicomFindAnswers& result, - const DicomMap& fields); + void Move(const std::string& targetAet, + const DicomMap& findResult); - void FindInstance(DicomFindAnswers& result, - const DicomMap& fields); + void MovePatient(const std::string& targetAet, + const std::string& patientId); - void MoveSeries(const std::string& targetAet, - const DicomMap& findResult); + void MoveStudy(const std::string& targetAet, + const std::string& studyUid); void MoveSeries(const std::string& targetAet, const std::string& studyUid, const std::string& seriesUid); void MoveInstance(const std::string& targetAet, - const DicomMap& findResult); - - void MoveInstance(const std::string& targetAet, const std::string& studyUid, const std::string& seriesUid, const std::string& instanceUid);
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -55,6 +55,7 @@ **/ virtual bool Handle(DicomFindAnswers& answers, const DicomMap& input, - const std::string& callingAETitle) = 0; + const std::string& remoteIp, + const std::string& remoteAet) = 0; }; }
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -68,7 +68,9 @@ } virtual IMoveRequestIterator* Handle(const std::string& target, - const DicomMap& input) = 0; + const DicomMap& input, + const std::string& remoteIp, + const std::string& remoteAet) = 0; }; }
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -50,6 +50,7 @@ virtual void Handle(const std::string& dicomFile, const DicomMap& dicomSummary, const Json::Value& dicomJson, + const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) = 0; };
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -48,15 +48,17 @@ { } - void RemoteModalityParameters::SetPort(int port) + RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer) { - if (port <= 0 || port >= 65535) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } + SetApplicationEntityTitle(aet); + SetHost(host); + SetPort(port); + SetManufacturer(manufacturer); + } - port_ = port; - } void RemoteModalityParameters::FromJson(const Json::Value& modality) { @@ -72,13 +74,20 @@ const Json::Value& portValue = modality.get(2u, ""); try { - SetPort(portValue.asInt()); + int tmp = portValue.asInt(); + + if (tmp <= 0 || tmp >= 65535) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + SetPort(static_cast<uint16_t>(tmp)); } catch (std::runtime_error /* error inside JsonCpp */) { try { - SetPort(boost::lexical_cast<int>(portValue.asString())); + SetPort(boost::lexical_cast<uint16_t>(portValue.asString())); } catch (boost::bad_lexical_cast) {
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,6 +34,7 @@ #include "../ServerEnumerations.h" +#include <stdint.h> #include <string> #include <json/json.h> @@ -41,17 +42,20 @@ { class RemoteModalityParameters { - // TODO Use the flyweight pattern for this class - private: std::string aet_; std::string host_; - int port_; + uint16_t port_; ModalityManufacturer manufacturer_; public: RemoteModalityParameters(); + RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer); + const std::string& GetApplicationEntityTitle() const { return aet_; @@ -72,12 +76,15 @@ host_ = host; } - int GetPort() const + uint16_t GetPort() const { return port_; } - void SetPort(int port); + void SetPort(uint16_t port) + { + port_ = port; + } ModalityManufacturer GetManufacturer() const {
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,10 +33,9 @@ #include "../PrecompiledHeadersServer.h" #include "ReusableDicomUserConnection.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" -#include <glog/logging.h> - namespace Orthanc { static boost::posix_time::ptime Now() @@ -44,16 +43,15 @@ return boost::posix_time::microsec_clock::local_time(); } - void ReusableDicomUserConnection::Open(const std::string& remoteAet, - const std::string& address, - int port, - ModalityManufacturer manufacturer) + void ReusableDicomUserConnection::Open(const std::string& localAet, + const RemoteModalityParameters& remote) { if (connection_ != NULL && - connection_->GetRemoteApplicationEntityTitle() == remoteAet && - connection_->GetRemoteHost() == address && - connection_->GetRemotePort() == port && - connection_->GetRemoteManufacturer() == manufacturer) + connection_->GetLocalApplicationEntityTitle() == localAet && + connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && + connection_->GetRemoteHost() == remote.GetHost() && + connection_->GetRemotePort() == remote.GetPort() && + connection_->GetRemoteManufacturer() == remote.GetManufacturer()) { // The current connection can be reused LOG(INFO) << "Reusing the previous SCU connection"; @@ -63,11 +61,8 @@ Close(); connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet_); - connection_->SetRemoteApplicationEntityTitle(remoteAet); - connection_->SetRemoteHost(address); - connection_->SetRemotePort(port); - connection_->SetRemoteManufacturer(manufacturer); + connection_->SetLocalApplicationEntityTitle(localAet); + connection_->SetRemoteModality(remote); connection_->Open(); } @@ -103,24 +98,13 @@ } } - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& aet, - const std::string& address, - int port, - ModalityManufacturer manufacturer) : - ::Orthanc::Locker(that) - { - that.Open(aet, address, port, manufacturer); - connection_ = that.connection_; - } - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, + const std::string& localAet, const RemoteModalityParameters& remote) : ::Orthanc::Locker(that) { - that.Open(remote.GetApplicationEntityTitle(), remote.GetHost(), - remote.GetPort(), remote.GetManufacturer()); + that.Open(localAet, remote); connection_ = that.connection_; } @@ -137,8 +121,7 @@ ReusableDicomUserConnection::ReusableDicomUserConnection() : connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)), // By default, close connection after 5 seconds - localAet_("ORTHANC") + timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds { lastUse_ = Now(); continue_ = true; @@ -147,9 +130,11 @@ ReusableDicomUserConnection::~ReusableDicomUserConnection() { - continue_ = false; - closeThread_.join(); - Close(); + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Finalize(); + } } void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) @@ -164,13 +149,6 @@ timeBeforeClose_ = boost::posix_time::milliseconds(ms); } - void ReusableDicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) - { - boost::mutex::scoped_lock lock(mutex_); - Close(); - localAet_ = aet; - } - void ReusableDicomUserConnection::Lock() { mutex_.lock(); @@ -189,5 +167,21 @@ lastUse_ = Now(); mutex_.unlock(); } + + + void ReusableDicomUserConnection::Finalize() + { + if (continue_) + { + continue_ = false; + + if (closeThread_.joinable()) + { + closeThread_.join(); + } + + Close(); + } + } }
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Wed Sep 30 13:23:31 2015 +0200 @@ -49,12 +49,9 @@ boost::posix_time::time_duration timeBeforeClose_; boost::posix_time::ptime lastUse_; boost::thread closeThread_; - std::string localAet_; - void Open(const std::string& remoteAet, - const std::string& address, - int port, - ModalityManufacturer manufacturer); + void Open(const std::string& localAet, + const RemoteModalityParameters& remote); void Close(); @@ -73,14 +70,9 @@ public: Locker(ReusableDicomUserConnection& that, + const std::string& localAet, const RemoteModalityParameters& remote); - Locker(ReusableDicomUserConnection& that, - const std::string& aet, - const std::string& address, - int port, - ModalityManufacturer manufacturer); - DicomUserConnection& GetConnection(); }; @@ -88,16 +80,9 @@ virtual ~ReusableDicomUserConnection(); - uint64_t GetMillisecondsBeforeClose() const - { - return static_cast<uint64_t>(timeBeforeClose_.total_milliseconds()); - } - void SetMillisecondsBeforeClose(uint64_t ms); - const std::string& GetLocalApplicationEntityTitle() const; - - void SetLocalApplicationEntityTitle(const std::string& aet); + void Finalize(); }; }
--- a/OrthancServer/ExportedResource.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ExportedResource.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "PrecompiledHeadersServer.h" #include "ExportedResource.h" #include "../Core/OrthancException.h"
--- a/OrthancServer/FromDcmtkBridge.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -42,9 +42,10 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" #include "OrthancInitialization.h" +#include "../Core/Logging.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Images/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" @@ -54,6 +55,7 @@ #include <limits> #include <boost/lexical_cast.hpp> +#include <boost/filesystem.hpp> #include <dcmtk/dcmdata/dcchrstr.h> #include <dcmtk/dcmdata/dcdicent.h> @@ -90,9 +92,9 @@ #include <dcmtk/dcmdata/dcpxitem.h> #include <dcmtk/dcmdata/dcvrat.h> +#include <dcmtk/dcmnet/dul.h> #include <boost/math/special_functions/round.hpp> -#include <glog/logging.h> #include <dcmtk/dcmdata/dcostrmb.h> @@ -119,10 +121,173 @@ } +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, + EmbeddedResources::FileResourceId resource) + { + Toolbox::TemporaryFile tmp; + + FILE* fp = fopen(tmp.GetPath().c_str(), "wb"); + fwrite(EmbeddedResources::GetFileResourceBuffer(resource), + EmbeddedResources::GetFileResourceSize(resource), 1, fp); + fclose(fp); + + if (!dictionary.loadDictionary(tmp.GetPath().c_str())) + { + throw OrthancException(ErrorCode_InternalError); + } + } + +#else + static void LoadExternalDictionary(DcmDataDictionary& dictionary, + const std::string& directory, + const std::string& filename) + { + boost::filesystem::path p = directory; + p = p / filename; + + LOG(WARNING) << "Loading the external DICOM dictionary " << p; + + if (!dictionary.loadDictionary(p.string().c_str())) + { + throw OrthancException(ErrorCode_InternalError); + } + } + +#endif + + + namespace + { + class DictionaryLocker + { + private: + DcmDataDictionary& dictionary_; + + public: + DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) + { + } + + ~DictionaryLocker() + { + dcmDataDict.unlock(); + } + + DcmDataDictionary& operator*() + { + return dictionary_; + } + + DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + } + + + void FromDcmtkBridge::InitializeDictionary() + { + /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ + dcmDisableGethostbyaddr.set(OFTrue); + + { + DictionaryLocker locker; + + locker->clear(); + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + LOG(WARNING) << "Loading the embedded dictionaries"; + /** + * Do not load DICONDE dictionary, it breaks the other tags. The + * command "strace storescu 2>&1 |grep dic" shows that DICONDE + * dictionary is not loaded by storescu. + **/ + //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); + + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); + +#elif defined(__linux) || defined(__FreeBSD_kernel__) + std::string path = DCMTK_DICTIONARY_DIR; + + const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); + if (env != NULL) + { + path = std::string(env); + } + + LoadExternalDictionary(*locker, path, "dicom.dic"); + LoadExternalDictionary(*locker, path, "private.dic"); + +#else +#error Support your platform here +#endif + } + + /* make sure data dictionary is loaded */ + if (!dcmDataDict.isDictionaryLoaded()) + { + LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; + throw OrthancException(ErrorCode_InternalError); + } + + { + // Test the dictionary with a simple DICOM tag + DcmTag key(0x0010, 0x1030); // This is PatientWeight + if (key.getEVR() != EVR_DS) + { + LOG(ERROR) << "The DICOM dictionary has not been correctly read"; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, + const DcmEVR& vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity) + { + if (minMultiplicity < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (maxMultiplicity == 0) + { + maxMultiplicity = DcmVariableVM; + } + else if (maxMultiplicity < minMultiplicity) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::auto_ptr<DcmDictEntry> entry(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + vr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + NULL /* version */, + OFTrue /* doCopyString */, + NULL /* private creator */)); + + entry->setGroupRangeRestriction(DcmDictRange_Unspecified); + entry->setElementRangeRestriction(DcmDictRange_Unspecified); + + { + DictionaryLocker locker; + locker->addEntry(entry.release()); + } + } + + Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset) { // By default, Latin1 encoding is assumed - std::string s = Configuration::GetGlobalStringParameter("DefaultEncoding", ""); + std::string s = Configuration::GetGlobalStringParameter("DefaultEncoding", "Latin1"); Encoding encoding = s.empty() ? Encoding_Latin1 : StringToEncoding(s.c_str()); OFString tmp; @@ -215,18 +380,25 @@ { if (!element.isLeaf()) { - throw OrthancException("Only applicable to leaf elements"); + // This function is only applicable to leaf elements + throw OrthancException(ErrorCode_BadParameterType); } if (element.isaString()) { char *c; - if (element.getString(c).good() && - c != NULL) + if (element.getString(c).good()) { - std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); - return new DicomString(utf8); + if (c == NULL) // This case corresponds to the empty string + { + return new DicomString(""); + } + else + { + std::string s(c); + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); + return new DicomString(utf8); + } } else { @@ -588,7 +760,7 @@ if (entry == NULL) { dcmDataDict.unlock(); - throw OrthancException("Unknown DICOM tag"); + throw OrthancException(ErrorCode_UnknownDicomTag); } else { @@ -605,12 +777,19 @@ } else { - throw OrthancException("Unknown DICOM tag"); + throw OrthancException(ErrorCode_UnknownDicomTag); } #endif } + bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) + { + DcmTag tmp(tag.GetGroup(), tag.GetElement()); + return tmp.isUnknownVR(); + } + + void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m) { for (DicomMap::Map::const_iterator @@ -624,7 +803,8 @@ void FromDcmtkBridge::ToJson(Json::Value& result, - const DicomMap& values) + const DicomMap& values, + bool simplify) { if (result.type() != Json::objectValue) { @@ -636,7 +816,29 @@ for (DicomMap::Map::const_iterator it = values.map_.begin(); it != values.map_.end(); ++it) { - result[GetName(it->first)] = it->second->AsString(); + if (simplify) + { + result[GetName(it->first)] = it->second->AsString(); + } + else + { + Json::Value value = Json::objectValue; + + value["Name"] = GetName(it->first); + + if (it->second->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + value["Type"] = "String"; + value["Value"] = it->second->AsString(); + } + + result[it->first.Format()] = value; + } } } @@ -696,6 +898,7 @@ // Create the meta-header information DcmFileFormat ff(&dataSet); ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); // Create a memory buffer with the proper size uint32_t s = ff.calcElementLength(xfer, encodingType); @@ -720,4 +923,28 @@ return false; } } + + + ValueRepresentation FromDcmtkBridge::GetValueRepresentation(const DicomTag& tag) + { + DcmTag t(tag.GetGroup(), tag.GetElement()); + switch (t.getEVR()) + { + case EVR_PN: + return ValueRepresentation_PatientName; + + case EVR_DA: + return ValueRepresentation_Date; + + case EVR_DT: + return ValueRepresentation_DateTime; + + case EVR_TM: + return ValueRepresentation_Time; + + default: + return ValueRepresentation_Other; + } + } + }
--- a/OrthancServer/FromDcmtkBridge.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Wed Sep 30 13:23:31 2015 +0200 @@ -44,6 +44,14 @@ class FromDcmtkBridge { public: + static void InitializeDictionary(); + + static void RegisterDictionaryTag(const DicomTag& tag, + const DcmEVR& vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity); + static Encoding DetectEncoding(DcmDataset& dataset); static void Convert(DicomMap& target, DcmDataset& dataset); @@ -56,6 +64,8 @@ static bool IsPrivateTag(const DicomTag& tag); + static bool IsUnknownTag(const DicomTag& tag); + static DicomValue* ConvertLeafElement(DcmElement& element, Encoding encoding); @@ -99,11 +109,14 @@ const DicomMap& m); static void ToJson(Json::Value& result, - const DicomMap& values); + const DicomMap& values, + bool simplify); static std::string GenerateUniqueIdentifier(ResourceType level); static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); + + static ValueRepresentation GetValueRepresentation(const DicomTag& tag); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/IDatabaseListener.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <string> +#include "ServerEnumerations.h" +#include "ServerIndexChange.h" + +namespace Orthanc +{ + class IDatabaseListener + { + public: + virtual ~IDatabaseListener() + { + } + + virtual void SignalRemainingAncestor(ResourceType parentType, + const std::string& publicId) = 0; + + virtual void SignalFileDeleted(const FileInfo& info) = 0; + + virtual void SignalChange(const ServerIndexChange& change) = 0; + }; +}
--- a/OrthancServer/IDatabaseWrapper.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/IDatabaseWrapper.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,8 +34,9 @@ #include "../Core/DicomFormat/DicomMap.h" #include "../Core/SQLite/ITransaction.h" +#include "../Core/FileStorage/IStorageArea.h" #include "../Core/FileStorage/FileInfo.h" -#include "IServerIndexListener.h" +#include "IDatabaseListener.h" #include "ExportedResource.h" #include <list> @@ -81,6 +82,10 @@ virtual void GetAllPublicIds(std::list<std::string>& target, ResourceType resourceType) = 0; + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) = 0; virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, bool& done /*out*/, @@ -176,6 +181,11 @@ virtual SQLite::ITransaction* StartTransaction() = 0; - virtual void SetListener(IServerIndexListener& listener) = 0; + virtual void SetListener(IDatabaseListener& listener) = 0; + + virtual unsigned int GetDatabaseVersion() = 0; + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) = 0; }; }
--- a/OrthancServer/IServerIndexListener.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <string> -#include "ServerEnumerations.h" -#include "ServerIndexChange.h" - -namespace Orthanc -{ - class IServerIndexListener - { - public: - virtual ~IServerIndexListener() - { - } - - virtual void SignalRemainingAncestor(ResourceType parentType, - const std::string& publicId) = 0; - - virtual void SignalFileDeleted(const FileInfo& info) = 0; - - virtual void SignalChange(const ServerIndexChange& change) = 0; - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/IServerListener.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "DicomInstanceToStore.h" +#include "ServerIndexChange.h" + +#include <json/value.h> + +namespace Orthanc +{ + class IServerListener + { + public: + virtual ~IServerListener() + { + } + + virtual void SignalStoredInstance(const std::string& publicId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags) = 0; + + virtual void SignalChange(const ServerIndexChange& change) = 0; + + virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) = 0; + }; +}
--- a/OrthancServer/Internals/CommandDispatcher.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -86,10 +86,10 @@ #include "StoreScp.h" #include "MoveScp.h" #include "../../Core/Toolbox.h" +#include "../../Core/Logging.h" #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ #include <boost/lexical_cast.hpp> -#include <glog/logging.h> #define ORTHANC_PROMISCUOUS 1 @@ -771,11 +771,11 @@ if (supported && request != DicomRequestType_Echo && // Always allow incoming ECHO requests filter_ != NULL && - !filter_->IsAllowedRequest(callingIP_, callingAETitle_, request)) + !filter_->IsAllowedRequest(remoteIp_, remoteAet_, request)) { LOG(ERROR) << EnumerationToString(request) << " requests are disallowed for the AET \"" - << callingAETitle_ << "\""; + << remoteAet_ << "\""; cond = DIMSE_ILLEGALASSOCIATION; supported = false; finished = true; @@ -798,7 +798,7 @@ { std::auto_ptr<IStoreRequestHandler> handler (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - cond = Internals::storeScp(assoc_, &msg, presID, *handler); + cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); } break; @@ -807,7 +807,7 @@ { std::auto_ptr<IMoveRequestHandler> handler (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - cond = Internals::moveScp(assoc_, &msg, presID, *handler); + cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_); } break; @@ -816,7 +816,7 @@ { std::auto_ptr<IFindRequestHandler> handler (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - cond = Internals::findScp(assoc_, &msg, presID, *handler, callingAETitle_); + cond = Internals::findScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_); } break;
--- a/OrthancServer/Internals/CommandDispatcher.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/CommandDispatcher.h Wed Sep 30 13:23:31 2015 +0200 @@ -50,20 +50,20 @@ uint32_t elapsedTimeSinceLastCommand_; const DicomServer& server_; T_ASC_Association* assoc_; - std::string callingIP_; - std::string callingAETitle_; + std::string remoteIp_; + std::string remoteAet_; IApplicationEntityFilter* filter_; public: CommandDispatcher(const DicomServer& server, T_ASC_Association* assoc, - const std::string& callingIP, - const std::string& callingAETitle, + const std::string& remoteIp, + const std::string& remoteAet, IApplicationEntityFilter* filter) : server_(server), assoc_(assoc), - callingIP_(callingIP), - callingAETitle_(callingAETitle), + remoteIp_(remoteIp), + remoteAet_(remoteAet), filter_(filter) { clientTimeout_ = server.GetClientTimeout();
--- a/OrthancServer/Internals/DicomImageDecoder.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/DicomImageDecoder.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -80,15 +80,14 @@ #include "../PrecompiledHeadersServer.h" #include "DicomImageDecoder.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" -#include "../../Core/ImageFormats/ImageProcessing.h" -#include "../../Core/ImageFormats/PngWriter.h" // TODO REMOVE THIS +#include "../../Core/Images/ImageProcessing.h" +#include "../../Core/Images/PngWriter.h" // TODO REMOVE THIS #include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" #include "../ToDcmtkBridge.h" #include "../FromDcmtkBridge.h" -#include <glog/logging.h> - #include <boost/lexical_cast.hpp> #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
--- a/OrthancServer/Internals/DicomImageDecoder.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/DicomImageDecoder.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,7 +34,7 @@ #include <dcmtk/dcmdata/dcfilefo.h> -#include "../../Core/ImageFormats/ImageBuffer.h" +#include "../../Core/Images/ImageBuffer.h" namespace Orthanc {
--- a/OrthancServer/Internals/FindScp.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/FindScp.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -84,9 +84,9 @@ #include "../FromDcmtkBridge.h" #include "../ToDcmtkBridge.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" -#include <glog/logging.h> namespace Orthanc @@ -99,7 +99,8 @@ DicomMap input_; DicomFindAnswers answers_; DcmDataset* lastRequest_; - const std::string* callingAETitle_; + const std::string* remoteIp_; + const std::string* remoteAet_; bool noCroppingOfResults_; }; @@ -126,7 +127,8 @@ try { - data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, *data.callingAETitle_); + data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, + *data.remoteIp_, *data.remoteAet_); } catch (OrthancException& e) { @@ -174,12 +176,14 @@ T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, IFindRequestHandler& handler, - const std::string& callingAETitle) + const std::string& remoteIp, + const std::string& remoteAet) { FindScpData data; data.lastRequest_ = NULL; data.handler_ = &handler; - data.callingAETitle_ = &callingAETitle; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; data.noCroppingOfResults_ = true; OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ,
--- a/OrthancServer/Internals/FindScp.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/FindScp.h Wed Sep 30 13:23:31 2015 +0200 @@ -44,6 +44,7 @@ T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, IFindRequestHandler& handler, - const std::string& callingAETitle); + const std::string& remoteIp, + const std::string& remoteAet); } }
--- a/OrthancServer/Internals/MoveScp.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/MoveScp.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -86,10 +86,9 @@ #include "../FromDcmtkBridge.h" #include "../ToDcmtkBridge.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" -#include <glog/logging.h> - namespace Orthanc { @@ -105,6 +104,8 @@ unsigned int failureCount_; unsigned int warningCount_; std::auto_ptr<IMoveRequestIterator> iterator_; + const std::string* remoteIp_; + const std::string* remoteAet_; }; @@ -131,7 +132,8 @@ try { - data.iterator_.reset(data.handler_->Handle(data.target_, data.input_)); + data.iterator_.reset(data.handler_->Handle(data.target_, data.input_, + *data.remoteIp_, *data.remoteAet_)); if (data.iterator_.get() == NULL) { @@ -211,12 +213,16 @@ OFCondition Internals::moveScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler) + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet) { MoveScpData data; data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); data.lastRequest_ = NULL; data.handler_ = &handler; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, MoveScpCallback, &data,
--- a/OrthancServer/Internals/MoveScp.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/MoveScp.h Wed Sep 30 13:23:31 2015 +0200 @@ -43,6 +43,8 @@ OFCondition moveScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler); + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet); } }
--- a/OrthancServer/Internals/StoreScp.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/StoreScp.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -86,13 +86,13 @@ #include "../ServerToolbox.h" #include "../ToDcmtkBridge.h" #include "../../Core/OrthancException.h" +#include "../../Core/Logging.h" #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmdata/dcmetinf.h> #include <dcmtk/dcmdata/dcostrmb.h> #include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmnet/diutil.h> -#include <glog/logging.h> namespace Orthanc @@ -102,6 +102,7 @@ struct StoreCallbackData { IStoreRequestHandler* handler; + const std::string* remoteIp; const char* remoteAET; const char* calledAET; const char* modality; @@ -182,7 +183,7 @@ // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond // to those mentioned in the request. If not, set the status in the response message variable. - if ((rsp->DimseStatus == STATUS_Success)) + if (rsp->DimseStatus == STATUS_Success) { // which SOP class and SOP instance ? if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) @@ -202,7 +203,7 @@ { try { - cbdata->handler->Handle(buffer, summary, dicomJson, cbdata->remoteAET, cbdata->calledAET); + cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); } catch (OrthancException& e) { @@ -237,7 +238,8 @@ OFCondition Internals::storeScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler) + IStoreRequestHandler& handler, + const std::string& remoteIp) { OFCondition cond = EC_Normal; T_DIMSE_C_StoreRQ *req; @@ -246,23 +248,24 @@ req = &msg->msg.CStoreRQ; // intialize some variables - StoreCallbackData callbackData; - callbackData.handler = &handler; - callbackData.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); - if (callbackData.modality == NULL) - callbackData.modality = "UNKNOWN"; + StoreCallbackData data; + data.handler = &handler; + data.remoteIp = &remoteIp; + data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); + if (data.modality == NULL) + data.modality = "UNKNOWN"; - callbackData.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; - callbackData.messageID = req->MessageID; + data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; + data.messageID = req->MessageID; if (assoc && assoc->params) { - callbackData.remoteAET = assoc->params->DULparams.callingAPTitle; - callbackData.calledAET = assoc->params->DULparams.calledAPTitle; + data.remoteAET = assoc->params->DULparams.callingAPTitle; + data.calledAET = assoc->params->DULparams.calledAPTitle; } else { - callbackData.remoteAET = ""; - callbackData.calledAET = ""; + data.remoteAET = ""; + data.calledAET = ""; } DcmFileFormat dcmff; @@ -278,7 +281,7 @@ DcmDataset *dset = dcmff.getDataset(); cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, - storeScpCallback, &callbackData, + storeScpCallback, &data, /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ 0);
--- a/OrthancServer/Internals/StoreScp.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Internals/StoreScp.h Wed Sep 30 13:23:31 2015 +0200 @@ -43,6 +43,7 @@ OFCondition storeScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler); + IStoreRequestHandler& handler, + const std::string& remoteIp); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/LuaScripting.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,538 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersServer.h" +#include "LuaScripting.h" + +#include "ServerContext.h" +#include "OrthancInitialization.h" +#include "../Core/Lua/LuaFunctionCall.h" +#include "../Core/HttpServer/StringHttpOutput.h" +#include "../Core/Logging.h" + +#include "Scheduler/DeleteInstanceCommand.h" +#include "Scheduler/StoreScuCommand.h" +#include "Scheduler/StorePeerCommand.h" +#include "Scheduler/ModifyInstanceCommand.h" +#include "Scheduler/CallSystemCommand.h" +#include "OrthancRestApi/OrthancRestApi.h" + +#include <EmbeddedResources.h> + + +namespace Orthanc +{ + ServerContext* LuaScripting::GetServerContext(lua_State *state) + { + const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext"); + return const_cast<ServerContext*>(reinterpret_cast<const ServerContext*>(value)); + } + + + // Syntax in Lua: RestApiGet(uri, builtin) + int LuaScripting::RestApiGet(lua_State *state) + { + ServerContext* serverContext = GetServerContext(state); + if (serverContext == NULL) + { + LOG(ERROR) << "Lua: The Orthanc API is unavailable"; + lua_pushnil(state); + return 1; + } + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if ((nArgs != 1 && nArgs != 2) || + !lua_isstring(state, 1) || // URI + (nArgs == 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? + { + LOG(ERROR) << "Lua: Bad parameters to RestApiGet()"; + lua_pushnil(state); + return 1; + } + + const char* uri = lua_tostring(state, 1); + bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false); + + try + { + std::string result; + if (HttpToolbox::SimpleGet(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), + RequestOrigin_Lua, uri)) + { + lua_pushlstring(state, result.c_str(), result.size()); + return 1; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: " << e.What(); + } + + LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri; + lua_pushnil(state); + return 1; + } + + + int LuaScripting::RestApiPostOrPut(lua_State *state, + bool isPost) + { + ServerContext* serverContext = GetServerContext(state); + if (serverContext == NULL) + { + LOG(ERROR) << "Lua: The Orthanc API is unavailable"; + lua_pushnil(state); + return 1; + } + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if ((nArgs != 2 && nArgs != 3) || + !lua_isstring(state, 1) || // URI + !lua_isstring(state, 2) || // Body + (nArgs == 3 && !lua_isboolean(state, 3))) // Restrict to built-in API? + { + LOG(ERROR) << "Lua: Bad parameters to " << (isPost ? "RestApiPost()" : "RestApiPut()"); + lua_pushnil(state); + return 1; + } + + const char* uri = lua_tostring(state, 1); + size_t bodySize = 0; + const char* bodyData = lua_tolstring(state, 2, &bodySize); + bool builtin = (nArgs == 3 ? lua_toboolean(state, 3) != 0 : false); + + try + { + std::string result; + if (isPost ? + HttpToolbox::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), + RequestOrigin_Lua, uri, bodyData, bodySize) : + HttpToolbox::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), + RequestOrigin_Lua, uri, bodyData, bodySize)) + { + lua_pushlstring(state, result.c_str(), result.size()); + return 1; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: " << e.What(); + } + + LOG(ERROR) << "Lua: Error in " << (isPost ? "RestApiPost()" : "RestApiPut()") << " for URI: " << uri; + lua_pushnil(state); + return 1; + } + + + // Syntax in Lua: RestApiPost(uri, body, builtin) + int LuaScripting::RestApiPost(lua_State *state) + { + return RestApiPostOrPut(state, true); + } + + + // Syntax in Lua: RestApiPut(uri, body, builtin) + int LuaScripting::RestApiPut(lua_State *state) + { + return RestApiPostOrPut(state, false); + } + + + // Syntax in Lua: RestApiDelete(uri, builtin) + int LuaScripting::RestApiDelete(lua_State *state) + { + ServerContext* serverContext = GetServerContext(state); + if (serverContext == NULL) + { + LOG(ERROR) << "Lua: The Orthanc API is unavailable"; + lua_pushnil(state); + return 1; + } + + // Check the types of the arguments + int nArgs = lua_gettop(state); + if ((nArgs != 1 && nArgs != 2) || + !lua_isstring(state, 1) || // URI + (nArgs == 2 && !lua_isboolean(state, 2))) // Restrict to built-in API? + { + LOG(ERROR) << "Lua: Bad parameters to RestApiDelete()"; + lua_pushnil(state); + return 1; + } + + const char* uri = lua_tostring(state, 1); + bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false); + + try + { + if (HttpToolbox::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), + RequestOrigin_Lua, uri)) + { + lua_pushboolean(state, 1); + return 1; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Lua: " << e.What(); + } + + LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri; + lua_pushnil(state); + + return 1; + } + + + // Syntax in Lua: GetOrthancConfiguration() + int LuaScripting::GetOrthancConfiguration(lua_State *state) + { + Json::Value configuration; + Configuration::GetConfiguration(configuration); + + LuaContext::GetLuaContext(state).PushJson(configuration); + + return 1; + } + + + IServerCommand* LuaScripting::ParseOperation(const std::string& operation, + const Json::Value& parameters) + { + if (operation == "delete") + { + LOG(INFO) << "Lua script to delete resource " << parameters["Resource"].asString(); + return new DeleteInstanceCommand(context_); + } + + if (operation == "store-scu") + { + std::string localAet; + if (parameters.isMember("LocalAet")) + { + localAet = parameters["LocalAet"].asString(); + } + else + { + localAet = context_.GetDefaultLocalApplicationEntityTitle(); + } + + std::string modality = parameters["Modality"].asString(); + LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() + << " to modality " << modality << " using Store-SCU"; + return new StoreScuCommand(context_, localAet, + Configuration::GetModalityUsingSymbolicName(modality), true); + } + + if (operation == "store-peer") + { + std::string peer = parameters["Peer"].asString(); + LOG(INFO) << "Lua script to send resource " << parameters["Resource"].asString() + << " to peer " << peer << " using HTTP"; + + OrthancPeerParameters parameters; + Configuration::GetOrthancPeer(parameters, peer); + return new StorePeerCommand(context_, parameters, true); + } + + if (operation == "modify") + { + LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString(); + DicomModification modification; + OrthancRestApi::ParseModifyRequest(modification, parameters); + + std::auto_ptr<ModifyInstanceCommand> command(new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification)); + return command.release(); + } + + if (operation == "call-system") + { + LOG(INFO) << "Lua script to call system command on " << parameters["Resource"].asString(); + + const Json::Value& argsIn = parameters["Arguments"]; + if (argsIn.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + std::vector<std::string> args; + args.reserve(argsIn.size()); + for (Json::Value::ArrayIndex i = 0; i < argsIn.size(); ++i) + { + // http://jsoncpp.sourceforge.net/namespace_json.html#7d654b75c16a57007925868e38212b4e + switch (argsIn[i].type()) + { + case Json::stringValue: + args.push_back(argsIn[i].asString()); + break; + + case Json::intValue: + args.push_back(boost::lexical_cast<std::string>(argsIn[i].asInt())); + break; + + case Json::uintValue: + args.push_back(boost::lexical_cast<std::string>(argsIn[i].asUInt())); + break; + + case Json::realValue: + args.push_back(boost::lexical_cast<std::string>(argsIn[i].asFloat())); + break; + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + } + + return new CallSystemCommand(context_, parameters["Command"].asString(), args); + } + + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + + void LuaScripting::InitializeJob() + { + lua_.Execute("_InitializeJob()"); + } + + + void LuaScripting::SubmitJob(const std::string& description) + { + Json::Value operations; + LuaFunctionCall call2(lua_, "_AccessJob"); + call2.ExecuteToJson(operations, false); + + if (operations.type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_InternalError); + } + + ServerJob job; + ServerCommandInstance* previousCommand = NULL; + + for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i) + { + if (operations[i].type() != Json::objectValue || + !operations[i].isMember("Operation")) + { + throw OrthancException(ErrorCode_InternalError); + } + + const Json::Value& parameters = operations[i]; + std::string operation = parameters["Operation"].asString(); + + ServerCommandInstance& command = job.AddCommand(ParseOperation(operation, operations[i])); + + if (!parameters.isMember("Resource")) + { + throw OrthancException(ErrorCode_InternalError); + } + + std::string resource = parameters["Resource"].asString(); + if (resource.empty()) + { + previousCommand->ConnectOutput(command); + } + else + { + command.AddInput(resource); + } + + previousCommand = &command; + } + + job.SetDescription(description); + context_.GetScheduler().Submit(job); + } + + + LuaScripting::LuaScripting(ServerContext& context) : context_(context) + { + lua_.SetGlobalVariable("_ServerContext", &context); + lua_.RegisterFunction("RestApiGet", RestApiGet); + lua_.RegisterFunction("RestApiPost", RestApiPost); + lua_.RegisterFunction("RestApiPut", RestApiPut); + lua_.RegisterFunction("RestApiDelete", RestApiDelete); + lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration); + + lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); + lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", "")); + } + + + void LuaScripting::ApplyOnStoredInstance(const std::string& instanceId, + const Json::Value& simplifiedTags, + const Json::Value& metadata, + const DicomInstanceToStore& instance) + { + static const char* NAME = "OnStoredInstance"; + + if (lua_.IsExistingFunction(NAME)) + { + InitializeJob(); + + LuaFunctionCall call(lua_, NAME); + call.PushString(instanceId); + call.PushJson(simplifiedTags); + call.PushJson(metadata); + + Json::Value origin; + instance.GetOriginInformation(origin); + call.PushJson(origin); + + call.Execute(); + + SubmitJob(std::string("Lua script: ") + NAME); + } + } + + + void LuaScripting::SignalStoredInstance(const std::string& publicId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + + Json::Value metadata = Json::objectValue; + + for (ServerIndex::MetadataMap::const_iterator + it = instance.GetMetadata().begin(); + it != instance.GetMetadata().end(); ++it) + { + if (it->first.first == ResourceType_Instance) + { + metadata[EnumerationToString(it->first.second)] = it->second; + } + } + + ApplyOnStoredInstance(publicId, simplifiedTags, metadata, instance); + } + + + + void LuaScripting::OnStableResource(const ServerIndexChange& change) + { + const char* name; + + switch (change.GetChangeType()) + { + case ChangeType_StablePatient: + name = "OnStablePatient"; + break; + + case ChangeType_StableStudy: + name = "OnStableStudy"; + break; + + case ChangeType_StableSeries: + name = "OnStableSeries"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + + Json::Value tags, metadata; + if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()) && + context_.GetIndex().GetMetadata(metadata, change.GetPublicId())) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + + if (lua_.IsExistingFunction(name)) + { + InitializeJob(); + + LuaFunctionCall call(lua_, name); + call.PushString(change.GetPublicId()); + call.PushJson(tags["MainDicomTags"]); + call.PushJson(metadata); + call.Execute(); + + SubmitJob(std::string("Lua script: ") + name); + } + } + } + + + + void LuaScripting::SignalChange(const ServerIndexChange& change) + { + if (change.GetChangeType() == ChangeType_StablePatient || + change.GetChangeType() == ChangeType_StableStudy || + change.GetChangeType() == ChangeType_StableSeries) + { + OnStableResource(change); + } + } + + + bool LuaScripting::FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + static const char* NAME = "ReceivedInstanceFilter"; + + boost::recursive_mutex::scoped_lock lock(mutex_); + + if (lua_.IsExistingFunction(NAME)) + { + LuaFunctionCall call(lua_, NAME); + call.PushJson(simplified); + + Json::Value origin; + instance.GetOriginInformation(origin); + call.PushJson(origin); + + if (!call.ExecutePredicate()) + { + return false; + } + } + + return true; + } + + + void LuaScripting::Execute(const std::string& command) + { + LuaScripting::Locker locker(*this); + + if (locker.GetLua().IsExistingFunction(command.c_str())) + { + LuaFunctionCall call(locker.GetLua(), command.c_str()); + call.Execute(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/LuaScripting.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,107 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "IServerListener.h" +#include "../Core/Lua/LuaContext.h" +#include "Scheduler/IServerCommand.h" + +namespace Orthanc +{ + class ServerContext; + + class LuaScripting : public IServerListener + { + private: + static ServerContext* GetServerContext(lua_State *state); + + static int RestApiPostOrPut(lua_State *state, + bool isPost); + static int RestApiGet(lua_State *state); + static int RestApiPost(lua_State *state); + static int RestApiPut(lua_State *state); + static int RestApiDelete(lua_State *state); + static int GetOrthancConfiguration(lua_State *state); + + void ApplyOnStoredInstance(const std::string& instanceId, + const Json::Value& simplifiedDicom, + const Json::Value& metadata, + const DicomInstanceToStore& instance); + + IServerCommand* ParseOperation(const std::string& operation, + const Json::Value& parameters); + + void InitializeJob(); + + void SubmitJob(const std::string& description); + + void OnStableResource(const ServerIndexChange& change); + + boost::recursive_mutex mutex_; + LuaContext lua_; + ServerContext& context_; + + public: + class Locker : public boost::noncopyable + { + private: + LuaScripting& that_; + boost::recursive_mutex::scoped_lock lock_; + + public: + Locker(LuaScripting& that) : + that_(that), + lock_(that.mutex_) + { + } + + LuaContext& GetLua() + { + return that_.lua_; + } + }; + + LuaScripting(ServerContext& context); + + virtual void SignalStoredInstance(const std::string& publicId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags); + + virtual void SignalChange(const ServerIndexChange& change); + + virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplifiedTags); + + void Execute(const std::string& command); + }; +}
--- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,168 +33,20 @@ #include "PrecompiledHeadersServer.h" #include "OrthancFindRequestHandler.h" -#include <glog/logging.h> -#include <boost/regex.hpp> - +#include "../Core/Logging.h" #include "../Core/DicomFormat/DicomArray.h" #include "ServerToolbox.h" #include "OrthancInitialization.h" #include "FromDcmtkBridge.h" -namespace Orthanc -{ - static bool IsWildcard(const std::string& constraint) - { - return (constraint.find('-') != std::string::npos || - constraint.find('*') != std::string::npos || - constraint.find('\\') != std::string::npos || - constraint.find('?') != std::string::npos); - } - - static bool ApplyRangeConstraint(const std::string& value, - const std::string& constraint) - { - size_t separator = constraint.find('-'); - 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) - { - return false; - } - - if (lower.size() == 0) - { - return v <= upper; - } +#include "ResourceFinder.h" +#include "DicomFindQuery.h" - if (upper.size() == 0) - { - return v >= lower; - } - - return (v >= lower && v <= upper); - } - - - static bool ApplyListConstraint(const std::string& value, - const std::string& constraint) - { - 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++) - { - std::string lower; - Toolbox::ToLowerCase(lower, items[i]); - if (lower == v1) - { - return true; - } - } - - return false; - } +#include <boost/regex.hpp> - static bool Matches(const std::string& value, - const std::string& constraint) - { - // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained - // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html - - if (constraint.find('-') != std::string::npos) - { - return ApplyRangeConstraint(value, constraint); - } - - if (constraint.find('\\') != std::string::npos) - { - return ApplyListConstraint(value, constraint); - } - - if (constraint.find('*') != std::string::npos || - constraint.find('?') != std::string::npos) - { - // TODO - Cache the constructed regular expression - boost::regex pattern(Toolbox::WildcardToRegularExpression(constraint), - boost::regex::icase /* case insensitive search */); - return boost::regex_match(value, pattern); - } - else - { - std::string v, c; - Toolbox::ToLowerCase(v, value); - Toolbox::ToLowerCase(c, constraint); - return v == c; - } - } - - - static bool LookupOneInstance(std::string& result, - ServerIndex& index, - const std::string& id, - ResourceType type) - { - if (type == ResourceType_Instance) - { - result = id; - return true; - } - - std::string childId; - - { - std::list<std::string> children; - index.GetChildInstances(children, id); - - if (children.empty()) - { - return false; - } - - childId = children.front(); - } - - return LookupOneInstance(result, index, childId, GetChildResourceType(type)); - } - - - static bool Matches(const Json::Value& resource, - const DicomArray& query) - { - for (size_t i = 0; i < query.GetSize(); i++) - { - if (query.GetElement(i).GetValue().IsNull() || - query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL || - query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET || - query.GetElement(i).GetTag() == DICOM_TAG_MODALITIES_IN_STUDY) - { - continue; - } - - std::string tag = query.GetElement(i).GetTag().Format(); - std::string value; - if (resource.isMember(tag)) - { - value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); - } - - if (!Matches(value, query.GetElement(i).GetValue().AsString())) - { - return false; - } - } - - return true; - } - - +namespace Orthanc +{ static void AddAnswer(DicomFindAnswers& answers, const Json::Value& resource, const DicomArray& query) @@ -203,8 +55,15 @@ for (size_t i = 0; i < query.GetSize(); i++) { - if (query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL && - query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET) + // Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) + if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) + { + result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue()); + } + else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + } + else { std::string tag = query.GetElement(i).GetTag().Format(); std::string value; @@ -220,267 +79,160 @@ } } - answers.Add(result); - } - - - static bool ApplyModalitiesInStudyFilter(std::list<std::string>& filteredStudies, - const std::list<std::string>& studies, - const DicomMap& input, - ServerIndex& index) - { - filteredStudies.clear(); - - const DicomValue& v = input.GetValue(DICOM_TAG_MODALITIES_IN_STUDY); - if (v.IsNull()) - { - return false; - } - - // Move the allowed modalities into a "std::set" - std::vector<std::string> tmp; - Toolbox::TokenizeString(tmp, v.AsString(), '\\'); - - std::set<std::string> modalities; - for (size_t i = 0; i < tmp.size(); i++) - { - modalities.insert(tmp[i]); - } - - // Loop over the studies - for (std::list<std::string>::const_iterator - it = studies.begin(); it != studies.end(); ++it) + if (result.GetSize() == 0) { - try - { - // We are considering a single study. Check whether one of - // its child series matches one of the modalities. - Json::Value study; - if (index.LookupResource(study, *it, ResourceType_Study)) - { - // Loop over the series of the considered study. - for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) // (*) - { - Json::Value series; - if (index.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) - { - // Get the modality of this series - if (series["MainDicomTags"].isMember("Modality")) - { - std::string modality = series["MainDicomTags"]["Modality"].asString(); - if (modalities.find(modality) != modalities.end()) - { - // This series of the considered study matches one - // of the required modalities. Take the study into - // consideration for future filtering. - filteredStudies.push_back(*it); - - // We have finished considering this study. Break the study loop at (*). - break; - } - } - } - } - } - } - catch (OrthancException&) - { - // This resource has probably been deleted during the find request - } + LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; } - - return true; + else + { + answers.Add(result); + } } namespace { - class CandidateResources + class CFindQuery : public DicomFindQuery { private: - ServerIndex& index_; - ModalityManufacturer manufacturer_; - ResourceType level_; - bool isFilterApplied_; - std::set<std::string> filtered_; - - static void ListToSet(std::set<std::string>& target, - const std::list<std::string>& source) - { - for (std::list<std::string>::const_iterator - it = source.begin(); it != source.end(); ++it) - { - target.insert(*it); - } - } - - void ApplyExactFilter(const DicomTag& tag, const std::string& value) - { - LOG(INFO) << "Applying exact filter on tag " - << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; - - std::list<std::string> resources; - index_.LookupIdentifier(resources, tag, value, level_); - - if (isFilterApplied_) - { - std::set<std::string> s; - ListToSet(s, resources); - - std::set<std::string> tmp = filtered_; - filtered_.clear(); - - for (std::set<std::string>::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - if (s.find(*it) != s.end()) - { - filtered_.insert(*it); - } - } - } - else - { - assert(filtered_.empty()); - isFilterApplied_ = true; - ListToSet(filtered_, resources); - } - } + DicomFindAnswers& answers_; + ServerIndex& index_; + const DicomArray& query_; + bool hasModalitiesInStudy_; + std::set<std::string> modalitiesInStudy_; public: - CandidateResources(ServerIndex& index, - ModalityManufacturer manufacturer) : - index_(index), - manufacturer_(manufacturer), - level_(ResourceType_Patient), - isFilterApplied_(false) + CFindQuery(DicomFindAnswers& answers, + ServerIndex& index, + const DicomArray& query) : + answers_(answers), + index_(index), + query_(query), + hasModalitiesInStudy_(false) { } - ResourceType GetLevel() const - { - return level_; - } - - void GoDown() + void SetModalitiesInStudy(const std::string& value) { - assert(level_ != ResourceType_Instance); - - if (isFilterApplied_) - { - std::set<std::string> tmp = filtered_; - - filtered_.clear(); + hasModalitiesInStudy_ = true; + + std::vector<std::string> tmp; + Toolbox::TokenizeString(tmp, value, '\\'); - 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); - } - } - - switch (level_) + for (size_t i = 0; i < tmp.size(); i++) { - case ResourceType_Patient: - level_ = ResourceType_Study; - break; - - case ResourceType_Study: - level_ = ResourceType_Series; - break; - - case ResourceType_Series: - level_ = ResourceType_Instance; - break; - - default: - throw OrthancException(ErrorCode_InternalError); + modalitiesInStudy_.insert(tmp[i]); } } - void Flatten(std::list<std::string>& resources) const + virtual bool HasMainDicomTagsFilter(ResourceType level) const { - resources.clear(); - - if (isFilterApplied_) + if (DicomFindQuery::HasMainDicomTagsFilter(level)) { - for (std::set<std::string>::const_iterator - it = filtered_.begin(); it != filtered_.end(); ++it) - { - resources.push_back(*it); - } + return true; } - else - { - Json::Value tmp; - index_.GetAllUuids(tmp, level_); - for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) - { - resources.push_back(tmp[i].asString()); - } - } + + return (level == ResourceType_Study && + hasModalitiesInStudy_); } - void ApplyFilter(const DicomTag& tag, const DicomMap& query) + virtual bool FilterMainDicomTags(const std::string& resourceId, + ResourceType level, + const DicomMap& mainTags) const { - if (query.HasTag(tag)) + if (!DicomFindQuery::FilterMainDicomTags(resourceId, level, mainTags)) + { + return false; + } + + if (level != ResourceType_Study || + !hasModalitiesInStudy_) + { + return true; + } + + try { - const DicomValue& value = query.GetValue(tag); - if (!value.IsNull()) + // We are considering a single study, and the + // "MODALITIES_IN_STUDY" tag is set in the C-Find. Check + // whether one of its child series matches one of the + // modalities. + + Json::Value study; + if (index_.LookupResource(study, resourceId, ResourceType_Study)) { - std::string value = query.GetValue(tag).AsString(); - if (!IsWildcard(value)) + // Loop over the series of the considered study. + for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) { - ApplyExactFilter(tag, value); + Json::Value series; + if (index_.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) + { + // Get the modality of this series + if (series["MainDicomTags"].isMember("Modality")) + { + std::string modality = series["MainDicomTags"]["Modality"].asString(); + if (modalitiesInStudy_.find(modality) != modalitiesInStudy_.end()) + { + // This series of the considered study matches one + // of the required modalities. Take the study into + // consideration for future filtering. + return true; + } + } + } } } } + catch (OrthancException&) + { + // This resource has probably been deleted during the find request + } + + return false; + } + + virtual bool HasInstanceFilter() const + { + return true; + } + + virtual bool FilterInstance(const std::string& instanceId, + const Json::Value& content) const + { + bool ok = DicomFindQuery::FilterInstance(instanceId, content); + + if (ok) + { + // Add this resource to the answers + AddAnswer(answers_, content, query_); + } + + return ok; } }; } - bool OrthancFindRequestHandler::HasReachedLimit(const DicomFindAnswers& answers, - ResourceType level) const - { - switch (level) - { - case ResourceType_Patient: - case ResourceType_Study: - case ResourceType_Series: - return (maxResults_ != 0 && answers.GetSize() >= maxResults_); - - case ResourceType_Instance: - return (maxInstances_ != 0 && answers.GetSize() >= maxInstances_); - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, - const std::string& callingAETitle) + const std::string& remoteIp, + const std::string& remoteAet) { /** - * Retrieve the manufacturer of this modality. + * Ensure that the calling modality is known to Orthanc. **/ - ModalityManufacturer manufacturer; - - { - RemoteModalityParameters modality; + RemoteModalityParameters modality; - if (!Configuration::LookupDicomModalityUsingAETitle(modality, callingAETitle)) - { - throw OrthancException("Unknown modality"); - } + if (!Configuration::LookupDicomModalityUsingAETitle(modality, remoteAet)) + { + throw OrthancException(ErrorCode_UnknownModality); + } - manufacturer = modality.GetManufacturer(); - } + // ModalityManufacturer manufacturer = modality.GetManufacturer(); + + bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false); /** @@ -519,134 +271,68 @@ /** - * Retrieve the candidate resources for this query level. Whenever - * possible, we avoid returning ALL the resources for this query - * level, as it would imply reading the JSON file on the harddisk - * for each of them. + * Build up the query object. **/ - CandidateResources candidates(context_.GetIndex(), manufacturer); - - for (;;) + CFindQuery findQuery(answers, context_.GetIndex(), query); + findQuery.SetLevel(level); + + for (size_t i = 0; i < query.GetSize(); i++) { - 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; + const DicomTag tag = query.GetElement(i).GetTag(); - 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) + if (query.GetElement(i).GetValue().IsNull() || + tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || + tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) { - break; + continue; } - candidates.GoDown(); - } - - std::list<std::string> resources; - candidates.Flatten(resources); - - LOG(INFO) << "Number of candidate resources after exact filtering: " << resources.size(); + std::string value = query.GetElement(i).GetValue().AsString(); + if (value.size() == 0) + { + // An empty string corresponds to a "*" wildcard constraint, so we ignore it + continue; + } - /** - * Apply filtering on modalities for studies, if asked (this is an - * extension to standard DICOM) - * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND - **/ - - if (level == ResourceType_Study && - input.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) - { - std::list<std::string> filtered; - if (ApplyModalitiesInStudyFilter(filtered, resources, input, context_.GetIndex())) + if (tag == DICOM_TAG_MODALITIES_IN_STUDY) { - resources = filtered; + findQuery.SetModalitiesInStudy(value); + } + else + { + findQuery.SetConstraint(tag, value, caseSensitivePN); } } /** - * Loop over all the resources for this query level. + * Run the query. **/ - for (std::list<std::string>::const_iterator - resource = resources.begin(); resource != resources.end(); ++resource) + ResourceFinder finder(context_); + + switch (level) { - try - { - std::string instance; - if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) - { - Json::Value info; - context_.ReadJson(info, instance); - - if (Matches(info, query)) - { - if (HasReachedLimit(answers, level)) - { - // Too many results, stop before recording this new match - return false; - } + case ResourceType_Patient: + case ResourceType_Study: + case ResourceType_Series: + finder.SetMaxResults(maxResults_); + break; - AddAnswer(answers, info, query); - } - } - } - catch (OrthancException&) - { - // This resource has probably been deleted during the find request - } + case ResourceType_Instance: + finder.SetMaxResults(maxInstances_); + break; + + default: + throw OrthancException(ErrorCode_InternalError); } - return true; // All the matching resources have been returned + std::list<std::string> tmp; + bool finished = finder.Apply(tmp, findQuery); + + LOG(INFO) << "Number of matching resources: " << tmp.size(); + + return finished; } } - - - -/** - * 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 - * - * "Except for Attributes with a PN Value Representation, only - * entities with values which match exactly the value specified in the - * request shall match. This matching is case-sensitive, i.e., - * sensitive to the exact encoding of the key attribute value in - * character sets where a letter may have multiple encodings (e.g., - * based on its case, its position in a word, or whether it is - * accented) - * - * For Attributes with a PN Value Representation (e.g., Patient Name - * (0010,0010)), an application may perform literal matching that is - * either case-sensitive, or that is insensitive to some or all - * aspects of case, position, accent, or other character encoding - * variants." - * - * (0008,0018) UI SOPInstanceUID => Case-sensitive - * (0008,0050) SH AccessionNumber => Case-sensitive - * (0010,0020) LO PatientID => Case-sensitive - * (0020,000D) UI StudyInstanceUID => Case-sensitive - * (0020,000E) UI SeriesInstanceUID => Case-sensitive - **/
--- a/OrthancServer/OrthancFindRequestHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -57,7 +57,8 @@ virtual bool Handle(DicomFindAnswers& answers, const DicomMap& input, - const std::string& callingAETitle); + const std::string& remoteIp, + const std::string& remoteAet); unsigned int GetMaxResults() const {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancHttpHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersServer.h" +#include "OrthancHttpHandler.h" + +#include "../Core/OrthancException.h" + + +namespace Orthanc +{ + bool OrthancHttpHandler::Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers, + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize) + { + bool found = false; + + for (Handlers::const_iterator it = handlers_.begin(); + it != handlers_.end() && !found; ++it) + { + found = (*it)->Handle(output, origin, remoteIp, username, method, uri, + headers, getArguments, bodyData, bodySize); + } + + return found; + } + + + void OrthancHttpHandler::Register(IHttpHandler& handler, + bool isOrthancRestApi) + { + handlers_.push_back(&handler); + + if (isOrthancRestApi) + { + orthancRestApi_ = &handler; + } + } + + + IHttpHandler& OrthancHttpHandler::RestrictToOrthancRestApi(bool restrict) + { + if (restrict) + { + if (orthancRestApi_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *orthancRestApi_; + } + else + { + return *this; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancHttpHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,73 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../Core/HttpServer/IHttpHandler.h" + +namespace Orthanc +{ + class OrthancHttpHandler : public IHttpHandler + { + private: + typedef std::list<IHttpHandler*> Handlers; + + Handlers handlers_; + IHttpHandler *orthancRestApi_; + + public: + OrthancHttpHandler() : orthancRestApi_(NULL) + { + } + + virtual bool Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers, + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize); + + void Register(IHttpHandler& handler, + bool isOrthancRestApi); + + bool HasOrthancRestApi() const + { + return orthancRestApi_ != NULL; + } + + IHttpHandler& RestrictToOrthancRestApi(bool restrict); + }; +}
--- a/OrthancServer/OrthancInitialization.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancInitialization.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,19 +34,29 @@ #include "OrthancInitialization.h" #include "../Core/HttpClient.h" +#include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" -#include "DicomProtocol/DicomServer.h" +#include "../Core/FileStorage/FilesystemStorage.h" + #include "ServerEnumerations.h" +#include "DatabaseWrapper.h" +#include "FromDcmtkBridge.h" #include <boost/lexical_cast.hpp> #include <boost/filesystem.hpp> #include <curl/curl.h> #include <boost/thread.hpp> -#include <glog/logging.h> + -#include "DatabaseWrapper.h" -#include "../Core/FileStorage/FilesystemStorage.h" +#if ORTHANC_SSL_ENABLED == 1 +// For OpenSSL initialization and finalization +#include <openssl/conf.h> +#include <openssl/engine.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/ssl.h> +#endif #if ORTHANC_JPEG_ENABLED == 1 @@ -62,23 +72,154 @@ namespace Orthanc { static boost::mutex globalMutex_; - static std::auto_ptr<Json::Value> configuration_; + static Json::Value configuration_; static boost::filesystem::path defaultDirectory_; static std::string configurationAbsolutePath_; + static FontRegistry fontRegistry_; + + + static std::string GetGlobalStringParameterInternal(const std::string& parameter, + const std::string& defaultValue) + { + if (configuration_.isMember(parameter)) + { + if (configuration_[parameter].type() != Json::stringValue) + { + LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a string"; + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return configuration_[parameter].asString(); + } + } + else + { + return defaultValue; + } + } + + + static bool GetGlobalBoolParameterInternal(const std::string& parameter, + bool defaultValue) + { + if (configuration_.isMember(parameter)) + { + if (configuration_[parameter].type() != Json::booleanValue) + { + LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a Boolean (true or false)"; + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return configuration_[parameter].asBool(); + } + } + else + { + return defaultValue; + } + } + + + + static void AddFileToConfiguration(const boost::filesystem::path& path) + { + LOG(WARNING) << "Reading the configuration from: " << path; + + Json::Value config; + + { + std::string content; + Toolbox::ReadFile(content, path.string()); + + Json::Value tmp; + Json::Reader reader; + if (!reader.parse(content, tmp) || + tmp.type() != Json::objectValue) + { + LOG(ERROR) << "The configuration file does not follow the JSON syntax: " << path; + throw OrthancException(ErrorCode_BadJson); + } + + Toolbox::CopyJsonWithoutComments(config, tmp); + } + + if (configuration_.size() == 0) + { + configuration_ = config; + } + else + { + Json::Value::Members members = config.getMemberNames(); + for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) + { + if (configuration_.isMember(members[i])) + { + LOG(ERROR) << "The configuration section \"" << members[i] << "\" is defined in 2 different configuration files"; + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + configuration_[members[i]] = config[members[i]]; + } + } + } + } + + + static void ScanFolderForConfiguration(const char* folder) + { + using namespace boost::filesystem; + + LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files"; + + directory_iterator end_it; // default construction yields past-the-end + for (directory_iterator it(folder); + it != end_it; + ++it) + { + if (!is_directory(it->status())) + { + std::string extension = boost::filesystem::extension(it->path()); + Toolbox::ToLowerCase(extension); + + if (extension == ".json") + { + AddFileToConfiguration(it->path().string()); + } + } + } + } static void ReadGlobalConfiguration(const char* configurationFile) { - configuration_.reset(new Json::Value); - - std::string content; + // Prepare the default configuration + defaultDirectory_ = boost::filesystem::current_path(); + configuration_ = Json::objectValue; + configurationAbsolutePath_ = ""; if (configurationFile) { - Toolbox::ReadFile(content, configurationFile); - defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); - LOG(WARNING) << "Using the configuration from: " << configurationFile; - configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string(); + if (!boost::filesystem::exists(configurationFile)) + { + LOG(ERROR) << "Inexistent path to configuration: " << configurationFile; + throw OrthancException(ErrorCode_InexistentFile); + } + + if (boost::filesystem::is_directory(configurationFile)) + { + defaultDirectory_ = boost::filesystem::path(configurationFile); + configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string(); + ScanFolderForConfiguration(configurationFile); + } + else + { + defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); + configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string(); + AddFileToConfiguration(configurationFile); + } } else { @@ -91,38 +232,22 @@ // In a non-standalone build, we use the // "Resources/Configuration.json" from the Orthanc source code - try - { - boost::filesystem::path p = ORTHANC_PATH; - p /= "Resources"; - p /= "Configuration.json"; - Toolbox::ReadFile(content, p.string()); - LOG(WARNING) << "Using the configuration from: " << p.string(); - configurationAbsolutePath_ = boost::filesystem::absolute(p).string(); - } - catch (OrthancException&) - { - // No configuration file found, give up with empty configuration - LOG(WARNING) << "Using the default Orthanc configuration"; - return; - } + boost::filesystem::path p = ORTHANC_PATH; + p /= "Resources"; + p /= "Configuration.json"; + configurationAbsolutePath_ = boost::filesystem::absolute(p).string(); + + AddFileToConfiguration(p); #endif } - - - Json::Reader reader; - if (!reader.parse(content, *configuration_)) - { - throw OrthancException("Unable to read the configuration file"); - } } static void RegisterUserMetadata() { - if (configuration_->isMember("UserMetadata")) + if (configuration_.isMember("UserMetadata")) { - const Json::Value& parameter = (*configuration_) ["UserMetadata"]; + const Json::Value& parameter = configuration_["UserMetadata"]; Json::Value::Members members = parameter.getMemberNames(); for (size_t i = 0; i < members.size(); i++) @@ -154,9 +279,9 @@ static void RegisterUserContentType() { - if (configuration_->isMember("UserContentType")) + if (configuration_.isMember("UserContentType")) { - const Json::Value& parameter = (*configuration_) ["UserContentType"]; + const Json::Value& parameter = configuration_["UserContentType"]; Json::Value::Members members = parameter.getMemberNames(); for (size_t i = 0; i < members.size(); i++) @@ -186,66 +311,35 @@ } - static std::string GetStringValue(const Json::Value& configuration, - const std::string& key, - const std::string& defaultValue) - { - if (configuration.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (!configuration.isMember(key)) - { - return defaultValue; - } - - if (configuration[key].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - return configuration[key].asString(); - } - - - static int GetIntegerValue(const Json::Value& configuration, - const std::string& key, - int defaultValue) - { - if (configuration.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (!configuration.isMember(key)) - { - return defaultValue; - } - - if (configuration[key].type() != Json::intValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - return configuration[key].asInt(); - } - void OrthancInitialize(const char* configurationFile) { boost::mutex::scoped_lock lock(globalMutex_); +#if ORTHANC_SSL_ENABLED == 1 + // https://wiki.openssl.org/index.php/Library_Initialization + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + curl_global_init(CURL_GLOBAL_ALL); +#else + curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL); +#endif + InitializeServerEnumerations(); - defaultDirectory_ = boost::filesystem::current_path(); + + // Read the user-provided configuration ReadGlobalConfiguration(configurationFile); - HttpClient::GlobalInitialize(); + HttpClient::GlobalInitialize(GetGlobalBoolParameterInternal("HttpsVerifyPeers", true), + GetGlobalStringParameterInternal("HttpsCACertificates", "")); RegisterUserMetadata(); RegisterUserContentType(); - DicomServer::InitializeDictionary(); + FromDcmtkBridge::InitializeDictionary(); #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 LOG(WARNING) << "Registering JPEG Lossless codecs"; @@ -256,6 +350,8 @@ LOG(WARNING) << "Registering JPEG codecs"; DJDecoderRegistration::registerCodecs(); #endif + + fontRegistry_.AddFromResource(EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); } @@ -264,7 +360,6 @@ { boost::mutex::scoped_lock lock(globalMutex_); HttpClient::GlobalFinalize(); - configuration_.reset(NULL); #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1 // Unregister JPEG-LS codecs @@ -275,23 +370,28 @@ // Unregister JPEG codecs DJDecoderRegistration::cleanup(); #endif + + curl_global_cleanup(); + +#if ORTHANC_SSL_ENABLED == 1 + // Finalize OpenSSL + // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup + FIPS_mode_set(0); + ENGINE_cleanup(); + CONF_modules_unload(1); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); +#endif } - std::string Configuration::GetGlobalStringParameter(const std::string& parameter, const std::string& defaultValue) { boost::mutex::scoped_lock lock(globalMutex_); - - if (configuration_->isMember(parameter)) - { - return (*configuration_) [parameter].asString(); - } - else - { - return defaultValue; - } + return GetGlobalStringParameterInternal(parameter, defaultValue); } @@ -300,9 +400,17 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (configuration_->isMember(parameter)) + if (configuration_.isMember(parameter)) { - return (*configuration_) [parameter].asInt(); + if (configuration_[parameter].type() != Json::intValue) + { + LOG(ERROR) << "The configuration option \"" << parameter << "\" must be an integer"; + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return configuration_[parameter].asInt(); + } } else { @@ -315,15 +423,7 @@ bool defaultValue) { boost::mutex::scoped_lock lock(globalMutex_); - - if (configuration_->isMember(parameter)) - { - return (*configuration_) [parameter].asBool(); - } - else - { - return defaultValue; - } + return GetGlobalBoolParameterInternal(parameter, defaultValue); } @@ -332,12 +432,13 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("DicomModalities")) + if (!configuration_.isMember("DicomModalities")) { - throw OrthancException(ErrorCode_BadFileFormat); + LOG(ERROR) << "No modality with symbolic name: " << name; + throw OrthancException(ErrorCode_InexistentItem); } - const Json::Value& modalities = (*configuration_) ["DicomModalities"]; + const Json::Value& modalities = configuration_["DicomModalities"]; if (modalities.type() != Json::objectValue || !modalities.isMember(name)) { @@ -349,9 +450,9 @@ { modality.FromJson(modalities[name]); } - catch (OrthancException& e) + catch (OrthancException&) { - LOG(ERROR) << "Syntax error in the definition of modality \"" << name + LOG(ERROR) << "Syntax error in the definition of DICOM modality \"" << name << "\". Please check your configuration file."; throw; } @@ -364,14 +465,15 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("OrthancPeers")) + if (!configuration_.isMember("OrthancPeers")) { - throw OrthancException(ErrorCode_BadFileFormat); + LOG(ERROR) << "No peer with symbolic name: " << name; + throw OrthancException(ErrorCode_InexistentItem); } try { - const Json::Value& modalities = (*configuration_) ["OrthancPeers"]; + const Json::Value& modalities = configuration_["OrthancPeers"]; if (modalities.type() != Json::objectValue || !modalities.isMember(name)) { @@ -381,7 +483,7 @@ peer.FromJson(modalities[name]); } - catch (OrthancException& e) + catch (OrthancException&) { LOG(ERROR) << "Syntax error in the definition of peer \"" << name << "\". Please check your configuration file."; @@ -398,14 +500,15 @@ target.clear(); - if (!configuration_->isMember(parameter)) + if (!configuration_.isMember(parameter)) { return true; } - const Json::Value& modalities = (*configuration_) [parameter]; + const Json::Value& modalities = configuration_[parameter]; if (modalities.type() != Json::objectValue) { + LOG(ERROR) << "Bad format of the \"DicomModalities\" configuration section"; throw OrthancException(ErrorCode_BadFileFormat); } @@ -434,7 +537,8 @@ { if (!ReadKeys(target, "DicomModalities", true)) { - throw OrthancException("Only alphanumeric and dash characters are allowed in the names of the modalities"); + LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of the modalities"; + throw OrthancException(ErrorCode_BadFileFormat); } } @@ -443,7 +547,8 @@ { if (!ReadKeys(target, "OrthancPeers", true)) { - throw OrthancException("Only alphanumeric and dash characters are allowed in the names of Orthanc peers"); + LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of Orthanc peers"; + throw OrthancException(ErrorCode_BadFileFormat); } } @@ -455,15 +560,16 @@ httpServer.ClearUsers(); - if (!configuration_->isMember("RegisteredUsers")) + if (!configuration_.isMember("RegisteredUsers")) { return; } - const Json::Value& users = (*configuration_) ["RegisteredUsers"]; + const Json::Value& users = configuration_["RegisteredUsers"]; if (users.type() != Json::objectValue) { - throw OrthancException("Badly formatted list of users"); + LOG(ERROR) << "Badly formatted list of users"; + throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members usernames = users.getMemberNames(); @@ -516,16 +622,17 @@ target.clear(); - if (!configuration_->isMember(key)) + if (!configuration_.isMember(key)) { return; } - const Json::Value& lst = (*configuration_) [key]; + const Json::Value& lst = configuration_[key]; if (lst.type() != Json::arrayValue) { - throw OrthancException("Badly formatted list of strings"); + LOG(ERROR) << "Badly formatted list of strings"; + throw OrthancException(ErrorCode_BadFileFormat); } for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++) @@ -607,7 +714,8 @@ } else { - throw OrthancException("Unknown modality for AET: " + aet); + LOG(ERROR) << "Unknown modality for AET: " << aet; + throw OrthancException(ErrorCode_InexistentItem); } } @@ -617,14 +725,15 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("DicomModalities")) + if (!configuration_.isMember("DicomModalities")) { - (*configuration_) ["DicomModalities"] = Json::objectValue; + configuration_["DicomModalities"] = Json::objectValue; } - Json::Value& modalities = (*configuration_) ["DicomModalities"]; + Json::Value& modalities = configuration_["DicomModalities"]; if (modalities.type() != Json::objectValue) { + LOG(ERROR) << "Bad file format for modality: " << symbolicName; throw OrthancException(ErrorCode_BadFileFormat); } @@ -640,14 +749,16 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("DicomModalities")) + if (!configuration_.isMember("DicomModalities")) { + LOG(ERROR) << "No modality with symbolic name: " << symbolicName; throw OrthancException(ErrorCode_BadFileFormat); } - Json::Value& modalities = (*configuration_) ["DicomModalities"]; + Json::Value& modalities = configuration_["DicomModalities"]; if (modalities.type() != Json::objectValue) { + LOG(ERROR) << "Bad file format for the \"DicomModalities\" configuration section"; throw OrthancException(ErrorCode_BadFileFormat); } @@ -660,14 +771,16 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("OrthancPeers")) + if (!configuration_.isMember("OrthancPeers")) { - (*configuration_) ["OrthancPeers"] = Json::objectValue; + LOG(ERROR) << "No peer with symbolic name: " << symbolicName; + configuration_["OrthancPeers"] = Json::objectValue; } - Json::Value& peers = (*configuration_) ["OrthancPeers"]; + Json::Value& peers = configuration_["OrthancPeers"]; if (peers.type() != Json::objectValue) { + LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; throw OrthancException(ErrorCode_BadFileFormat); } @@ -683,14 +796,16 @@ { boost::mutex::scoped_lock lock(globalMutex_); - if (!configuration_->isMember("OrthancPeers")) + if (!configuration_.isMember("OrthancPeers")) { + LOG(ERROR) << "No peer with symbolic name: " << symbolicName; throw OrthancException(ErrorCode_BadFileFormat); } - Json::Value& peers = (*configuration_) ["OrthancPeers"]; + Json::Value& peers = configuration_["OrthancPeers"]; if (peers.type() != Json::objectValue) { + LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; throw OrthancException(ErrorCode_BadFileFormat); } @@ -807,4 +922,27 @@ { return CreateFilesystemStorage(); } + + + void Configuration::GetConfiguration(Json::Value& result) + { + boost::mutex::scoped_lock lock(globalMutex_); + result = configuration_; + } + + + void Configuration::FormatConfiguration(std::string& result) + { + Json::Value config; + GetConfiguration(config); + + Json::StyledWriter w; + result = w.write(config); + } + + + const FontRegistry& Configuration::GetFontRegistry() + { + return fontRegistry_; + } }
--- a/OrthancServer/OrthancInitialization.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancInitialization.h Wed Sep 30 13:23:31 2015 +0200 @@ -42,6 +42,7 @@ #include "OrthancPeerParameters.h" #include "IDatabaseWrapper.h" #include "../Core/FileStorage/IStorageArea.h" +#include "../Core/Images/FontRegistry.h" namespace Orthanc { @@ -108,5 +109,11 @@ static IDatabaseWrapper* CreateDatabaseWrapper(); static IStorageArea* CreateStorageArea(); + + static void GetConfiguration(Json::Value& result); + + static void FormatConfiguration(std::string& result); + + static const FontRegistry& GetFontRegistry(); }; }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,9 +33,10 @@ #include "PrecompiledHeadersServer.h" #include "OrthancMoveRequestHandler.h" -#include <glog/logging.h> - #include "OrthancInitialization.h" +#include "FromDcmtkBridge.h" +#include "../Core/DicomFormat/DicomArray.h" +#include "../Core/Logging.h" namespace Orthanc { @@ -47,6 +48,7 @@ { private: ServerContext& context_; + const std::string& localAet_; std::vector<std::string> instances_; size_t position_; RemoteModalityParameters remote_; @@ -56,6 +58,7 @@ const std::string& aet, const std::string& publicId) : context_(context), + localAet_(context.GetDefaultLocalApplicationEntityTitle()), position_(0) { LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\""; @@ -91,7 +94,7 @@ { ReusableDicomUserConnection::Locker locker - (context_.GetReusableDicomUserConnection(), remote_); + (context_.GetReusableDicomUserConnection(), localAet_, remote_); locker.GetConnection().Store(dicom); } @@ -127,10 +130,27 @@ } - IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& aet, - const DicomMap& input) + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& remoteIp, + const std::string& remoteAet) { - LOG(WARNING) << "Move-SCU request received for AET \"" << aet << "\""; + LOG(WARNING) << "Move-SCU request received for AET \"" << targetAet << "\""; + + { + DicomArray query(input); + 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(); + } + } + } + + /** * Retrieve the query level. @@ -158,7 +178,7 @@ LookupIdentifier(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input) || LookupIdentifier(publicId, DICOM_TAG_PATIENT_ID, input)) { - return new OrthancMoveRequestIterator(context_, aet, publicId); + return new OrthancMoveRequestIterator(context_, targetAet, publicId); } else { @@ -203,6 +223,6 @@ throw OrthancException(ErrorCode_BadRequest); } - return new OrthancMoveRequestIterator(context_, aet, publicId); + return new OrthancMoveRequestIterator(context_, targetAet, publicId); } }
--- a/OrthancServer/OrthancMoveRequestHandler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -51,7 +51,9 @@ { } - virtual IMoveRequestIterator* Handle(const std::string& target, - const DicomMap& input); + virtual IMoveRequestIterator* Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& remoteIp, + const std::string& remoteAet); }; }
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,10 +33,14 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/Logging.h" #include "../../Core/Uuid.h" +#include "../FromDcmtkBridge.h" +#include "../ServerContext.h" +#include "../OrthancInitialization.h" -#include <glog/logging.h> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> namespace Orthanc { @@ -275,6 +279,7 @@ modification.Apply(*modified); DicomInstanceToStore toStore; + toStore.SetRestOrigin(call); toStore.SetParsedDicomFile(*modified); @@ -312,7 +317,7 @@ if (context.Store(modifiedInstance, toStore) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << *it; - return; + throw OrthancException(ErrorCode_CannotStoreInstance); } // Sanity checks in debug mode @@ -428,46 +433,425 @@ } - static void CreateDicom(RestApiPostCall& call) + static void StoreCreatedInstance(std::string& id /* out */, + RestApiPostCall& call, + ParsedDicomFile& dicom) + { + DicomInstanceToStore toStore; + toStore.SetRestOrigin(call); + toStore.SetParsedDicomFile(dicom); + + ServerContext& context = OrthancRestApi::GetContext(call); + StoreStatus status = context.Store(id, toStore); + + if (status == StoreStatus_Failure) + { + throw OrthancException(ErrorCode_CannotStoreInstance); + } + } + + + static void CreateDicomV1(ParsedDicomFile& dicom, + RestApiPostCall& call, + const Json::Value& request) { // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}' - Json::Value replacements; - if (call.ParseJsonRequest(replacements) && replacements.isObject()) + assert(request.isObject()); + LOG(WARNING) << "Using a deprecated call to /tools/create-dicom"; + + Json::Value::Members members = request.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) { - ParsedDicomFile dicom; + const std::string& name = members[i]; + if (request[name].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CreateDicomNotString); + } + + std::string value = request[name].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + if (tag == DICOM_TAG_PIXEL_DATA) + { + dicom.EmbedContent(value); + } + else + { + dicom.Replace(tag, value); + } + } + } + - Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) + static void InjectTags(ParsedDicomFile& dicom, + const Json::Value& tags, + bool interpretBinaryTags) + { + if (tags.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + // Inject the user-specified tags + Json::Value::Members members = tags.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + if (tags[name].type() != Json::stringValue) { - const std::string& name = members[i]; - std::string value = replacements[name].asString(); + throw OrthancException(ErrorCode_CreateDicomNotString); + } + + std::string value = tags[name].asString(); + DicomTag tag = FromDcmtkBridge::ParseTag(name); - DicomTag tag = FromDcmtkBridge::ParseTag(name); + if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + if (tag != DICOM_TAG_PATIENT_ID && + tag != DICOM_TAG_ACQUISITION_DATE && + tag != DICOM_TAG_ACQUISITION_TIME && + tag != DICOM_TAG_CONTENT_DATE && + tag != DICOM_TAG_CONTENT_TIME && + tag != DICOM_TAG_INSTANCE_CREATION_DATE && + tag != DICOM_TAG_INSTANCE_CREATION_TIME && + tag != DICOM_TAG_SERIES_DATE && + tag != DICOM_TAG_SERIES_TIME && + tag != DICOM_TAG_STUDY_DATE && + tag != DICOM_TAG_STUDY_TIME && + dicom.HasTag(tag)) + { + throw OrthancException(ErrorCode_CreateDicomOverrideTag); + } + if (tag == DICOM_TAG_PIXEL_DATA) { - dicom.EmbedImage(value); + throw OrthancException(ErrorCode_CreateDicomUseContent); + } + else if (interpretBinaryTags && + boost::starts_with(value, "data:application/octet-stream;base64,")) + { + std::string mime, binary; + Toolbox::DecodeDataUriScheme(mime, binary, value); + dicom.Replace(tag, binary); } else { - dicom.Replace(tag, value); + dicom.Replace(tag, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); + } + } + } + } + + + static void CreateSeries(RestApiPostCall& call, + ParsedDicomFile& base /* in */, + const Json::Value& content, + bool interpretBinaryTags) + { + assert(content.isArray()); + assert(content.size() > 0); + ServerContext& context = OrthancRestApi::GetContext(call); + + base.Replace(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size())); + base.Replace(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1"); + + std::string someInstance; + + try + { + for (Json::ArrayIndex i = 0; i < content.size(); i++) + { + std::auto_ptr<ParsedDicomFile> dicom(base.Clone()); + const Json::Value* payload = NULL; + + if (content[i].type() == Json::stringValue) + { + payload = &content[i]; + } + else if (content[i].type() == Json::objectValue) + { + if (!content[i].isMember("Content")) + { + throw OrthancException(ErrorCode_CreateDicomNoPayload); + } + + payload = &content[i]["Content"]; + + if (content[i].isMember("Tags")) + { + InjectTags(*dicom, content[i]["Tags"], interpretBinaryTags); + } + } + + if (payload == NULL || + payload->type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); + } + + dicom->EmbedContent(payload->asString()); + dicom->Replace(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1)); + dicom->Replace(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1)); + + StoreCreatedInstance(someInstance, call, *dicom); + } + } + catch (OrthancException&) + { + // Error: Remove the newly-created series + + std::string series; + if (context.GetIndex().LookupParent(series, someInstance)) + { + Json::Value dummy; + context.GetIndex().DeleteResource(dummy, series, ResourceType_Series); + } + + throw; + } + + std::string series; + if (context.GetIndex().LookupParent(series, someInstance)) + { + OrthancRestApi::GetApi(call).AnswerStoredResource(call, series, ResourceType_Series, StoreStatus_Success); + } + } + + + static void CreateDicomV2(RestApiPostCall& call, + const Json::Value& request) + { + assert(request.isObject()); + ServerContext& context = OrthancRestApi::GetContext(call); + + if (!request.isMember("Tags") || + request["Tags"].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ParsedDicomFile dicom; + + { + Encoding encoding; + + if (request["Tags"].isMember("SpecificCharacterSet")) + { + const char* tmp = request["Tags"]["SpecificCharacterSet"].asCString(); + if (!GetDicomEncoding(encoding, tmp)) + { + LOG(ERROR) << "Unknown specific character set: " << std::string(tmp); + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + else + { + std::string tmp = Configuration::GetGlobalStringParameter("DefaultEncoding", "Latin1"); + encoding = StringToEncoding(tmp.c_str()); + } + + dicom.SetEncoding(encoding); + } + + ResourceType parentType = ResourceType_Instance; + + if (request.isMember("Parent")) + { + // Locate the parent tags + std::string parent = request["Parent"].asString(); + if (!context.GetIndex().LookupResourceType(parentType, parent)) + { + throw OrthancException(ErrorCode_CreateDicomBadParent); + } + + if (parentType == ResourceType_Instance) + { + throw OrthancException(ErrorCode_CreateDicomParentIsInstance); + } + + // Select one existing child instance of the parent resource, to + // retrieve all its tags + Json::Value siblingTags; + + { + // Retrieve all the instances of the parent resource + std::list<std::string> siblingInstances; + context.GetIndex().GetChildInstances(siblingInstances, parent); + + if (siblingInstances.empty()) + { + // Error: No instance (should never happen) + throw OrthancException(ErrorCode_InternalError); + } + + context.ReadJson(siblingTags, siblingInstances.front()); + } + + + // Choose the same encoding as the parent resource + { + static const char* SPECIFIC_CHARACTER_SET = "0008,0005"; + + if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) + { + Encoding encoding; + if (!siblingTags[SPECIFIC_CHARACTER_SET].isMember("Value") || + siblingTags[SPECIFIC_CHARACTER_SET]["Value"].type() != Json::stringValue || + !GetDicomEncoding(encoding, siblingTags[SPECIFIC_CHARACTER_SET]["Value"].asCString())) + { + throw OrthancException(ErrorCode_CreateDicomParentEncoding); + } + + dicom.SetEncoding(encoding); } } - DicomInstanceToStore toStore; - toStore.SetParsedDicomFile(dicom); + + // Retrieve the tags for all the parent modules + typedef std::set<DicomTag> ModuleTags; + ModuleTags moduleTags; + + ResourceType type = parentType; + for (;;) + { + DicomTag::AddTagsForModule(moduleTags, GetModule(type)); + + if (type == ResourceType_Patient) + { + break; // We're done + } + + // Go up + std::string tmp; + if (!context.GetIndex().LookupParent(tmp, parent)) + { + throw OrthancException(ErrorCode_InternalError); + } + + parent = tmp; + type = GetParentResourceType(type); + } + + for (ModuleTags::const_iterator it = moduleTags.begin(); + it != moduleTags.end(); ++it) + { + std::string t = it->Format(); + if (siblingTags.isMember(t)) + { + const Json::Value& tag = siblingTags[t]; + if (tag["Type"] == "Null") + { + dicom.Replace(*it, ""); + } + else if (tag["Type"] == "String") + { + std::string value = tag["Value"].asString(); + dicom.Replace(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); + } + } + } + } + + + bool interpretBinaryTags = true; + if (request.isMember("InterpretBinaryTags")) + { + const Json::Value& v = request["InterpretBinaryTags"]; + if (v.type() != Json::booleanValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + interpretBinaryTags = v.asBool(); + } + + + // Inject time-related information + std::string date, time; + Toolbox::GetNowDicom(date, time); + dicom.Replace(DICOM_TAG_ACQUISITION_DATE, date); + dicom.Replace(DICOM_TAG_ACQUISITION_TIME, time); + dicom.Replace(DICOM_TAG_CONTENT_DATE, date); + dicom.Replace(DICOM_TAG_CONTENT_TIME, time); + dicom.Replace(DICOM_TAG_INSTANCE_CREATION_DATE, date); + dicom.Replace(DICOM_TAG_INSTANCE_CREATION_TIME, time); + + if (parentType == ResourceType_Patient || + parentType == ResourceType_Study || + parentType == ResourceType_Instance /* no parent */) + { + dicom.Replace(DICOM_TAG_SERIES_DATE, date); + dicom.Replace(DICOM_TAG_SERIES_TIME, time); + } + + if (parentType == ResourceType_Patient || + parentType == ResourceType_Instance /* no parent */) + { + dicom.Replace(DICOM_TAG_STUDY_DATE, date); + dicom.Replace(DICOM_TAG_STUDY_TIME, time); + } + + + InjectTags(dicom, request["Tags"], interpretBinaryTags); + + + // Inject the content (either an image, or a PDF file) + if (request.isMember("Content")) + { + const Json::Value& content = request["Content"]; + + if (content.type() == Json::stringValue) + { + dicom.EmbedContent(request["Content"].asString()); + + } + else if (content.type() == Json::arrayValue) + { + if (content.size() > 0) + { + // Let's create a series instead of a single instance + CreateSeries(call, dicom, content, interpretBinaryTags); + return; + } + } + else + { + throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); + } + } + + std::string id; + StoreCreatedInstance(id, call, dicom); + OrthancRestApi::GetApi(call).AnswerStoredResource(call, id, ResourceType_Instance, StoreStatus_Success); + + return; + } + + + static void CreateDicom(RestApiPostCall& call) + { + Json::Value request; + if (!call.ParseJsonRequest(request) || + !request.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (request.isMember("Tags")) + { + CreateDicomV2(call, request); + } + else + { + // Compatibility with Orthanc <= 0.9.3 + ParsedDicomFile dicom; + CreateDicomV1(dicom, call, request); std::string id; - StoreStatus status = OrthancRestApi::GetContext(call).Store(id, toStore); - - if (status == StoreStatus_Failure) - { - LOG(ERROR) << "Error while storing a manually-created instance"; - return; - } - - OrthancRestApi::GetApi(call).AnswerStoredInstance(call, id, status); + StoreCreatedInstance(id, call, dicom); + OrthancRestApi::GetApi(call).AnswerStoredResource(call, id, ResourceType_Instance, StoreStatus_Success); } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,14 +33,15 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/Logging.h" #include "../DicomModification.h" - -#include <glog/logging.h> +#include "../ServerContext.h" namespace Orthanc { - void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call, + void OrthancRestApi::AnswerStoredResource(RestApiPostCall& call, const std::string& publicId, + ResourceType resourceType, StoreStatus status) const { Json::Value result = Json::objectValue; @@ -48,7 +49,7 @@ if (status != StoreStatus_Failure) { result["ID"] = publicId; - result["Path"] = GetBasePath(ResourceType_Instance, publicId); + result["Path"] = GetBasePath(resourceType, publicId); } result["Status"] = EnumerationToString(status); @@ -72,21 +73,24 @@ { ServerContext& context = OrthancRestApi::GetContext(call); - const std::string& postData = call.GetPostBody(); - if (postData.size() == 0) + if (call.GetBodySize() == 0) { return; } - LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; + LOG(INFO) << "Receiving a DICOM file of " << call.GetBodySize() << " bytes through HTTP"; + + // TODO Remove unneccessary memcpy + std::string postData(call.GetBodyData(), call.GetBodySize()); DicomInstanceToStore toStore; + toStore.SetRestOrigin(call); toStore.SetBuffer(postData); std::string publicId; StoreStatus status = context.Store(publicId, toStore); - OrthancRestApi::GetApi(call).AnswerStoredInstance(call, publicId, status); + OrthancRestApi::GetApi(call).AnswerStoredResource(call, publicId, ResourceType_Instance, status); } @@ -112,4 +116,16 @@ Register("/tools/reset", ResetOrthanc); Register("/instances/{id}/frames/{frame}", RestApi::AutoListChildren); } + + + ServerContext& OrthancRestApi::GetContext(RestApiCall& call) + { + return GetApi(call).context_; + } + + + ServerIndex& OrthancRestApi::GetIndex(RestApiCall& call) + { + return GetContext(call).GetIndex(); + } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Wed Sep 30 13:23:31 2015 +0200 @@ -34,12 +34,14 @@ #include "../../Core/RestApi/RestApi.h" #include "../DicomModification.h" -#include "../ServerContext.h" #include <set> namespace Orthanc { + class ServerContext; + class ServerIndex; + class OrthancRestApi : public RestApi { public: @@ -76,18 +78,13 @@ return dynamic_cast<OrthancRestApi&>(call.GetContext()); } - static ServerContext& GetContext(RestApiCall& call) - { - return GetApi(call).context_; - } + static ServerContext& GetContext(RestApiCall& call); + + static ServerIndex& GetIndex(RestApiCall& call); - static ServerIndex& GetIndex(RestApiCall& call) - { - return GetContext(call).GetIndex(); - } - - void AnswerStoredInstance(RestApiPostCall& call, + void AnswerStoredResource(RestApiPostCall& call, const std::string& publicId, + ResourceType resourceType, StoreStatus status) const; static bool ParseModifyRequest(DicomModification& target,
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -36,9 +36,10 @@ #include "../DicomDirWriter.h" #include "../../Core/Compression/HierarchicalZipWriter.h" #include "../../Core/HttpServer/FilesystemHttpSender.h" +#include "../../Core/Logging.h" #include "../../Core/Uuid.h" +#include "../ServerContext.h" -#include <glog/logging.h> #include <stdio.h> #if defined(_MSC_VER) @@ -56,27 +57,34 @@ ResourceType resourceType) { std::string s; + const Json::Value& tags = resource["MainDicomTags"]; switch (resourceType) { case ResourceType_Patient: { - std::string p = resource["MainDicomTags"]["PatientID"].asString(); - std::string n = resource["MainDicomTags"]["PatientName"].asString(); + std::string p = tags["PatientID"].asString(); + std::string n = tags["PatientName"].asString(); s = p + " " + n; break; } case ResourceType_Study: { - s = resource["MainDicomTags"]["StudyDescription"].asString(); + std::string p; + if (tags.isMember("AccessionNumber")) + { + p = tags["AccessionNumber"].asString() + " "; + } + + s = p + tags["StudyDescription"].asString(); break; } case ResourceType_Series: { - std::string d = resource["MainDicomTags"]["SeriesDescription"].asString(); - std::string m = resource["MainDicomTags"]["Modality"].asString(); + std::string d = tags["SeriesDescription"].asString(); + std::string m = tags["Modality"].asString(); s = m + " " + d; break; } @@ -191,7 +199,7 @@ case ResourceType_Series: { // Create a filename prefix, depending on the modality - char format[16] = "%08d"; + char format[24] = "%08d.dcm"; if (resource["MainDicomTags"].isMember("Modality")) { @@ -199,15 +207,15 @@ if (modality.size() == 1) { - snprintf(format, sizeof(format) - 1, "%c%%07d", toupper(modality[0])); + snprintf(format, sizeof(format) - 1, "%c%%07d.dcm", toupper(modality[0])); } else if (modality.size() >= 2) { - snprintf(format, sizeof(format) - 1, "%c%c%%06d", toupper(modality[0]), toupper(modality[1])); + snprintf(format, sizeof(format) - 1, "%c%c%%06d.dcm", toupper(modality[0]), toupper(modality[1])); } } - char filename[16]; + char filename[24]; for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++) { @@ -288,12 +296,12 @@ } // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath().c_str()); + FilesystemHttpSender sender(tmp.GetPath()); sender.SetContentType("application/zip"); - sender.SetDownloadFilename(id + ".zip"); + sender.SetContentFilename(id + ".zip"); // Send the ZIP - call.GetOutput().AnswerFile(sender); + call.GetOutput().AnswerStream(sender); // The temporary file is automatically removed thanks to the RAII } @@ -349,12 +357,12 @@ } // Prepare the sending of the ZIP file - FilesystemHttpSender sender(tmp.GetPath().c_str()); + FilesystemHttpSender sender(tmp.GetPath()); sender.SetContentType("application/zip"); - sender.SetDownloadFilename(id + ".zip"); + sender.SetContentFilename(id + ".zip"); // Send the ZIP - call.GetOutput().AnswerFile(sender); + call.GetOutput().AnswerStream(sender); // The temporary file is automatically removed thanks to the RAII }
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,7 +33,7 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include <glog/logging.h> +#include "../ServerContext.h" namespace Orthanc {
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -35,46 +35,27 @@ #include "../OrthancInitialization.h" #include "../../Core/HttpClient.h" +#include "../../Core/Logging.h" #include "../FromDcmtkBridge.h" #include "../Scheduler/ServerJob.h" #include "../Scheduler/StoreScuCommand.h" #include "../Scheduler/StorePeerCommand.h" - -#include <glog/logging.h> +#include "../QueryRetrieveHandler.h" +#include "../ServerToolbox.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; - } - + /*************************************************************************** + * DICOM C-Echo SCU + ***************************************************************************/ static void DicomEcho(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); try { @@ -94,148 +75,246 @@ } + + /*************************************************************************** + * DICOM C-Find SCU => DEPRECATED! + ***************************************************************************/ + + static bool MergeQueryAndTemplate(DicomMap& result, + const char* postData, + size_t postSize) + { + Json::Value query; + Json::Reader reader; + + if (!reader.parse(postData, postData + postSize, 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 FindPatient(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the patient + DicomMap s; + fields.ExtractPatientInformation(s); + connection.Find(result, ResourceType_Patient, s); + } + + + static void FindStudy(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the study + DicomMap s; + fields.ExtractStudyInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_MODALITIES_IN_STUDY); + + connection.Find(result, ResourceType_Study, s); + } + + static void FindSeries(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the series + DicomMap s; + fields.ExtractSeriesInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); + + connection.Find(result, ResourceType_Series, s); + } + + static void FindInstance(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the instance + DicomMap s; + fields.ExtractInstanceInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); + s.CopyTagIfExists(fields, DICOM_TAG_SERIES_INSTANCE_UID); + + connection.Find(result, ResourceType_Instance, s); + } + + static void DicomFindPatient(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindPatientTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetBodyData(), call.GetBodySize())) { return; } + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); DicomFindAnswers answers; - locker.GetConnection().FindPatient(answers, m); + FindPatient(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindStudy(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindStudyTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetBodyData(), call.GetBodySize())) { return; } - if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) { return; } + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); DicomFindAnswers answers; - locker.GetConnection().FindStudy(answers, m); + FindStudy(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindSeries(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindSeriesTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetBodyData(), call.GetBodySize())) { 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) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) { return; } + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); DicomFindAnswers answers; - locker.GetConnection().FindSeries(answers, m); + FindSeries(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindInstance(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindInstanceTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindInstanceTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetBodyData(), call.GetBodySize())) { 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) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || + fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) { return; } + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); DicomFindAnswers answers; - locker.GetConnection().FindInstance(answers, m); + FindInstance(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } + static void DicomFind(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); DicomMap m; DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize())) { return; } + const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); RemoteModalityParameters remote = Configuration::GetModalityUsingSymbolicName(call.GetUriComponent("id", "")); - ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); + ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), localAet, remote); DicomFindAnswers patients; - locker.GetConnection().FindPatient(patients, m); + FindPatient(patients, locker.GetConnection(), 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)); + FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i), true); DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize())) { return; } m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); DicomFindAnswers studies; - locker.GetConnection().FindStudy(studies, m); + FindStudy(studies, locker.GetConnection(), m); patient["Studies"] = Json::arrayValue; @@ -243,10 +322,10 @@ for (size_t j = 0; j < studies.GetSize(); j++) { Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); + FromDcmtkBridge::ToJson(study, studies.GetAnswer(j), true); DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize())) { return; } @@ -254,14 +333,14 @@ m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); DicomFindAnswers series; - locker.GetConnection().FindSeries(series, m); + FindSeries(series, locker.GetConnection(), 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)); + FromDcmtkBridge::ToJson(series2, series.GetAnswer(k), true); study["Series"].append(series2); } @@ -275,19 +354,228 @@ } - static bool GetInstancesToExport(std::list<std::string>& instances, - const std::string& remote, - RestApiPostCall& call) + + /*************************************************************************** + * DICOM C-Find and C-Move SCU => Recommended since Orthanc 0.9.0 + ***************************************************************************/ + + static void DicomQuery(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + Json::Value request; + + if (call.ParseJsonRequest(request) && + request.type() == Json::objectValue && + request.isMember("Level") && request["Level"].type() == Json::stringValue && + (!request.isMember("Query") || request["Query"].type() == Json::objectValue)) + { + std::auto_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); + + handler->SetModality(call.GetUriComponent("id", "")); + handler->SetLevel(StringToResourceType(request["Level"].asString().c_str())); + + if (request.isMember("Query")) + { + Json::Value::Members tags = request["Query"].getMemberNames(); + for (size_t i = 0; i < tags.size(); i++) + { + handler->SetQuery(FromDcmtkBridge::ParseTag(tags[i].c_str()), + request["Query"][tags[i]].asString()); + } + } + + handler->Run(); + + std::string s = context.GetQueryRetrieveArchive().Add(handler.release()); + Json::Value result = Json::objectValue; + result["ID"] = s; + result["Path"] = "/queries/" + s; + call.GetOutput().AnswerJson(result); + } + } + + + static void ListQueries(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); - std::string stripped = Toolbox::StripSpaces(call.GetPostBody()); + std::list<std::string> queries; + context.GetQueryRetrieveArchive().List(queries); + + Json::Value result = Json::arrayValue; + for (std::list<std::string>::const_iterator + it = queries.begin(); it != queries.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + + namespace + { + class QueryAccessor + { + private: + ServerContext& context_; + SharedArchive::Accessor accessor_; + QueryRetrieveHandler& handler_; + + public: + QueryAccessor(RestApiCall& call) : + context_(OrthancRestApi::GetContext(call)), + accessor_(context_.GetQueryRetrieveArchive(), call.GetUriComponent("id", "")), + handler_(dynamic_cast<QueryRetrieveHandler&>(accessor_.GetItem())) + { + } + + QueryRetrieveHandler* operator->() + { + return &handler_; + } + }; + + static void AnswerDicomMap(RestApiCall& call, + const DicomMap& value, + bool simplify) + { + Json::Value full = Json::objectValue; + FromDcmtkBridge::ToJson(full, value, simplify); + call.GetOutput().AnswerJson(full); + } + } + + + static void ListQueryAnswers(RestApiGetCall& call) + { + QueryAccessor query(call); + size_t count = query->GetAnswerCount(); + + Json::Value result = Json::arrayValue; + for (size_t i = 0; i < count; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + call.GetOutput().AnswerJson(result); + } + + + static void GetQueryOneAnswer(RestApiGetCall& call) + { + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + QueryAccessor query(call); + AnswerDicomMap(call, query->GetAnswer(index), call.HasArgument("simplify")); + } + + + static void RetrieveOneAnswer(RestApiPostCall& call) + { + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + + std::string modality; + call.BodyToString(modality); + + LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; + + QueryAccessor query(call); + query->Retrieve(modality, index); + + // Retrieve has succeeded + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + static void RetrieveAllAnswers(RestApiPostCall& call) + { + std::string modality; + call.BodyToString(modality); + + LOG(WARNING) << "Driving C-Move SCU on modality: " << modality; + + QueryAccessor query(call); + query->Retrieve(modality); + + // Retrieve has succeeded + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + static void GetQueryArguments(RestApiGetCall& call) + { + QueryAccessor query(call); + AnswerDicomMap(call, query->GetQuery(), call.HasArgument("simplify")); + } + + + static void GetQueryLevel(RestApiGetCall& call) + { + QueryAccessor query(call); + call.GetOutput().AnswerBuffer(EnumerationToString(query->GetLevel()), "text/plain"); + } + + + static void GetQueryModality(RestApiGetCall& call) + { + QueryAccessor query(call); + call.GetOutput().AnswerBuffer(query->GetModalitySymbolicName(), "text/plain"); + } + + + static void DeleteQuery(RestApiDeleteCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + context.GetQueryRetrieveArchive().Remove(call.GetUriComponent("id", "")); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + static void ListQueryOperations(RestApiGetCall& call) + { + // Ensure that the query of interest does exist + QueryAccessor query(call); + + RestApi::AutoListChildren(call); + } + + + static void ListQueryAnswerOperations(RestApiGetCall& call) + { + // Ensure that the query of interest does exist + QueryAccessor query(call); + + // Ensure that the answer of interest does exist + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + query->GetAnswer(index); + + RestApi::AutoListChildren(call); + } + + + + + /*************************************************************************** + * DICOM C-Store SCU + ***************************************************************************/ + + static bool GetInstancesToExport(Json::Value& otherArguments, + std::list<std::string>& instances, + const std::string& remote, + RestApiPostCall& call) + { + otherArguments = Json::objectValue; + ServerContext& context = OrthancRestApi::GetContext(call); Json::Value request; - if (Toolbox::IsSHA1(stripped)) + if (Toolbox::IsSHA1(call.GetBodyData(), call.GetBodySize())) { + std::string s; + call.BodyToString(s); + // This is for compatibility with Orthanc <= 0.5.1. - request = stripped; + request = Json::arrayValue; + request.append(Toolbox::StripSpaces(s)); } else if (!call.ParseJsonRequest(request)) { @@ -297,47 +585,64 @@ if (request.isString()) { - if (Configuration::GetGlobalBoolParameter("LogExportedResources", true)) - { - context.GetIndex().LogExportedResource(request.asString(), remote); - } - - context.GetIndex().GetChildInstances(instances, request.asString()); + std::string item = request.asString(); + request = Json::arrayValue; + request.append(item); } - 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; - } - - if (Configuration::GetGlobalBoolParameter("LogExportedResources", true)) - { - 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); - } - } + const Json::Value* resources; + if (request.isArray()) + { + resources = &request; } else { - // Neither a string, nor a list of strings. Bad request. - return false; + if (request.type() != Json::objectValue || + !request.isMember("Resources")) + { + return false; + } + + resources = &request["Resources"]; + if (!resources->isArray()) + { + return false; + } + + // Copy the remaining arguments + Json::Value::Members members = request.getMemberNames(); + for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) + { + otherArguments[members[i]] = request[members[i]]; + } + } + + for (Json::Value::ArrayIndex i = 0; i < resources->size(); i++) + { + if (!(*resources) [i].isString()) + { + return false; + } + + std::string stripped = Toolbox::StripSpaces((*resources) [i].asString()); + if (!Toolbox::IsSHA1(stripped)) + { + return false; + } + + if (Configuration::GetGlobalBoolParameter("LogExportedResources", true)) + { + 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); + } } return true; @@ -350,19 +655,26 @@ std::string remote = call.GetUriComponent("id", ""); + Json::Value request; std::list<std::string> instances; - if (!GetInstancesToExport(instances, remote, call)) + if (!GetInstancesToExport(request, instances, remote, call)) { return; } + std::string localAet = context.GetDefaultLocalApplicationEntityTitle(); + if (request.isMember("LocalAet")) + { + localAet = request["LocalAet"].asString(); + } + RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote); ServerJob job; for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) { - job.AddCommand(new StoreScuCommand(context, p, false)).AddInput(*it); + job.AddCommand(new StoreScuCommand(context, localAet, p, false)).AddInput(*it); } job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\""); @@ -379,7 +691,9 @@ } - // Orthanc Peers ------------------------------------------------------------ + /*************************************************************************** + * Orthanc Peers => Store client + ***************************************************************************/ static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, const std::string& id) @@ -420,8 +734,9 @@ std::string remote = call.GetUriComponent("id", ""); + Json::Value request; std::list<std::string> instances; - if (!GetInstancesToExport(instances, remote, call)) + if (!GetInstancesToExport(request, instances, remote, call)) { return; } @@ -491,7 +806,7 @@ { Json::Value json; Json::Reader reader; - if (reader.parse(call.GetPutBody(), json)) + if (reader.parse(call.GetBodyData(), call.GetBodyData() + call.GetBodySize(), json)) { RemoteModalityParameters modality; modality.FromJson(json); @@ -512,7 +827,7 @@ { Json::Value json; Json::Reader reader; - if (reader.parse(call.GetPutBody(), json)) + if (reader.parse(call.GetBodyData(), call.GetBodyData() + call.GetBodySize(), json)) { OrthancPeerParameters peer; peer.FromJson(json); @@ -543,6 +858,20 @@ Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); + // For Query/Retrieve + Register("/modalities/{id}/query", DicomQuery); + Register("/queries", ListQueries); + Register("/queries/{id}", DeleteQuery); + Register("/queries/{id}", ListQueryOperations); + Register("/queries/{id}/answers", ListQueryAnswers); + Register("/queries/{id}/answers/{index}", ListQueryAnswerOperations); + Register("/queries/{id}/answers/{index}/content", GetQueryOneAnswer); + Register("/queries/{id}/answers/{index}/retrieve", RetrieveOneAnswer); + Register("/queries/{id}/level", GetQueryLevel); + Register("/queries/{id}/modality", GetQueryModality); + Register("/queries/{id}/query", GetQueryArguments); + Register("/queries/{id}/retrieve", RetrieveAllAnswers); + Register("/peers", ListPeers); Register("/peers/{id}", ListPeerOperations); Register("/peers/{id}", UpdatePeer);
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,21 +33,80 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" +#include "../../Core/Logging.h" #include "../ServerToolbox.h" #include "../FromDcmtkBridge.h" +#include "../ResourceFinder.h" +#include "../DicomFindQuery.h" +#include "../ServerContext.h" -#include <glog/logging.h> namespace Orthanc { // List all the patients, studies, series or instances ---------------------- + static void AnswerListOfResources(RestApiOutput& output, + ServerIndex& index, + const std::list<std::string>& resources, + ResourceType level, + bool expand) + { + Json::Value answer = Json::arrayValue; + + for (std::list<std::string>::const_iterator + resource = resources.begin(); resource != resources.end(); ++resource) + { + if (expand) + { + Json::Value item; + if (index.LookupResource(item, *resource, level)) + { + answer.append(item); + } + } + else + { + answer.append(*resource); + } + } + + output.AnswerJson(answer); + } + + template <enum ResourceType resourceType> static void ListResources(RestApiGetCall& call) { - Json::Value result; - OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); - call.GetOutput().AnswerJson(result); + ServerIndex& index = OrthancRestApi::GetIndex(call); + + std::list<std::string> result; + + if (call.HasArgument("limit") || + call.HasArgument("since")) + { + if (!call.HasArgument("limit")) + { + LOG(ERROR) << "Missing \"limit\" argument for GET request against: " << call.FlattenUri(); + throw OrthancException(ErrorCode_BadRequest); + } + + if (!call.HasArgument("since")) + { + LOG(ERROR) << "Missing \"since\" argument for GET request against: " << call.FlattenUri(); + throw OrthancException(ErrorCode_BadRequest); + } + + size_t since = boost::lexical_cast<size_t>(call.GetArgument("since", "")); + size_t limit = boost::lexical_cast<size_t>(call.GetArgument("limit", "")); + index.GetAllUuids(result, resourceType, since, limit); + } + else + { + index.GetAllUuids(result, resourceType); + } + + + AnswerListOfResources(call.GetOutput(), index, result, resourceType, call.HasArgument("expand")); } template <enum ResourceType resourceType> @@ -86,14 +145,17 @@ ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); - std::string s = Toolbox::StripSpaces(call.GetPutBody()); - if (s == "0") + std::string body; + call.BodyToString(body); + body = Toolbox::StripSpaces(body); + + if (body == "0") { context.GetIndex().SetProtectedPatient(publicId, false); call.GetOutput().AnswerBuffer("", "text/plain"); } - else if (s == "1") + else if (body == "1") { context.GetIndex().SetProtectedPatient(publicId, true); call.GetOutput().AnswerBuffer("", "text/plain"); @@ -125,7 +187,9 @@ std::string dicom; context.ReadFile(dicom, publicId, FileContentType_Dicom); - Toolbox::WriteFile(dicom, call.GetPostBody()); + std::string target; + call.BodyToString(target); + Toolbox::WriteFile(dicom, target); call.GetOutput().AnswerBuffer("{}", "application/json"); } @@ -359,7 +423,9 @@ std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); - std::string value = call.GetPutBody(); + + std::string value; + call.BodyToString(value); if (metadata >= MetadataType_StartUser && metadata <= MetadataType_EndUser) @@ -569,12 +635,10 @@ 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())) + context.AddAttachment(publicId, StringToContentType(name), call.GetBodyData(), call.GetBodySize())) { call.GetOutput().AnswerBuffer("{}", "application/json"); } @@ -731,7 +795,7 @@ typedef std::set<DicomTag> ModuleTags; ModuleTags moduleTags; - DicomTag::GetTagsForModule(moduleTags, module); + DicomTag::AddTagsForModule(moduleTags, module); Json::Value tags; @@ -790,7 +854,8 @@ { typedef std::list< std::pair<ResourceType, std::string> > Resources; - std::string tag = call.GetPostBody(); + std::string tag; + call.BodyToString(tag); Resources resources; OrthancRestApi::GetIndex(call).LookupIdentifier(resources, tag); @@ -815,6 +880,61 @@ } + static void Find(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + if (call.ParseJsonRequest(request) && + request.type() == Json::objectValue && + request.isMember("Level") && + request.isMember("Query") && + request["Level"].type() == Json::stringValue && + request["Query"].type() == Json::objectValue && + (!request.isMember("CaseSensitive") || request["CaseSensitive"].type() == Json::booleanValue)) + { + bool expand = false; + if (request.isMember("Expand")) + { + expand = request["Expand"].asBool(); + } + + bool caseSensitive = false; + if (request.isMember("CaseSensitive")) + { + caseSensitive = request["CaseSensitive"].asBool(); + } + + std::string level = request["Level"].asString(); + + DicomFindQuery query; + query.SetLevel(StringToResourceType(level.c_str())); + + Json::Value::Members members = request["Query"].getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + if (request["Query"][members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + query.SetConstraint(FromDcmtkBridge::ParseTag(members[i]), + request["Query"][members[i]].asString(), + caseSensitive); + } + + std::list<std::string> resources; + ResourceFinder finder(context); + finder.Apply(resources, query); + AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, query.GetLevel(), expand); + } + else + { + throw OrthancException(ErrorCode_BadRequest); + } + } + + template <enum ResourceType start, enum ResourceType end> static void GetChildResources(RestApiGetCall& call) @@ -836,23 +956,7 @@ b.splice(b.begin(), c); } - switch (type) - { - case ResourceType_Patient: - type = ResourceType_Study; - break; - - case ResourceType_Study: - type = ResourceType_Series; - break; - - case ResourceType_Series: - type = ResourceType_Instance; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } + type = GetChildResourceType(type); a.clear(); a.splice(a.begin(), b); @@ -933,13 +1037,7 @@ } current = parent; - switch (currentType) - { - case ResourceType_Instance: currentType = ResourceType_Series; break; - case ResourceType_Series: currentType = ResourceType_Study; break; - case ResourceType_Study: currentType = ResourceType_Patient; break; - default: throw OrthancException(ErrorCode_InternalError); - } + currentType = GetParentResourceType(currentType); } assert(currentType == end); @@ -952,6 +1050,20 @@ } + static void ExtractPdf(RestApiGetCall& call) + { + const std::string id = call.GetUriComponent("id", ""); + + std::string pdf; + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); + + if (locker.GetDicom().ExtractPdf(pdf)) + { + call.GetOutput().AnswerBuffer(pdf, "application/pdf"); + return; + } + } + void OrthancRestApi::RegisterResources() { @@ -995,6 +1107,7 @@ Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); + Register("/instances/{id}/pdf", ExtractPdf); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); @@ -1022,6 +1135,7 @@ Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); Register("/tools/lookup", Lookup); + Register("/tools/find", Find); Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>); Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>);
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -37,8 +37,7 @@ #include "../FromDcmtkBridge.h" #include "../../Plugins/Engine/PluginsManager.h" #include "../../Plugins/Engine/OrthancPlugins.h" - -#include <glog/logging.h> +#include "../ServerContext.h" namespace Orthanc @@ -54,9 +53,36 @@ { Json::Value result = Json::objectValue; - result["Version"] = ORTHANC_VERSION; + std::string dbVersion = OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0"); + + result["DatabaseVersion"] = boost::lexical_cast<int>(dbVersion); + result["DicomAet"] = Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"); + result["DicomPort"] = Configuration::GetGlobalIntegerParameter("DicomPort", 4242); + result["HttpPort"] = Configuration::GetGlobalIntegerParameter("HttpPort", 8042); result["Name"] = Configuration::GetGlobalStringParameter("Name", ""); - result["DatabaseVersion"] = boost::lexical_cast<int>(OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0")); + result["Version"] = ORTHANC_VERSION; + + result["StorageAreaPlugin"] = Json::nullValue; + result["DatabaseBackendPlugin"] = Json::nullValue; + +#if ORTHANC_PLUGINS_ENABLED == 1 + result["PluginsEnabled"] = true; + const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); + + if (plugins.HasStorageArea()) + { + std::string p = plugins.GetStorageAreaLibrary().GetPath(); + result["StorageAreaPlugin"] = boost::filesystem::canonical(p).string(); + } + + if (plugins.HasDatabaseBackend()) + { + std::string p = plugins.GetDatabaseBackendLibrary().GetPath(); + result["DatabaseBackendPlugin"] = boost::filesystem::canonical(p).string(); + } +#else + result["PluginsEnabled"] = false; +#endif call.GetOutput().AnswerJson(result); } @@ -94,9 +120,12 @@ std::string result; ServerContext& context = OrthancRestApi::GetContext(call); + std::string command; + call.BodyToString(command); + { - ServerContext::LuaContextLocker locker(context); - locker.GetLua().Execute(result, call.GetPostBody()); + LuaScripting::Locker locker(context.GetLua()); + locker.GetLua().Execute(result, command); } call.GetOutput().AnswerBuffer(result, "text/plain"); @@ -126,14 +155,16 @@ if (OrthancRestApi::GetContext(call).HasPlugins()) { +#if ORTHANC_PLUGINS_ENABLED == 1 std::list<std::string> plugins; - OrthancRestApi::GetContext(call).GetPluginsManager().ListPlugins(plugins); + OrthancRestApi::GetContext(call).GetPlugins().GetManager().ListPlugins(plugins); for (std::list<std::string>::const_iterator it = plugins.begin(); it != plugins.end(); ++it) { v.append(*it); } +#endif } call.GetOutput().AnswerJson(v); @@ -147,7 +178,8 @@ return; } - const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPluginsManager(); +#if ORTHANC_PLUGINS_ENABLED == 1 + const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPlugins().GetManager(); std::string id = call.GetUriComponent("id", ""); if (manager.HasPlugin(id)) @@ -156,11 +188,21 @@ v["ID"] = id; v["Version"] = manager.GetPluginVersion(id); - const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetOrthancPlugins(); + const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); const char *c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_RootUri); if (c != NULL) { - v["RootUri"] = c; + std::string root = c; + if (!root.empty()) + { + // Turn the root URI into a URI relative to "/app/explorer.js" + if (root[0] == '/') + { + root = ".." + root; + } + + v["RootUri"] = root; + } } c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_Description); @@ -174,6 +216,7 @@ call.GetOutput().AnswerJson(v); } +#endif } @@ -183,11 +226,12 @@ if (OrthancRestApi::GetContext(call).HasPlugins()) { - const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPluginsManager(); - const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetOrthancPlugins(); +#if ORTHANC_PLUGINS_ENABLED == 1 + const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); + const PluginsManager& manager = plugins.GetManager(); std::list<std::string> lst; - OrthancRestApi::GetContext(call).GetPluginsManager().ListPlugins(lst); + manager.ListPlugins(lst); for (std::list<std::string>::const_iterator it = lst.begin(); it != lst.end(); ++it) @@ -199,6 +243,7 @@ s += std::string(tmp) + "\n\n"; } } +#endif } call.GetOutput().AnswerBuffer(s, "application/javascript");
--- a/OrthancServer/ParsedDicomFile.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ParsedDicomFile.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -84,15 +84,16 @@ #include "FromDcmtkBridge.h" #include "ToDcmtkBridge.h" #include "Internals/DicomImageDecoder.h" +#include "../Core/Logging.h" #include "../Core/Toolbox.h" #include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Images/ImageBuffer.h" +#include "../Core/Images/PngWriter.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomString.h" #include "../Core/DicomFormat/DicomNullValue.h" #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" -#include "../Core/ImageFormats/PngReader.h" +#include "../Core/Images/PngReader.h" #include <list> #include <limits> @@ -106,6 +107,7 @@ #include <dcmtk/dcmdata/dcistrmb.h> #include <dcmtk/dcmdata/dcuid.h> #include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmdata/dcvrae.h> #include <dcmtk/dcmdata/dcvras.h> @@ -134,7 +136,6 @@ #include <boost/math/special_functions/round.hpp> -#include <glog/logging.h> #include <dcmtk/dcmdata/dcostrmb.h> @@ -255,46 +256,94 @@ } - static void AnswerDicomField(RestApiOutput& output, - DcmElement& element, - E_TransferSyntax transferSyntax) + namespace { - // This element is nor a sequence, neither a pixel-data - std::string buffer; - buffer.resize(65536); - Uint32 length = element.getLength(transferSyntax); - Uint32 offset = 0; + class DicomFieldStream : public IHttpStreamAnswer + { + private: + DcmElement& element_; + uint32_t length_; + uint32_t offset_; + std::string chunk_; + size_t chunkSize_; + + public: + DicomFieldStream(DcmElement& element, + E_TransferSyntax transferSyntax) : + element_(element), + length_(element.getLength(transferSyntax)), + offset_(0), + chunkSize_(0) + { + static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB + chunk_.resize(CHUNK_SIZE); + } - output.GetLowLevelOutput().SetContentType(CONTENT_TYPE_OCTET_STREAM); - output.GetLowLevelOutput().SetContentLength(length); + virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, + bool /*deflateAllowed*/) + { + // No support for compression + return HttpCompression_None; + } - while (offset < length) - { - Uint32 nbytes; - if (length - offset < buffer.size()) + virtual bool HasContentFilename(std::string& filename) { - nbytes = length - offset; + return false; } - else + + virtual std::string GetContentType() { - nbytes = buffer.size(); + return ""; } - OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); - - if (cond.good()) + virtual uint64_t GetContentLength() { - output.GetLowLevelOutput().SendBody(&buffer[0], nbytes); - offset += nbytes; + return length_; } - else + + virtual bool ReadNextChunk() { - LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); - return; + assert(offset_ <= length_); + + if (offset_ == length_) + { + return false; + } + else + { + if (length_ - offset_ < chunk_.size()) + { + chunkSize_ = length_ - offset_; + } + else + { + chunkSize_ = chunk_.size(); + } + + OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); + + offset_ += chunkSize_; + + if (!cond.good()) + { + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); + throw OrthancException(ErrorCode_InternalError); + } + + return true; + } } - } - - output.MarkLowLevelOutputDone(); + + virtual const char *GetChunkContent() + { + return chunk_.c_str(); + } + + virtual size_t GetChunkSize() + { + return chunkSize_; + } + }; } @@ -365,7 +414,8 @@ { // This is the case for raw, uncompressed image buffers assert(*blockUri == "0"); - AnswerDicomField(output, *element, transferSyntax); + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); } } } @@ -406,7 +456,8 @@ //element->getVR() != EVR_UNKNOWN && // This would forbid private tags element->getVR() != EVR_SQ) { - AnswerDicomField(output, *element, transferSyntax); + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); } } @@ -813,7 +864,8 @@ { OFCondition cond; - if (FromDcmtkBridge::IsPrivateTag(tag)) + if (FromDcmtkBridge::IsPrivateTag(tag) || + FromDcmtkBridge::IsUnknownTag(tag)) { // This is a private tag // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata @@ -864,7 +916,8 @@ } else { - if (FromDcmtkBridge::IsPrivateTag(tag)) + if (FromDcmtkBridge::IsPrivateTag(tag) || + FromDcmtkBridge::IsUnknownTag(tag)) { if (!element->putUint8Array((const Uint8*) value.c_str(), value.size()).good()) { @@ -916,7 +969,10 @@ DcmTagKey k(tag.GetGroup(), tag.GetElement()); DcmDataset& dataset = *pimpl_->file_->getDataset(); - if (FromDcmtkBridge::IsPrivateTag(tag)) + if (FromDcmtkBridge::IsPrivateTag(tag) || + FromDcmtkBridge::IsUnknownTag(tag) || + tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) { const Uint8* data = NULL; // This is freed in the destructor of the dataset long unsigned int count = 0; @@ -950,7 +1006,7 @@ } std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_)); - + if (v.get() == NULL) { value = ""; @@ -959,7 +1015,7 @@ { value = v->AsString(); } - + return true; } } @@ -1059,8 +1115,10 @@ pimpl_(new PImpl) { pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); + pimpl_->encoding_ = other.pimpl_->encoding_; - pimpl_->encoding_ = other.pimpl_->encoding_; + // Create a new instance-level identifier + Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); } @@ -1082,18 +1140,35 @@ } - void ParsedDicomFile::EmbedImage(const std::string& dataUriScheme) + void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) { std::string mime, content; Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme); - - std::string decoded; - Toolbox::DecodeBase64(decoded, content); + Toolbox::ToLowerCase(mime); if (mime == "image/png") { + EmbedImage(mime, content); + } + else if (mime == "application/pdf") + { + EmbedPdf(content); + } + else + { + LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file"; + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void ParsedDicomFile::EmbedImage(const std::string& mime, + const std::string& content) + { + if (mime == "image/png") + { PngReader reader; - reader.ReadFromMemory(decoded); + reader.ReadFromMemory(content); EmbedImage(reader); } else @@ -1276,68 +1351,16 @@ void ParsedDicomFile::SetEncoding(Encoding encoding) { - std::string s; - - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - switch (encoding) + if (encoding == Encoding_Windows1251) { - case Encoding_Utf8: - case Encoding_Ascii: - s = "ISO_IR 192"; - break; - - case Encoding_Latin1: - s = "ISO_IR 100"; - break; - - case Encoding_Latin2: - s = "ISO_IR 101"; - break; - - case Encoding_Latin3: - s = "ISO_IR 109"; - break; - - case Encoding_Latin4: - s = "ISO_IR 110"; - break; - - case Encoding_Latin5: - s = "ISO_IR 148"; - break; - - case Encoding_Cyrillic: - s = "ISO_IR 144"; - break; - - case Encoding_Arabic: - s = "ISO_IR 127"; - break; - - case Encoding_Greek: - s = "ISO_IR 126"; - break; - - case Encoding_Hebrew: - s = "ISO_IR 138"; - break; - - case Encoding_Japanese: - s = "ISO_IR 13"; - break; - - case Encoding_Chinese: - s = "GB18030"; - break; - - case Encoding_Thai: - s = "ISO_IR 166"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + // This Cyrillic codepage is not officially supported by the + // DICOM standard. Do not set the SpecificCharacterSet tag. + return; } + pimpl_->encoding_ = encoding; + + std::string s = GetDicomSpecificCharacterSet(encoding); Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent); } @@ -1354,4 +1377,94 @@ FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset()); } } + + + bool ParsedDicomFile::HasTag(const DicomTag& tag) const + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + return pimpl_->file_->getDataset()->tagExists(key); + } + + + void ParsedDicomFile::EmbedPdf(const std::string& pdf) + { + if (pdf.size() < 5 || // (*) + strncmp("%PDF-", pdf.c_str(), 5) != 0) + { + LOG(ERROR) << "Not a PDF file"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + Replace(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); + Replace(FromDcmtkBridge::Convert(DCM_Modality), "OT"); + Replace(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); + Replace(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf"); + //Replace(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); + + std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); + + size_t s = pdf.size(); + if (s & 1) + { + // The size of the buffer must be even + s += 1; + } + + Uint8* bytes = NULL; + OFCondition result = element->createUint8Array(s, bytes); + if (!result.good() || bytes == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) + bytes[s - 1] = 0; + + memcpy(bytes, pdf.c_str(), pdf.size()); + + DcmPolymorphOBOW* obj = element.release(); + result = pimpl_->file_->getDataset()->insert(obj); + + if (!result.good()) + { + delete obj; + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + + bool ParsedDicomFile::ExtractPdf(std::string& pdf) + { + std::string sop, mime; + + if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || + !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || + sop != UID_EncapsulatedPDFStorage || + mime != "application/pdf") + { + return false; + } + + if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) + { + return false; + } + + // Strip the possible pad byte at the end of file, because the + // encapsulated documents must always have an even length. The PDF + // format expects files to end with %%EOF followed by CR/LF. If + // the last character of the file is not a CR or LF, we assume it + // is a pad byte and remove it. + if (pdf.size() > 0) + { + char last = *pdf.rbegin(); + + if (last != 10 && last != 13) + { + pdf.resize(pdf.size() - 1); + } + } + + return true; + } }
--- a/OrthancServer/ParsedDicomFile.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ParsedDicomFile.h Wed Sep 30 13:23:31 2015 +0200 @@ -35,8 +35,8 @@ #include "../Core/DicomFormat/DicomInstanceHasher.h" #include "../Core/RestApi/RestApiOutput.h" #include "ServerEnumerations.h" -#include "../Core/ImageFormats/ImageAccessor.h" -#include "../Core/ImageFormats/ImageBuffer.h" +#include "../Core/Images/ImageAccessor.h" +#include "../Core/Images/ImageBuffer.h" namespace Orthanc { @@ -100,9 +100,12 @@ void SaveToFile(const std::string& path); + void EmbedContent(const std::string& dataUriScheme); + void EmbedImage(const ImageAccessor& accessor); - void EmbedImage(const std::string& dataUriScheme); + void EmbedImage(const std::string& mime, + const std::string& content); void ExtractImage(ImageBuffer& result, unsigned int frame); @@ -121,6 +124,12 @@ void ToJson(Json::Value& target, bool simplify); + + bool HasTag(const DicomTag& tag) const; + + void EmbedPdf(const std::string& pdf); + + bool ExtractPdf(std::string& pdf); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/QueryRetrieveHandler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,133 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersServer.h" +#include "QueryRetrieveHandler.h" + +#include "OrthancInitialization.h" + + +namespace Orthanc +{ + void QueryRetrieveHandler::Invalidate() + { + done_ = false; + answers_.Clear(); + } + + + void QueryRetrieveHandler::Run() + { + if (!done_) + { + ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); + locker.GetConnection().Find(answers_, level_, query_); + done_ = true; + } + } + + + QueryRetrieveHandler::QueryRetrieveHandler(ServerContext& context) : + context_(context), + localAet_(context.GetDefaultLocalApplicationEntityTitle()), + done_(false), + level_(ResourceType_Study) + { + } + + + void QueryRetrieveHandler::SetModality(const std::string& symbolicName) + { + Invalidate(); + modalityName_ = symbolicName; + Configuration::GetDicomModalityUsingSymbolicName(modality_, symbolicName); + } + + + void QueryRetrieveHandler::SetLevel(ResourceType level) + { + Invalidate(); + level_ = level; + } + + + void QueryRetrieveHandler::SetQuery(const DicomTag& tag, + const std::string& value) + { + Invalidate(); + query_.SetValue(tag, value); + } + + + size_t QueryRetrieveHandler::GetAnswerCount() + { + Run(); + return answers_.GetSize(); + } + + + const DicomMap& QueryRetrieveHandler::GetAnswer(size_t i) + { + Run(); + + if (i >= answers_.GetSize()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + return answers_.GetAnswer(i); + } + + + void QueryRetrieveHandler::Retrieve(const std::string& target, + size_t i) + { + Run(); + + if (i >= answers_.GetSize()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); + locker.GetConnection().Move(target, answers_.GetAnswer(i)); + } + + + void QueryRetrieveHandler::Retrieve(const std::string& target) + { + for (size_t i = 0; i < GetAnswerCount(); i++) + { + Retrieve(target, i); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/QueryRetrieveHandler.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,95 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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" + +namespace Orthanc +{ + class QueryRetrieveHandler : public IDynamicObject + { + private: + ServerContext& context_; + const std::string& localAet_; + bool done_; + RemoteModalityParameters modality_; + ResourceType level_; + DicomMap query_; + DicomFindAnswers answers_; + std::string modalityName_; + + void Invalidate(); + + + public: + QueryRetrieveHandler(ServerContext& context); + + void SetModality(const std::string& symbolicName); + + const RemoteModalityParameters& GetModality() const + { + return modality_; + } + + const std::string& GetModalitySymbolicName() const + { + return modalityName_; + } + + void SetLevel(ResourceType level); + + ResourceType GetLevel() const + { + return level_; + } + + void SetQuery(const DicomTag& tag, + const std::string& value); + + const DicomMap& GetQuery() const + { + return query_; + } + + void Run(); + + size_t GetAnswerCount(); + + const DicomMap& GetAnswer(size_t i); + + void Retrieve(const std::string& target, + size_t i); + + void Retrieve(const std::string& target); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ResourceFinder.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,383 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersServer.h" +#include "ResourceFinder.h" + +#include "../Core/Logging.h" +#include "FromDcmtkBridge.h" +#include "ServerContext.h" + +#include <boost/algorithm/string/predicate.hpp> + +namespace Orthanc +{ + class ResourceFinder::CandidateResources + { + private: + typedef std::map<DicomTag, std::string> Query; + + ResourceFinder& finder_; + ServerIndex& index_; + ResourceType level_; + bool isFilterApplied_; + std::set<std::string> filtered_; + + + static void ListToSet(std::set<std::string>& target, + const std::list<std::string>& source) + { + for (std::list<std::string>::const_iterator + it = source.begin(); it != source.end(); ++it) + { + target.insert(*it); + } + } + + + public: + CandidateResources(ResourceFinder& finder) : + finder_(finder), + index_(finder.context_.GetIndex()), + 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; + try + { + index_.GetChildren(children, *it); + ListToSet(filtered_, children); + } + catch (OrthancException&) + { + // The resource was removed in the meantime + } + } + } + + switch (level_) + { + case ResourceType_Patient: + level_ = ResourceType_Study; + break; + + case ResourceType_Study: + level_ = ResourceType_Series; + break; + + case ResourceType_Series: + level_ = ResourceType_Instance; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + void Flatten(std::list<std::string>& resources) const + { + resources.clear(); + + if (isFilterApplied_) + { + for (std::set<std::string>::const_iterator + it = filtered_.begin(); it != filtered_.end(); ++it) + { + resources.push_back(*it); + } + } + else + { + index_.GetAllUuids(resources, level_); + } + } + + + void RestrictIdentifier(const IQuery& query, + const DicomTag& tag) + { + assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || + (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || + (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || + (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || + (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); + + std::string value; + if (!query.RestrictIdentifier(value, tag)) + { + return; + } + + LOG(INFO) << "Lookup for identifier tag " + << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; + + std::list<std::string> resources; + index_.LookupIdentifier(resources, tag, value, level_); + + if (isFilterApplied_) + { + std::set<std::string> s; + ListToSet(s, resources); + + std::set<std::string> tmp = filtered_; + filtered_.clear(); + + for (std::set<std::string>::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + if (s.find(*it) != s.end()) + { + filtered_.insert(*it); + } + } + } + else + { + assert(filtered_.empty()); + isFilterApplied_ = true; + ListToSet(filtered_, resources); + } + } + + + void RestrictMainDicomTags(const IQuery& query) + { + if (!query.HasMainDicomTagsFilter(level_)) + { + return; + } + + std::list<std::string> resources; + Flatten(resources); + + isFilterApplied_ = true; + filtered_.clear(); + + for (std::list<std::string>::const_iterator + it = resources.begin(); it != resources.end(); ++it) + { + DicomMap mainTags; + if (index_.GetMainDicomTags(mainTags, *it, level_)) + { + if (query.FilterMainDicomTags(*it, level_, mainTags)) + { + filtered_.insert(*it); + } + } + } + } + }; + + + ResourceFinder::ResourceFinder(ServerContext& context) : + context_(context), + maxResults_(0) + { + } + + + void ResourceFinder::ApplyAtLevel(CandidateResources& candidates, + const IQuery& query, + ResourceType level) + { + if (level != ResourceType_Patient) + { + candidates.GoDown(); + } + + switch (level) + { + case ResourceType_Patient: + { + candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID); + break; + } + + case ResourceType_Study: + { + candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID); + candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER); + break; + } + + case ResourceType_Series: + { + candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID); + break; + } + + case ResourceType_Instance: + { + candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID); + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + candidates.RestrictMainDicomTags(query); + } + + + + static bool LookupOneInstance(std::string& result, + ServerIndex& index, + const std::string& id, + ResourceType type) + { + if (type == ResourceType_Instance) + { + result = id; + return true; + } + + std::string childId; + + { + std::list<std::string> children; + index.GetChildInstances(children, id); + + if (children.empty()) + { + return false; + } + + childId = children.front(); + } + + return LookupOneInstance(result, index, childId, GetChildResourceType(type)); + } + + + bool ResourceFinder::Apply(std::list<std::string>& result, + const IQuery& query) + { + CandidateResources candidates(*this); + + ApplyAtLevel(candidates, query, ResourceType_Patient); + + const ResourceType level = query.GetLevel(); + + if (level == ResourceType_Study || + level == ResourceType_Series || + level == ResourceType_Instance) + { + ApplyAtLevel(candidates, query, ResourceType_Study); + } + + if (level == ResourceType_Series || + level == ResourceType_Instance) + { + ApplyAtLevel(candidates, query, ResourceType_Series); + } + + if (level == ResourceType_Instance) + { + ApplyAtLevel(candidates, query, ResourceType_Instance); + } + + if (!query.HasInstanceFilter()) + { + candidates.Flatten(result); + + if (maxResults_ != 0 && + result.size() >= maxResults_) + { + result.resize(maxResults_); + return false; + } + else + { + return true; + } + } + else + { + std::list<std::string> tmp; + candidates.Flatten(tmp); + + result.clear(); + for (std::list<std::string>::const_iterator + resource = tmp.begin(); resource != tmp.end(); ++resource) + { + try + { + std::string instance; + if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) + { + Json::Value content; + context_.ReadJson(content, instance); + if (query.FilterInstance(*resource, content)) + { + result.push_back(*resource); + + if (maxResults_ != 0 && + result.size() >= maxResults_) + { + // Too many results, stop before recording this new match + return false; + } + } + } + } + catch (OrthancException&) + { + // This resource has been deleted since the search was started + } + } + } + + return true; // All the matching resources have been returned + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/ResourceFinder.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,101 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "ServerIndex.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class ResourceFinder : public boost::noncopyable + { + public: + class IQuery : public boost::noncopyable + { + public: + virtual ~IQuery() + { + } + + virtual ResourceType GetLevel() const = 0; + + virtual bool RestrictIdentifier(std::string& value, + DicomTag identifier) const = 0; + + virtual bool HasMainDicomTagsFilter(ResourceType level) const = 0; + + virtual bool FilterMainDicomTags(const std::string& resourceId, + ResourceType level, + const DicomMap& mainTags) const = 0; + + virtual bool HasInstanceFilter() const = 0; + + virtual bool FilterInstance(const std::string& instanceId, + const Json::Value& content) const = 0; + }; + + + private: + typedef std::map<DicomTag, std::string> Identifiers; + + class CandidateResources; + + ServerContext& context_; + size_t maxResults_; + + void ApplyAtLevel(CandidateResources& candidates, + const IQuery& query, + ResourceType level); + + public: + ResourceFinder(ServerContext& context); + + void SetMaxResults(size_t value) + { + maxResults_ = value; + } + + size_t GetMaxResults() const + { + return maxResults_; + } + + // Returns "true" iff. all the matching resources have been + // returned. Will be "false" if the results were truncated by + // "SetMaxResults()". + bool Apply(std::list<std::string>& result, + const IQuery& query); + }; + +}
--- a/OrthancServer/Scheduler/CallSystemCommand.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/CallSystemCommand.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,9 +30,10 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "CallSystemCommand.h" -#include <glog/logging.h> +#include "../../Core/Logging.h" #include "../../Core/Toolbox.h" #include "../../Core/Uuid.h"
--- a/OrthancServer/Scheduler/DeleteInstanceCommand.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/DeleteInstanceCommand.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,9 +30,10 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "DeleteInstanceCommand.h" -#include <glog/logging.h> +#include "../../Core/Logging.h" namespace Orthanc {
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,12 +30,47 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "ModifyInstanceCommand.h" -#include <glog/logging.h> +#include "../../Core/Logging.h" namespace Orthanc { + ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + const DicomModification& modification) : + context_(context), + origin_(origin), + modification_(modification) + { + modification_.SetAllowManualIdentifiers(true); + + if (modification_.IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification_.SetLevel(ResourceType_Patient); + } + else if (modification_.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification_.SetLevel(ResourceType_Study); + } + else if (modification_.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification_.SetLevel(ResourceType_Series); + } + else + { + modification_.SetLevel(ResourceType_Instance); + } + + if (origin_ != RequestOrigin_Lua) + { + // TODO If issued from HTTP, "remoteIp" and "username" must be provided + throw OrthancException(ErrorCode_NotImplemented); + } + } + + bool ModifyInstanceCommand::Apply(ListOfStrings& outputs, const ListOfStrings& inputs) { @@ -56,6 +91,8 @@ modification_.Apply(*modified); DicomInstanceToStore toStore; + assert(origin_ == RequestOrigin_Lua); + toStore.SetLuaOrigin(); toStore.SetParsedDicomFile(*modified); // TODO other metadata toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it);
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h Wed Sep 30 13:23:31 2015 +0200 @@ -42,18 +42,13 @@ { private: ServerContext& context_; + RequestOrigin origin_; DicomModification modification_; public: - ModifyInstanceCommand(ServerContext& context) : - context_(context) - { - } - - DicomModification& GetModification() - { - return modification_; - } + ModifyInstanceCommand(ServerContext& context, + RequestOrigin origin, + const DicomModification& modification); const DicomModification& GetModification() const {
--- a/OrthancServer/Scheduler/ServerCommandInstance.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ServerCommandInstance.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "ServerCommandInstance.h" #include "../../Core/OrthancException.h"
--- a/OrthancServer/Scheduler/ServerJob.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ServerJob.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,6 +30,7 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "ServerJob.h" #include "../../Core/OrthancException.h" @@ -61,7 +62,7 @@ index[*next] <= index[*it]) { // You must reorder your calls to "ServerJob::AddCommand" - throw OrthancException("Bad ordering of filters in a job"); + throw OrthancException(ErrorCode_BadJobOrdering); } } }
--- a/OrthancServer/Scheduler/ServerScheduler.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ServerScheduler.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,11 +30,11 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "ServerScheduler.h" #include "../../Core/OrthancException.h" - -#include <glog/logging.h> +#include "../../Core/Logging.h" namespace Orthanc { @@ -199,8 +199,25 @@ ServerScheduler::~ServerScheduler() { - finish_ = true; - worker_.join(); + if (!finish_) + { + LOG(ERROR) << "INTERNAL ERROR: ServerScheduler::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + + void ServerScheduler::Stop() + { + if (!finish_) + { + finish_ = true; + + if (worker_.joinable()) + { + worker_.join(); + } + } } @@ -313,7 +330,7 @@ if (job->second.size_ == 1) { - return job->second.success_; + return static_cast<float>(job->second.success_); } return (static_cast<float>(job->second.success_) /
--- a/OrthancServer/Scheduler/ServerScheduler.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/ServerScheduler.h Wed Sep 30 13:23:31 2015 +0200 @@ -86,6 +86,8 @@ ~ServerScheduler(); + void Stop(); + void Submit(ServerJob& job); bool SubmitAndWait(ListOfStrings& outputs,
--- a/OrthancServer/Scheduler/StorePeerCommand.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/StorePeerCommand.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,12 +30,12 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "StorePeerCommand.h" +#include "../../Core/Logging.h" #include "../../Core/HttpClient.h" -#include <glog/logging.h> - namespace Orthanc { StorePeerCommand::StorePeerCommand(ServerContext& context, @@ -71,7 +71,7 @@ try { - context_.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom); + context_.ReadFile(client.GetBody(), *it, FileContentType_Dicom); std::string answer; if (!client.Apply(answer))
--- a/OrthancServer/Scheduler/StoreScuCommand.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/StoreScuCommand.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,25 +30,28 @@ **/ +#include "../PrecompiledHeadersServer.h" #include "StoreScuCommand.h" -#include <glog/logging.h> +#include "../../Core/Logging.h" namespace Orthanc { StoreScuCommand::StoreScuCommand(ServerContext& context, + const std::string& localAet, const RemoteModalityParameters& modality, bool ignoreExceptions) : context_(context), modality_(modality), - ignoreExceptions_(ignoreExceptions) + ignoreExceptions_(ignoreExceptions), + localAet_(localAet) { } bool StoreScuCommand::Apply(ListOfStrings& outputs, const ListOfStrings& inputs) { - ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), modality_); + ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_); for (ListOfStrings::const_iterator it = inputs.begin(); it != inputs.end(); ++it)
--- a/OrthancServer/Scheduler/StoreScuCommand.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/Scheduler/StoreScuCommand.h Wed Sep 30 13:23:31 2015 +0200 @@ -43,9 +43,11 @@ ServerContext& context_; RemoteModalityParameters modality_; bool ignoreExceptions_; + std::string localAet_; public: StoreScuCommand(ServerContext& context, + const std::string& localAet, const RemoteModalityParameters& modality, bool ignoreExceptions);
--- a/OrthancServer/ServerContext.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerContext.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,13 +33,14 @@ #include "PrecompiledHeadersServer.h" #include "ServerContext.h" +#include "../Core/FileStorage/StorageAccessor.h" #include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/Lua/LuaFunctionCall.h" +#include "../Core/HttpServer/HttpStreamTranscoder.h" +#include "../Core/Logging.h" #include "FromDcmtkBridge.h" #include "ServerToolbox.h" #include "OrthancInitialization.h" -#include <glog/logging.h> #include <EmbeddedResources.h> #include <dcmtk/dcmdata/dcfilefo.h> @@ -55,9 +56,6 @@ #define ENABLE_DICOM_CACHE 1 -static const char* RECEIVED_INSTANCE_FILTER = "ReceivedInstanceFilter"; -static const char* ON_STORED_INSTANCE = "OnStoredInstance"; - static const size_t DICOM_CACHE_SIZE = 2; /** @@ -71,24 +69,97 @@ namespace Orthanc { - ServerContext::ServerContext(IDatabaseWrapper& database) : + void ServerContext::ChangeThread(ServerContext* that) + { + while (!that->done_) + { + std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(100)); + + if (obj.get() != NULL) + { + const ServerIndexChange& change = dynamic_cast<const ServerIndexChange&>(*obj.get()); + + boost::recursive_mutex::scoped_lock lock(that->listenersMutex_); + for (ServerListeners::iterator it = that->listeners_.begin(); + it != that->listeners_.end(); ++it) + { + try + { + it->GetListener().SignalChange(change); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error in the " << it->GetDescription() + << " callback while signaling a change: " << e.What(); + } + } + } + } + } + + + ServerContext::ServerContext(IDatabaseWrapper& database, + IStorageArea& area) : index_(*this, database), + area_(area), compressionEnabled_(false), + storeMD5_(true), provider_(*this), dicomCache_(provider_, DICOM_CACHE_SIZE), scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10)), + lua_(*this), +#if ORTHANC_PLUGINS_ENABLED == 1 plugins_(NULL), - pluginsManager_(NULL) +#endif + done_(false), + queryRetrieveArchive_(Configuration::GetGlobalIntegerParameter("QueryRetrieveSize", 10)), + defaultLocalAet_(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")) { - scu_.SetLocalApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); - uint64_t s = Configuration::GetGlobalIntegerParameter("DicomAssociationCloseDelay", 5); // In seconds scu_.SetMillisecondsBeforeClose(s * 1000); // Milliseconds are expected here - lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX); - lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", "")); + listeners_.push_back(ServerListener(lua_, "Lua")); + + changeThread_ = boost::thread(ChangeThread, this); + } + + + + ServerContext::~ServerContext() + { + if (!done_) + { + LOG(ERROR) << "INTERNAL ERROR: ServerContext::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } } + + void ServerContext::Stop() + { + if (!done_) + { + { + boost::recursive_mutex::scoped_lock lock(listenersMutex_); + listeners_.clear(); + } + + done_ = true; + + if (changeThread_.joinable()) + { + changeThread_.join(); + } + + scu_.Finalize(); + + // Do not change the order below! + scheduler_.Stop(); + index_.Stop(); + } + } + + void ServerContext::SetCompressionEnabled(bool enabled) { if (enabled) @@ -99,182 +170,11 @@ compressionEnabled_ = enabled; } + void ServerContext::RemoveFile(const std::string& fileUuid, FileContentType type) { - accessor_.Remove(fileUuid, type); - } - - - bool ServerContext::ApplyReceivedInstanceFilter(const Json::Value& simplified, - const std::string& remoteAet) - { - LuaContextLocker locker(*this); - - if (locker.GetLua().IsExistingFunction(RECEIVED_INSTANCE_FILTER)) - { - LuaFunctionCall call(locker.GetLua(), RECEIVED_INSTANCE_FILTER); - call.PushJson(simplified); - call.PushString(remoteAet); - - if (!call.ExecutePredicate()) - { - return false; - } - } - - return true; - } - - - static IServerCommand* ParseOperation(ServerContext& context, - const std::string& operation, - const Json::Value& parameters) - { - if (operation == "delete") - { - LOG(INFO) << "Lua script to delete instance " << parameters["Instance"].asString(); - return new DeleteInstanceCommand(context); - } - - if (operation == "store-scu") - { - std::string modality = parameters["Modality"].asString(); - LOG(INFO) << "Lua script to send instance " << parameters["Instance"].asString() - << " to modality " << modality << " using Store-SCU"; - return new StoreScuCommand(context, Configuration::GetModalityUsingSymbolicName(modality), true); - } - - if (operation == "store-peer") - { - std::string peer = parameters["Peer"].asString(); - LOG(INFO) << "Lua script to send instance " << parameters["Instance"].asString() - << " to peer " << peer << " using HTTP"; - - OrthancPeerParameters parameters; - Configuration::GetOrthancPeer(parameters, peer); - return new StorePeerCommand(context, parameters, true); - } - - if (operation == "modify") - { - LOG(INFO) << "Lua script to modify instance " << parameters["Instance"].asString(); - std::auto_ptr<ModifyInstanceCommand> command(new ModifyInstanceCommand(context)); - OrthancRestApi::ParseModifyRequest(command->GetModification(), parameters); - return command.release(); - } - - if (operation == "call-system") - { - LOG(INFO) << "Lua script to call system command on " << parameters["Instance"].asString(); - - const Json::Value& argsIn = parameters["Arguments"]; - if (argsIn.type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - std::vector<std::string> args; - args.reserve(argsIn.size()); - for (Json::Value::ArrayIndex i = 0; i < argsIn.size(); ++i) - { - // http://jsoncpp.sourceforge.net/namespace_json.html#7d654b75c16a57007925868e38212b4e - switch (argsIn[i].type()) - { - case Json::stringValue: - args.push_back(argsIn[i].asString()); - break; - - case Json::intValue: - args.push_back(boost::lexical_cast<std::string>(argsIn[i].asInt())); - break; - - case Json::uintValue: - args.push_back(boost::lexical_cast<std::string>(argsIn[i].asUInt())); - break; - - case Json::realValue: - args.push_back(boost::lexical_cast<std::string>(argsIn[i].asFloat())); - break; - - default: - throw OrthancException(ErrorCode_BadParameterType); - } - } - - return new CallSystemCommand(context, parameters["Command"].asString(), args); - } - - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - void ServerContext::ApplyLuaOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedDicom, - const Json::Value& metadata, - const std::string& remoteAet, - const std::string& calledAet) - { - LuaContextLocker locker(*this); - - if (locker.GetLua().IsExistingFunction(ON_STORED_INSTANCE)) - { - locker.GetLua().Execute("_InitializeJob()"); - - LuaFunctionCall call(locker.GetLua(), ON_STORED_INSTANCE); - call.PushString(instanceId); - call.PushJson(simplifiedDicom); - call.PushJson(metadata); - call.PushJson(remoteAet); - call.PushJson(calledAet); - call.Execute(); - - Json::Value operations; - LuaFunctionCall call2(locker.GetLua(), "_AccessJob"); - call2.ExecuteToJson(operations); - - if (operations.type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_InternalError); - } - - ServerJob job; - ServerCommandInstance* previousCommand = NULL; - - for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i) - { - if (operations[i].type() != Json::objectValue || - !operations[i].isMember("Operation")) - { - throw OrthancException(ErrorCode_InternalError); - } - - const Json::Value& parameters = operations[i]; - std::string operation = parameters["Operation"].asString(); - - ServerCommandInstance& command = job.AddCommand(ParseOperation(*this, operation, operations[i])); - - if (!parameters.isMember("Instance")) - { - throw OrthancException(ErrorCode_InternalError); - } - - std::string instance = parameters["Instance"].asString(); - if (instance.empty()) - { - previousCommand->ConnectOutput(command); - } - else - { - command.AddInput(instance); - } - - previousCommand = &command; - } - - job.SetDescription(std::string("Lua script: ") + ON_STORED_INSTANCE); - scheduler_.Submit(job); - } + area_.Remove(fileUuid, type); } @@ -283,30 +183,52 @@ { try { + StorageAccessor accessor(area_); + DicomInstanceHasher hasher(dicom.GetSummary()); resultPublicId = hasher.HashInstance(); - Json::Value simplified; - SimplifyTags(simplified, dicom.GetJson()); + Json::Value simplifiedTags; + SimplifyTags(simplifiedTags, dicom.GetJson()); // Test if the instance must be filtered out - if (!ApplyReceivedInstanceFilter(simplified, dicom.GetRemoteAet())) + bool accepted = true; + + { + boost::recursive_mutex::scoped_lock lock(listenersMutex_); + + for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it) + { + try + { + if (!it->GetListener().FilterIncomingInstance(dicom, simplifiedTags)) + { + accepted = false; + break; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error in the " << it->GetDescription() + << " callback while receiving an instance: " << e.What(); + throw; + } + } + } + + if (!accepted) { LOG(INFO) << "An incoming instance has been discarded by the filter"; return StoreStatus_FilteredOut; } - if (compressionEnabled_) - { - accessor_.SetCompressionForNextOperations(CompressionType_Zlib); - } - else - { - accessor_.SetCompressionForNextOperations(CompressionType_None); - } + // TODO Should we use "gzip" instead? + CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); - FileInfo dicomInfo = accessor_.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom); - FileInfo jsonInfo = accessor_.Write(dicom.GetJson().toStyledString(), FileContentType_DicomAsJson); + FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(), + FileContentType_Dicom, compression, storeMD5_); + FileInfo jsonInfo = accessor.Write(dicom.GetJson().toStyledString(), + FileContentType_DicomAsJson, compression, storeMD5_); ServerIndex::Attachments attachments; attachments.push_back(dicomInfo); @@ -317,6 +239,7 @@ StoreStatus status = index_.Store(instanceMetadata, dicom.GetSummary(), attachments, dicom.GetRemoteAet(), dicom.GetMetadata()); + // Only keep the metadata for the "instance" level dicom.GetMetadata().clear(); for (InstanceMetadata::const_iterator it = instanceMetadata.begin(); @@ -328,8 +251,8 @@ if (status != StoreStatus_Success) { - accessor_.Remove(dicomInfo.GetUuid(), FileContentType_Dicom); - accessor_.Remove(jsonInfo.GetUuid(), FileContentType_DicomAsJson); + accessor.Remove(dicomInfo); + accessor.Remove(jsonInfo); } switch (status) @@ -354,33 +277,18 @@ if (status == StoreStatus_Success || status == StoreStatus_AlreadyStored) { - Json::Value metadata = Json::objectValue; - for (std::map<MetadataType, std::string>::const_iterator - it = instanceMetadata.begin(); - it != instanceMetadata.end(); ++it) - { - metadata[EnumerationToString(it->first)] = it->second; - } + boost::recursive_mutex::scoped_lock lock(listenersMutex_); - try - { - ApplyLuaOnStoredInstance(resultPublicId, simplified, metadata, - dicom.GetRemoteAet(), dicom.GetCalledAet()); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Error in " << ON_STORED_INSTANCE << " callback (Lua): " << e.What(); - } - - if (plugins_ != NULL) + for (ServerListeners::iterator it = listeners_.begin(); it != listeners_.end(); ++it) { try { - plugins_->SignalStoredInstance(dicom, resultPublicId); + it->GetListener().SignalStoredInstance(resultPublicId, dicom, simplifiedTags); } catch (OrthancException& e) { - LOG(ERROR) << "Error in " << ON_STORED_INSTANCE << " callback (plugins): " << e.What(); + LOG(ERROR) << "Error in the " << it->GetDescription() + << " callback while receiving an instance: " << e.What(); } } } @@ -410,12 +318,8 @@ throw OrthancException(ErrorCode_InternalError); } - accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); - - std::auto_ptr<HttpFileSender> sender(accessor_.ConstructHttpFileSender(attachment.GetUuid(), attachment.GetContentType())); - sender->SetContentType(GetMimeType(content)); - sender->SetDownloadFilename(instancePublicId + ".dcm"); - output.AnswerFile(*sender); + StorageAccessor accessor(area_); + accessor.AnswerFile(output, attachment); } @@ -428,7 +332,7 @@ Json::Reader reader; if (!reader.parse(s, result)) { - throw OrthancException("Corrupted JSON file"); + throw OrthancException(ErrorCode_CorruptedFile); } } @@ -446,14 +350,15 @@ if (uncompressIfNeeded) { - accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); + StorageAccessor accessor(area_); + accessor.Read(result, attachment); } else { - accessor_.SetCompressionForNextOperations(CompressionType_None); + // Do not interpret the content of the storage area, return the + // raw data + area_.Read(result, attachment.GetUuid(), content); } - - accessor_.Read(result, attachment.GetUuid(), attachment.GetContentType()); } @@ -488,7 +393,7 @@ void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) { LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); - accessor_.SetStoreMD5(storeMD5); + storeMD5_ = storeMD5; } @@ -499,21 +404,16 @@ { LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; - if (compressionEnabled_) - { - accessor_.SetCompressionForNextOperations(CompressionType_Zlib); - } - else - { - accessor_.SetCompressionForNextOperations(CompressionType_None); - } + // TODO Should we use "gzip" instead? + CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); - FileInfo info = accessor_.Write(data, size, attachmentType); - StoreStatus status = index_.AddAttachment(info, resourceId); + StorageAccessor accessor(area_); + FileInfo attachment = accessor.Write(data, size, attachmentType, compression, storeMD5_); + StoreStatus status = index_.AddAttachment(attachment, resourceId); if (status != StoreStatus_Success) { - accessor_.Remove(info.GetUuid(), info.GetContentType()); + accessor.Remove(attachment); return false; } else @@ -533,40 +433,37 @@ void ServerContext::SignalChange(const ServerIndexChange& change) { - if (plugins_ != NULL) - { - try - { - plugins_->SignalChange(change); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Error in OnChangeCallback (plugins): " << e.What(); - } - } + pendingChanges_.Enqueue(change.Clone()); } - bool ServerContext::HasPlugins() const +#if ORTHANC_PLUGINS_ENABLED == 1 + void ServerContext::SetPlugins(OrthancPlugins& plugins) { - return (pluginsManager_ && plugins_); + boost::recursive_mutex::scoped_lock lock(listenersMutex_); + + plugins_ = &plugins; + + // TODO REFACTOR THIS + listeners_.clear(); + listeners_.push_back(ServerListener(lua_, "Lua")); + listeners_.push_back(ServerListener(plugins, "plugin")); } - const PluginsManager& ServerContext::GetPluginsManager() const + void ServerContext::ResetPlugins() { - if (HasPlugins()) - { - return *pluginsManager_; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } + boost::recursive_mutex::scoped_lock lock(listenersMutex_); + + plugins_ = NULL; + + // TODO REFACTOR THIS + listeners_.clear(); + listeners_.push_back(ServerListener(lua_, "Lua")); } - const OrthancPlugins& ServerContext::GetOrthancPlugins() const + const OrthancPlugins& ServerContext::GetPlugins() const { if (HasPlugins()) { @@ -577,4 +474,16 @@ throw OrthancException(ErrorCode_InternalError); } } + +#endif + + + bool ServerContext::HasPlugins() const + { +#if ORTHANC_PLUGINS_ENABLED == 1 + return (plugins_ != NULL); +#else + return false; +#endif + } }
--- a/OrthancServer/ServerContext.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerContext.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,25 +32,28 @@ #pragma once +#include "../Core/MultiThreading/SharedMessageQueue.h" #include "../Core/Cache/MemoryCache.h" -#include "../Core/FileStorage/CompressedFileStorageAccessor.h" +#include "../Core/Cache/SharedArchive.h" #include "../Core/FileStorage/IStorageArea.h" +#include "../Core/Lua/LuaContext.h" #include "../Core/RestApi/RestApiOutput.h" -#include "../Core/Lua/LuaContext.h" +#include "../Plugins/Engine/OrthancPlugins.h" +#include "DicomInstanceToStore.h" +#include "DicomProtocol/ReusableDicomUserConnection.h" +#include "IServerListener.h" +#include "LuaScripting.h" +#include "ParsedDicomFile.h" +#include "Scheduler/ServerScheduler.h" #include "ServerIndex.h" -#include "ParsedDicomFile.h" -#include "DicomProtocol/ReusableDicomUserConnection.h" -#include "Scheduler/ServerScheduler.h" -#include "DicomInstanceToStore.h" -#include "ServerIndexChange.h" +#include "OrthancHttpHandler.h" #include <boost/filesystem.hpp> +#include <boost/thread.hpp> + namespace Orthanc { - class OrthancPlugins; - class PluginsManager; - /** * This class is responsible for maintaining the storage area on the * filesystem (including compression), as well as the index of the @@ -72,18 +75,42 @@ virtual IDynamicObject* Provide(const std::string& id); }; - bool ApplyReceivedInstanceFilter(const Json::Value& simplified, - const std::string& remoteAet); + class ServerListener + { + private: + IServerListener *listener_; + std::string description_; + + public: + ServerListener(IServerListener& listener, + const std::string& description) : + listener_(&listener), + description_(description) + { + } - void ApplyLuaOnStoredInstance(const std::string& instanceId, - const Json::Value& simplifiedDicom, - const Json::Value& metadata, - const std::string& remoteAet, - const std::string& calledAet); + IServerListener& GetListener() + { + return *listener_; + } + + const std::string& GetDescription() + { + return description_; + } + }; + + typedef std::list<ServerListener> ServerListeners; + + + static void ChangeThread(ServerContext* that); + ServerIndex index_; - CompressedFileStorageAccessor accessor_; + IStorageArea& area_; + bool compressionEnabled_; + bool storeMD5_; DicomCacheProvider provider_; boost::mutex dicomCacheMutex_; @@ -91,10 +118,22 @@ ReusableDicomUserConnection scu_; ServerScheduler scheduler_; - boost::mutex luaMutex_; - LuaContext lua_; - OrthancPlugins* plugins_; // TODO Turn it into a listener pattern (idem for Lua callbacks) - const PluginsManager* pluginsManager_; + LuaScripting lua_; + +#if ORTHANC_PLUGINS_ENABLED == 1 + OrthancPlugins* plugins_; +#endif + + ServerListeners listeners_; + boost::recursive_mutex listenersMutex_; + + bool done_; + SharedMessageQueue pendingChanges_; + boost::thread changeThread_; + + SharedArchive queryRetrieveArchive_; + std::string defaultLocalAet_; + OrthancHttpHandler httpHandler_; public: class DicomCacheLocker : public boost::noncopyable @@ -116,35 +155,10 @@ } }; - class LuaContextLocker : public boost::noncopyable - { - private: - ServerContext& that_; - - public: - LuaContextLocker(ServerContext& that) : that_(that) - { - that.luaMutex_.lock(); - } + ServerContext(IDatabaseWrapper& database, + IStorageArea& area); - ~LuaContextLocker() - { - that_.luaMutex_.unlock(); - } - - LuaContext& GetLua() - { - return that_.lua_; - } - }; - - - ServerContext(IDatabaseWrapper& database); - - void SetStorageArea(IStorageArea& storage) - { - accessor_.SetStorageArea(storage); - } + ~ServerContext(); ServerIndex& GetIndex() { @@ -186,7 +200,7 @@ bool IsStoreMD5ForAttachments() const { - return accessor_.IsStoreMD5(); + return storeMD5_; } ReusableDicomUserConnection& GetReusableDicomUserConnection() @@ -199,29 +213,48 @@ return scheduler_; } - void SetOrthancPlugins(const PluginsManager& manager, - OrthancPlugins& plugins) - { - pluginsManager_ = &manager; - plugins_ = &plugins; - } - - void ResetOrthancPlugins() - { - pluginsManager_ = NULL; - plugins_ = NULL; - } - bool DeleteResource(Json::Value& target, const std::string& uuid, ResourceType expectedType); void SignalChange(const ServerIndexChange& change); + SharedArchive& GetQueryRetrieveArchive() + { + return queryRetrieveArchive_; + } + + const std::string& GetDefaultLocalApplicationEntityTitle() const + { + return defaultLocalAet_; + } + + LuaScripting& GetLua() + { + return lua_; + } + + OrthancHttpHandler& GetHttpHandler() + { + return httpHandler_; + } + + void Stop(); + + + /** + * Management of the plugins + **/ + +#if ORTHANC_PLUGINS_ENABLED == 1 + void SetPlugins(OrthancPlugins& plugins); + + void ResetPlugins(); + + const OrthancPlugins& GetPlugins() const; +#endif + bool HasPlugins() const; - const PluginsManager& GetPluginsManager() const; - - const OrthancPlugins& GetOrthancPlugins() const; }; }
--- a/OrthancServer/ServerEnumerations.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerEnumerations.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -243,44 +243,6 @@ } - ResourceType GetParentResourceType(ResourceType type) - { - switch (type) - { - case ResourceType_Study: - return ResourceType_Patient; - - case ResourceType_Series: - return ResourceType_Study; - - case ResourceType_Instance: - return ResourceType_Series; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ResourceType GetChildResourceType(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return ResourceType_Study; - - case ResourceType_Study: - return ResourceType_Series; - - case ResourceType_Series: - return ResourceType_Instance; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - const char* EnumerationToString(ModalityManufacturer manufacturer) { switch (manufacturer) @@ -300,6 +262,9 @@ case ModalityManufacturer_Dcm4Chee: return "Dcm4Chee"; + case ModalityManufacturer_SyngoVia: + return "SyngoVia"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -359,6 +324,10 @@ { return ModalityManufacturer_Dcm4Chee; } + else if (manufacturer == "SyngoVia") + { + return ModalityManufacturer_SyngoVia; + } else { throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancServer/ServerEnumerations.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerEnumerations.h Wed Sep 30 13:23:31 2015 +0200 @@ -60,7 +60,8 @@ ModalityManufacturer_StoreScp, ModalityManufacturer_ClearCanvas, ModalityManufacturer_MedInria, - ModalityManufacturer_Dcm4Chee + ModalityManufacturer_Dcm4Chee, + ModalityManufacturer_SyngoVia }; enum DicomRequestType @@ -90,6 +91,15 @@ TransferSyntax_Rle }; + enum ValueRepresentation + { + ValueRepresentation_Other, + ValueRepresentation_PatientName, + ValueRepresentation_Date, + ValueRepresentation_DateTime, + ValueRepresentation_Time + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -177,8 +187,4 @@ const char* EnumerationToString(TransferSyntax syntax); ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); - - ResourceType GetParentResourceType(ResourceType type); - - ResourceType GetChildResourceType(ResourceType type); }
--- a/OrthancServer/ServerIndex.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerIndex.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -41,172 +41,170 @@ #include "EmbeddedResources.h" #include "OrthancInitialization.h" #include "../Core/Toolbox.h" +#include "../Core/Logging.h" #include "../Core/Uuid.h" #include "../Core/DicomFormat/DicomArray.h" + #include "FromDcmtkBridge.h" #include "ServerContext.h" #include <boost/lexical_cast.hpp> #include <stdio.h> -#include <glog/logging.h> static const uint64_t MEGA_BYTES = 1024 * 1024; namespace Orthanc { - namespace Internals + class ServerIndex::Listener : public IDatabaseListener { - class ServerIndexListener : public IServerIndexListener + private: + struct FileToRemove { private: - struct FileToRemove - { - private: - std::string uuid_; - FileContentType type_; - - public: - FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()), - type_(info.GetContentType()) - { - } - - const std::string& GetUuid() const - { - return uuid_; - } + std::string uuid_; + FileContentType type_; - FileContentType GetContentType() const - { - return type_; - } - }; - - ServerContext& context_; - bool hasRemainingLevel_; - ResourceType remainingType_; - std::string remainingPublicId_; - std::list<FileToRemove> pendingFilesToRemove_; - std::list<ServerIndexChange> pendingChanges_; - uint64_t sizeOfFilesToRemove_; - bool insideTransaction_; - - void Reset() + public: + FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()), + type_(info.GetContentType()) { - sizeOfFilesToRemove_ = 0; - hasRemainingLevel_ = false; - pendingFilesToRemove_.clear(); - pendingChanges_.clear(); } - public: - ServerIndexListener(ServerContext& context) : context_(context), - insideTransaction_(false) - { - Reset(); - assert(ResourceType_Patient < ResourceType_Study && - ResourceType_Study < ResourceType_Series && - ResourceType_Series < ResourceType_Instance); - } - - void StartTransaction() + const std::string& GetUuid() const { - Reset(); - insideTransaction_ = true; - } - - void EndTransaction() - { - insideTransaction_ = false; - } - - uint64_t GetSizeOfFilesToRemove() - { - return sizeOfFilesToRemove_; + return uuid_; } - void CommitFilesToRemove() + FileContentType GetContentType() const { - for (std::list<FileToRemove>::const_iterator - it = pendingFilesToRemove_.begin(); - it != pendingFilesToRemove_.end(); ++it) - { - context_.RemoveFile(it->GetUuid(), it->GetContentType()); - } + return type_; } + }; - void CommitChanges() + ServerContext& context_; + bool hasRemainingLevel_; + ResourceType remainingType_; + std::string remainingPublicId_; + std::list<FileToRemove> pendingFilesToRemove_; + std::list<ServerIndexChange> pendingChanges_; + uint64_t sizeOfFilesToRemove_; + bool insideTransaction_; + + void Reset() + { + sizeOfFilesToRemove_ = 0; + hasRemainingLevel_ = false; + pendingFilesToRemove_.clear(); + pendingChanges_.clear(); + } + + public: + Listener(ServerContext& context) : context_(context), + insideTransaction_(false) + { + Reset(); + assert(ResourceType_Patient < ResourceType_Study && + ResourceType_Study < ResourceType_Series && + ResourceType_Series < ResourceType_Instance); + } + + void StartTransaction() + { + Reset(); + insideTransaction_ = true; + } + + void EndTransaction() + { + insideTransaction_ = false; + } + + uint64_t GetSizeOfFilesToRemove() + { + return sizeOfFilesToRemove_; + } + + void CommitFilesToRemove() + { + for (std::list<FileToRemove>::const_iterator + it = pendingFilesToRemove_.begin(); + it != pendingFilesToRemove_.end(); ++it) { - for (std::list<ServerIndexChange>::const_iterator - it = pendingChanges_.begin(); - it != pendingChanges_.end(); ++it) + context_.RemoveFile(it->GetUuid(), it->GetContentType()); + } + } + + void CommitChanges() + { + for (std::list<ServerIndexChange>::const_iterator + it = pendingChanges_.begin(); + it != pendingChanges_.end(); ++it) + { + context_.SignalChange(*it); + } + } + + virtual void SignalRemainingAncestor(ResourceType parentType, + const std::string& publicId) + { + VLOG(1) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")"; + + if (hasRemainingLevel_) + { + if (parentType < remainingType_) { - context_.SignalChange(*it); - } - } - - virtual void SignalRemainingAncestor(ResourceType parentType, - const std::string& publicId) - { - LOG(INFO) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")"; - - if (hasRemainingLevel_) - { - if (parentType < remainingType_) - { - remainingType_ = parentType; - remainingPublicId_ = publicId; - } - } - else - { - hasRemainingLevel_ = true; remainingType_ = parentType; remainingPublicId_ = publicId; - } - } - - virtual void SignalFileDeleted(const FileInfo& info) - { - assert(Toolbox::IsUuid(info.GetUuid())); - pendingFilesToRemove_.push_back(FileToRemove(info)); - sizeOfFilesToRemove_ += info.GetCompressedSize(); - } - - virtual void SignalChange(const ServerIndexChange& change) - { - LOG(INFO) << "Change related to resource " << change.GetPublicId() << " of type " - << EnumerationToString(change.GetResourceType()) << ": " - << EnumerationToString(change.GetChangeType()); - - if (insideTransaction_) - { - pendingChanges_.push_back(change); - } - else - { - context_.SignalChange(change); } } - - bool HasRemainingLevel() const + else { - return hasRemainingLevel_; - } + hasRemainingLevel_ = true; + remainingType_ = parentType; + remainingPublicId_ = publicId; + } + } - ResourceType GetRemainingType() const + virtual void SignalFileDeleted(const FileInfo& info) + { + assert(Toolbox::IsUuid(info.GetUuid())); + pendingFilesToRemove_.push_back(FileToRemove(info)); + sizeOfFilesToRemove_ += info.GetCompressedSize(); + } + + virtual void SignalChange(const ServerIndexChange& change) + { + VLOG(1) << "Change related to resource " << change.GetPublicId() << " of type " + << EnumerationToString(change.GetResourceType()) << ": " + << EnumerationToString(change.GetChangeType()); + + if (insideTransaction_) { - assert(HasRemainingLevel()); - return remainingType_; + pendingChanges_.push_back(change); } - - const std::string& GetRemainingPublicId() const + else { - assert(HasRemainingLevel()); - return remainingPublicId_; - } - }; - } + context_.SignalChange(change); + } + } + + bool HasRemainingLevel() const + { + return hasRemainingLevel_; + } + + ResourceType GetRemainingType() const + { + assert(HasRemainingLevel()); + return remainingType_; + } + + const std::string& GetRemainingPublicId() const + { + assert(HasRemainingLevel()); + return remainingPublicId_; + } + }; class ServerIndex::Transaction @@ -549,7 +547,7 @@ maximumStorageSize_(0), maximumPatients_(0) { - listener_.reset(new Internals::ServerIndexListener(context)); + listener_.reset(new Listener(context)); db_.SetListener(*listener_); currentStorageSize_ = db_.GetTotalCompressedSize(); @@ -567,23 +565,39 @@ } + ServerIndex::~ServerIndex() { - done_ = true; - - if (db_.HasFlushToDisk() && - flushThread_.joinable()) + if (!done_) { - flushThread_.join(); - } - - if (unstableResourcesMonitorThread_.joinable()) - { - unstableResourcesMonitorThread_.join(); + LOG(ERROR) << "INTERNAL ERROR: ServerIndex::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); } } + + void ServerIndex::Stop() + { + if (!done_) + { + done_ = true; + + if (db_.HasFlushToDisk() && + flushThread_.joinable()) + { + flushThread_.join(); + } + + if (unstableResourcesMonitorThread_.joinable()) + { + unstableResourcesMonitorThread_.join(); + } + } + } + + + StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, const DicomMap& dicomSummary, const Attachments& attachments, @@ -883,7 +897,7 @@ DicomMap tags; db_.GetMainDicomTags(tags, resourceId); target["MainDicomTags"] = Json::objectValue; - FromDcmtkBridge::ToJson(target["MainDicomTags"], tags); + FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true); } bool ServerIndex::LookupResource(Json::Value& result, @@ -1075,22 +1089,27 @@ - void ServerIndex::GetAllUuids(Json::Value& target, + void ServerIndex::GetAllUuids(std::list<std::string>& target, ResourceType resourceType) { - std::list<std::string> lst; + boost::mutex::scoped_lock lock(mutex_); + db_.GetAllPublicIds(target, resourceType); + } + + void ServerIndex::GetAllUuids(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (limit == 0) { - boost::mutex::scoped_lock lock(mutex_); - db_.GetAllPublicIds(lst, resourceType); + target.clear(); + return; } - target = Json::arrayValue; - for (std::list<std::string>::const_iterator - it = lst.begin(); it != lst.end(); ++it) - { - target.append(*it); - } + boost::mutex::scoped_lock lock(mutex_); + db_.GetAllPublicIds(target, resourceType, since, limit); } @@ -1316,7 +1335,7 @@ throw OrthancException(ErrorCode_FullStorage); } - LOG(INFO) << "Recycling one patient"; + VLOG(1) << "Recycling one patient"; db_.DeleteResource(patientToRecycle); if (!IsRecyclingNeeded(instanceSize)) @@ -2055,4 +2074,38 @@ } } + + bool ServerIndex::GetMainDicomTags(DicomMap& result, + const std::string& publicId, + ResourceType expectedType) + { + result.Clear(); + + boost::mutex::scoped_lock lock(mutex_); + + // Lookup for the requested resource + int64_t id; + ResourceType type; + if (!db_.LookupResource(id, type, publicId) || + type != expectedType) + { + return false; + } + else + { + db_.GetMainDicomTags(result, id); + return true; + } + } + + + bool ServerIndex::LookupResourceType(ResourceType& type, + const std::string& publicId) + { + boost::mutex::scoped_lock lock(mutex_); + + int64_t id; + return db_.LookupResource(id, type, publicId); + } + }
--- a/OrthancServer/ServerIndex.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerIndex.h Wed Sep 30 13:23:31 2015 +0200 @@ -47,11 +47,6 @@ { class ServerContext; - namespace Internals - { - class ServerIndexListener; - } - class ServerIndex : public boost::noncopyable { public: @@ -59,6 +54,7 @@ typedef std::map< std::pair<ResourceType, MetadataType>, std::string> MetadataMap; private: + class Listener; class Transaction; class UnstableResourcePayload; @@ -67,7 +63,7 @@ boost::thread flushThread_; boost::thread unstableResourcesMonitorThread_; - std::auto_ptr<Internals::ServerIndexListener> listener_; + std::auto_ptr<Listener> listener_; IDatabaseWrapper& db_; LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload> unstableResources_; @@ -126,6 +122,8 @@ ~ServerIndex(); + void Stop(); + uint64_t GetMaximumStorageSize() const { return maximumStorageSize_; @@ -158,9 +156,14 @@ const std::string& instanceUuid, FileContentType contentType); - void GetAllUuids(Json::Value& target, + void GetAllUuids(std::list<std::string>& target, ResourceType resourceType); + void GetAllUuids(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit); + bool DeleteResource(Json::Value& target /* out */, const std::string& uuid, ResourceType expectedType); @@ -257,5 +260,12 @@ std::string GetGlobalProperty(GlobalProperty property, const std::string& defaultValue); + + bool GetMainDicomTags(DicomMap& result, + const std::string& publicId, + ResourceType expectedType); + + bool LookupResourceType(ResourceType& type, + const std::string& publicId); }; }
--- a/OrthancServer/ServerIndexChange.h Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerIndexChange.h Wed Sep 30 13:23:31 2015 +0200 @@ -40,7 +40,7 @@ namespace Orthanc { - struct ServerIndexChange + struct ServerIndexChange : public IDynamicObject { private: int64_t seq_; @@ -74,6 +74,20 @@ { } + ServerIndexChange(const ServerIndexChange& other) + : seq_(other.seq_), + changeType_(other.changeType_), + resourceType_(other.resourceType_), + publicId_(other.publicId_), + date_(other.date_) + { + } + + ServerIndexChange* Clone() const + { + return new ServerIndexChange(*this); + } + int64_t GetSeq() const { return seq_;
--- a/OrthancServer/ServerToolbox.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/ServerToolbox.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,10 +33,10 @@ #include "PrecompiledHeadersServer.h" #include "ServerToolbox.h" +#include "../Core/Logging.h" #include "../Core/OrthancException.h" #include <cassert> -#include <glog/logging.h> namespace Orthanc {
--- a/OrthancServer/main.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/OrthancServer/main.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,9 +34,9 @@ #include "OrthancRestApi/OrthancRestApi.h" #include <fstream> -#include <glog/logging.h> #include <boost/algorithm/string/predicate.hpp> +#include "../Core/Logging.h" #include "../Core/Uuid.h" #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h" #include "../Core/HttpServer/FilesystemHttpHandler.h" @@ -49,15 +49,12 @@ #include "OrthancFindRequestHandler.h" #include "OrthancMoveRequestHandler.h" #include "ServerToolbox.h" -#include "../Plugins/Engine/PluginsManager.h" #include "../Plugins/Engine/OrthancPlugins.h" +#include "FromDcmtkBridge.h" using namespace Orthanc; -#define ENABLE_PLUGINS 1 - - class OrthancStoreRequestHandler : public IStoreRequestHandler { private: @@ -69,20 +66,21 @@ { } + virtual void Handle(const std::string& dicomFile, const DicomMap& dicomSummary, const Json::Value& dicomJson, + const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) + const std::string& calledAet) { if (dicomFile.size() > 0) { DicomInstanceToStore toStore; + toStore.SetDicomProtocolOrigin(remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()); toStore.SetBuffer(dicomFile); toStore.SetSummary(dicomSummary); toStore.SetJson(dicomJson); - toStore.SetRemoteAet(remoteAet); - toStore.SetCalledAet(calledAet); std::string id; server_.Store(id, toStore); @@ -231,7 +229,7 @@ { std::string lua = "Is" + configuration; - ServerContext::LuaContextLocker locker(context_); + LuaScripting::Locker locker(context_.GetLua()); if (locker.GetLua().IsExistingFunction(lua.c_str())) { @@ -264,7 +262,7 @@ { static const char* HTTP_FILTER = "IncomingHttpRequestFilter"; - ServerContext::LuaContextLocker locker(context_); + LuaScripting::Locker locker(context_.GetLua()); // Test if the instance must be filtered out if (locker.GetLua().IsExistingFunction(HTTP_FILTER)) @@ -309,6 +307,86 @@ }; + +class MyHttpExceptionFormatter : public IHttpExceptionFormatter +{ +private: + bool describeErrors_; + OrthancPlugins* plugins_; + +public: + MyHttpExceptionFormatter(bool describeErrors, + OrthancPlugins* plugins) : + describeErrors_(describeErrors), + plugins_(plugins) + { + } + + virtual void Format(HttpOutput& output, + const OrthancException& exception, + HttpMethod method, + const char* uri) + { + { + bool isPlugin = false; + +#if ORTHANC_PLUGINS_ENABLED == 1 + if (plugins_ != NULL) + { + plugins_->GetErrorDictionary().LogError(exception.GetErrorCode(), true); + isPlugin = true; + } +#endif + + if (!isPlugin) + { + LOG(ERROR) << "Exception in the HTTP handler: " << exception.What(); + } + } + + Json::Value message = Json::objectValue; + ErrorCode errorCode = exception.GetErrorCode(); + HttpStatus httpStatus = exception.GetHttpStatus(); + + { + bool isPlugin = false; + +#if ORTHANC_PLUGINS_ENABLED == 1 + if (plugins_ != NULL && + plugins_->GetErrorDictionary().Format(message, httpStatus, exception)) + { + errorCode = ErrorCode_Plugin; + isPlugin = true; + } +#endif + + if (!isPlugin) + { + message["Message"] = exception.What(); + } + } + + if (!describeErrors_) + { + output.SendStatus(httpStatus); + } + else + { + message["Method"] = EnumerationToString(method); + message["Uri"] = uri; + message["HttpError"] = EnumerationToString(httpStatus); + message["HttpStatus"] = httpStatus; + message["OrthancError"] = EnumerationToString(errorCode); + message["OrthancStatus"] = errorCode; + + std::string info = message.toStyledString(); + output.SendStatus(httpStatus, info); + } + } +}; + + + static void PrintHelp(char* path) { std::cout @@ -317,21 +395,23 @@ << std::endl << "If no configuration file is given on the command line, a set of default " << std::endl << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl - << "instructions about how to use Orthanc " << std::endl - << "<https://code.google.com/p/orthanc/wiki/OrthancCookbook>." << std::endl + << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl << std::endl << "Command-line options:" << std::endl << " --help\t\tdisplay this help and exit" << std::endl << " --logdir=[dir]\tdirectory where to store the log files" << std::endl << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl << " --config=[file]\tcreate a sample configuration file and exit" << std::endl + << " --verbose\t\tbe verbose in logs" << std::endl << " --trace\t\thighest verbosity in logs (for debug)" << std::endl - << " --verbose\t\tbe verbose in logs" << std::endl + << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl + << "\t\t\tdatabase (beware that the database will become" << std::endl + << "\t\t\tincompatible with former versions of Orthanc)" << std::endl << " --version\t\toutput version information and exit" << std::endl << std::endl << "Exit status:" << std::endl - << " 0 if OK," << std::endl - << " -1 if error (have a look at the logs)." << std::endl + << " 0 if success," << std::endl + << " -1 if error (have a look at the logs)." << std::endl << std::endl; } @@ -362,210 +442,334 @@ std::string script; Toolbox::ReadFile(script, path); - ServerContext::LuaContextLocker locker(context); + LuaScripting::Locker locker(context.GetLua()); locker.GetLua().Execute(script); } } -static void LoadPlugins(PluginsManager& pluginsManager) + +#if ORTHANC_PLUGINS_ENABLED == 1 +static void LoadPlugins(OrthancPlugins& plugins) { - std::list<std::string> plugins; - Configuration::GetGlobalListOfStringsParameter(plugins, "Plugins"); + std::list<std::string> path; + Configuration::GetGlobalListOfStringsParameter(path, "Plugins"); for (std::list<std::string>::const_iterator - it = plugins.begin(); it != plugins.end(); ++it) + it = path.begin(); it != path.end(); ++it) { std::string path = Configuration::InterpretStringParameterAsPath(*it); - LOG(WARNING) << "Registering a plugin from: " << path; - pluginsManager.RegisterPlugin(path); + LOG(WARNING) << "Loading plugin(s) from: " << path; + plugins.GetManager().RegisterPlugin(path); } } +#endif + + + +// Returns "true" if restart is required +static bool WaitForExit(ServerContext& context, + OrthancRestApi& restApi) +{ + LOG(WARNING) << "Orthanc has started"; + + context.GetLua().Execute("Initialize"); + + Toolbox::ServerBarrier(restApi.ResetRequestReceivedFlag()); + bool restart = restApi.ResetRequestReceivedFlag(); + + context.GetLua().Execute("Finalize"); + + if (restart) + { + LOG(WARNING) << "Reset request received, restarting Orthanc"; + } + + // We're done + LOG(WARNING) << "Orthanc is stopping"; + + return restart; +} -static bool StartOrthanc(int argc, char *argv[]) +static bool StartHttpServer(ServerContext& context, + OrthancRestApi& restApi, + OrthancPlugins* plugins) { -#if ENABLE_PLUGINS == 1 - OrthancPlugins orthancPlugins; - orthancPlugins.SetCommandLineArguments(argc, argv); - PluginsManager pluginsManager; - pluginsManager.RegisterServiceProvider(orthancPlugins); - LoadPlugins(pluginsManager); -#endif + if (!Configuration::GetGlobalBoolParameter("HttpServerEnabled", true)) + { + LOG(WARNING) << "The HTTP server is disabled"; + return WaitForExit(context, restApi); + } + + MyHttpExceptionFormatter exceptionFormatter(Configuration::GetGlobalBoolParameter("HttpDescribeErrors", true), plugins); + - // "storage" and "database" must be declared BEFORE "ServerContext - // context", to avoid mess in the invokation order of the destructors. - std::auto_ptr<IDatabaseWrapper> database; - std::auto_ptr<IStorageArea> storage; - std::auto_ptr<ServerContext> context; + // HTTP server + MyIncomingHttpRequestFilter httpFilter(context); + MongooseServer httpServer; + httpServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("HttpPort", 8042)); + httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false)); + httpServer.SetKeepAliveEnabled(Configuration::GetGlobalBoolParameter("KeepAlive", false)); + httpServer.SetHttpCompressionEnabled(Configuration::GetGlobalBoolParameter("HttpCompressionEnabled", true)); + httpServer.SetIncomingHttpRequestFilter(httpFilter); + httpServer.SetHttpExceptionFormatter(exceptionFormatter); - if (orthancPlugins.HasDatabase()) + httpServer.SetAuthenticationEnabled(Configuration::GetGlobalBoolParameter("AuthenticationEnabled", false)); + Configuration::SetupRegisteredUsers(httpServer); + + if (Configuration::GetGlobalBoolParameter("SslEnabled", false)) { - context.reset(new ServerContext(orthancPlugins.GetDatabase())); + std::string certificate = Configuration::InterpretStringParameterAsPath( + Configuration::GetGlobalStringParameter("SslCertificate", "certificate.pem")); + httpServer.SetSslEnabled(true); + httpServer.SetSslCertificate(certificate.c_str()); } else { - database.reset(Configuration::CreateDatabaseWrapper()); - context.reset(new ServerContext(*database)); + httpServer.SetSslEnabled(false); + } + + httpServer.Register(context.GetHttpHandler()); + + httpServer.Start(); + LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); + + bool restart = WaitForExit(context, restApi); + + httpServer.Stop(); + LOG(WARNING) << " HTTP server has stopped"; + + return restart; +} + + +static bool StartDicomServer(ServerContext& context, + OrthancRestApi& restApi, + OrthancPlugins* plugins) +{ + if (!Configuration::GetGlobalBoolParameter("DicomServerEnabled", true)) + { + LOG(WARNING) << "The DICOM server is disabled"; + return StartHttpServer(context, restApi, plugins); } - context->SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); - context->SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true)); + MyDicomServerFactory serverFactory(context); + + // DICOM server + DicomServer dicomServer; + OrthancApplicationEntityFilter dicomFilter(context); + dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false)); + dicomServer.SetStoreRequestHandlerFactory(serverFactory); + dicomServer.SetMoveRequestHandlerFactory(serverFactory); + dicomServer.SetFindRequestHandlerFactory(serverFactory); + dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242)); + dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); + dicomServer.SetApplicationEntityFilter(dicomFilter); + + dicomServer.Start(); + LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); + + bool restart = StartHttpServer(context, restApi, plugins); + + dicomServer.Stop(); + LOG(WARNING) << " DICOM server has stopped"; + + serverFactory.Done(); + + return restart; +} + + +static bool ConfigureHttpHandler(ServerContext& context, + OrthancPlugins *plugins) +{ +#if ORTHANC_PLUGINS_ENABLED == 1 + // By order of priority, first apply the "plugins" layer, so that + // plugins can overwrite the built-in REST API of Orthanc + if (plugins) + { + assert(context.HasPlugins()); + context.GetHttpHandler().Register(*plugins, false); + } +#endif + + // Secondly, apply the "static resources" layer +#if ORTHANC_STANDALONE == 1 + EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER); +#else + FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer"); +#endif + + context.GetHttpHandler().Register(staticResources, false); + + // Thirdly, consider the built-in REST API of Orthanc + OrthancRestApi restApi(context); + context.GetHttpHandler().Register(restApi, true); - LoadLuaScripts(*context); + return StartDicomServer(context, restApi, plugins); +} + + +static bool UpgradeDatabase(IDatabaseWrapper& database, + IStorageArea& storageArea, + bool allowDatabaseUpgrade) +{ + // Upgrade the database, if needed + unsigned int currentVersion = database.GetDatabaseVersion(); + if (currentVersion == ORTHANC_DATABASE_VERSION) + { + return true; + } + + if (!allowDatabaseUpgrade) + { + LOG(ERROR) << "The database must be upgraded from version " + << currentVersion << " to " << ORTHANC_DATABASE_VERSION + << ": Please run Orthanc with the \"--upgrade\" command-line option"; + return false; + } + + LOG(WARNING) << "Upgrading the database from version " + << currentVersion << " to " << ORTHANC_DATABASE_VERSION; + database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + + // Sanity check + currentVersion = database.GetDatabaseVersion(); + if (ORTHANC_DATABASE_VERSION != currentVersion) + { + LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion; + throw OrthancException(ErrorCode_InternalError); + } + + return true; +} + + +static bool ConfigureServerContext(IDatabaseWrapper& database, + IStorageArea& storageArea, + OrthancPlugins *plugins, + bool allowDatabaseUpgrade) +{ + if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade)) + { + return false; + } + + ServerContext context(database, storageArea); + + HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0)); + context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false)); + context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true)); try { - context->GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0)); + context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0)); } catch (...) { - context->GetIndex().SetMaximumPatientCount(0); + context.GetIndex().SetMaximumPatientCount(0); } try { uint64_t size = Configuration::GetGlobalIntegerParameter("MaximumStorageSize", 0); - context->GetIndex().SetMaximumStorageSize(size * 1024 * 1024); + context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); } catch (...) { - context->GetIndex().SetMaximumStorageSize(0); + context.GetIndex().SetMaximumStorageSize(0); } - MyDicomServerFactory serverFactory(*context); - bool isReset = false; - - { - // DICOM server - DicomServer dicomServer; - OrthancApplicationEntityFilter dicomFilter(*context); - dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false)); - dicomServer.SetStoreRequestHandlerFactory(serverFactory); - dicomServer.SetMoveRequestHandlerFactory(serverFactory); - dicomServer.SetFindRequestHandlerFactory(serverFactory); - dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242)); - dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC")); - dicomServer.SetApplicationEntityFilter(dicomFilter); - - // HTTP server - MyIncomingHttpRequestFilter httpFilter(*context); - MongooseServer httpServer; - httpServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("HttpPort", 8042)); - httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false)); - httpServer.SetKeepAliveEnabled(Configuration::GetGlobalBoolParameter("KeepAlive", false)); - httpServer.SetIncomingHttpRequestFilter(httpFilter); - - httpServer.SetAuthenticationEnabled(Configuration::GetGlobalBoolParameter("AuthenticationEnabled", false)); - Configuration::SetupRegisteredUsers(httpServer); + LoadLuaScripts(context); - if (Configuration::GetGlobalBoolParameter("SslEnabled", false)) - { - std::string certificate = Configuration::InterpretStringParameterAsPath( - Configuration::GetGlobalStringParameter("SslCertificate", "certificate.pem")); - httpServer.SetSslEnabled(true); - httpServer.SetSslCertificate(certificate.c_str()); - } - else - { - httpServer.SetSslEnabled(false); - } - - OrthancRestApi restApi(*context); - -#if ORTHANC_STANDALONE == 1 - EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER); -#else - FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer"); -#endif - -#if ENABLE_PLUGINS == 1 - orthancPlugins.SetServerContext(*context); - orthancPlugins.SetOrthancRestApi(restApi); - httpServer.RegisterHandler(orthancPlugins); - context->SetOrthancPlugins(pluginsManager, orthancPlugins); +#if ORTHANC_PLUGINS_ENABLED == 1 + if (plugins) + { + plugins->SetServerContext(context); + context.SetPlugins(*plugins); + } #endif - httpServer.RegisterHandler(staticResources); - httpServer.RegisterHandler(restApi); - - -#if ENABLE_PLUGINS == 1 - // Prepare the storage area - if (orthancPlugins.HasStorageArea()) - { - LOG(WARNING) << "Using a custom storage area from plugins"; - storage.reset(orthancPlugins.GetStorageArea()); - } - else -#endif - { - storage.reset(Configuration::CreateStorageArea()); - } - - context->SetStorageArea(*storage); - + bool restart = ConfigureHttpHandler(context, plugins); + context.Stop(); - // GO !!! Start the requested servers - if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true)) - { - httpServer.Start(); - LOG(WARNING) << "HTTP server listening on port: " << httpServer.GetPortNumber(); - } - else - { - LOG(WARNING) << "The HTTP server is disabled"; - } - - if (Configuration::GetGlobalBoolParameter("DicomServerEnabled", true)) - { - dicomServer.Start(); - LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber(); - } - else - { - LOG(WARNING) << "The DICOM server is disabled"; - } - - LOG(WARNING) << "Orthanc has started"; - Toolbox::ServerBarrier(restApi.ResetRequestReceivedFlag()); - isReset = restApi.ResetRequestReceivedFlag(); - - if (isReset) - { - LOG(WARNING) << "Reset request received, restarting Orthanc"; - } - - // We're done - LOG(WARNING) << "Orthanc is stopping"; - -#if ENABLE_PLUGINS == 1 - context->ResetOrthancPlugins(); - orthancPlugins.Stop(); - LOG(WARNING) << " Plugins have stopped"; +#if ORTHANC_PLUGINS_ENABLED == 1 + if (plugins) + { + context.ResetPlugins(); + } #endif - dicomServer.Stop(); - LOG(WARNING) << " DICOM server has stopped"; - - httpServer.Stop(); - LOG(WARNING) << " HTTP server has stopped"; - } - - serverFactory.Done(); - - return isReset; + return restart; } +static bool ConfigurePlugins(int argc, + char* argv[], + bool allowDatabaseUpgrade) +{ + std::auto_ptr<IDatabaseWrapper> databasePtr; + std::auto_ptr<IStorageArea> storage; + +#if ORTHANC_PLUGINS_ENABLED == 1 + OrthancPlugins plugins; + plugins.SetCommandLineArguments(argc, argv); + LoadPlugins(plugins); + + IDatabaseWrapper* database = NULL; + if (plugins.HasDatabaseBackend()) + { + LOG(WARNING) << "Using a custom database from plugins"; + database = &plugins.GetDatabaseBackend(); + } + else + { + databasePtr.reset(Configuration::CreateDatabaseWrapper()); + database = databasePtr.get(); + } + + if (plugins.HasStorageArea()) + { + LOG(WARNING) << "Using a custom storage area from plugins"; + storage.reset(plugins.CreateStorageArea()); + } + else + { + storage.reset(Configuration::CreateStorageArea()); + } + + assert(database != NULL); + assert(storage.get() != NULL); + + return ConfigureServerContext(*database, *storage, &plugins, allowDatabaseUpgrade); + +#elif ORTHANC_PLUGINS_ENABLED == 0 + // The plugins are disabled + databasePtr.reset(Configuration::CreateDatabaseWrapper()); + storage.reset(Configuration::CreateStorageArea()); + + return ConfigureServerContext(*databasePtr, *storage, NULL, allowDatabaseUpgrade); + +#else +# error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1 +#endif +} + + +static bool StartOrthanc(int argc, + char* argv[], + bool allowDatabaseUpgrade) +{ + return ConfigurePlugins(argc, argv, allowDatabaseUpgrade); +} int main(int argc, char* argv[]) { - // Initialize Google's logging library. - FLAGS_logtostderr = true; - FLAGS_minloglevel = 1; - FLAGS_v = 0; + Logging::Initialize(); + + bool allowDatabaseUpgrade = false; for (int i = 1; i < argc; i++) { @@ -583,19 +787,32 @@ if (std::string(argv[i]) == "--verbose") { - FLAGS_minloglevel = 0; + Logging::EnableInfoLevel(true); } if (std::string(argv[i]) == "--trace") { - FLAGS_minloglevel = 0; - FLAGS_v = 1; + Logging::EnableTraceLevel(true); } if (boost::starts_with(argv[i], "--logdir=")) { - FLAGS_logtostderr = false; - FLAGS_log_dir = std::string(argv[i]).substr(9); + std::string directory = std::string(argv[i]).substr(9); + + try + { + Logging::SetTargetFolder(directory); + } + catch (OrthancException&) + { + fprintf(stderr, "The directory where to store the log files (%s) is inexistent, aborting.\n", directory.c_str()); + return -1; + } + } + + if (std::string(argv[i]) == "--upgrade") + { + allowDatabaseUpgrade = true; } if (boost::starts_with(argv[i], "--config=")) @@ -616,8 +833,6 @@ } } - google::InitGoogleLogging("Orthanc"); - const char* configurationFile = NULL; for (int i = 1; i < argc; i++) { @@ -638,8 +853,8 @@ { OrthancInitialize(configurationFile); - bool reset = StartOrthanc(argc, argv); - if (reset) + bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade); + if (restart) { OrthancFinalize(); } @@ -674,5 +889,7 @@ LOG(WARNING) << "Orthanc has stopped"; + Logging::Finalize(); + return status; }
--- a/Plugins/Engine/IPluginServiceProvider.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/IPluginServiceProvider.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,9 +32,11 @@ #pragma once -#include "../Include/OrthancCPlugin.h" +#if ORTHANC_PLUGINS_ENABLED == 1 -#include <boost/noncopyable.hpp> +#include "../Include/orthanc/OrthancCPlugin.h" + +#include "SharedLibrary.h" namespace Orthanc { @@ -45,7 +47,10 @@ { } - virtual bool InvokeService(_OrthancPluginService service, + virtual bool InvokeService(SharedLibrary& plugin, + _OrthancPluginService service, const void* parameters) = 0; }; } + +#endif
--- a/Plugins/Engine/OrthancPluginDatabase.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,12 +30,19 @@ **/ +#include "../../OrthancServer/PrecompiledHeadersServer.h" #include "OrthancPluginDatabase.h" +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + #include "../../Core/OrthancException.h" +#include "../../Core/Logging.h" +#include "PluginsEnumerations.h" #include <cassert> -#include <glog/logging.h> namespace Orthanc { @@ -111,7 +118,7 @@ if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_Int64) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } target.clear(); @@ -119,7 +126,7 @@ if (type_ == _OrthancPluginDatabaseAnswerType_Int64) { for (std::list<int64_t>::const_iterator - it = answerInt64_.begin(); it != answerInt64_.end(); it++) + it = answerInt64_.begin(); it != answerInt64_.end(); ++it) { target.push_back(*it); } @@ -132,7 +139,7 @@ if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_String) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } target.clear(); @@ -140,7 +147,7 @@ if (type_ == _OrthancPluginDatabaseAnswerType_String) { for (std::list<std::string>::const_iterator - it = answerStrings_.begin(); it != answerStrings_.end(); it++) + it = answerStrings_.begin(); it != answerStrings_.end(); ++it) { target.push_back(*it); } @@ -162,7 +169,7 @@ } else { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } @@ -181,11 +188,40 @@ } else { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } + OrthancPluginDatabase::OrthancPluginDatabase(SharedLibrary& library, + PluginsErrorDictionary& errorDictionary, + const OrthancPluginDatabaseBackend& backend, + const OrthancPluginDatabaseExtensions* extensions, + size_t extensionsSize, + void *payload) : + library_(library), + errorDictionary_(errorDictionary), + type_(_OrthancPluginDatabaseAnswerType_None), + backend_(backend), + payload_(payload), + listener_(NULL), + answerDicomMap_(NULL), + answerChanges_(NULL), + answerExportedResources_(NULL), + answerDone_(NULL) + { + memset(&extensions_, 0, sizeof(extensions_)); + + size_t size = sizeof(extensions_); + if (extensionsSize < size) + { + size = extensionsSize; // Not all the extensions are available + } + + memcpy(&extensions_, extensions, size); + } + + void OrthancPluginDatabase::AddAttachment(int64_t id, const FileInfo& attachment) { @@ -198,9 +234,12 @@ tmp.compressedSize = attachment.GetCompressedSize(); tmp.compressedHash = attachment.GetCompressedMD5().c_str(); - if (backend_.addAttachment(payload_, id, &tmp) != 0) + OrthancPluginErrorCode error = backend_.addAttachment(payload_, id, &tmp); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -208,27 +247,36 @@ void OrthancPluginDatabase::AttachChild(int64_t parent, int64_t child) { - if (backend_.attachChild(payload_, parent, child) != 0) + OrthancPluginErrorCode error = backend_.attachChild(payload_, parent, child); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } void OrthancPluginDatabase::ClearChanges() { - if (backend_.clearChanges(payload_) != 0) + OrthancPluginErrorCode error = backend_.clearChanges(payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } void OrthancPluginDatabase::ClearExportedResources() { - if (backend_.clearExportedResources(payload_) != 0) + OrthancPluginErrorCode error = backend_.clearExportedResources(payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -238,9 +286,12 @@ { int64_t id; - if (backend_.createResource(&id, payload_, publicId.c_str(), Convert(type)) != 0) + OrthancPluginErrorCode error = backend_.createResource(&id, payload_, publicId.c_str(), Convert(type)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return id; @@ -250,9 +301,12 @@ void OrthancPluginDatabase::DeleteAttachment(int64_t id, FileContentType attachment) { - if (backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)) != 0) + OrthancPluginErrorCode error = backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -260,18 +314,24 @@ void OrthancPluginDatabase::DeleteMetadata(int64_t id, MetadataType type) { - if (backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)) != 0) + OrthancPluginErrorCode error = backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } void OrthancPluginDatabase::DeleteResource(int64_t id) { - if (backend_.deleteResource(payload_, id) != 0) + OrthancPluginErrorCode error = backend_.deleteResource(payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -285,12 +345,12 @@ target.clear(); for (std::list<MetadataType>::const_iterator - it = metadata.begin(); it != metadata.end(); it++) + it = metadata.begin(); it != metadata.end(); ++it) { std::string value; if (!LookupMetadata(value, id, *it)) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } target[*it] = value; @@ -303,15 +363,72 @@ { ResetAnswers(); - if (backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType)) != 0) + OrthancPluginErrorCode error = backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } ForwardAnswers(target); } + void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (extensions_.getAllPublicIdsWithLimit != NULL) + { + // This extension is available since Orthanc 0.9.4 + ResetAnswers(); + + OrthancPluginErrorCode error = extensions_.getAllPublicIdsWithLimit + (GetContext(), payload_, Convert(resourceType), since, limit); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + + ForwardAnswers(target); + } + else + { + // The extension is not available in the database plugin, use a + // fallback implementation + target.clear(); + + if (limit == 0) + { + return; + } + + std::list<std::string> tmp; + GetAllPublicIds(tmp, resourceType); + + if (tmp.size() <= since) + { + // Not enough results => empty answer + return; + } + + std::list<std::string>::iterator current = tmp.begin(); + std::advance(current, since); + + while (limit > 0 && current != tmp.end()) + { + target.push_back(*current); + --limit; + ++current; + } + } + } + + void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/, bool& done /*out*/, @@ -323,9 +440,12 @@ answerDone_ = &done; done = false; - if (backend_.getChanges(GetContext(), payload_, since, maxResults) != 0) + OrthancPluginErrorCode error = backend_.getChanges(GetContext(), payload_, since, maxResults); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -335,9 +455,12 @@ { ResetAnswers(); - if (backend_.getChildrenInternalId(GetContext(), payload_, id) != 0) + OrthancPluginErrorCode error = backend_.getChildrenInternalId(GetContext(), payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } ForwardAnswers(target); @@ -349,9 +472,12 @@ { ResetAnswers(); - if (backend_.getChildrenPublicId(GetContext(), payload_, id) != 0) + OrthancPluginErrorCode error = backend_.getChildrenPublicId(GetContext(), payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } ForwardAnswers(target); @@ -368,9 +494,12 @@ answerDone_ = &done; done = false; - if (backend_.getExportedResources(GetContext(), payload_, since, maxResults) != 0) + OrthancPluginErrorCode error = backend_.getExportedResources(GetContext(), payload_, since, maxResults); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -383,9 +512,12 @@ answerChanges_ = ⌖ answerDone_ = &ignored; - if (backend_.getLastChange(GetContext(), payload_) != 0) + OrthancPluginErrorCode error = backend_.getLastChange(GetContext(), payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -398,9 +530,12 @@ answerExportedResources_ = ⌖ answerDone_ = &ignored; - if (backend_.getLastExportedResource(GetContext(), payload_) != 0) + OrthancPluginErrorCode error = backend_.getLastExportedResource(GetContext(), payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -411,9 +546,12 @@ ResetAnswers(); answerDicomMap_ = ↦ - if (backend_.getMainDicomTags(GetContext(), payload_, id) != 0) + OrthancPluginErrorCode error = backend_.getMainDicomTags(GetContext(), payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -423,10 +561,17 @@ ResetAnswers(); std::string s; - if (backend_.getPublicId(GetContext(), payload_, resourceId) != 0 || - !ForwardSingleAnswer(s)) + OrthancPluginErrorCode error = backend_.getPublicId(GetContext(), payload_, resourceId); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + + if (!ForwardSingleAnswer(s)) + { + throw OrthancException(ErrorCode_DatabasePlugin); } return s; @@ -437,9 +582,12 @@ { uint64_t count; - if (backend_.getResourceCount(&count, payload_, Convert(resourceType)) != 0) + OrthancPluginErrorCode error = backend_.getResourceCount(&count, payload_, Convert(resourceType)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return count; @@ -450,9 +598,12 @@ { OrthancPluginResourceType type; - if (backend_.getResourceType(&type, payload_, resourceId) != 0) + OrthancPluginErrorCode error = backend_.getResourceType(&type, payload_, resourceId); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return Convert(type); @@ -463,9 +614,12 @@ { uint64_t size; - if (backend_.getTotalCompressedSize(&size, payload_) != 0) + OrthancPluginErrorCode error = backend_.getTotalCompressedSize(&size, payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return size; @@ -476,9 +630,12 @@ { uint64_t size; - if (backend_.getTotalUncompressedSize(&size, payload_) != 0) + OrthancPluginErrorCode error = backend_.getTotalUncompressedSize(&size, payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return size; @@ -489,12 +646,15 @@ { int32_t existing; - if (backend_.isExistingResource(&existing, payload_, internalId) != 0) + OrthancPluginErrorCode error = backend_.isExistingResource(&existing, payload_, internalId); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } - return existing; + return (existing != 0); } @@ -502,12 +662,15 @@ { int32_t isProtected; - if (backend_.isProtectedPatient(&isProtected, payload_, internalId) != 0) + OrthancPluginErrorCode error = backend_.isProtectedPatient(&isProtected, payload_, internalId); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } - return isProtected; + return (isProtected != 0); } @@ -516,15 +679,18 @@ { ResetAnswers(); - if (backend_.listAvailableMetadata(GetContext(), payload_, id) != 0) + OrthancPluginErrorCode error = backend_.listAvailableMetadata(GetContext(), payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_Int32) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } target.clear(); @@ -532,7 +698,7 @@ if (type_ == _OrthancPluginDatabaseAnswerType_Int32) { for (std::list<int32_t>::const_iterator - it = answerInt32_.begin(); it != answerInt32_.end(); it++) + it = answerInt32_.begin(); it != answerInt32_.end(); ++it) { target.push_back(static_cast<MetadataType>(*it)); } @@ -545,15 +711,18 @@ { ResetAnswers(); - if (backend_.listAvailableAttachments(GetContext(), payload_, id) != 0) + OrthancPluginErrorCode error = backend_.listAvailableAttachments(GetContext(), payload_, id); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } if (type_ != _OrthancPluginDatabaseAnswerType_None && type_ != _OrthancPluginDatabaseAnswerType_Int32) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } target.clear(); @@ -561,7 +730,7 @@ if (type_ == _OrthancPluginDatabaseAnswerType_Int32) { for (std::list<int32_t>::const_iterator - it = answerInt32_.begin(); it != answerInt32_.end(); it++) + it = answerInt32_.begin(); it != answerInt32_.end(); ++it) { target.push_back(static_cast<FileContentType>(*it)); } @@ -579,9 +748,12 @@ tmp.publicId = change.GetPublicId().c_str(); tmp.date = change.GetDate().c_str(); - if (backend_.logChange(payload_, &tmp) != 0) + OrthancPluginErrorCode error = backend_.logChange(payload_, &tmp); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -599,9 +771,12 @@ tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str(); tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str(); - if (backend_.logExportedResource(payload_, &tmp) != 0) + OrthancPluginErrorCode error = backend_.logExportedResource(payload_, &tmp); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -612,9 +787,13 @@ { ResetAnswers(); - if (backend_.lookupAttachment(GetContext(), payload_, id, static_cast<int32_t>(contentType))) + OrthancPluginErrorCode error = backend_.lookupAttachment + (GetContext(), payload_, id, static_cast<int32_t>(contentType)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } if (type_ == _OrthancPluginDatabaseAnswerType_None) @@ -629,7 +808,7 @@ } else { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } @@ -639,10 +818,13 @@ { ResetAnswers(); - if (backend_.lookupGlobalProperty(GetContext(), payload_, - static_cast<int32_t>(property))) + OrthancPluginErrorCode error = backend_.lookupGlobalProperty + (GetContext(), payload_, static_cast<int32_t>(property)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return ForwardSingleAnswer(target); @@ -660,9 +842,12 @@ tmp.element = tag.GetElement(); tmp.value = value.c_str(); - if (backend_.lookupIdentifier(GetContext(), payload_, &tmp) != 0) + OrthancPluginErrorCode error = backend_.lookupIdentifier(GetContext(), payload_, &tmp); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } ForwardAnswers(target); @@ -674,9 +859,12 @@ { ResetAnswers(); - if (backend_.lookupIdentifier2(GetContext(), payload_, value.c_str()) != 0) + OrthancPluginErrorCode error = backend_.lookupIdentifier2(GetContext(), payload_, value.c_str()); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } ForwardAnswers(target); @@ -689,9 +877,12 @@ { ResetAnswers(); - if (backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type))) + OrthancPluginErrorCode error = backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return ForwardSingleAnswer(target); @@ -703,9 +894,12 @@ { ResetAnswers(); - if (backend_.lookupParent(GetContext(), payload_, resourceId)) + OrthancPluginErrorCode error = backend_.lookupParent(GetContext(), payload_, resourceId); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return ForwardSingleAnswer(parentId); @@ -718,9 +912,12 @@ { ResetAnswers(); - if (backend_.lookupResource(GetContext(), payload_, publicId.c_str())) + OrthancPluginErrorCode error = backend_.lookupResource(GetContext(), payload_, publicId.c_str()); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } if (type_ == _OrthancPluginDatabaseAnswerType_None) @@ -736,7 +933,7 @@ } else { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } @@ -745,9 +942,12 @@ { ResetAnswers(); - if (backend_.selectPatientToRecycle(GetContext(), payload_)) + OrthancPluginErrorCode error = backend_.selectPatientToRecycle(GetContext(), payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return ForwardSingleAnswer(internalId); @@ -759,9 +959,12 @@ { ResetAnswers(); - if (backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid)) + OrthancPluginErrorCode error = backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } return ForwardSingleAnswer(internalId); @@ -771,10 +974,13 @@ void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property, const std::string& value) { - if (backend_.setGlobalProperty(payload_, static_cast<int32_t>(property), - value.c_str()) != 0) + OrthancPluginErrorCode error = backend_.setGlobalProperty + (payload_, static_cast<int32_t>(property), value.c_str()); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -783,24 +989,26 @@ const DicomTag& tag, const std::string& value) { - int32_t status; OrthancPluginDicomTag tmp; tmp.group = tag.GetGroup(); tmp.element = tag.GetElement(); tmp.value = value.c_str(); + OrthancPluginErrorCode error; + if (tag.IsIdentifier()) { - status = backend_.setIdentifierTag(payload_, id, &tmp); + error = backend_.setIdentifierTag(payload_, id, &tmp); } else { - status = backend_.setMainDicomTag(payload_, id, &tmp); + error = backend_.setMainDicomTag(payload_, id, &tmp); } - if (status != 0) + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -809,10 +1017,13 @@ MetadataType type, const std::string& value) { - if (backend_.setMetadata(payload_, id, static_cast<int32_t>(type), - value.c_str()) != 0) + OrthancPluginErrorCode error = backend_.setMetadata + (payload_, id, static_cast<int32_t>(type), value.c_str()); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -820,9 +1031,12 @@ void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, bool isProtected) { - if (backend_.setProtectedPatient(payload_, internalId, isProtected) != 0) + OrthancPluginErrorCode error = backend_.setProtectedPatient(payload_, internalId, isProtected); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } @@ -832,36 +1046,48 @@ private: const OrthancPluginDatabaseBackend& backend_; void* payload_; + PluginsErrorDictionary& errorDictionary_; public: Transaction(const OrthancPluginDatabaseBackend& backend, - void* payload) : + void* payload, + PluginsErrorDictionary& errorDictionary) : backend_(backend), - payload_(payload) + payload_(payload), + errorDictionary_(errorDictionary) { } virtual void Begin() { - if (backend_.startTransaction(payload_) != 0) + OrthancPluginErrorCode error = backend_.startTransaction(payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } virtual void Rollback() { - if (backend_.rollbackTransaction(payload_) != 0) + OrthancPluginErrorCode error = backend_.rollbackTransaction(payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } virtual void Commit() { - if (backend_.commitTransaction(payload_) != 0) + OrthancPluginErrorCode error = backend_.commitTransaction(payload_); + + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_Plugin); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } }; @@ -869,11 +1095,11 @@ SQLite::ITransaction* OrthancPluginDatabase::StartTransaction() { - return new Transaction(backend_, payload_); + return new Transaction(backend_, payload_, errorDictionary_); } - static void ProcessEvent(IServerIndexListener& listener, + static void ProcessEvent(IDatabaseListener& listener, const _OrthancPluginDatabaseAnswer& answer) { switch (answer.type) @@ -902,7 +1128,50 @@ } default: - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + unsigned int OrthancPluginDatabase::GetDatabaseVersion() + { + if (extensions_.getDatabaseVersion != NULL) + { + uint32_t version; + OrthancPluginErrorCode error = extensions_.getDatabaseVersion(&version, payload_); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + + return version; + } + else + { + // Before adding the "GetDatabaseVersion()" extension in plugins + // (OrthancPostgreSQL <= 1.2), the only supported DB schema was + // version 5. + return 5; + } + } + + + void OrthancPluginDatabase::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + if (extensions_.upgradeDatabase != NULL) + { + OrthancPluginErrorCode error = extensions_.upgradeDatabase( + payload_, targetVersion, + reinterpret_cast<OrthancPluginStorageArea*>(&storageArea)); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } } } @@ -911,7 +1180,7 @@ { if (answer.type == _OrthancPluginDatabaseAnswerType_None) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } if (answer.type == _OrthancPluginDatabaseAnswerType_DeletedAttachment || @@ -966,13 +1235,13 @@ default: LOG(ERROR) << "Unhandled type of answer for custom index plugin: " << answer.type; - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } else if (type_ != answer.type) { LOG(ERROR) << "Error in the plugin protocol: Cannot change the answer type"; - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } switch (answer.type) @@ -1017,7 +1286,7 @@ { if (answer.valueString == NULL) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } if (type_ == _OrthancPluginDatabaseAnswerType_None) @@ -1027,7 +1296,7 @@ } else if (type_ != _OrthancPluginDatabaseAnswerType_String) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } answerStrings_.push_back(std::string(answer.valueString)); @@ -1043,7 +1312,7 @@ } else if (*answerDone_) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } else { @@ -1069,7 +1338,7 @@ } else if (*answerDone_) { - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } else { @@ -1093,7 +1362,7 @@ default: LOG(ERROR) << "Unhandled type of answer for custom index plugin: " << answer.type; - throw OrthancException(ErrorCode_Plugin); + throw OrthancException(ErrorCode_DatabasePlugin); } } }
--- a/Plugins/Engine/OrthancPluginDatabase.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/OrthancPluginDatabase.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,8 +32,12 @@ #pragma once +#if ORTHANC_PLUGINS_ENABLED == 1 + #include "../../OrthancServer/IDatabaseWrapper.h" -#include "../Include/OrthancCDatabasePlugin.h" +#include "../Include/orthanc/OrthancCDatabasePlugin.h" +#include "PluginsErrorDictionary.h" +#include "SharedLibrary.h" namespace Orthanc { @@ -44,10 +48,13 @@ typedef std::pair<int64_t, ResourceType> AnswerResource; + SharedLibrary& library_; + PluginsErrorDictionary& errorDictionary_; _OrthancPluginDatabaseAnswerType type_; OrthancPluginDatabaseBackend backend_; + OrthancPluginDatabaseExtensions extensions_; void* payload_; - IServerIndexListener* listener_; + IDatabaseListener* listener_; std::list<std::string> answerStrings_; std::list<int32_t> answerInt32_; @@ -76,13 +83,16 @@ bool ForwardSingleAnswer(int64_t& target); public: - OrthancPluginDatabase(const OrthancPluginDatabaseBackend& backend, - void *payload) : - type_(_OrthancPluginDatabaseAnswerType_None), - backend_(backend), - payload_(payload), - listener_(NULL) + OrthancPluginDatabase(SharedLibrary& library, + PluginsErrorDictionary& errorDictionary, + const OrthancPluginDatabaseBackend& backend, + const OrthancPluginDatabaseExtensions* extensions, + size_t extensionsSize, + void *payload); + + const SharedLibrary& GetSharedLibrary() const { + return library_; } virtual void AddAttachment(int64_t id, @@ -121,6 +131,10 @@ virtual void GetAllPublicIds(std::list<std::string>& target, ResourceType resourceType); + virtual void GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit); virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/, bool& done /*out*/, @@ -216,11 +230,18 @@ virtual SQLite::ITransaction* StartTransaction(); - virtual void SetListener(IServerIndexListener& listener) + virtual void SetListener(IDatabaseListener& listener) { listener_ = &listener; } + virtual unsigned int GetDatabaseVersion(); + + virtual void Upgrade(unsigned int targetVersion, + IStorageArea& storageArea); + void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer); }; } + +#endif
--- a/Plugins/Engine/OrthancPlugins.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,197 +30,236 @@ **/ +#include "../../OrthancServer/PrecompiledHeadersServer.h" #include "OrthancPlugins.h" +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + #include "../../Core/ChunkedBuffer.h" +#include "../../Core/HttpServer/HttpToolbox.h" +#include "../../Core/Logging.h" #include "../../Core/OrthancException.h" #include "../../Core/Toolbox.h" -#include "../../Core/HttpServer/HttpOutput.h" -#include "../../Core/ImageFormats/PngWriter.h" -#include "../../OrthancServer/ServerToolbox.h" +#include "../../OrthancServer/FromDcmtkBridge.h" #include "../../OrthancServer/OrthancInitialization.h" -#include "../../Core/MultiThreading/SharedMessageQueue.h" +#include "../../OrthancServer/ServerContext.h" +#include "../../OrthancServer/ServerToolbox.h" +#include "../../Core/Compression/ZlibCompressor.h" +#include "../../Core/Compression/GzipCompressor.h" +#include "../../Core/Images/Image.h" +#include "../../Core/Images/PngReader.h" +#include "../../Core/Images/PngWriter.h" +#include "../../Core/Images/JpegReader.h" +#include "../../Core/Images/JpegWriter.h" +#include "../../Core/Images/ImageProcessing.h" +#include "PluginsEnumerations.h" -#include <boost/thread.hpp> #include <boost/regex.hpp> -#include <glog/logging.h> namespace Orthanc { - static OrthancPluginResourceType Convert(ResourceType type) + namespace { - switch (type) + class PluginStorageArea : public IStorageArea { - case ResourceType_Patient: - return OrthancPluginResourceType_Patient; - - case ResourceType_Study: - return OrthancPluginResourceType_Study; + private: + _OrthancPluginRegisterStorageArea callbacks_; + PluginsErrorDictionary& errorDictionary_; - case ResourceType_Series: - return OrthancPluginResourceType_Series; + void Free(void* buffer) const + { + if (buffer != NULL) + { + callbacks_.free(buffer); + } + } - case ResourceType_Instance: - return OrthancPluginResourceType_Instance; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } + public: + PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks, + PluginsErrorDictionary& errorDictionary) : + callbacks_(callbacks), + errorDictionary_(errorDictionary) + { + } - static OrthancPluginChangeType Convert(ChangeType type) - { - switch (type) - { - case ChangeType_CompletedSeries: - return OrthancPluginChangeType_CompletedSeries; - - case ChangeType_Deleted: - return OrthancPluginChangeType_Deleted; - - case ChangeType_NewChildInstance: - return OrthancPluginChangeType_NewChildInstance; - - case ChangeType_NewInstance: - return OrthancPluginChangeType_NewInstance; - - case ChangeType_NewPatient: - return OrthancPluginChangeType_NewPatient; - - case ChangeType_NewSeries: - return OrthancPluginChangeType_NewSeries; - - case ChangeType_NewStudy: - return OrthancPluginChangeType_NewStudy; - - case ChangeType_StablePatient: - return OrthancPluginChangeType_StablePatient; + virtual void Create(const std::string& uuid, + const void* content, + size_t size, + FileContentType type) + { + OrthancPluginErrorCode error = callbacks_.create + (uuid.c_str(), content, size, Plugins::Convert(type)); - case ChangeType_StableSeries: - return OrthancPluginChangeType_StableSeries; - - case ChangeType_StableStudy: - return OrthancPluginChangeType_StableStudy; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class StringHttpOutput : public IHttpOutputStream - { - private: - ChunkedBuffer buffer_; - - public: - void GetOutput(std::string& output) - { - buffer_.Flatten(output); - } - - virtual void OnHttpStatusReceived(HttpStatus status) - { - if (status != HttpStatus_200_Ok) + if (error != OrthancPluginErrorCode_Success) { - throw OrthancException(ErrorCode_BadRequest); + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } - virtual void Send(bool isHeader, const void* buffer, size_t length) + + virtual void Read(std::string& content, + const std::string& uuid, + FileContentType type) { - if (!isHeader) + void* buffer = NULL; + int64_t size = 0; + + OrthancPluginErrorCode error = callbacks_.read + (&buffer, &size, uuid.c_str(), Plugins::Convert(type)); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + + try + { + content.resize(static_cast<size_t>(size)); + } + catch (...) { - buffer_.AddChunk(reinterpret_cast<const char*>(buffer), length); + Free(buffer); + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + if (size > 0) + { + memcpy(&content[0], buffer, static_cast<size_t>(size)); + } + + Free(buffer); + } + + + virtual void Remove(const std::string& uuid, + FileContentType type) + { + OrthancPluginErrorCode error = callbacks_.remove + (uuid.c_str(), Plugins::Convert(type)); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } }; - class PendingChange : public IDynamicObject + class StorageAreaFactory : public boost::noncopyable { private: - OrthancPluginChangeType changeType_; - OrthancPluginResourceType resourceType_; - std::string publicId_; + SharedLibrary& sharedLibrary_; + _OrthancPluginRegisterStorageArea callbacks_; + PluginsErrorDictionary& errorDictionary_; public: - PendingChange(const ServerIndexChange& change) + StorageAreaFactory(SharedLibrary& sharedLibrary, + const _OrthancPluginRegisterStorageArea& callbacks, + PluginsErrorDictionary& errorDictionary) : + sharedLibrary_(sharedLibrary), + callbacks_(callbacks), + errorDictionary_(errorDictionary) { - changeType_ = Convert(change.GetChangeType()); - resourceType_ = Convert(change.GetResourceType()); - publicId_ = change.GetPublicId(); } - void Submit(std::list<OrthancPluginOnChangeCallback>& callbacks) + SharedLibrary& GetSharedLibrary() { - for (std::list<OrthancPluginOnChangeCallback>::const_iterator - callback = callbacks.begin(); - callback != callbacks.end(); ++callback) - { - (*callback) (changeType_, resourceType_, publicId_.c_str()); - } + return sharedLibrary_; + } + + IStorageArea* Create() const + { + return new PluginStorageArea(callbacks_, errorDictionary_); } }; } - struct OrthancPlugins::PImpl { - typedef std::pair<std::string, _OrthancPluginProperty> Property; + class RestCallback : public boost::noncopyable + { + private: + boost::regex regex_; + OrthancPluginRestCallback callback_; + bool lock_; + + OrthancPluginErrorCode InvokeInternal(HttpOutput& output, + const std::string& flatUri, + const OrthancPluginHttpRequest& request) + { + return callback_(reinterpret_cast<OrthancPluginRestOutput*>(&output), + flatUri.c_str(), + &request); + } + + public: + RestCallback(const char* regex, + OrthancPluginRestCallback callback, + bool lockRestCallbacks) : + regex_(regex), + callback_(callback), + lock_(lockRestCallbacks) + { + } - typedef std::pair<boost::regex*, OrthancPluginRestCallback> RestCallback; - typedef std::list<RestCallback> RestCallbacks; + const boost::regex& GetRegularExpression() const + { + return regex_; + } + + OrthancPluginErrorCode Invoke(boost::recursive_mutex& restCallbackMutex, + HttpOutput& output, + const std::string& flatUri, + const OrthancPluginHttpRequest& request) + { + if (lock_) + { + boost::recursive_mutex::scoped_lock lock(restCallbackMutex); + return InvokeInternal(output, flatUri, request); + } + else + { + return InvokeInternal(output, flatUri, request); + } + } + }; + + + typedef std::pair<std::string, _OrthancPluginProperty> Property; + typedef std::list<RestCallback*> RestCallbacks; typedef std::list<OrthancPluginOnStoredInstanceCallback> OnStoredCallbacks; typedef std::list<OrthancPluginOnChangeCallback> OnChangeCallbacks; typedef std::map<Property, std::string> Properties; + PluginsManager manager_; ServerContext* context_; RestCallbacks restCallbacks_; - OrthancRestApi* restApi_; OnStoredCallbacks onStoredCallbacks_; OnChangeCallbacks onChangeCallbacks_; - bool hasStorageArea_; - _OrthancPluginRegisterStorageArea storageArea_; - boost::recursive_mutex callbackMutex_; - SharedMessageQueue pendingChanges_; - boost::thread changeThread_; - bool done_; + std::auto_ptr<StorageAreaFactory> storageArea_; + boost::recursive_mutex restCallbackMutex_; + boost::recursive_mutex storedCallbackMutex_; + boost::recursive_mutex changeCallbackMutex_; + boost::recursive_mutex invokeServiceMutex_; Properties properties_; int argc_; char** argv_; std::auto_ptr<OrthancPluginDatabase> database_; + PluginsErrorDictionary dictionary_; PImpl() : context_(NULL), - restApi_(NULL), - hasStorageArea_(false), - done_(false), argc_(1), argv_(NULL) { - memset(&storageArea_, 0, sizeof(storageArea_)); - } - - - static void ChangeThread(PImpl* that) - { - while (!that->done_) - { - std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(500)); - - if (obj.get() != NULL) - { - boost::recursive_mutex::scoped_lock lock(that->callbackMutex_); - PendingChange& change = *dynamic_cast<PendingChange*>(obj.get()); - change.Submit(that->onChangeCallbacks_); - } - } } }; @@ -249,8 +288,25 @@ OrthancPlugins::OrthancPlugins() { + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + /* Sanity check of the compiler */ + throw OrthancException(ErrorCode_Plugin); + } + pimpl_.reset(new PImpl()); - pimpl_->changeThread_ = boost::thread(PImpl::ChangeThread, pimpl_.get()); + pimpl_->manager_.RegisterServiceProvider(*this); } @@ -263,37 +319,23 @@ OrthancPlugins::~OrthancPlugins() { - Stop(); - for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin(); it != pimpl_->restCallbacks_.end(); ++it) { - // Delete the regular expression associated with this callback - delete it->first; + delete *it; } } - void OrthancPlugins::Stop() - { - if (!pimpl_->done_) - { - pimpl_->done_ = true; - pimpl_->changeThread_.join(); - } - } - - - static void ArgumentsToPlugin(std::vector<const char*>& keys, std::vector<const char*>& values, - const HttpHandler::Arguments& arguments) + const IHttpHandler::Arguments& arguments) { keys.resize(arguments.size()); values.resize(arguments.size()); size_t pos = 0; - for (HttpHandler::Arguments::const_iterator + for (IHttpHandler::Arguments::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { keys[pos] = it->first.c_str(); @@ -303,15 +345,34 @@ } + static void ArgumentsToPlugin(std::vector<const char*>& keys, + std::vector<const char*>& values, + const IHttpHandler::GetArguments& arguments) + { + keys.resize(arguments.size()); + values.resize(arguments.size()); + + for (size_t i = 0; i < arguments.size(); i++) + { + keys[i] = arguments[i].first.c_str(); + values[i] = arguments[i].second.c_str(); + } + } + + bool OrthancPlugins::Handle(HttpOutput& output, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const Arguments& getArguments, - const std::string& postData) + RequestOrigin /*origin*/, + const char* /*remoteIp*/, + const char* /*username*/, + HttpMethod method, + const UriComponents& uri, + const Arguments& headers, + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize) { std::string flatUri = Toolbox::FlattenUri(uri); - OrthancPluginRestCallback callback = NULL; + PImpl::RestCallback* callback = NULL; std::vector<std::string> groups; std::vector<const char*> cgroups; @@ -324,9 +385,9 @@ // Check whether the regular expression associated to this // callback matches the URI boost::cmatch what; - if (boost::regex_match(flatUri.c_str(), what, *(it->first))) + if (boost::regex_match(flatUri.c_str(), what, (*it)->GetRegularExpression())) { - callback = it->second; + callback = *it; // Extract the value of the free parameters of the regular expression if (what.size() > 1) @@ -339,13 +400,12 @@ cgroups[i - 1] = groups[i - 1].c_str(); } } - - found = true; } } - if (!found) + if (callback == NULL) { + // Callback not found return false; } @@ -385,8 +445,8 @@ request.groups = (cgroups.size() ? &cgroups[0] : NULL); request.groupsCount = cgroups.size(); request.getCount = getArguments.size(); - request.body = (postData.size() ? &postData[0] : NULL); - request.bodySize = postData.size(); + request.body = bodyData; + request.bodySize = bodySize; request.headersCount = headers.size(); if (getArguments.size() > 0) @@ -402,43 +462,45 @@ } assert(callback != NULL); - int32_t error; + OrthancPluginErrorCode error = callback->Invoke(pimpl_->restCallbackMutex_, output, flatUri, request); + if (error == OrthancPluginErrorCode_Success && + output.IsWritingMultipart()) { - boost::recursive_mutex::scoped_lock lock(pimpl_->callbackMutex_); - error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), - flatUri.c_str(), - &request); + output.CloseMultipart(); } - if (error < 0) + if (error == OrthancPluginErrorCode_Success) { - LOG(ERROR) << "Plugin callback failed with error code " << error; - return false; + return true; } else { - if (error > 0) - { - LOG(WARNING) << "Plugin callback finished with warning code " << error; - } - - return true; + GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } } - void OrthancPlugins::SignalStoredInstance(DicomInstanceToStore& instance, - const std::string& instanceId) + void OrthancPlugins::SignalStoredInstance(const std::string& instanceId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags) { - boost::recursive_mutex::scoped_lock lock(pimpl_->callbackMutex_); + boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_); for (PImpl::OnStoredCallbacks::const_iterator callback = pimpl_->onStoredCallbacks_.begin(); callback != pimpl_->onStoredCallbacks_.end(); ++callback) { - (*callback) (reinterpret_cast<OrthancPluginDicomInstance*>(&instance), - instanceId.c_str()); + OrthancPluginErrorCode error = (*callback) + (reinterpret_cast<OrthancPluginDicomInstance*>(&instance), + instanceId.c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } } } @@ -446,14 +508,22 @@ void OrthancPlugins::SignalChange(const ServerIndexChange& change) { - try + boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_); + + for (std::list<OrthancPluginOnChangeCallback>::const_iterator + callback = pimpl_->onChangeCallbacks_.begin(); + callback != pimpl_->onChangeCallbacks_.end(); ++callback) { - pimpl_->pendingChanges_.Enqueue(new PendingChange(change)); - } - catch (OrthancException&) - { - // This change type or resource type is not supported by the plugin SDK - return; + OrthancPluginErrorCode error = (*callback) + (Plugins::Convert(change.GetChangeType()), + Plugins::Convert(change.GetResourceType()), + change.GetPublicId().c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } } } @@ -499,13 +569,18 @@ } - void OrthancPlugins::RegisterRestCallback(const void* parameters) + void OrthancPlugins::RegisterRestCallback(const void* parameters, + bool lock) { const _OrthancPluginRestCallback& p = *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters); - LOG(INFO) << "Plugin has registered a REST callback on: " << p.pathRegularExpression; - pimpl_->restCallbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback)); + LOG(INFO) << "Plugin has registered a REST callback " + << (lock ? "with" : "witout") + << " mutual exclusion on: " + << p.pathRegularExpression; + + pimpl_->restCallbacks_.push_back(new PImpl::RestCallback(p.pathRegularExpression, p.callback, lock)); } @@ -538,7 +613,7 @@ HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output); translatedOutput->SetContentType(p.mimeType); - translatedOutput->SendBody(p.answer, p.answerSize); + translatedOutput->Answer(p.answer, p.answerSize); } @@ -562,6 +637,25 @@ } + void OrthancPlugins::SendHttpStatus(const void* parameters) + { + const _OrthancPluginSendHttpStatus& p = + *reinterpret_cast<const _OrthancPluginSendHttpStatus*>(parameters); + + HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output); + HttpStatus status = static_cast<HttpStatus>(p.status); + + if (p.bodySize > 0 && p.body != NULL) + { + translatedOutput->SendStatus(status, p.body, p.bodySize); + } + else + { + translatedOutput->SendStatus(status); + } + } + + void OrthancPlugins::SendUnauthorized(const void* parameters) { const _OrthancPluginOutputPlusArgument& p = @@ -604,59 +698,82 @@ void OrthancPlugins::CompressAndAnswerPngImage(const void* parameters) { + // Bridge for backward compatibility with Orthanc <= 0.9.3 const _OrthancPluginCompressAndAnswerPngImage& p = *reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters); + _OrthancPluginCompressAndAnswerImage p2; + p2.output = p.output; + p2.imageFormat = OrthancPluginImageFormat_Png; + p2.pixelFormat = p.format; + p2.width = p.width; + p2.height = p.height; + p2.pitch = p.height; + p2.buffer = p.buffer; + p2.quality = 0; + + CompressAndAnswerImage(&p2); + } + + + void OrthancPlugins::CompressAndAnswerImage(const void* parameters) + { + const _OrthancPluginCompressAndAnswerImage& p = + *reinterpret_cast<const _OrthancPluginCompressAndAnswerImage*>(parameters); + HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output); - PixelFormat format; - switch (p.format) - { - case OrthancPluginPixelFormat_Grayscale8: - format = PixelFormat_Grayscale8; - break; + ImageAccessor accessor; + accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer); - case OrthancPluginPixelFormat_Grayscale16: - format = PixelFormat_Grayscale16; - break; + std::string compressed; - case OrthancPluginPixelFormat_SignedGrayscale16: - format = PixelFormat_SignedGrayscale16; + switch (p.imageFormat) + { + case OrthancPluginImageFormat_Png: + { + PngWriter writer; + writer.WriteToMemory(compressed, accessor); + translatedOutput->SetContentType("image/png"); break; + } - case OrthancPluginPixelFormat_RGB24: - format = PixelFormat_RGB24; + case OrthancPluginImageFormat_Jpeg: + { + JpegWriter writer; + writer.SetQuality(p.quality); + writer.WriteToMemory(compressed, accessor); + translatedOutput->SetContentType("image/jpeg"); break; - - case OrthancPluginPixelFormat_RGBA32: - format = PixelFormat_RGBA32; - break; + } default: throw OrthancException(ErrorCode_ParameterOutOfRange); } - ImageAccessor accessor; - accessor.AssignReadOnly(format, p.width, p.height, p.pitch, p.buffer); + translatedOutput->Answer(compressed); + } + - PngWriter writer; - std::string png; - writer.WriteToMemory(png, accessor); - - translatedOutput->SetContentType("image/png"); - translatedOutput->SendBody(png); + void OrthancPlugins::CheckContextAvailable() + { + if (!pimpl_->context_) + { + throw OrthancException(ErrorCode_DatabaseNotInitialized); + } } void OrthancPlugins::GetDicomForInstance(const void* parameters) { - assert(pimpl_->context_ != NULL); - const _OrthancPluginGetDicomForInstance& p = *reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters); std::string dicom; + + CheckContextAvailable(); pimpl_->context_->ReadFile(dicom, p.instanceId, FileContentType_Dicom); + CopyToMemoryBuffer(*p.target, dicom); } @@ -667,36 +784,15 @@ const _OrthancPluginRestApiGet& p = *reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters); - HttpHandler::Arguments headers; // No HTTP header - std::string body; // No body for a GET request - - UriComponents uri; - HttpHandler::Arguments getArguments; - HttpHandler::ParseGetQuery(uri, getArguments, p.uri); - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - LOG(INFO) << "Plugin making REST GET call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - bool ok = false; - std::string result; - - if (afterPlugins) - { - ok = Handle(http, HttpMethod_Get, uri, headers, getArguments, body); - } + CheckContextAvailable(); + IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); - if (!ok) + std::string result; + if (HttpToolbox::SimpleGet(result, handler, RequestOrigin_Plugins, p.uri)) { - ok = (pimpl_->restApi_ != NULL && - pimpl_->restApi_->Handle(http, HttpMethod_Get, uri, headers, getArguments, body)); - } - - if (ok) - { - stream.GetOutput(result); CopyToMemoryBuffer(*p.target, result); } else @@ -713,39 +809,17 @@ const _OrthancPluginRestApiPostPut& p = *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters); - HttpHandler::Arguments headers; // No HTTP header - HttpHandler::Arguments getArguments; // No GET argument for POST/PUT - - UriComponents uri; - Toolbox::SplitUriComponents(uri, p.uri); + LOG(INFO) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put) + << " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - // TODO Avoid unecessary memcpy - std::string body(p.body, p.bodySize); - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - - HttpMethod method = (isPost ? HttpMethod_Post : HttpMethod_Put); - LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri - << (afterPlugins ? " (after plugins)" : " (built-in API)"); + CheckContextAvailable(); + IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); - bool ok = false; std::string result; - - if (afterPlugins) + if (isPost ? + HttpToolbox::SimplePost(result, handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize) : + HttpToolbox::SimplePut (result, handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize)) { - ok = Handle(http, method, uri, headers, getArguments, body); - } - - if (!ok) - { - ok = (pimpl_->restApi_ != NULL && - pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, body)); - } - - if (ok) - { - stream.GetOutput(result); CopyToMemoryBuffer(*p.target, result); } else @@ -758,35 +832,14 @@ void OrthancPlugins::RestApiDelete(const void* parameters, bool afterPlugins) { - // The "parameters" point to the URI - UriComponents uri; - Toolbox::SplitUriComponents(uri, reinterpret_cast<const char*>(parameters)); - - HttpHandler::Arguments headers; // No HTTP header - HttpHandler::Arguments getArguments; // No GET argument for POST/PUT - std::string body; // No body for DELETE - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - - LOG(INFO) << "Plugin making REST DELETE call on URI " - << reinterpret_cast<const char*>(parameters) + const char* uri = reinterpret_cast<const char*>(parameters); + LOG(INFO) << "Plugin making REST DELETE call on URI " << uri << (afterPlugins ? " (after plugins)" : " (built-in API)"); - bool ok = false; - - if (afterPlugins) - { - ok = Handle(http, HttpMethod_Delete, uri, headers, getArguments, body); - } + CheckContextAvailable(); + IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins); - if (!ok) - { - ok = (pimpl_->restApi_ != NULL && - pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, body)); - } - - if (!ok) + if (!HttpToolbox::SimpleDelete(handler, RequestOrigin_Plugins, uri)) { throw OrthancException(ErrorCode_BadRequest); } @@ -839,7 +892,7 @@ throw OrthancException(ErrorCode_InternalError); } - assert(pimpl_->context_ != NULL); + CheckContextAvailable(); std::list<std::string> result; pimpl_->context_->GetIndex().LookupIdentifier(result, tag, p.argument, level); @@ -921,7 +974,7 @@ switch (service) { case _OrthancPluginService_GetInstanceRemoteAet: - *p.resultString = instance.GetRemoteAet().c_str(); + *p.resultString = instance.GetRemoteAet(); return; case _OrthancPluginService_GetInstanceSize: @@ -967,9 +1020,225 @@ } - bool OrthancPlugins::InvokeService(_OrthancPluginService service, + void OrthancPlugins::BufferCompression(const void* parameters) + { + const _OrthancPluginBufferCompression& p = + *reinterpret_cast<const _OrthancPluginBufferCompression*>(parameters); + + std::string result; + + { + std::auto_ptr<DeflateBaseCompressor> compressor; + + switch (p.compression) + { + case OrthancPluginCompressionType_Zlib: + { + compressor.reset(new ZlibCompressor); + compressor->SetPrefixWithUncompressedSize(false); + break; + } + + case OrthancPluginCompressionType_ZlibWithSize: + { + compressor.reset(new ZlibCompressor); + compressor->SetPrefixWithUncompressedSize(true); + break; + } + + case OrthancPluginCompressionType_Gzip: + { + compressor.reset(new GzipCompressor); + compressor->SetPrefixWithUncompressedSize(false); + break; + } + + case OrthancPluginCompressionType_GzipWithSize: + { + compressor.reset(new GzipCompressor); + compressor->SetPrefixWithUncompressedSize(true); + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (p.uncompress) + { + compressor->Uncompress(result, p.source, p.size); + } + else + { + compressor->Compress(result, p.source, p.size); + } + } + + CopyToMemoryBuffer(*p.target, result); + } + + + void OrthancPlugins::UncompressImage(const void* parameters) + { + const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters); + + std::auto_ptr<ImageAccessor> image; + + switch (p.format) + { + case OrthancPluginImageFormat_Png: + { + image.reset(new PngReader); + reinterpret_cast<PngReader&>(*image).ReadFromMemory(p.data, p.size); + break; + } + + case OrthancPluginImageFormat_Jpeg: + { + image.reset(new JpegReader); + reinterpret_cast<JpegReader&>(*image).ReadFromMemory(p.data, p.size); + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + *(p.target) = reinterpret_cast<OrthancPluginImage*>(image.release()); + } + + + void OrthancPlugins::CompressImage(const void* parameters) + { + const _OrthancPluginCompressImage& p = *reinterpret_cast<const _OrthancPluginCompressImage*>(parameters); + + std::string compressed; + + switch (p.imageFormat) + { + case OrthancPluginImageFormat_Png: + { + PngWriter writer; + writer.WriteToMemory(compressed, p.width, p.height, p.pitch, Plugins::Convert(p.pixelFormat), p.buffer); + break; + } + + case OrthancPluginImageFormat_Jpeg: + { + JpegWriter writer; + writer.SetQuality(p.quality); + writer.WriteToMemory(compressed, p.width, p.height, p.pitch, Plugins::Convert(p.pixelFormat), p.buffer); + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + CopyToMemoryBuffer(*p.target, compressed.size() > 0 ? compressed.c_str() : NULL, compressed.size()); + } + + + void OrthancPlugins::CallHttpClient(const void* parameters) + { + const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters); + + HttpClient client; + client.SetUrl(p.url); + + if (p.username != NULL && + p.password != NULL) + { + client.SetCredentials(p.username, p.password); + } + + switch (p.method) + { + case OrthancPluginHttpMethod_Get: + client.SetMethod(HttpMethod_Get); + break; + + case OrthancPluginHttpMethod_Post: + client.SetMethod(HttpMethod_Post); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Put: + client.SetMethod(HttpMethod_Put); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Delete: + client.SetMethod(HttpMethod_Delete); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string s; + client.ApplyAndThrowException(s); + + if (p.method != OrthancPluginHttpMethod_Delete) + { + CopyToMemoryBuffer(*p.target, s); + } + } + + + void OrthancPlugins::ConvertPixelFormat(const void* parameters) + { + const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters); + const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source); + + std::auto_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight())); + ImageProcessing::Convert(*target, source); + + *(p.target) = reinterpret_cast<OrthancPluginImage*>(target.release()); + } + + + + void OrthancPlugins::GetFontInfo(const void* parameters) + { + const _OrthancPluginGetFontInfo& p = *reinterpret_cast<const _OrthancPluginGetFontInfo*>(parameters); + + const Font& font = Configuration::GetFontRegistry().GetFont(p.fontIndex); + + if (p.name != NULL) + { + *(p.name) = font.GetName().c_str(); + } + else if (p.size != NULL) + { + *(p.size) = font.GetSize(); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + void OrthancPlugins::DrawText(const void* parameters) + { + const _OrthancPluginDrawText& p = *reinterpret_cast<const _OrthancPluginDrawText*>(parameters); + + ImageAccessor& target = *reinterpret_cast<ImageAccessor*>(p.image); + const Font& font = Configuration::GetFontRegistry().GetFont(p.fontIndex); + + font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b); + } + + + bool OrthancPlugins::InvokeService(SharedLibrary& plugin, + _OrthancPluginService service, const void* parameters) { + VLOG(1) << "Calling service " << service << " from plugin " << plugin.GetPath(); + + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + switch (service) { case _OrthancPluginService_GetOrthancPath: @@ -993,8 +1262,25 @@ return true; } + case _OrthancPluginService_GetConfiguration: + { + std::string s; + Configuration::FormatConfiguration(s); + + *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s); + return true; + } + + case _OrthancPluginService_BufferCompression: + BufferCompression(parameters); + return true; + case _OrthancPluginService_RegisterRestCallback: - RegisterRestCallback(parameters); + RegisterRestCallback(parameters, true); + return true; + + case _OrthancPluginService_RegisterRestCallbackNoLock: + RegisterRestCallback(parameters, false); return true; case _OrthancPluginService_RegisterOnStoredInstanceCallback: @@ -1013,6 +1299,10 @@ CompressAndAnswerPngImage(parameters); return true; + case _OrthancPluginService_CompressAndAnswerImage: + CompressAndAnswerImage(parameters); + return true; + case _OrthancPluginService_GetDicomForInstance: GetDicomForInstance(parameters); return true; @@ -1061,6 +1351,10 @@ SendMethodNotAllowed(parameters); return true; + case _OrthancPluginService_SendHttpStatus: + SendHttpStatus(parameters); + return true; + case _OrthancPluginService_SendHttpStatusCode: SendHttpStatusCode(parameters); return true; @@ -1097,8 +1391,15 @@ const _OrthancPluginRegisterStorageArea& p = *reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters); - pimpl_->storageArea_ = p; - pimpl_->hasStorageArea_ = true; + if (pimpl_->storageArea_.get() == NULL) + { + pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary())); + } + else + { + throw OrthancException(ErrorCode_StorageAreaAlreadyRegistered); + } + return true; } @@ -1120,7 +1421,7 @@ } else { - assert(pimpl_->context_ != NULL); + CheckContextAvailable(); pimpl_->context_->GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value); return true; } @@ -1128,7 +1429,7 @@ case _OrthancPluginService_GetGlobalProperty: { - assert(pimpl_->context_ != NULL); + CheckContextAvailable(); const _OrthancPluginGlobalProperty& p = *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); @@ -1165,10 +1466,43 @@ case _OrthancPluginService_RegisterDatabaseBackend: { LOG(INFO) << "Plugin has registered a custom database back-end"; + const _OrthancPluginRegisterDatabaseBackend& p = *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters); - pimpl_->database_.reset(new OrthancPluginDatabase(*p.backend, p.payload)); + if (pimpl_->database_.get() == NULL) + { + pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), + *p.backend, NULL, 0, p.payload)); + } + else + { + throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered); + } + + *(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get()); + + return true; + } + + case _OrthancPluginService_RegisterDatabaseBackendV2: + { + LOG(INFO) << "Plugin has registered a custom database back-end"; + + const _OrthancPluginRegisterDatabaseBackendV2& p = + *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV2*>(parameters); + + if (pimpl_->database_.get() == NULL) + { + pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), + *p.backend, p.extensions, + p.extensionsSize, p.payload)); + } + else + { + throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered); + } + *(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get()); return true; @@ -1178,6 +1512,7 @@ { const _OrthancPluginDatabaseAnswer& p = *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters); + if (pimpl_->database_.get() != NULL) { pimpl_->database_->AnswerReceived(p); @@ -1190,141 +1525,260 @@ } } + case _OrthancPluginService_GetExpectedDatabaseVersion: + { + const _OrthancPluginReturnSingleValue& p = + *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); + *(p.resultUint32) = ORTHANC_DATABASE_VERSION; + return true; + } + + case _OrthancPluginService_StartMultipartAnswer: + { + const _OrthancPluginStartMultipartAnswer& p = + *reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters); + HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); + output->StartMultipart(p.subType, p.contentType); + return true; + } + + case _OrthancPluginService_SendMultipartItem: + { + // An exception might be raised in this function if the + // connection was closed by the HTTP client. + const _OrthancPluginAnswerBuffer& p = + *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters); + HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output); + output->SendMultipartItem(p.answer, p.answerSize); + return true; + } + + case _OrthancPluginService_ReadFile: + { + const _OrthancPluginReadFile& p = + *reinterpret_cast<const _OrthancPluginReadFile*>(parameters); + + std::string content; + Toolbox::ReadFile(content, p.path); + CopyToMemoryBuffer(*p.target, content.size() > 0 ? content.c_str() : NULL, content.size()); + + return true; + } + + case _OrthancPluginService_WriteFile: + { + const _OrthancPluginWriteFile& p = + *reinterpret_cast<const _OrthancPluginWriteFile*>(parameters); + Toolbox::WriteFile(p.data, p.size, p.path); + return true; + } + + case _OrthancPluginService_GetErrorDescription: + { + const _OrthancPluginGetErrorDescription& p = + *reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters); + *(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error)); + return true; + } + + case _OrthancPluginService_GetImagePixelFormat: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat()); + return true; + } + + case _OrthancPluginService_GetImageWidth: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth(); + return true; + } + + case _OrthancPluginService_GetImageHeight: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight(); + return true; + } + + case _OrthancPluginService_GetImagePitch: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch(); + return true; + } + + case _OrthancPluginService_GetImageBuffer: + { + const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters); + *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetConstBuffer(); + return true; + } + + case _OrthancPluginService_FreeImage: + { + const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters); + if (p.image == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + delete reinterpret_cast<ImageAccessor*>(p.image); + return true; + } + } + + case _OrthancPluginService_UncompressImage: + UncompressImage(parameters); + return true; + + case _OrthancPluginService_CompressImage: + CompressImage(parameters); + return true; + + case _OrthancPluginService_CallHttpClient: + CallHttpClient(parameters); + return true; + + case _OrthancPluginService_ConvertPixelFormat: + ConvertPixelFormat(parameters); + return true; + + case _OrthancPluginService_GetFontsCount: + { + const _OrthancPluginReturnSingleValue& p = + *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters); + *(p.resultUint32) = Configuration::GetFontRegistry().GetSize(); + return true; + } + + case _OrthancPluginService_GetFontInfo: + GetFontInfo(parameters); + return true; + + case _OrthancPluginService_DrawText: + DrawText(parameters); + return true; + + case _OrthancPluginService_StorageAreaCreate: + { + const _OrthancPluginStorageAreaCreate& p = + *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Create(p.uuid, p.content, p.size, Plugins::Convert(p.type)); + return true; + } + + case _OrthancPluginService_StorageAreaRead: + { + const _OrthancPluginStorageAreaRead& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + std::string content; + storage.Read(content, p.uuid, Plugins::Convert(p.type)); + CopyToMemoryBuffer(*p.target, content); + return true; + } + + case _OrthancPluginService_StorageAreaRemove: + { + const _OrthancPluginStorageAreaRemove& p = + *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters); + IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea); + storage.Remove(p.uuid, Plugins::Convert(p.type)); + return true; + } + + case _OrthancPluginService_RegisterErrorCode: + { + const _OrthancPluginRegisterErrorCode& p = + *reinterpret_cast<const _OrthancPluginRegisterErrorCode*>(parameters); + *(p.target) = pimpl_->dictionary_.Register(plugin, p.code, p.httpStatus, p.message); + return true; + } + + case _OrthancPluginService_RegisterDictionaryTag: + { + const _OrthancPluginRegisterDictionaryTag& p = + *reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element), + Plugins::Convert(p.vr), p.name, + p.minMultiplicity, p.maxMultiplicity); + return true; + } + default: + { + // This service is unknown to the Orthanc plugin engine return false; + } } } - void OrthancPlugins::SetOrthancRestApi(OrthancRestApi& restApi) - { - pimpl_->restApi_ = &restApi; - } - - bool OrthancPlugins::HasStorageArea() const { - return pimpl_->hasStorageArea_; + return pimpl_->storageArea_.get() != NULL; } - bool OrthancPlugins::HasDatabase() const + bool OrthancPlugins::HasDatabaseBackend() const { return pimpl_->database_.get() != NULL; } - - namespace + IStorageArea* OrthancPlugins::CreateStorageArea() { - class PluginStorageArea : public IStorageArea + if (!HasStorageArea()) { - private: - _OrthancPluginRegisterStorageArea params_; - - void Free(void* buffer) const - { - if (buffer != NULL) - { - params_.free(buffer); - } - } - - OrthancPluginContentType Convert(FileContentType type) const - { - switch (type) - { - case FileContentType_Dicom: - return OrthancPluginContentType_Dicom; - - case FileContentType_DicomAsJson: - return OrthancPluginContentType_DicomAsJson; - - default: - return OrthancPluginContentType_Unknown; - } - } - - public: - PluginStorageArea(const _OrthancPluginRegisterStorageArea& params) : params_(params) - { - } - - virtual void Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type) - { - if (params_.create(uuid.c_str(), content, size, Convert(type)) != 0) - { - throw OrthancException(ErrorCode_Plugin); - } - } - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type) - { - void* buffer = NULL; - int64_t size = 0; - - if (params_.read(&buffer, &size, uuid.c_str(), Convert(type)) != 0) - { - throw OrthancException(ErrorCode_Plugin); - } - - try - { - content.resize(size); - } - catch (OrthancException&) - { - Free(buffer); - throw; - } - - if (size > 0) - { - memcpy(&content[0], buffer, size); - } - - Free(buffer); - } - - virtual void Remove(const std::string& uuid, - FileContentType type) - { - if (params_.remove(uuid.c_str(), Convert(type)) != 0) - { - throw OrthancException(ErrorCode_Plugin); - } - } - }; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return pimpl_->storageArea_->Create(); + } } - IStorageArea* OrthancPlugins::GetStorageArea() + const SharedLibrary& OrthancPlugins::GetStorageAreaLibrary() const { if (!HasStorageArea()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } - - return new PluginStorageArea(pimpl_->storageArea_); + else + { + return pimpl_->storageArea_->GetSharedLibrary(); + } } - IDatabaseWrapper& OrthancPlugins::GetDatabase() + IDatabaseWrapper& OrthancPlugins::GetDatabaseBackend() { - if (!HasDatabase()) + if (!HasDatabaseBackend()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } - - return *pimpl_->database_; + else + { + return *pimpl_->database_; + } } - + const SharedLibrary& OrthancPlugins::GetDatabaseBackendLibrary() const + { + if (!HasDatabaseBackend()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return pimpl_->database_->GetSharedLibrary(); + } + } const char* OrthancPlugins::GetProperty(const char* plugin, @@ -1354,4 +1808,22 @@ pimpl_->argc_ = argc; pimpl_->argv_ = argv; } + + + PluginsManager& OrthancPlugins::GetManager() + { + return pimpl_->manager_; + } + + + const PluginsManager& OrthancPlugins::GetManager() const + { + return pimpl_->manager_; + } + + + PluginsErrorDictionary& OrthancPlugins::GetErrorDictionary() + { + return pimpl_->dictionary_; + } }
--- a/Plugins/Engine/OrthancPlugins.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,24 +32,47 @@ #pragma once +#include "PluginsErrorDictionary.h" + +#if ORTHANC_PLUGINS_ENABLED != 1 + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class OrthancPlugins : public boost::noncopyable + { + }; +} + +#else + +#include "../../Core/FileStorage/IStorageArea.h" +#include "../../Core/HttpServer/IHttpHandler.h" +#include "../../OrthancServer/IServerListener.h" +#include "OrthancPluginDatabase.h" #include "PluginsManager.h" -#include "../../Core/HttpServer/HttpHandler.h" -#include "../../OrthancServer/ServerContext.h" -#include "../../OrthancServer/OrthancRestApi/OrthancRestApi.h" -#include "OrthancPluginDatabase.h" #include <list> #include <boost/shared_ptr.hpp> namespace Orthanc { - class OrthancPlugins : public HttpHandler, public IPluginServiceProvider + class ServerContext; + + class OrthancPlugins : + public IHttpHandler, + public IPluginServiceProvider, + public IServerListener { private: struct PImpl; boost::shared_ptr<PImpl> pimpl_; - void RegisterRestCallback(const void* parameters); + void CheckContextAvailable(); + + void RegisterRestCallback(const void* parameters, + bool lock); void RegisterOnStoredInstanceCallback(const void* parameters); @@ -61,6 +84,8 @@ void CompressAndAnswerPngImage(const void* parameters); + void CompressAndAnswerImage(const void* parameters); + void GetDicomForInstance(const void* parameters); void RestApiGet(const void* parameters, @@ -78,6 +103,8 @@ void SendHttpStatusCode(const void* parameters); + void SendHttpStatus(const void* parameters); + void SendUnauthorized(const void* parameters); void SendMethodNotAllowed(const void* parameters); @@ -86,6 +113,20 @@ void SetHttpHeader(const void* parameters); + void BufferCompression(const void* parameters); + + void UncompressImage(const void* parameters); + + void CompressImage(const void* parameters); + + void ConvertPixelFormat(const void* parameters); + + void CallHttpClient(const void* parameters); + + void GetFontInfo(const void* parameters); + + void DrawText(const void* parameters); + public: OrthancPlugins(); @@ -94,35 +135,55 @@ void SetServerContext(ServerContext& context); virtual bool Handle(HttpOutput& output, + RequestOrigin origin, + const char* remoteIp, + const char* username, HttpMethod method, const UriComponents& uri, const Arguments& headers, - const Arguments& getArguments, - const std::string& postData); + const GetArguments& getArguments, + const char* bodyData, + size_t bodySize); - virtual bool InvokeService(_OrthancPluginService service, + virtual bool InvokeService(SharedLibrary& plugin, + _OrthancPluginService service, const void* parameters); - void SignalChange(const ServerIndexChange& change); + virtual void SignalChange(const ServerIndexChange& change); + + virtual void SignalStoredInstance(const std::string& instanceId, + DicomInstanceToStore& instance, + const Json::Value& simplifiedTags); - void SignalStoredInstance(DicomInstanceToStore& instance, - const std::string& instanceId); - - void SetOrthancRestApi(OrthancRestApi& restApi); + virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + return true; // TODO Enable filtering of instances from plugins + } bool HasStorageArea() const; - IStorageArea* GetStorageArea(); // To be freed after use + IStorageArea* CreateStorageArea(); // To be freed after use - bool HasDatabase() const; + const SharedLibrary& GetStorageAreaLibrary() const; - IDatabaseWrapper& GetDatabase(); + bool HasDatabaseBackend() const; - void Stop(); + IDatabaseWrapper& GetDatabaseBackend(); + + const SharedLibrary& GetDatabaseBackendLibrary() const; const char* GetProperty(const char* plugin, _OrthancPluginProperty property) const; void SetCommandLineArguments(int argc, char* argv[]); + + PluginsManager& GetManager(); + + const PluginsManager& GetManager() const; + + PluginsErrorDictionary& GetErrorDictionary(); }; } + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsEnumerations.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,282 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../../OrthancServer/PrecompiledHeadersServer.h" +#include "PluginsEnumerations.h" + +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + +#include "../../Core/OrthancException.h" + +namespace Orthanc +{ + namespace Plugins + { + OrthancPluginResourceType Convert(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return OrthancPluginResourceType_Patient; + + case ResourceType_Study: + return OrthancPluginResourceType_Study; + + case ResourceType_Series: + return OrthancPluginResourceType_Series; + + case ResourceType_Instance: + return OrthancPluginResourceType_Instance; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + OrthancPluginChangeType Convert(ChangeType type) + { + switch (type) + { + case ChangeType_CompletedSeries: + return OrthancPluginChangeType_CompletedSeries; + + case ChangeType_Deleted: + return OrthancPluginChangeType_Deleted; + + case ChangeType_NewChildInstance: + return OrthancPluginChangeType_NewChildInstance; + + case ChangeType_NewInstance: + return OrthancPluginChangeType_NewInstance; + + case ChangeType_NewPatient: + return OrthancPluginChangeType_NewPatient; + + case ChangeType_NewSeries: + return OrthancPluginChangeType_NewSeries; + + case ChangeType_NewStudy: + return OrthancPluginChangeType_NewStudy; + + case ChangeType_StablePatient: + return OrthancPluginChangeType_StablePatient; + + case ChangeType_StableSeries: + return OrthancPluginChangeType_StableSeries; + + case ChangeType_StableStudy: + return OrthancPluginChangeType_StableStudy; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + OrthancPluginPixelFormat Convert(PixelFormat format) + { + switch (format) + { + case PixelFormat_Grayscale16: + return OrthancPluginPixelFormat_Grayscale16; + + case PixelFormat_Grayscale8: + return OrthancPluginPixelFormat_Grayscale8; + + case PixelFormat_RGB24: + return OrthancPluginPixelFormat_RGB24; + + case PixelFormat_RGBA32: + return OrthancPluginPixelFormat_RGBA32; + + case PixelFormat_SignedGrayscale16: + return OrthancPluginPixelFormat_SignedGrayscale16; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + PixelFormat Convert(OrthancPluginPixelFormat format) + { + switch (format) + { + case OrthancPluginPixelFormat_Grayscale16: + return PixelFormat_Grayscale16; + + case OrthancPluginPixelFormat_Grayscale8: + return PixelFormat_Grayscale8; + + case OrthancPluginPixelFormat_RGB24: + return PixelFormat_RGB24; + + case OrthancPluginPixelFormat_RGBA32: + return PixelFormat_RGBA32; + + case OrthancPluginPixelFormat_SignedGrayscale16: + return PixelFormat_SignedGrayscale16; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + OrthancPluginContentType Convert(FileContentType type) + { + switch (type) + { + case FileContentType_Dicom: + return OrthancPluginContentType_Dicom; + + case FileContentType_DicomAsJson: + return OrthancPluginContentType_DicomAsJson; + + default: + return OrthancPluginContentType_Unknown; + } + } + + + FileContentType Convert(OrthancPluginContentType type) + { + switch (type) + { + case OrthancPluginContentType_Dicom: + return FileContentType_Dicom; + + case OrthancPluginContentType_DicomAsJson: + return FileContentType_DicomAsJson; + + default: + return FileContentType_Unknown; + } + } + + + + DcmEVR Convert(OrthancPluginValueRepresentation vr) + { + switch (vr) + { + case OrthancPluginValueRepresentation_AE: + return EVR_AE; + + case OrthancPluginValueRepresentation_AS: + return EVR_AS; + + case OrthancPluginValueRepresentation_AT: + return EVR_AT; + + case OrthancPluginValueRepresentation_CS: + return EVR_CS; + + case OrthancPluginValueRepresentation_DA: + return EVR_DA; + + case OrthancPluginValueRepresentation_DS: + return EVR_DS; + + case OrthancPluginValueRepresentation_DT: + return EVR_DT; + + case OrthancPluginValueRepresentation_FD: + return EVR_FD; + + case OrthancPluginValueRepresentation_FL: + return EVR_FL; + + case OrthancPluginValueRepresentation_IS: + return EVR_IS; + + case OrthancPluginValueRepresentation_LO: + return EVR_LO; + + case OrthancPluginValueRepresentation_LT: + return EVR_LT; + + case OrthancPluginValueRepresentation_OB: + return EVR_OB; + + case OrthancPluginValueRepresentation_OF: + return EVR_OF; + + case OrthancPluginValueRepresentation_OW: + return EVR_OW; + + case OrthancPluginValueRepresentation_PN: + return EVR_PN; + + case OrthancPluginValueRepresentation_SH: + return EVR_SH; + + case OrthancPluginValueRepresentation_SL: + return EVR_SL; + + case OrthancPluginValueRepresentation_SQ: + return EVR_SQ; + + case OrthancPluginValueRepresentation_SS: + return EVR_SS; + + case OrthancPluginValueRepresentation_ST: + return EVR_ST; + + case OrthancPluginValueRepresentation_TM: + return EVR_TM; + + case OrthancPluginValueRepresentation_UI: + return EVR_UI; + + case OrthancPluginValueRepresentation_UL: + return EVR_UL; + + case OrthancPluginValueRepresentation_UN: + return EVR_UN; + + case OrthancPluginValueRepresentation_US: + return EVR_US; + + case OrthancPluginValueRepresentation_UT: + return EVR_UT; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsEnumerations.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 + +#if ORTHANC_PLUGINS_ENABLED == 1 + +#include "../Include/orthanc/OrthancCPlugin.h" +#include "../../OrthancServer/ServerEnumerations.h" + +#include <dcmtk/dcmdata/dcvr.h> + +namespace Orthanc +{ + namespace Plugins + { + OrthancPluginResourceType Convert(ResourceType type); + + OrthancPluginChangeType Convert(ChangeType type); + + OrthancPluginPixelFormat Convert(PixelFormat format); + + PixelFormat Convert(OrthancPluginPixelFormat format); + + OrthancPluginContentType Convert(FileContentType type); + + FileContentType Convert(OrthancPluginContentType type); + + DcmEVR Convert(OrthancPluginValueRepresentation vr); + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsErrorDictionary.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,138 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "../../OrthancServer/PrecompiledHeadersServer.h" +#include "PluginsErrorDictionary.h" + +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + + +#include "PluginsEnumerations.h" +#include "PluginsManager.h" +#include "../../Core/Logging.h" + +#include <memory> + + +namespace Orthanc +{ + PluginsErrorDictionary::PluginsErrorDictionary() : + pos_(ErrorCode_START_PLUGINS) + { + } + + + PluginsErrorDictionary::~PluginsErrorDictionary() + { + for (Errors::iterator it = errors_.begin(); it != errors_.end(); ++it) + { + delete it->second; + } + } + + + OrthancPluginErrorCode PluginsErrorDictionary::Register(SharedLibrary& library, + int32_t pluginCode, + uint16_t httpStatus, + const char* message) + { + std::auto_ptr<Error> error(new Error); + + error->pluginName_ = PluginsManager::GetPluginName(library); + error->pluginCode_ = pluginCode; + error->message_ = message; + error->httpStatus_ = static_cast<HttpStatus>(httpStatus); + + OrthancPluginErrorCode code; + + { + boost::mutex::scoped_lock lock(mutex_); + errors_[pos_] = error.release(); + code = static_cast<OrthancPluginErrorCode>(pos_); + pos_ += 1; + } + + return code; + } + + + void PluginsErrorDictionary::LogError(ErrorCode code, + bool ignoreBuiltinErrors) + { + if (code >= ErrorCode_START_PLUGINS) + { + boost::mutex::scoped_lock lock(mutex_); + Errors::const_iterator error = errors_.find(static_cast<int32_t>(code)); + + if (error != errors_.end()) + { + LOG(ERROR) << "Error code " << error->second->pluginCode_ + << " inside plugin \"" << error->second->pluginName_ + << "\": " << error->second->message_; + return; + } + } + + if (!ignoreBuiltinErrors) + { + LOG(ERROR) << "Exception inside the plugin engine: " + << EnumerationToString(code); + } + } + + + bool PluginsErrorDictionary::Format(Json::Value& message, /* out */ + HttpStatus& httpStatus, /* out */ + const OrthancException& exception) + { + if (exception.GetErrorCode() >= ErrorCode_START_PLUGINS) + { + boost::mutex::scoped_lock lock(mutex_); + Errors::const_iterator error = errors_.find(static_cast<int32_t>(exception.GetErrorCode())); + + if (error != errors_.end()) + { + httpStatus = error->second->httpStatus_; + message["PluginName"] = error->second->pluginName_; + message["PluginCode"] = error->second->pluginCode_; + message["Message"] = error->second->message_; + + return true; + } + } + + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Engine/PluginsErrorDictionary.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,92 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 + +#if ORTHANC_PLUGINS_ENABLED == 1 + +#include "../Include/orthanc/OrthancCPlugin.h" +#include "../../Core/OrthancException.h" +#include "SharedLibrary.h" + +#include <map> +#include <string> +#include <boost/noncopyable.hpp> +#include <boost/thread/mutex.hpp> +#include <json/value.h> + + +namespace Orthanc +{ + class PluginsErrorDictionary : public boost::noncopyable + { + private: + struct Error + { + std::string pluginName_; + int32_t pluginCode_; + HttpStatus httpStatus_; + std::string message_; + }; + + typedef std::map<int32_t, Error*> Errors; + + boost::mutex mutex_; + int32_t pos_; + Errors errors_; + + public: + PluginsErrorDictionary(); + + ~PluginsErrorDictionary(); + + OrthancPluginErrorCode Register(SharedLibrary& library, + int32_t pluginCode, + uint16_t httpStatus, + const char* message); + + void LogError(ErrorCode code, + bool ignoreBuiltinErrors); + + void LogError(OrthancPluginErrorCode code, + bool ignoreBuiltinErrors) + { + LogError(static_cast<ErrorCode>(code), ignoreBuiltinErrors); + } + + bool Format(Json::Value& message, /* out */ + HttpStatus& httpStatus, /* out */ + const OrthancException& exception); + }; +} + +#endif
--- a/Plugins/Engine/PluginsManager.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/PluginsManager.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,19 +30,25 @@ **/ +#include "../../OrthancServer/PrecompiledHeadersServer.h" #include "PluginsManager.h" +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + #include "../../Core/Toolbox.h" #include "../../Core/HttpServer/HttpOutput.h" +#include "../../Core/Logging.h" -#include <glog/logging.h> #include <cassert> #include <memory> #include <boost/filesystem.hpp> #ifdef WIN32 #define PLUGIN_EXTENSION ".dll" -#elif defined(__linux) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) #define PLUGIN_EXTENSION ".so" #elif defined(__APPLE__) && defined(__MACH__) #define PLUGIN_EXTENSION ".dylib" @@ -53,6 +59,19 @@ namespace Orthanc { + PluginsManager::Plugin::Plugin(PluginsManager& pluginManager, + const std::string& path) : + library_(path), + pluginManager_(pluginManager) + { + memset(&context_, 0, sizeof(context_)); + context_.pluginsManager = this; + context_.orthancVersion = ORTHANC_VERSION; + context_.Free = ::free; + context_.InvokeService = InvokeService; + } + + static void CallInitialize(SharedLibrary& plugin, const OrthancPluginContext& context) { @@ -134,69 +153,56 @@ } - int32_t PluginsManager::InvokeService(OrthancPluginContext* context, - _OrthancPluginService service, - const void* params) + OrthancPluginErrorCode PluginsManager::InvokeService(OrthancPluginContext* context, + _OrthancPluginService service, + const void* params) { switch (service) { case _OrthancPluginService_LogError: LOG(ERROR) << reinterpret_cast<const char*>(params); - return 0; + return OrthancPluginErrorCode_Success; case _OrthancPluginService_LogWarning: LOG(WARNING) << reinterpret_cast<const char*>(params); - return 0; + return OrthancPluginErrorCode_Success; case _OrthancPluginService_LogInfo: LOG(INFO) << reinterpret_cast<const char*>(params); - return 0; + return OrthancPluginErrorCode_Success; default: break; } - PluginsManager* that = reinterpret_cast<PluginsManager*>(context->pluginsManager); - bool error = false; + Plugin* that = reinterpret_cast<Plugin*>(context->pluginsManager); for (std::list<IPluginServiceProvider*>::iterator - it = that->serviceProviders_.begin(); - it != that->serviceProviders_.end(); ++it) + it = that->GetPluginManager().serviceProviders_.begin(); + it != that->GetPluginManager().serviceProviders_.end(); ++it) { try { - if ((*it)->InvokeService(service, params)) + if ((*it)->InvokeService(that->GetSharedLibrary(), service, params)) { - return 0; + return OrthancPluginErrorCode_Success; } } - catch (OrthancException&) + catch (OrthancException& e) { - // This service provider has failed, go to the next - error = true; + // This service provider has failed + LOG(ERROR) << "Exception while invoking a plugin service: " << e.What(); + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); } } - if (error) - { - // LOG(ERROR) << "Exception when dealing with service " << service; - } - else - { - LOG(ERROR) << "Plugin invoking unknown service " << service; - } - - return -1; + LOG(ERROR) << "Plugin invoking unknown service: " << service; + return OrthancPluginErrorCode_UnknownPluginService; } PluginsManager::PluginsManager() { - memset(&context_, 0, sizeof(context_)); - context_.pluginsManager = this; - context_.orthancVersion = ORTHANC_VERSION; - context_.Free = ::free; - context_.InvokeService = InvokeService; } PluginsManager::~PluginsManager() @@ -208,7 +214,7 @@ LOG(WARNING) << "Unregistering plugin '" << it->first << "' (version " << it->second->GetVersion() << ")"; - CallFinalize(it->second->GetLibrary()); + CallFinalize(it->second->GetSharedLibrary()); delete it->second; } } @@ -238,27 +244,27 @@ return; } - std::auto_ptr<Plugin> plugin(new Plugin(path)); + std::auto_ptr<Plugin> plugin(new Plugin(*this, path)); - if (!IsOrthancPlugin(plugin->GetLibrary())) + if (!IsOrthancPlugin(plugin->GetSharedLibrary())) { - LOG(ERROR) << "Plugin " << plugin->GetLibrary().GetPath() + LOG(ERROR) << "Plugin " << plugin->GetSharedLibrary().GetPath() << " does not declare the proper entry functions"; throw OrthancException(ErrorCode_SharedLibrary); } - std::string name(CallGetName(plugin->GetLibrary())); + std::string name(CallGetName(plugin->GetSharedLibrary())); if (plugins_.find(name) != plugins_.end()) { LOG(ERROR) << "Plugin '" << name << "' already registered"; throw OrthancException(ErrorCode_SharedLibrary); } - plugin->SetVersion(CallGetVersion(plugin->GetLibrary())); + plugin->SetVersion(CallGetVersion(plugin->GetSharedLibrary())); LOG(WARNING) << "Registering plugin '" << name << "' (version " << plugin->GetVersion() << ")"; - CallInitialize(plugin->GetLibrary(), context_); + CallInitialize(plugin->GetSharedLibrary(), plugin->GetContext()); plugins_[name] = plugin.release(); } @@ -292,7 +298,10 @@ } else { - if (boost::filesystem::extension(it->path()) == PLUGIN_EXTENSION) + std::string extension = boost::filesystem::extension(it->path()); + Toolbox::ToLowerCase(extension); + + if (extension == PLUGIN_EXTENSION) { LOG(INFO) << "Found a shared library: " << it->path(); @@ -338,4 +347,9 @@ } } + + std::string PluginsManager::GetPluginName(SharedLibrary& library) + { + return CallGetName(library); + } }
--- a/Plugins/Engine/PluginsManager.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/PluginsManager.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,6 +32,8 @@ #pragma once +#if ORTHANC_PLUGINS_ENABLED == 1 + #include "SharedLibrary.h" #include "IPluginServiceProvider.h" @@ -40,21 +42,22 @@ namespace Orthanc { - class PluginsManager : boost::noncopyable + class PluginsManager : public boost::noncopyable { private: - class Plugin + class Plugin : public boost::noncopyable { private: - SharedLibrary library_; - std::string version_; + OrthancPluginContext context_; + SharedLibrary library_; + std::string version_; + PluginsManager& pluginManager_; public: - Plugin(const std::string& path) : library_(path) - { - } + Plugin(PluginsManager& pluginManager, + const std::string& path); - SharedLibrary& GetLibrary() + SharedLibrary& GetSharedLibrary() { return library_; } @@ -68,17 +71,26 @@ { return version_; } + + PluginsManager& GetPluginManager() + { + return pluginManager_; + } + + OrthancPluginContext& GetContext() + { + return context_; + } }; typedef std::map<std::string, Plugin*> Plugins; - OrthancPluginContext context_; Plugins plugins_; std::list<IPluginServiceProvider*> serviceProviders_; - static int32_t InvokeService(OrthancPluginContext* context, - _OrthancPluginService service, - const void* parameters); + static OrthancPluginErrorCode InvokeService(OrthancPluginContext* context, + _OrthancPluginService service, + const void* parameters); public: PluginsManager(); @@ -100,5 +112,9 @@ bool HasPlugin(const std::string& name) const; const std::string& GetPluginVersion(const std::string& name) const; + + static std::string GetPluginName(SharedLibrary& library); }; } + +#endif
--- a/Plugins/Engine/SharedLibrary.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/SharedLibrary.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -30,36 +30,43 @@ **/ +#include "../../OrthancServer/PrecompiledHeadersServer.h" #include "SharedLibrary.h" +#if ORTHANC_PLUGINS_ENABLED != 1 +#error The plugin support is disabled +#endif + + +#include "../../Core/Logging.h" #include "../../Core/Toolbox.h" +#include <boost/filesystem.hpp> + #if defined(_WIN32) #include <windows.h> -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) #include <dlfcn.h> #else #error Support your platform here #endif -#include <glog/logging.h> - namespace Orthanc { SharedLibrary::SharedLibrary(const std::string& path) : - path_(path), + path_(path), handle_(NULL) { #if defined(_WIN32) - handle_ = ::LoadLibraryA(path.c_str()); + handle_ = ::LoadLibraryA(path_.c_str()); if (handle_ == NULL) { - LOG(ERROR) << "LoadLibrary(" << path << ") failed: Error " << ::GetLastError(); + LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError(); throw OrthancException(ErrorCode_SharedLibrary); } -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) - handle_ = ::dlopen(path.c_str(), RTLD_NOW); +#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) + handle_ = ::dlopen(path_.c_str(), RTLD_NOW); if (handle_ == NULL) { std::string explanation; @@ -69,7 +76,7 @@ explanation = ": Error " + std::string(tmp); } - LOG(ERROR) << "dlopen(" << path << ") failed" << explanation; + LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation; throw OrthancException(ErrorCode_SharedLibrary); } @@ -84,7 +91,7 @@ { #if defined(_WIN32) ::FreeLibrary((HMODULE)handle_); -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) ::dlclose(handle_); #else #error Support your platform here @@ -102,7 +109,7 @@ #if defined(_WIN32) return ::GetProcAddress((HMODULE)handle_, name.c_str()); -#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) +#elif defined(__linux) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) return ::dlsym(handle_, name.c_str()); #else #error Support your platform here
--- a/Plugins/Engine/SharedLibrary.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Engine/SharedLibrary.h Wed Sep 30 13:23:31 2015 +0200 @@ -32,13 +32,19 @@ #pragma once +#if ORTHANC_PLUGINS_ENABLED == 1 + #include "../../Core/OrthancException.h" #include <boost/noncopyable.hpp> +#if defined(_WIN32) +#include <windows.h> +#endif + namespace Orthanc { - class SharedLibrary : boost::noncopyable + class SharedLibrary : public boost::noncopyable { public: #if defined(_WIN32) @@ -68,3 +74,5 @@ FunctionPointer GetFunction(const std::string& name); }; } + +#endif
--- a/Plugins/Include/OrthancCDatabasePlugin.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,664 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "OrthancCPlugin.h" - - -/** @{ */ - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; - - - typedef enum - { - _OrthancPluginDatabaseAnswerType_None = 0, - - /* Events */ - _OrthancPluginDatabaseAnswerType_DeletedAttachment = 1, - _OrthancPluginDatabaseAnswerType_DeletedResource = 2, - _OrthancPluginDatabaseAnswerType_RemainingAncestor = 3, - - /* Return value */ - _OrthancPluginDatabaseAnswerType_Attachment = 10, - _OrthancPluginDatabaseAnswerType_Change = 11, - _OrthancPluginDatabaseAnswerType_DicomTag = 12, - _OrthancPluginDatabaseAnswerType_ExportedResource = 13, - _OrthancPluginDatabaseAnswerType_Int32 = 14, - _OrthancPluginDatabaseAnswerType_Int64 = 15, - _OrthancPluginDatabaseAnswerType_Resource = 16, - _OrthancPluginDatabaseAnswerType_String = 17 - } _OrthancPluginDatabaseAnswerType; - - - typedef struct - { - const char* uuid; - int32_t contentType; - uint64_t uncompressedSize; - const char* uncompressedHash; - int32_t compressionType; - uint64_t compressedSize; - const char* compressedHash; - } OrthancPluginAttachment; - - typedef struct - { - uint16_t group; - uint16_t element; - const char* value; - } OrthancPluginDicomTag; - - typedef struct - { - int64_t seq; - int32_t changeType; - OrthancPluginResourceType resourceType; - const char* publicId; - const char* date; - } OrthancPluginChange; - - typedef struct - { - int64_t seq; - OrthancPluginResourceType resourceType; - const char* publicId; - const char* modality; - const char* date; - const char* patientId; - const char* studyInstanceUid; - const char* seriesInstanceUid; - const char* sopInstanceUid; - } OrthancPluginExportedResource; - - - typedef struct - { - OrthancPluginDatabaseContext* database; - _OrthancPluginDatabaseAnswerType type; - int32_t valueInt32; - uint32_t valueUint32; - int64_t valueInt64; - const char *valueString; - const void *valueGeneric; - } _OrthancPluginDatabaseAnswer; - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerString( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const char* value) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_String; - params.valueString = value; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const OrthancPluginChange* change) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Change; - params.valueUint32 = 0; - params.valueGeneric = change; - - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Change; - params.valueUint32 = 1; - params.valueGeneric = NULL; - - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - int32_t value) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Int32; - params.valueInt32 = value; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - int64_t value) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Int64; - params.valueInt64 = value; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const OrthancPluginExportedResource* exported) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; - params.valueUint32 = 0; - params.valueGeneric = exported; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; - params.valueUint32 = 1; - params.valueGeneric = NULL; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const OrthancPluginDicomTag* tag) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_DicomTag; - params.valueGeneric = tag; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const OrthancPluginAttachment* attachment) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Attachment; - params.valueGeneric = attachment; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - int64_t id, - OrthancPluginResourceType resourceType) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_Resource; - params.valueInt64 = id; - params.valueInt32 = (int32_t) resourceType; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const OrthancPluginAttachment* attachment) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment; - params.valueGeneric = attachment; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const char* publicId, - OrthancPluginResourceType resourceType) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_DeletedResource; - params.valueString = publicId; - params.valueInt32 = (int32_t) resourceType; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor( - OrthancPluginContext* context, - OrthancPluginDatabaseContext* database, - const char* ancestorId, - OrthancPluginResourceType ancestorType) - { - _OrthancPluginDatabaseAnswer params; - memset(¶ms, 0, sizeof(params)); - params.database = database; - params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor; - params.valueString = ancestorId; - params.valueInt32 = (int32_t) ancestorType; - context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); - } - - - - - - typedef struct - { - int32_t (*addAttachment) ( - /* inputs */ - void* payload, - int64_t id, - const OrthancPluginAttachment* attachment); - - int32_t (*attachChild) ( - /* inputs */ - void* payload, - int64_t parent, - int64_t child); - - int32_t (*clearChanges) ( - /* inputs */ - void* payload); - - int32_t (*clearExportedResources) ( - /* inputs */ - void* payload); - - int32_t (*createResource) ( - /* outputs */ - int64_t* id, - /* inputs */ - void* payload, - const char* publicId, - OrthancPluginResourceType resourceType); - - int32_t (*deleteAttachment) ( - /* inputs */ - void* payload, - int64_t id, - int32_t contentType); - - int32_t (*deleteMetadata) ( - /* inputs */ - void* payload, - int64_t id, - int32_t metadataType); - - int32_t (*deleteResource) ( - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerString() */ - int32_t (*getAllPublicIds) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - OrthancPluginResourceType resourceType); - - /* Output: Use OrthancPluginDatabaseAnswerChange() and - * OrthancPluginDatabaseAnswerChangesDone() */ - int32_t (*getChanges) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t since, - uint32_t maxResult); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*getChildrenInternalId) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerString() */ - int32_t (*getChildrenPublicId) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and - * OrthancPluginDatabaseAnswerExportedResourcesDone() */ - int32_t (*getExportedResources) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t since, - uint32_t maxResult); - - /* Output: Use OrthancPluginDatabaseAnswerChange() */ - int32_t (*getLastChange) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload); - - /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */ - int32_t (*getLastExportedResource) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload); - - /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */ - int32_t (*getMainDicomTags) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerString() */ - int32_t (*getPublicId) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - int32_t (*getResourceCount) ( - /* outputs */ - uint64_t* target, - /* inputs */ - void* payload, - OrthancPluginResourceType resourceType); - - int32_t (*getResourceType) ( - /* outputs */ - OrthancPluginResourceType* resourceType, - /* inputs */ - void* payload, - int64_t id); - - int32_t (*getTotalCompressedSize) ( - /* outputs */ - uint64_t* target, - /* inputs */ - void* payload); - - int32_t (*getTotalUncompressedSize) ( - /* outputs */ - uint64_t* target, - /* inputs */ - void* payload); - - int32_t (*isExistingResource) ( - /* outputs */ - int32_t* existing, - /* inputs */ - void* payload, - int64_t id); - - int32_t (*isProtectedPatient) ( - /* outputs */ - int32_t* isProtected, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerInt32() */ - int32_t (*listAvailableMetadata) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerInt32() */ - int32_t (*listAvailableAttachments) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - int32_t (*logChange) ( - /* inputs */ - void* payload, - const OrthancPluginChange* change); - - int32_t (*logExportedResource) ( - /* inputs */ - void* payload, - const OrthancPluginExportedResource* exported); - - /* Output: Use OrthancPluginDatabaseAnswerAttachment() */ - int32_t (*lookupAttachment) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id, - int32_t contentType); - - /* Output: Use OrthancPluginDatabaseAnswerString() */ - int32_t (*lookupGlobalProperty) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int32_t property); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*lookupIdentifier) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - const OrthancPluginDicomTag* tag); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*lookupIdentifier2) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - const char* value); - - /* Output: Use OrthancPluginDatabaseAnswerString() */ - int32_t (*lookupMetadata) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id, - int32_t metadata); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*lookupParent) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t id); - - /* Output: Use OrthancPluginDatabaseAnswerResource() */ - int32_t (*lookupResource) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - const char* publicId); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*selectPatientToRecycle) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload); - - /* Output: Use OrthancPluginDatabaseAnswerInt64() */ - int32_t (*selectPatientToRecycle2) ( - /* outputs */ - OrthancPluginDatabaseContext* context, - /* inputs */ - void* payload, - int64_t patientIdToAvoid); - - int32_t (*setGlobalProperty) ( - /* inputs */ - void* payload, - int32_t property, - const char* value); - - int32_t (*setMainDicomTag) ( - /* inputs */ - void* payload, - int64_t id, - const OrthancPluginDicomTag* tag); - - int32_t (*setIdentifierTag) ( - /* inputs */ - void* payload, - int64_t id, - const OrthancPluginDicomTag* tag); - - int32_t (*setMetadata) ( - /* inputs */ - void* payload, - int64_t id, - int32_t metadata, - const char* value); - - int32_t (*setProtectedPatient) ( - /* inputs */ - void* payload, - int64_t id, - int32_t isProtected); - - int32_t (*startTransaction) ( - /* inputs */ - void* payload); - - int32_t (*rollbackTransaction) ( - /* inputs */ - void* payload); - - int32_t (*commitTransaction) ( - /* inputs */ - void* payload); - - int32_t (*open) ( - /* inputs */ - void* payload); - - int32_t (*close) ( - /* inputs */ - void* payload); - - } OrthancPluginDatabaseBackend; - - - - typedef struct - { - OrthancPluginDatabaseContext** result; - const OrthancPluginDatabaseBackend* backend; - void* payload; - } _OrthancPluginRegisterDatabaseBackend; - - ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackend( - OrthancPluginContext* context, - const OrthancPluginDatabaseBackend* backend, - void* payload) - { - OrthancPluginDatabaseContext* result = NULL; - - _OrthancPluginRegisterDatabaseBackend params; - memset(¶ms, 0, sizeof(params)); - params.backend = backend; - params.result = &result; - params.payload = payload; - - if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, ¶ms) || - result == NULL) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - -#ifdef __cplusplus -} -#endif - - -/** @} */ -
--- a/Plugins/Include/OrthancCPlugin.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2093 +0,0 @@ -/** - * \mainpage - * - * This C/C++ SDK allows external developers to create plugins that - * can be loaded into Orthanc to extend its functionality. Each - * Orthanc plugin must expose 4 public functions with the following - * signatures: - * - * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: - * This function is invoked by Orthanc when it loads the plugin on startup. - * The plugin must: - * - Check its compatibility with the Orthanc version using - * ::OrthancPluginCheckVersion(). - * - Store the context pointer so that it can use the plugin - * services of Orthanc. - * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). - * - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). - * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). - * - Possibly register a custom database back-end area using ::OrthancPluginRegisterDatabaseBackend(). - * -# <tt>void OrthancPluginFinalize()</tt>: - * This function is invoked by Orthanc during its shutdown. The plugin - * must free all its memory. - * -# <tt>const char* OrthancPluginGetName()</tt>: - * The plugin must return a short string to identify itself. - * -# <tt>const char* OrthancPluginGetVersion()</tt>: - * The plugin must return a string containing its version number. - * - * The name and the version of a plugin is only used to prevent it - * from being loaded twice. - * - * The various callbacks are guaranteed to be executed in mutual - * exclusion since Orthanc 0.8.5. - **/ - - - -/** - * @defgroup CInterface C Interface - * @brief The C interface to create Orthanc plugins. - * - * These functions must be used to create C plugins for Orthanc. - **/ - - - -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 <stdio.h> -#include <string.h> - -#ifdef WIN32 -#define ORTHANC_PLUGINS_API __declspec(dllexport) -#else -#define ORTHANC_PLUGINS_API -#endif - -#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 8 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 6 - - - -/******************************************************************** - ** Check that function inlining is properly supported. The use of - ** inlining is required, to avoid the duplication of object code - ** between two compilation modules that would use the Orthanc Plugin - ** API. - ********************************************************************/ - -/* If the auto-detection of the "inline" keyword below does not work - automatically and that your compiler is known to properly support - inlining, uncomment the following #define and adapt the definition - of "static inline". */ - -/* #define ORTHANC_PLUGIN_INLINE static inline */ - -#ifndef ORTHANC_PLUGIN_INLINE -# if __STDC_VERSION__ >= 199901L -/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__cplusplus) -/* This is C++ */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__GNUC__) -/* This is GCC running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# elif defined(_MSC_VER) -/* This is Visual Studio running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# else -# error Your compiler is not known to support the "inline" keyword -# endif -#endif - - - -/******************************************************************** - ** Inclusion of standard libraries. - ********************************************************************/ - -#ifdef _MSC_VER -#include "../../Resources/ThirdParty/VisualStudio/stdint.h" -#else -#include <stdint.h> -#endif - -#include <stdlib.h> - - - -/******************************************************************** - ** Definition of the Orthanc Plugin API. - ********************************************************************/ - -/** @{ */ - -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * Forward declaration of one of the mandatory functions for Orthanc - * plugins. - **/ - ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); - - - /** - * The various HTTP methods for a REST call. - **/ - typedef enum - { - OrthancPluginHttpMethod_Get = 1, /*!< GET request */ - OrthancPluginHttpMethod_Post = 2, /*!< POST request */ - OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ - OrthancPluginHttpMethod_Delete = 4 /*!< DELETE request */ - } OrthancPluginHttpMethod; - - - /** - * @brief The parameters of a REST request. - **/ - typedef struct - { - /** - * @brief The HTTP method. - **/ - OrthancPluginHttpMethod method; - - /** - * @brief The number of groups of the regular expression. - **/ - uint32_t groupsCount; - - /** - * @brief The matched values for the groups of the regular expression. - **/ - const char* const* groups; - - /** - * @brief For a GET request, the number of GET parameters. - **/ - uint32_t getCount; - - /** - * @brief For a GET request, the keys of the GET parameters. - **/ - const char* const* getKeys; - - /** - * @brief For a GET request, the values of the GET parameters. - **/ - const char* const* getValues; - - /** - * @brief For a PUT or POST request, the content of the body. - **/ - const char* body; - - /** - * @brief For a PUT or POST request, the number of bytes of the body. - **/ - uint32_t bodySize; - - - /* -------------------------------------------------- - New in version 0.8.1 - -------------------------------------------------- */ - - /** - * @brief The number of HTTP headers. - **/ - uint32_t headersCount; - - /** - * @brief The keys of the HTTP headers (always converted to low-case). - **/ - const char* const* headersKeys; - - /** - * @brief The values of the HTTP headers. - **/ - const char* const* headersValues; - - } OrthancPluginHttpRequest; - - - typedef enum - { - /* Generic services */ - _OrthancPluginService_LogInfo = 1, - _OrthancPluginService_LogWarning = 2, - _OrthancPluginService_LogError = 3, - _OrthancPluginService_GetOrthancPath = 4, - _OrthancPluginService_GetOrthancDirectory = 5, - _OrthancPluginService_GetConfigurationPath = 6, - _OrthancPluginService_SetPluginProperty = 7, - _OrthancPluginService_GetGlobalProperty = 8, - _OrthancPluginService_SetGlobalProperty = 9, - _OrthancPluginService_GetCommandLineArgumentsCount = 10, - _OrthancPluginService_GetCommandLineArgument = 11, - - /* Registration of callbacks */ - _OrthancPluginService_RegisterRestCallback = 1000, - _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, - _OrthancPluginService_RegisterStorageArea = 1002, - _OrthancPluginService_RegisterOnChangeCallback = 1003, - - /* Sending answers to REST calls */ - _OrthancPluginService_AnswerBuffer = 2000, - _OrthancPluginService_CompressAndAnswerPngImage = 2001, - _OrthancPluginService_Redirect = 2002, - _OrthancPluginService_SendHttpStatusCode = 2003, - _OrthancPluginService_SendUnauthorized = 2004, - _OrthancPluginService_SendMethodNotAllowed = 2005, - _OrthancPluginService_SetCookie = 2006, - _OrthancPluginService_SetHttpHeader = 2007, - - /* Access to the Orthanc database and API */ - _OrthancPluginService_GetDicomForInstance = 3000, - _OrthancPluginService_RestApiGet = 3001, - _OrthancPluginService_RestApiPost = 3002, - _OrthancPluginService_RestApiDelete = 3003, - _OrthancPluginService_RestApiPut = 3004, - _OrthancPluginService_LookupPatient = 3005, - _OrthancPluginService_LookupStudy = 3006, - _OrthancPluginService_LookupSeries = 3007, - _OrthancPluginService_LookupInstance = 3008, - _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, - _OrthancPluginService_RestApiGetAfterPlugins = 3010, - _OrthancPluginService_RestApiPostAfterPlugins = 3011, - _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, - _OrthancPluginService_RestApiPutAfterPlugins = 3013, - - /* Access to DICOM instances */ - _OrthancPluginService_GetInstanceRemoteAet = 4000, - _OrthancPluginService_GetInstanceSize = 4001, - _OrthancPluginService_GetInstanceData = 4002, - _OrthancPluginService_GetInstanceJson = 4003, - _OrthancPluginService_GetInstanceSimplifiedJson = 4004, - _OrthancPluginService_HasInstanceMetadata = 4005, - _OrthancPluginService_GetInstanceMetadata = 4006, - - /* Services for plugins implementing a database back-end */ - _OrthancPluginService_RegisterDatabaseBackend = 5000, - _OrthancPluginService_DatabaseAnswer = 5001 - - } _OrthancPluginService; - - - typedef enum - { - _OrthancPluginProperty_Description = 1, - _OrthancPluginProperty_RootUri = 2, - _OrthancPluginProperty_OrthancExplorer = 3 - } _OrthancPluginProperty; - - - - /** - * The memory layout of the pixels of an image. - **/ - typedef enum - { - /** - * @brief Graylevel 8bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * one byte. - **/ - OrthancPluginPixelFormat_Grayscale8 = 1, - - /** - * @brief Graylevel, unsigned 16bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * two bytes. - **/ - OrthancPluginPixelFormat_Grayscale16 = 2, - - /** - * @brief Graylevel, signed 16bpp image. - * - * The image is graylevel. Each pixel is signed and stored in two - * bytes. - **/ - OrthancPluginPixelFormat_SignedGrayscale16 = 3, - - /** - * @brief Color image in RGB24 format. - * - * This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB. - **/ - OrthancPluginPixelFormat_RGB24 = 4, - - /** - * @brief Color image in RGBA32 format. - * - * This format describes a color image. The pixels are stored in 4 - * consecutive bytes. The memory layout is RGBA. - **/ - OrthancPluginPixelFormat_RGBA32 = 5 - } OrthancPluginPixelFormat; - - - - /** - * The content types that are supported by Orthanc plugins. - **/ - typedef enum - { - OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ - OrthancPluginContentType_Dicom = 1, /*!< DICOM */ - OrthancPluginContentType_DicomAsJson = 2 /*!< JSON summary of a DICOM file */ - } OrthancPluginContentType; - - - - /** - * The supported type of DICOM resources. - **/ - typedef enum - { - OrthancPluginResourceType_Patient = 0, /*!< Patient */ - OrthancPluginResourceType_Study = 1, /*!< Study */ - OrthancPluginResourceType_Series = 2, /*!< Series */ - OrthancPluginResourceType_Instance = 3 /*!< Instance */ - } OrthancPluginResourceType; - - - - /** - * The supported type of changes that can happen to DICOM resources. - **/ - typedef enum - { - OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ - OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ - OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ - OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ - OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ - OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ - OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ - OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ - OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ - OrthancPluginChangeType_StableStudy = 9 /*!< Timeout: No new instance in this study */ - } OrthancPluginChangeType; - - - - /** - * @brief A memory buffer allocated by the core system of Orthanc. - * - * A memory buffer allocated by the core system of Orthanc. When the - * content of the buffer is not useful anymore, it must be free by a - * call to ::OrthancPluginFreeMemoryBuffer(). - **/ - typedef struct - { - /** - * @brief The content of the buffer. - **/ - void* data; - - /** - * @brief The number of bytes in the buffer. - **/ - uint32_t size; - } OrthancPluginMemoryBuffer; - - - - - /** - * @brief Opaque structure that represents the HTTP connection to the client application. - **/ - typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; - - - - /** - * @brief Opaque structure that represents a DICOM instance received by Orthanc. - **/ - typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; - - - - /** - * @brief Signature of a callback function that answers to a REST request. - **/ - typedef int32_t (*OrthancPluginRestCallback) ( - OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - - - /** - * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. - **/ - typedef int32_t (*OrthancPluginOnStoredInstanceCallback) ( - OrthancPluginDicomInstance* instance, - const char* instanceId); - - - - /** - * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. - **/ - typedef int32_t (*OrthancPluginOnChangeCallback) ( - OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId); - - - - /** - * @brief Signature of a function to free dynamic memory. - **/ - typedef void (*OrthancPluginFree) (void* buffer); - - - - /** - * @brief Callback for writing to the storage area. - * - * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. - * - * @param uuid The UUID of the file. - * @param content The content of the file. - * @param size The size of the file. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - **/ - typedef int32_t (*OrthancPluginStorageCreate) ( - const char* uuid, - const void* content, - int64_t size, - OrthancPluginContentType type); - - - - /** - * @brief Callback for reading from the storage area. - * - * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. - * - * @param content The content of the file (output). - * @param size The size of the file (output). - * @param uuid The UUID of the file of interest. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - **/ - typedef int32_t (*OrthancPluginStorageRead) ( - void** content, - int64_t* size, - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Callback for removing a file from the storage area. - * - * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. - * - * @param uuid The UUID of the file to be removed. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - **/ - typedef int32_t (*OrthancPluginStorageRemove) ( - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Data structure that contains information about the Orthanc core. - **/ - typedef struct _OrthancPluginContext_t - { - void* pluginsManager; - const char* orthancVersion; - OrthancPluginFree Free; - int32_t (*InvokeService) (struct _OrthancPluginContext_t* context, - _OrthancPluginService service, - const void* params); - } OrthancPluginContext; - - - - /** - * @brief Free a string. - * - * Free a string that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param str The string to be freed. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( - OrthancPluginContext* context, - char* str) - { - if (str != NULL) - { - context->Free(str); - } - } - - - /** - * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. - * - * This function checks whether the version of this C header is - * compatible with the current version of Orthanc. The result of - * this function should always be checked in the - * OrthancPluginInitialize() entry point of the plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return 1 if and only if the versions are compatible. If the - * result is 0, the initialization of the plugin should fail. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( - OrthancPluginContext* context) - { - int major, minor, revision; - - /* Assume compatibility with the mainline */ - if (!strcmp(context->orthancVersion, "mainline")) - { - return 1; - } - - /* Parse the version of the Orthanc core */ - if ( -#ifdef _MSC_VER - sscanf_s -#else - sscanf -#endif - (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) - { - return 0; - } - - /* Check the major number of the version */ - - if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 1; - } - - if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 0; - } - - /* Check the minor number of the version */ - - if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 1; - } - - if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 0; - } - - /* Check the revision number of the version */ - - if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) - { - return 1; - } - else - { - return 0; - } - } - - - /** - * @brief Free a memory buffer. - * - * Free a memory buffer that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The memory buffer to release. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* buffer) - { - context->Free(buffer->data); - } - - - /** - * @brief Log an error. - * - * Log an error message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogError, message); - } - - - /** - * @brief Log a warning. - * - * Log a warning message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogWarning, message); - } - - - /** - * @brief Log an information. - * - * Log an information message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogInfo, message); - } - - - - typedef struct - { - const char* pathRegularExpression; - OrthancPluginRestCallback callback; - } _OrthancPluginRestCallback; - - /** - * @brief Register a REST callback. - * - * This function registers a REST callback against a regular - * expression for a URI. This function must be called during the - * initialization of the plugin, i.e. inside the - * OrthancPluginInitialize() public function. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param pathRegularExpression Regular expression for the URI. May contain groups. - * @param callback The callback function to handle the REST call. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( - OrthancPluginContext* context, - const char* pathRegularExpression, - OrthancPluginRestCallback callback) - { - _OrthancPluginRestCallback params; - params.pathRegularExpression = pathRegularExpression; - params.callback = callback; - context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginOnStoredInstanceCallback callback; - } _OrthancPluginOnStoredInstanceCallback; - - /** - * @brief Register a callback for received instances. - * - * This function registers a callback function that is called - * whenever a new DICOM instance is stored into the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( - OrthancPluginContext* context, - OrthancPluginOnStoredInstanceCallback callback) - { - _OrthancPluginOnStoredInstanceCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* answer; - uint32_t answerSize; - const char* mimeType; - } _OrthancPluginAnswerBuffer; - - /** - * @brief Answer to a REST request. - * - * This function answers to a REST request with the content of a memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the answer. - * @param answerSize Number of bytes of the answer. - * @param mimeType The MIME type of the answer. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize, - const char* mimeType) - { - _OrthancPluginAnswerBuffer params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.mimeType = mimeType; - context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - OrthancPluginPixelFormat format; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - } _OrthancPluginCompressAndAnswerPngImage; - - /** - * @brief Answer to a REST request with a PNG image. - * - * This function answers to a REST request with a PNG image. The - * parameters of this function describe a memory buffer that - * contains an uncompressed image. The image will be automatically compressed - * as a PNG image by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer. - * @param buffer The memory buffer containing the uncompressed image. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer) - { - _OrthancPluginCompressAndAnswerPngImage params; - params.output = output; - params.format = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - context->InvokeService(context, _OrthancPluginService_CompressAndAnswerPngImage, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* instanceId; - } _OrthancPluginGetDicomForInstance; - - /** - * @brief Retrieve a DICOM instance using its Orthanc identifier. - * - * Retrieve a DICOM instance using its Orthanc identifier. The DICOM - * file is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param instanceId The Orthanc identifier of the DICOM instance of interest. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginGetDicomForInstance( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* instanceId) - { - _OrthancPluginGetDicomForInstance params; - params.target = target; - params.instanceId = instanceId; - return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - } _OrthancPluginRestApiGet; - - /** - * @brief Make a GET call to the built-in Orthanc REST API. - * - * Make a GET call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiGet( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); - } - - - - /** - * @brief Make a GET call to the REST API, as tainted by the plugins. - * - * Make a GET call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiGetAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - const char* body; - uint32_t bodySize; - } _OrthancPluginRestApiPostPut; - - /** - * @brief Make a POST call to the built-in Orthanc REST API. - * - * Make a POST call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiPost( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); - } - - - /** - * @brief Make a POST call to the REST API, as tainted by the plugins. - * - * Make a POST call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiPostAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); - } - - - - /** - * @brief Make a DELETE call to the built-in Orthanc REST API. - * - * Make a DELETE call to the built-in Orthanc REST API. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiDelete( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); - } - - - /** - * @brief Make a DELETE call to the REST API, as tainted by the plugins. - * - * Make a DELETE call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiDeleteAfterPlugins( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); - } - - - - /** - * @brief Make a PUT call to the built-in Orthanc REST API. - * - * Make a PUT call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiPut( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); - } - - - - /** - * @brief Make a PUT call to the REST API, as tainted by the plugins. - * - * Make a PUT call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, other value if error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginRestApiPutAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* argument; - } _OrthancPluginOutputPlusArgument; - - /** - * @brief Redirect a REST request. - * - * This function answers to a REST request by redirecting the user - * to another URI using HTTP status 301. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param redirection Where to redirect. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* redirection) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = redirection; - context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); - } - - - - typedef struct - { - char** result; - const char* argument; - } _OrthancPluginRetrieveDynamicString; - - /** - * @brief Look for a patient. - * - * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored patients). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param patientID The Patient ID of interest. - * @return The NULL value if the patient is non-existent, or a string containing the - * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( - OrthancPluginContext* context, - const char* patientID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = patientID; - - if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study. - * - * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param studyUID The Study Instance UID of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( - OrthancPluginContext* context, - const char* studyUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = studyUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study, using the accession number. - * - * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param accessionNumber The Accession Number of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( - OrthancPluginContext* context, - const char* accessionNumber) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = accessionNumber; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a series. - * - * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored series). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param seriesUID The Series Instance UID of interest. - * @return The NULL value if the series is non-existent, or a string containing the - * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( - OrthancPluginContext* context, - const char* seriesUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = seriesUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for an instance. - * - * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored instances). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param sopInstanceUID The SOP Instance UID of interest. - * @return The NULL value if the instance is non-existent, or a string containing the - * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( - OrthancPluginContext* context, - const char* sopInstanceUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = sopInstanceUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - uint16_t status; - } _OrthancPluginSendHttpStatusCode; - - /** - * @brief Send a HTTP status code. - * - * This function answers to a REST request by sending a HTTP status - * code (such as "400 - Bad Request"). Note that: - * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). - * - Redirections (status 301) must use ::OrthancPluginRedirect(). - * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). - * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param status The HTTP status code to be sent. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - uint16_t status) - { - _OrthancPluginSendHttpStatusCode params; - params.output = output; - params.status = status; - context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); - } - - - /** - * @brief Signal that a REST request is not authorized. - * - * This function answers to a REST request by signaling that it is - * not authorized. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param realm The realm for the authorization process. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* realm) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = realm; - context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); - } - - - /** - * @brief Signal that this URI does not support this HTTP method. - * - * This function answers to a REST request by signaling that the - * queried URI does not support this method. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* allowedMethods) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = allowedMethods; - context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* key; - const char* value; - } _OrthancPluginSetHttpHeader; - - /** - * @brief Set a cookie. - * - * This function sets a cookie in the HTTP client. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param cookie The cookie to be set. - * @param value The value of the cookie. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* cookie, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = cookie; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); - } - - - /** - * @brief Set some HTTP header. - * - * This function sets a HTTP header in the HTTP answer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param key The HTTP header to be set. - * @param value The value of the HTTP header. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* key, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = key; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); - } - - - typedef struct - { - char** resultStringToFree; - const char** resultString; - int64_t* resultInt64; - const char* key; - OrthancPluginDicomInstance* instance; - } _OrthancPluginAccessDicomInstance; - - - /** - * @brief Get the AET of a DICOM instance. - * - * This function returns the Application Entity Title (AET) of the - * DICOM modality from which a DICOM instance originates. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The AET if success, NULL if error. - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the size of a DICOM file. - * - * This function returns the number of bytes of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The size of the file, -1 in case of error. - **/ - ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - int64_t size; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &size; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms)) - { - /* Error */ - return -1; - } - else - { - return size; - } - } - - - /** - * @brief Get the data of a DICOM file. - * - * This function returns a pointer to the content of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The pointer to the DICOM data, NULL in case of error. - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file. - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. In contrast with - * ::OrthancPluginGetInstanceJson(), the returned JSON file is in - * its simplified version. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Check whether a DICOM instance is associated with some metadata. - * - * This function checks whether the DICOM instance of interest is - * associated with some metadata. As of Orthanc 0.8.1, in the - * callbacks registered by - * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only - * possibly available metadata are "ReceptionDate", "RemoteAET" and - * "IndexInSeries". - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - int64_t result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms)) - { - /* Error */ - return -1; - } - else - { - return (result != 0); - } - } - - - /** - * @brief Get the value of some metadata associated with a given DICOM instance. - * - * This functions returns the value of some metadata that is associated with the DICOM instance of interest. - * Before calling this function, the existence of the metadata must have been checked with - * ::OrthancPluginHasInstanceMetadata(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return The metadata value if success, NULL if error. - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginStorageCreate create; - OrthancPluginStorageRead read; - OrthancPluginStorageRemove remove; - OrthancPluginFree free; - } _OrthancPluginRegisterStorageArea; - - /** - * @brief Register a custom storage area. - * - * This function registers a custom storage area, to replace the - * built-in way Orthanc stores its files on the filesystem. This - * function must be called during the initialization of the plugin, - * i.e. inside the OrthancPluginInitialize() public function. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param create The callback function to store a file on the custom storage area. - * @param read The callback function to read a file from the custom storage area. - * @param remove The callback function to remove a file from the custom storage area. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( - OrthancPluginContext* context, - OrthancPluginStorageCreate create, - OrthancPluginStorageRead read, - OrthancPluginStorageRemove remove) - { - _OrthancPluginRegisterStorageArea params; - params.create = create; - params.read = read; - params.remove = remove; - -#ifdef __cplusplus - params.free = ::free; -#else - params.free = free; -#endif - - context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); - } - - - - /** - * @brief Return the path to the Orthanc executable. - * - * This function returns the path to the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the directory containing the Orthanc. - * - * This function returns the path to the directory containing the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the path to the configuration file. - * - * This function returns the path to the configuration file that was - * specified when starting Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginOnChangeCallback callback; - } _OrthancPluginOnChangeCallback; - - /** - * @brief Register a callback to monitor changes. - * - * This function registers a callback function that is called - * whenever a change happens to some DICOM resource. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( - OrthancPluginContext* context, - OrthancPluginOnChangeCallback callback) - { - _OrthancPluginOnChangeCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); - } - - - - typedef struct - { - const char* plugin; - _OrthancPluginProperty property; - const char* value; - } _OrthancPluginSetPluginProperty; - - - /** - * @brief Set the URI where the plugin provides its Web interface. - * - * For plugins that come with a Web interface, this function - * declares the entry path where to find this interface. This - * information is notably used in the "Plugins" page of Orthanc - * Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The root URI for this plugin. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( - OrthancPluginContext* context, - const char* uri) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_RootUri; - params.value = uri; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Set a description for this plugin. - * - * Set a description for this plugin. It is displayed in the - * "Plugins" page of Orthanc Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param description The description. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( - OrthancPluginContext* context, - const char* description) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_Description; - params.value = description; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Extend the JavaScript code of Orthanc Explorer. - * - * Add JavaScript code to customize the default behavior of Orthanc - * Explorer. This can for instance be used to add new buttons. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param javascript The custom JavaScript code. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( - OrthancPluginContext* context, - const char* javascript) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_OrthancExplorer; - params.value = javascript; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - typedef struct - { - char** result; - int32_t property; - const char* value; - } _OrthancPluginGlobalProperty; - - - /** - * @brief Get the value of a global property. - * - * Get the value of a global property that is stored in the Orthanc database. Global - * properties whose index is below 1024 are reserved by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param defaultValue The value to return, if the global property is unset. - * @return The value of the global property, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* defaultValue) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = property; - params.value = defaultValue; - - if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Set the value of a global property. - * - * Set the value of a global property into the Orthanc - * database. Setting a global property can be used by plugins to - * save their internal parameters. Plugins are only allowed to set - * properties whose index are above or equal to 1024 (properties - * below 1024 are read-only and reserved by Orthanc). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param value The value to be set in the global property. - * @return 0 if success, -1 in case of error. - **/ - ORTHANC_PLUGIN_INLINE int32_t OrthancPluginSetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* value) - { - _OrthancPluginGlobalProperty params; - params.result = NULL; - params.property = property; - params.value = value; - - if (context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms)) - { - /* Error */ - return -1; - } - else - { - return 0; - } - } - - - - typedef struct - { - int32_t *resultInt32; - uint32_t *resultUint32; - int64_t *resultInt64; - uint64_t *resultUint64; - } _OrthancPluginReturnSingleValue; - - /** - * @brief Get the number of command-line arguments. - * - * Retrieve the number of command-line arguments that were used to launch Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The number of arguments. - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms)) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - /** - * @brief Get the value of a command-line argument. - * - * Get the value of one of the command-line arguments that were used - * to launch Orthanc. The number of available arguments can be - * retrieved by OrthancPluginGetCommandLineArgumentsCount(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param argument The index of the argument. - * @return The value of the argument, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( - OrthancPluginContext* context, - uint32_t argument) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = (int32_t) argument; - params.value = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms)) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - -#ifdef __cplusplus -} -#endif - - -/** @} */ -
--- a/Plugins/Include/OrthancCppDatabasePlugin.h Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1532 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "OrthancCDatabasePlugin.h" - -#include <stdexcept> -#include <list> -#include <string> - -namespace OrthancPlugins -{ - // This class mimics "boost::noncopyable" - class NonCopyable - { - private: - NonCopyable(const NonCopyable&); - - NonCopyable& operator= (const NonCopyable&); - - protected: - NonCopyable() - { - } - - ~NonCopyable() - { - } - }; - - - - class DatabaseBackendOutput : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - enum AllowedAnswers - { - AllowedAnswers_All, - AllowedAnswers_None, - AllowedAnswers_Attachment, - AllowedAnswers_Change, - AllowedAnswers_DicomTag, - AllowedAnswers_ExportedResource - }; - - OrthancPluginContext* context_; - OrthancPluginDatabaseContext* database_; - AllowedAnswers allowedAnswers_; - - void SetAllowedAnswers(AllowedAnswers allowed) - { - allowedAnswers_ = allowed; - } - - public: - DatabaseBackendOutput(OrthancPluginContext* context, - OrthancPluginDatabaseContext* database) : - context_(context), - database_(database), - allowedAnswers_(AllowedAnswers_All /* for unit tests */) - { - } - - void LogError(const std::string& message) - { - OrthancPluginLogError(context_, message.c_str()); - } - - void LogWarning(const std::string& message) - { - OrthancPluginLogWarning(context_, message.c_str()); - } - - void LogInfo(const std::string& message) - { - OrthancPluginLogInfo(context_, message.c_str()); - } - - void SignalDeletedAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment); - } - - void SignalDeletedResource(const std::string& publicId, - OrthancPluginResourceType resourceType) - { - OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType); - } - - void SignalRemainingAncestor(const std::string& ancestorId, - OrthancPluginResourceType ancestorType) - { - OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType); - } - - void AnswerAttachment(const std::string& uuid, - int32_t contentType, - uint64_t uncompressedSize, - const std::string& uncompressedHash, - int32_t compressionType, - uint64_t compressedSize, - const std::string& compressedHash) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Attachment) - { - throw std::runtime_error("Cannot answer with an attachment in the current state"); - } - - OrthancPluginAttachment attachment; - attachment.uuid = uuid.c_str(); - attachment.contentType = contentType; - attachment.uncompressedSize = uncompressedSize; - attachment.uncompressedHash = uncompressedHash.c_str(); - attachment.compressionType = compressionType; - attachment.compressedSize = compressedSize; - attachment.compressedHash = compressedHash.c_str(); - - OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment); - } - - void AnswerChange(int64_t seq, - int32_t changeType, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& date) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_Change) - { - throw std::runtime_error("Cannot answer with a change in the current state"); - } - - OrthancPluginChange change; - change.seq = seq; - change.changeType = changeType; - change.resourceType = resourceType; - change.publicId = publicId.c_str(); - change.date = date.c_str(); - - OrthancPluginDatabaseAnswerChange(context_, database_, &change); - } - - void AnswerDicomTag(uint16_t group, - uint16_t element, - const std::string& value) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_DicomTag) - { - throw std::runtime_error("Cannot answer with a DICOM tag in the current state"); - } - - OrthancPluginDicomTag tag; - tag.group = group; - tag.element = element; - tag.value = value.c_str(); - - OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag); - } - - void AnswerExportedResource(int64_t seq, - OrthancPluginResourceType resourceType, - const std::string& publicId, - const std::string& modality, - const std::string& date, - const std::string& patientId, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid) - { - if (allowedAnswers_ != AllowedAnswers_All && - allowedAnswers_ != AllowedAnswers_ExportedResource) - { - throw std::runtime_error("Cannot answer with an exported resource in the current state"); - } - - OrthancPluginExportedResource exported; - exported.seq = seq; - exported.resourceType = resourceType; - exported.publicId = publicId.c_str(); - exported.modality = modality.c_str(); - exported.date = date.c_str(); - exported.patientId = patientId.c_str(); - exported.studyInstanceUid = studyInstanceUid.c_str(); - exported.seriesInstanceUid = seriesInstanceUid.c_str(); - exported.sopInstanceUid = sopInstanceUid.c_str(); - - OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported); - } - }; - - - - class IDatabaseBackend : public NonCopyable - { - friend class DatabaseBackendAdapter; - - private: - DatabaseBackendOutput* output_; - - void Finalize() - { - if (output_ != NULL) - { - delete output_; - output_ = NULL; - } - } - - protected: - DatabaseBackendOutput& GetOutput() - { - return *output_; - } - - public: - IDatabaseBackend() : output_(NULL) - { - } - - virtual ~IDatabaseBackend() - { - Finalize(); - } - - // This takes the ownership - void RegisterOutput(DatabaseBackendOutput* output) - { - Finalize(); - output_ = output; - } - - virtual void Open() = 0; - - virtual void Close() = 0; - - virtual void AddAttachment(int64_t id, - const OrthancPluginAttachment& attachment) = 0; - - virtual void AttachChild(int64_t parent, - int64_t child) = 0; - - virtual void ClearChanges() = 0; - - virtual void ClearExportedResources() = 0; - - virtual int64_t CreateResource(const char* publicId, - OrthancPluginResourceType type) = 0; - - virtual void DeleteAttachment(int64_t id, - int32_t attachment) = 0; - - virtual void DeleteMetadata(int64_t id, - int32_t metadataType) = 0; - - virtual void DeleteResource(int64_t id) = 0; - - virtual void GetAllPublicIds(std::list<std::string>& target, - OrthancPluginResourceType resourceType) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetChanges(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, - int64_t id) = 0; - - virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, - int64_t id) = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetExportedResources(bool& done /*out*/, - int64_t since, - uint32_t maxResults) = 0; - - /* Use GetOutput().AnswerChange() */ - virtual void GetLastChange() = 0; - - /* Use GetOutput().AnswerExportedResource() */ - virtual void GetLastExportedResource() = 0; - - /* Use GetOutput().AnswerDicomTag() */ - virtual void GetMainDicomTags(int64_t id) = 0; - - virtual std::string GetPublicId(int64_t resourceId) = 0; - - virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0; - - virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0; - - virtual uint64_t GetTotalCompressedSize() = 0; - - virtual uint64_t GetTotalUncompressedSize() = 0; - - virtual bool IsExistingResource(int64_t internalId) = 0; - - virtual bool IsProtectedPatient(int64_t internalId) = 0; - - virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, - int64_t id) = 0; - - virtual void LogChange(const OrthancPluginChange& change) = 0; - - virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0; - - /* Use GetOutput().AnswerAttachment() */ - virtual bool LookupAttachment(int64_t id, - int32_t contentType) = 0; - - virtual bool LookupGlobalProperty(std::string& target /*out*/, - int32_t property) = 0; - - /** - * "Identifiers" are necessarily one of the following tags: - * PatientID (0x0010, 0x0020), StudyInstanceUID (0x0020, 0x000d), - * SeriesInstanceUID (0x0020, 0x000e), SOPInstanceUID (0x0008, - * 0x0018) or AccessionNumber (0x0008, 0x0050). - **/ - virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, - const char* value) = 0; - - virtual bool LookupMetadata(std::string& target /*out*/, - int64_t id, - int32_t metadataType) = 0; - - virtual bool LookupParent(int64_t& parentId /*out*/, - int64_t resourceId) = 0; - - virtual bool LookupResource(int64_t& id /*out*/, - OrthancPluginResourceType& type /*out*/, - const char* publicId) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0; - - virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, - int64_t patientIdToAvoid) = 0; - - virtual void SetGlobalProperty(int32_t property, - const char* value) = 0; - - virtual void SetMainDicomTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetIdentifierTag(int64_t id, - uint16_t group, - uint16_t element, - const char* value) = 0; - - virtual void SetMetadata(int64_t id, - int32_t metadataType, - const char* value) = 0; - - virtual void SetProtectedPatient(int64_t internalId, - bool isProtected) = 0; - - virtual void StartTransaction() = 0; - - virtual void RollbackTransaction() = 0; - - virtual void CommitTransaction() = 0; - }; - - - - class DatabaseBackendAdapter - { - private: - // This class cannot be instantiated - DatabaseBackendAdapter() - { - } - - static void LogError(IDatabaseBackend* backend, - const std::runtime_error& e) - { - backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what())); - } - - - static int32_t AddAttachment(void* payload, - int64_t id, - const OrthancPluginAttachment* attachment) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AddAttachment(id, *attachment); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t AttachChild(void* payload, - int64_t parent, - int64_t child) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->AttachChild(parent, child); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t ClearChanges(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearChanges(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t ClearExportedResources(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->ClearExportedResources(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t CreateResource(int64_t* id, - void* payload, - const char* publicId, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *id = backend->CreateResource(publicId, resourceType); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t DeleteAttachment(void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteAttachment(id, contentType); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t DeleteMetadata(void* payload, - int64_t id, - int32_t metadataType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteMetadata(id, metadataType); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t DeleteResource(void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->DeleteResource(id); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetAllPublicIds(OrthancPluginDatabaseContext* context, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetAllPublicIds(ids, resourceType); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetChanges(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - bool done; - backend->GetChanges(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetChildrenInternalId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->GetChildrenInternalId(target, id); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetChildrenPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<std::string> ids; - backend->GetChildrenPublicId(ids, id); - - for (std::list<std::string>::const_iterator - it = ids.begin(); it != ids.end(); ++it) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - it->c_str()); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetExportedResources(OrthancPluginDatabaseContext* context, - void* payload, - int64_t since, - uint32_t maxResult) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - bool done; - backend->GetExportedResources(done, since, maxResult); - - if (done) - { - OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_, - backend->GetOutput().database_); - } - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetLastChange(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); - - try - { - backend->GetLastChange(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetLastExportedResource(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); - - try - { - backend->GetLastExportedResource(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetMainDicomTags(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag); - - try - { - backend->GetMainDicomTags(id); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetPublicId(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s = backend->GetPublicId(id); - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetResourceCount(uint64_t* target, - void* payload, - OrthancPluginResourceType resourceType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetResourceCount(resourceType); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetResourceType(OrthancPluginResourceType* resourceType, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *resourceType = backend->GetResourceType(id); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetTotalCompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalCompressedSize(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t GetTotalUncompressedSize(uint64_t* target, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *target = backend->GetTotalUncompressedSize(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t IsExistingResource(int32_t* existing, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *existing = backend->IsExistingResource(id); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t IsProtectedPatient(int32_t* isProtected, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - *isProtected = backend->IsProtectedPatient(id); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t ListAvailableMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableMetadata(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t ListAvailableAttachments(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int32_t> target; - backend->ListAvailableAttachments(target, id); - - for (std::list<int32_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, - backend->GetOutput().database_, - *it); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LogChange(void* payload, - const OrthancPluginChange* change) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogChange(*change); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LogExportedResource(void* payload, - const OrthancPluginExportedResource* exported) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->LogExportedResource(*exported); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupAttachment(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t contentType) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment); - - try - { - backend->LookupAttachment(id, contentType); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupGlobalProperty(OrthancPluginDatabaseContext* context, - void* payload, - int32_t property) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupGlobalProperty(s, property)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, - s.c_str()); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupIdentifier(OrthancPluginDatabaseContext* context, - void* payload, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->LookupIdentifier(target, tag->group, tag->element, tag->value); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupIdentifier2(OrthancPluginDatabaseContext* context, - void* payload, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::list<int64_t> target; - backend->LookupIdentifier(target, value); - - for (std::list<int64_t>::const_iterator - it = target.begin(); it != target.end(); ++it) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, *it); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupMetadata(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id, - int32_t metadata) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - std::string s; - if (backend->LookupMetadata(s, id, metadata)) - { - OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, - backend->GetOutput().database_, s.c_str()); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupParent(OrthancPluginDatabaseContext* context, - void* payload, - int64_t id) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t parent; - if (backend->LookupParent(parent, id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, parent); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t LookupResource(OrthancPluginDatabaseContext* context, - void* payload, - const char* publicId) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - OrthancPluginResourceType type; - if (backend->LookupResource(id, type, publicId)) - { - OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_, - backend->GetOutput().database_, - id, type); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SelectPatientToRecycle(OrthancPluginDatabaseContext* context, - void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SelectPatientToRecycle2(OrthancPluginDatabaseContext* context, - void* payload, - int64_t patientIdToAvoid) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - int64_t id; - if (backend->SelectPatientToRecycle(id, patientIdToAvoid)) - { - OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, - backend->GetOutput().database_, id); - } - - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SetGlobalProperty(void* payload, - int32_t property, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetGlobalProperty(property, value); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SetMainDicomTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMainDicomTag(id, tag->group, tag->element, tag->value); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SetIdentifierTag(void* payload, - int64_t id, - const OrthancPluginDicomTag* tag) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetIdentifierTag(id, tag->group, tag->element, tag->value); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SetMetadata(void* payload, - int64_t id, - int32_t metadata, - const char* value) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetMetadata(id, metadata, value); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t SetProtectedPatient(void* payload, - int64_t id, - int32_t isProtected) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->SetProtectedPatient(id, isProtected); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t StartTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->StartTransaction(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t RollbackTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->RollbackTransaction(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t CommitTransaction(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->CommitTransaction(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t Open(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Open(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - static int32_t Close(void* payload) - { - IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); - backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); - - try - { - backend->Close(); - return 0; - } - catch (std::runtime_error& e) - { - LogError(backend, e); - return -1; - } - } - - - public: - static void Register(OrthancPluginContext* context, - IDatabaseBackend& backend) - { - OrthancPluginDatabaseBackend params; - memset(¶ms, 0, sizeof(params)); - - params.addAttachment = AddAttachment; - params.attachChild = AttachChild; - params.clearChanges = ClearChanges; - params.clearExportedResources = ClearExportedResources; - params.createResource = CreateResource; - params.deleteAttachment = DeleteAttachment; - params.deleteMetadata = DeleteMetadata; - params.deleteResource = DeleteResource; - params.getAllPublicIds = GetAllPublicIds; - params.getChanges = GetChanges; - params.getChildrenInternalId = GetChildrenInternalId; - params.getChildrenPublicId = GetChildrenPublicId; - params.getExportedResources = GetExportedResources; - params.getLastChange = GetLastChange; - params.getLastExportedResource = GetLastExportedResource; - params.getMainDicomTags = GetMainDicomTags; - params.getPublicId = GetPublicId; - params.getResourceCount = GetResourceCount; - params.getResourceType = GetResourceType; - params.getTotalCompressedSize = GetTotalCompressedSize; - params.getTotalUncompressedSize = GetTotalUncompressedSize; - params.isExistingResource = IsExistingResource; - params.isProtectedPatient = IsProtectedPatient; - params.listAvailableMetadata = ListAvailableMetadata; - params.listAvailableAttachments = ListAvailableAttachments; - params.logChange = LogChange; - params.logExportedResource = LogExportedResource; - params.lookupAttachment = LookupAttachment; - params.lookupGlobalProperty = LookupGlobalProperty; - params.lookupIdentifier = LookupIdentifier; - params.lookupIdentifier2 = LookupIdentifier2; - params.lookupMetadata = LookupMetadata; - params.lookupParent = LookupParent; - params.lookupResource = LookupResource; - params.selectPatientToRecycle = SelectPatientToRecycle; - params.selectPatientToRecycle2 = SelectPatientToRecycle2; - params.setGlobalProperty = SetGlobalProperty; - params.setMainDicomTag = SetMainDicomTag; - params.setIdentifierTag = SetIdentifierTag; - params.setMetadata = SetMetadata; - params.setProtectedPatient = SetProtectedPatient; - params.startTransaction = StartTransaction; - params.rollbackTransaction = RollbackTransaction; - params.commitTransaction = CommitTransaction; - params.open = Open; - params.close = Close; - - OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackend(context, ¶ms, &backend); - if (!context) - { - throw std::runtime_error("Unable to register the database backend"); - } - - backend.RegisterOutput(new DatabaseBackendOutput(context, database)); - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,782 @@ +/** + * @ingroup CInterface + **/ + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "OrthancCPlugin.h" + + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + + /** + * Opaque structure that represents the context of a custom database engine. + * @ingroup Callbacks + **/ + typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext; + + +/*<! @cond Doxygen_Suppress */ + typedef enum + { + _OrthancPluginDatabaseAnswerType_None = 0, + + /* Events */ + _OrthancPluginDatabaseAnswerType_DeletedAttachment = 1, + _OrthancPluginDatabaseAnswerType_DeletedResource = 2, + _OrthancPluginDatabaseAnswerType_RemainingAncestor = 3, + + /* Return value */ + _OrthancPluginDatabaseAnswerType_Attachment = 10, + _OrthancPluginDatabaseAnswerType_Change = 11, + _OrthancPluginDatabaseAnswerType_DicomTag = 12, + _OrthancPluginDatabaseAnswerType_ExportedResource = 13, + _OrthancPluginDatabaseAnswerType_Int32 = 14, + _OrthancPluginDatabaseAnswerType_Int64 = 15, + _OrthancPluginDatabaseAnswerType_Resource = 16, + _OrthancPluginDatabaseAnswerType_String = 17, + + _OrthancPluginDatabaseAnswerType_INTERNAL = 0x7fffffff + } _OrthancPluginDatabaseAnswerType; + + + typedef struct + { + const char* uuid; + int32_t contentType; + uint64_t uncompressedSize; + const char* uncompressedHash; + int32_t compressionType; + uint64_t compressedSize; + const char* compressedHash; + } OrthancPluginAttachment; + + typedef struct + { + uint16_t group; + uint16_t element; + const char* value; + } OrthancPluginDicomTag; + + typedef struct + { + int64_t seq; + int32_t changeType; + OrthancPluginResourceType resourceType; + const char* publicId; + const char* date; + } OrthancPluginChange; + + typedef struct + { + int64_t seq; + OrthancPluginResourceType resourceType; + const char* publicId; + const char* modality; + const char* date; + const char* patientId; + const char* studyInstanceUid; + const char* seriesInstanceUid; + const char* sopInstanceUid; + } OrthancPluginExportedResource; + + + typedef struct + { + OrthancPluginDatabaseContext* database; + _OrthancPluginDatabaseAnswerType type; + int32_t valueInt32; + uint32_t valueUint32; + int64_t valueInt64; + const char *valueString; + const void *valueGeneric; + } _OrthancPluginDatabaseAnswer; + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerString( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_String; + params.valueString = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginChange* change) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 0; + params.valueGeneric = change; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Change; + params.valueUint32 = 1; + params.valueGeneric = NULL; + + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int32_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int32; + params.valueInt32 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t value) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Int64; + params.valueInt64 = value; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginExportedResource* exported) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 0; + params.valueGeneric = exported; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_ExportedResource; + params.valueUint32 = 1; + params.valueGeneric = NULL; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginDicomTag* tag) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DicomTag; + params.valueGeneric = tag; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Attachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + int64_t id, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_Resource; + params.valueInt64 = id; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const OrthancPluginAttachment* attachment) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment; + params.valueGeneric = attachment; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* publicId, + OrthancPluginResourceType resourceType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_DeletedResource; + params.valueString = publicId; + params.valueInt32 = (int32_t) resourceType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor( + OrthancPluginContext* context, + OrthancPluginDatabaseContext* database, + const char* ancestorId, + OrthancPluginResourceType ancestorType) + { + _OrthancPluginDatabaseAnswer params; + memset(¶ms, 0, sizeof(params)); + params.database = database; + params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor; + params.valueString = ancestorId; + params.valueInt32 = (int32_t) ancestorType; + context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, ¶ms); + } + + + + + + typedef struct + { + OrthancPluginErrorCode (*addAttachment) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginAttachment* attachment); + + OrthancPluginErrorCode (*attachChild) ( + /* inputs */ + void* payload, + int64_t parent, + int64_t child); + + OrthancPluginErrorCode (*clearChanges) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*clearExportedResources) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*createResource) ( + /* outputs */ + int64_t* id, + /* inputs */ + void* payload, + const char* publicId, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*deleteAttachment) ( + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + OrthancPluginErrorCode (*deleteMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadataType); + + OrthancPluginErrorCode (*deleteResource) ( + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIds) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + /* Output: Use OrthancPluginDatabaseAnswerChange() and + * OrthancPluginDatabaseAnswerChangesDone() */ + OrthancPluginErrorCode (*getChanges) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*getChildrenInternalId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getChildrenPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and + * OrthancPluginDatabaseAnswerExportedResourcesDone() */ + OrthancPluginErrorCode (*getExportedResources) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t since, + uint32_t maxResult); + + /* Output: Use OrthancPluginDatabaseAnswerChange() */ + OrthancPluginErrorCode (*getLastChange) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */ + OrthancPluginErrorCode (*getLastExportedResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */ + OrthancPluginErrorCode (*getMainDicomTags) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getPublicId) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getResourceCount) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType); + + OrthancPluginErrorCode (*getResourceType) ( + /* outputs */ + OrthancPluginResourceType* resourceType, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*getTotalCompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*getTotalUncompressedSize) ( + /* outputs */ + uint64_t* target, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*isExistingResource) ( + /* outputs */ + int32_t* existing, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*isProtectedPatient) ( + /* outputs */ + int32_t* isProtected, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerInt32() */ + OrthancPluginErrorCode (*listAvailableAttachments) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + OrthancPluginErrorCode (*logChange) ( + /* inputs */ + void* payload, + const OrthancPluginChange* change); + + OrthancPluginErrorCode (*logExportedResource) ( + /* inputs */ + void* payload, + const OrthancPluginExportedResource* exported); + + /* Output: Use OrthancPluginDatabaseAnswerAttachment() */ + OrthancPluginErrorCode (*lookupAttachment) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t contentType); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupGlobalProperty) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int32_t property); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const OrthancPluginDicomTag* tag); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupIdentifier2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* value); + + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*lookupMetadata) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id, + int32_t metadata); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*lookupParent) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t id); + + /* Output: Use OrthancPluginDatabaseAnswerResource() */ + OrthancPluginErrorCode (*lookupResource) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + const char* publicId); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload); + + /* Output: Use OrthancPluginDatabaseAnswerInt64() */ + OrthancPluginErrorCode (*selectPatientToRecycle2) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + int64_t patientIdToAvoid); + + OrthancPluginErrorCode (*setGlobalProperty) ( + /* inputs */ + void* payload, + int32_t property, + const char* value); + + OrthancPluginErrorCode (*setMainDicomTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setIdentifierTag) ( + /* inputs */ + void* payload, + int64_t id, + const OrthancPluginDicomTag* tag); + + OrthancPluginErrorCode (*setMetadata) ( + /* inputs */ + void* payload, + int64_t id, + int32_t metadata, + const char* value); + + OrthancPluginErrorCode (*setProtectedPatient) ( + /* inputs */ + void* payload, + int64_t id, + int32_t isProtected); + + OrthancPluginErrorCode (*startTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*rollbackTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*commitTransaction) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*open) ( + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*close) ( + /* inputs */ + void* payload); + + } OrthancPluginDatabaseBackend; + + + typedef struct + { + /* Output: Use OrthancPluginDatabaseAnswerString() */ + OrthancPluginErrorCode (*getAllPublicIdsWithLimit) ( + /* outputs */ + OrthancPluginDatabaseContext* context, + /* inputs */ + void* payload, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit); + + OrthancPluginErrorCode (*getDatabaseVersion) ( + /* outputs */ + uint32_t* version, + /* inputs */ + void* payload); + + OrthancPluginErrorCode (*upgradeDatabase) ( + /* inputs */ + void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea); + } OrthancPluginDatabaseExtensions; + +/*<! @endcond */ + + + typedef struct + { + OrthancPluginDatabaseContext** result; + const OrthancPluginDatabaseBackend* backend; + void* payload; + } _OrthancPluginRegisterDatabaseBackend; + + /** + * Register a custom database back-end. + * + * Instead of manually filling the OrthancPluginDatabaseBackend + * structure, you should instead implement a concrete C++ class + * deriving from ::OrthancPlugins::IDatabaseBackend, and register it + * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @return The context of the database engine (it must not be manually freed). + * @ingroup Callbacks + * @deprecated + * @see OrthancPluginRegisterDatabaseBackendV2 + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackend( + OrthancPluginContext* context, + const OrthancPluginDatabaseBackend* backend, + void* payload) + { + OrthancPluginDatabaseContext* result = NULL; + _OrthancPluginRegisterDatabaseBackend params; + + if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + return NULL; + } + + memset(¶ms, 0, sizeof(params)); + params.backend = backend; + params.result = &result; + params.payload = payload; + + if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + typedef struct + { + OrthancPluginDatabaseContext** result; + const OrthancPluginDatabaseBackend* backend; + void* payload; + const OrthancPluginDatabaseExtensions* extensions; + uint32_t extensionsSize; + } _OrthancPluginRegisterDatabaseBackendV2; + + + /** + * Register a custom database back-end. + * + * Instead of manually filling the OrthancPluginDatabaseBackendV2 + * structure, you should instead implement a concrete C++ class + * deriving from ::OrthancPlugins::IDatabaseBackend, and register it + * using ::OrthancPlugins::DatabaseBackendAdapter::Register(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend The callbacks of the custom database engine. + * @param payload Pointer containing private information for the database engine. + * @param extensions Extensions to the base database SDK that was shipped until Orthanc 0.9.3. + * @return The context of the database engine (it must not be manually freed). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackendV2( + OrthancPluginContext* context, + const OrthancPluginDatabaseBackend* backend, + const OrthancPluginDatabaseExtensions* extensions, + void* payload) + { + OrthancPluginDatabaseContext* result = NULL; + _OrthancPluginRegisterDatabaseBackendV2 params; + + if (sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + { + return NULL; + } + + memset(¶ms, 0, sizeof(params)); + params.backend = backend; + params.result = &result; + params.payload = payload; + params.extensions = extensions; + params.extensionsSize = sizeof(OrthancPluginDatabaseExtensions); + + if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackendV2, ¶ms) || + result == NULL) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,3775 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). + * -# <tt>void OrthancPluginFinalize()</tt>: + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# <tt>const char* OrthancPluginGetName()</tt>: + * The plugin must return a short string to identify itself. + * -# <tt>const char* OrthancPluginGetVersion()</tt>: + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an <tt>extern "C"</tt> section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <stdio.h> +#include <string.h> + +#ifdef WIN32 +#define ORTHANC_PLUGINS_API __declspec(dllexport) +#else +#define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 9 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 5 + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include <stdint.h> + +#include <stdlib.h> + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const char* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + + /* Sending answers to REST calls */ + _OrthancPluginService_AnswerBuffer = 2000, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can happen to DICOM resources. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ + OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ + OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * @brief A memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callback + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance received by Orthanc. + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a function to free dynamic memory. + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of this C header is + * compatible with the current version of Orthanc. The result of + * this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + int major, minor, revision; + + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 1; + } + + if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 1; + } + + if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, it is up to the plugin to + * implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const char* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPostAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPost + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDeleteAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPutAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPut + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + OrthancPluginDicomInstance* instance; + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. This + * function must be called during the initialization of the plugin, + * i.e. inside the OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning If your change callback has to call the REST API of + * Orthanc, you should make these calls in a separate thread (with + * the events passing through a message queue). Otherwise, this + * could result in deadlocks in the presence of other plugins or Lua + * script. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const char* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const char* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + const void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + const void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const char* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param url The URL of interest. + * @param username The username (can be <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new tag in the dictionary of DICOM tags + * that is known to Orthanc. This function should be used in the + * OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("<tt>n</tt>"). + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,1860 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "OrthancCDatabasePlugin.h" + +#include <stdexcept> +#include <list> +#include <string> + +namespace OrthancPlugins +{ +//! @cond Doxygen_Suppress + // This class mimics "boost::noncopyable" + class NonCopyable + { + private: + NonCopyable(const NonCopyable&); + + NonCopyable& operator= (const NonCopyable&); + + protected: + NonCopyable() + { + } + + ~NonCopyable() + { + } + }; +//! @endcond + + + /** + * @ingroup Callbacks + **/ + class DatabaseException + { + private: + OrthancPluginErrorCode code_; + + public: + DatabaseException() : code_(OrthancPluginErrorCode_DatabasePlugin) + { + } + + DatabaseException(OrthancPluginErrorCode code) : code_(code) + { + } + + OrthancPluginErrorCode GetErrorCode() const + { + return code_; + } + }; + + + /** + * @ingroup Callbacks + **/ + class DatabaseBackendOutput : public NonCopyable + { + friend class DatabaseBackendAdapter; + + private: + enum AllowedAnswers + { + AllowedAnswers_All, + AllowedAnswers_None, + AllowedAnswers_Attachment, + AllowedAnswers_Change, + AllowedAnswers_DicomTag, + AllowedAnswers_ExportedResource + }; + + OrthancPluginContext* context_; + OrthancPluginDatabaseContext* database_; + AllowedAnswers allowedAnswers_; + + void SetAllowedAnswers(AllowedAnswers allowed) + { + allowedAnswers_ = allowed; + } + + public: + DatabaseBackendOutput(OrthancPluginContext* context, + OrthancPluginDatabaseContext* database) : + context_(context), + database_(database), + allowedAnswers_(AllowedAnswers_All /* for unit tests */) + { + } + + void LogError(const std::string& message) + { + OrthancPluginLogError(context_, message.c_str()); + } + + void LogWarning(const std::string& message) + { + OrthancPluginLogWarning(context_, message.c_str()); + } + + void LogInfo(const std::string& message) + { + OrthancPluginLogInfo(context_, message.c_str()); + } + + void SignalDeletedAttachment(const std::string& uuid, + int32_t contentType, + uint64_t uncompressedSize, + const std::string& uncompressedHash, + int32_t compressionType, + uint64_t compressedSize, + const std::string& compressedHash) + { + OrthancPluginAttachment attachment; + attachment.uuid = uuid.c_str(); + attachment.contentType = contentType; + attachment.uncompressedSize = uncompressedSize; + attachment.uncompressedHash = uncompressedHash.c_str(); + attachment.compressionType = compressionType; + attachment.compressedSize = compressedSize; + attachment.compressedHash = compressedHash.c_str(); + + OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment); + } + + void SignalDeletedResource(const std::string& publicId, + OrthancPluginResourceType resourceType) + { + OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType); + } + + void SignalRemainingAncestor(const std::string& ancestorId, + OrthancPluginResourceType ancestorType) + { + OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType); + } + + void AnswerAttachment(const std::string& uuid, + int32_t contentType, + uint64_t uncompressedSize, + const std::string& uncompressedHash, + int32_t compressionType, + uint64_t compressedSize, + const std::string& compressedHash) + { + if (allowedAnswers_ != AllowedAnswers_All && + allowedAnswers_ != AllowedAnswers_Attachment) + { + throw std::runtime_error("Cannot answer with an attachment in the current state"); + } + + OrthancPluginAttachment attachment; + attachment.uuid = uuid.c_str(); + attachment.contentType = contentType; + attachment.uncompressedSize = uncompressedSize; + attachment.uncompressedHash = uncompressedHash.c_str(); + attachment.compressionType = compressionType; + attachment.compressedSize = compressedSize; + attachment.compressedHash = compressedHash.c_str(); + + OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment); + } + + void AnswerChange(int64_t seq, + int32_t changeType, + OrthancPluginResourceType resourceType, + const std::string& publicId, + const std::string& date) + { + if (allowedAnswers_ != AllowedAnswers_All && + allowedAnswers_ != AllowedAnswers_Change) + { + throw std::runtime_error("Cannot answer with a change in the current state"); + } + + OrthancPluginChange change; + change.seq = seq; + change.changeType = changeType; + change.resourceType = resourceType; + change.publicId = publicId.c_str(); + change.date = date.c_str(); + + OrthancPluginDatabaseAnswerChange(context_, database_, &change); + } + + void AnswerDicomTag(uint16_t group, + uint16_t element, + const std::string& value) + { + if (allowedAnswers_ != AllowedAnswers_All && + allowedAnswers_ != AllowedAnswers_DicomTag) + { + throw std::runtime_error("Cannot answer with a DICOM tag in the current state"); + } + + OrthancPluginDicomTag tag; + tag.group = group; + tag.element = element; + tag.value = value.c_str(); + + OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag); + } + + void AnswerExportedResource(int64_t seq, + OrthancPluginResourceType resourceType, + const std::string& publicId, + const std::string& modality, + const std::string& date, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) + { + if (allowedAnswers_ != AllowedAnswers_All && + allowedAnswers_ != AllowedAnswers_ExportedResource) + { + throw std::runtime_error("Cannot answer with an exported resource in the current state"); + } + + OrthancPluginExportedResource exported; + exported.seq = seq; + exported.resourceType = resourceType; + exported.publicId = publicId.c_str(); + exported.modality = modality.c_str(); + exported.date = date.c_str(); + exported.patientId = patientId.c_str(); + exported.studyInstanceUid = studyInstanceUid.c_str(); + exported.seriesInstanceUid = seriesInstanceUid.c_str(); + exported.sopInstanceUid = sopInstanceUid.c_str(); + + OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported); + } + }; + + + /** + * @ingroup Callbacks + **/ + class IDatabaseBackend : public NonCopyable + { + friend class DatabaseBackendAdapter; + + private: + DatabaseBackendOutput* output_; + + void Finalize() + { + if (output_ != NULL) + { + delete output_; + output_ = NULL; + } + } + + protected: + DatabaseBackendOutput& GetOutput() + { + return *output_; + } + + public: + IDatabaseBackend() : output_(NULL) + { + } + + virtual ~IDatabaseBackend() + { + Finalize(); + } + + // This takes the ownership + void RegisterOutput(DatabaseBackendOutput* output) + { + Finalize(); + output_ = output; + } + + virtual void Open() = 0; + + virtual void Close() = 0; + + virtual void AddAttachment(int64_t id, + const OrthancPluginAttachment& attachment) = 0; + + virtual void AttachChild(int64_t parent, + int64_t child) = 0; + + virtual void ClearChanges() = 0; + + virtual void ClearExportedResources() = 0; + + virtual int64_t CreateResource(const char* publicId, + OrthancPluginResourceType type) = 0; + + virtual void DeleteAttachment(int64_t id, + int32_t attachment) = 0; + + virtual void DeleteMetadata(int64_t id, + int32_t metadataType) = 0; + + virtual void DeleteResource(int64_t id) = 0; + + virtual void GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType) = 0; + + virtual void GetAllPublicIds(std::list<std::string>& target, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) = 0; + + /* Use GetOutput().AnswerChange() */ + virtual void GetChanges(bool& done /*out*/, + int64_t since, + uint32_t maxResults) = 0; + + virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/, + int64_t id) = 0; + + virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/, + int64_t id) = 0; + + /* Use GetOutput().AnswerExportedResource() */ + virtual void GetExportedResources(bool& done /*out*/, + int64_t since, + uint32_t maxResults) = 0; + + /* Use GetOutput().AnswerChange() */ + virtual void GetLastChange() = 0; + + /* Use GetOutput().AnswerExportedResource() */ + virtual void GetLastExportedResource() = 0; + + /* Use GetOutput().AnswerDicomTag() */ + virtual void GetMainDicomTags(int64_t id) = 0; + + virtual std::string GetPublicId(int64_t resourceId) = 0; + + virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0; + + virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0; + + virtual uint64_t GetTotalCompressedSize() = 0; + + virtual uint64_t GetTotalUncompressedSize() = 0; + + virtual bool IsExistingResource(int64_t internalId) = 0; + + virtual bool IsProtectedPatient(int64_t internalId) = 0; + + virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/, + int64_t id) = 0; + + virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/, + int64_t id) = 0; + + virtual void LogChange(const OrthancPluginChange& change) = 0; + + virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0; + + /* Use GetOutput().AnswerAttachment() */ + virtual bool LookupAttachment(int64_t id, + int32_t contentType) = 0; + + virtual bool LookupGlobalProperty(std::string& target /*out*/, + int32_t property) = 0; + + /** + * "Identifiers" are necessarily one of the following tags: + * PatientID (0x0010, 0x0020), StudyInstanceUID (0x0020, 0x000d), + * SeriesInstanceUID (0x0020, 0x000e), SOPInstanceUID (0x0008, + * 0x0018) or AccessionNumber (0x0008, 0x0050). + **/ + virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, + uint16_t group, + uint16_t element, + const char* value) = 0; + + virtual void LookupIdentifier(std::list<int64_t>& target /*out*/, + const char* value) = 0; + + virtual bool LookupMetadata(std::string& target /*out*/, + int64_t id, + int32_t metadataType) = 0; + + virtual bool LookupParent(int64_t& parentId /*out*/, + int64_t resourceId) = 0; + + virtual bool LookupResource(int64_t& id /*out*/, + OrthancPluginResourceType& type /*out*/, + const char* publicId) = 0; + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0; + + virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/, + int64_t patientIdToAvoid) = 0; + + virtual void SetGlobalProperty(int32_t property, + const char* value) = 0; + + virtual void SetMainDicomTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) = 0; + + virtual void SetIdentifierTag(int64_t id, + uint16_t group, + uint16_t element, + const char* value) = 0; + + virtual void SetMetadata(int64_t id, + int32_t metadataType, + const char* value) = 0; + + virtual void SetProtectedPatient(int64_t internalId, + bool isProtected) = 0; + + virtual void StartTransaction() = 0; + + virtual void RollbackTransaction() = 0; + + virtual void CommitTransaction() = 0; + + virtual uint32_t GetDatabaseVersion() = 0; + + virtual void UpgradeDatabase(uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) = 0; + }; + + + + /** + * @brief Bridge between C and C++ database engines. + * + * Class creating the bridge between the C low-level primitives for + * custom database engines, and the high-level IDatabaseBackend C++ + * interface. + * + * @ingroup Callbacks + **/ + class DatabaseBackendAdapter + { + private: + // This class cannot be instantiated + DatabaseBackendAdapter() + { + } + + static void LogError(IDatabaseBackend* backend, + const std::runtime_error& e) + { + backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what())); + } + + + static OrthancPluginErrorCode AddAttachment(void* payload, + int64_t id, + const OrthancPluginAttachment* attachment) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->AddAttachment(id, *attachment); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode AttachChild(void* payload, + int64_t parent, + int64_t child) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->AttachChild(parent, child); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode ClearChanges(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->ClearChanges(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode ClearExportedResources(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->ClearExportedResources(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode CreateResource(int64_t* id, + void* payload, + const char* publicId, + OrthancPluginResourceType resourceType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *id = backend->CreateResource(publicId, resourceType); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode DeleteAttachment(void* payload, + int64_t id, + int32_t contentType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->DeleteAttachment(id, contentType); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode DeleteMetadata(void* payload, + int64_t id, + int32_t metadataType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->DeleteMetadata(id, metadataType); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode DeleteResource(void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->DeleteResource(id); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetAllPublicIds(OrthancPluginDatabaseContext* context, + void* payload, + OrthancPluginResourceType resourceType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<std::string> ids; + backend->GetAllPublicIds(ids, resourceType); + + for (std::list<std::string>::const_iterator + it = ids.begin(); it != ids.end(); ++it) + { + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, + it->c_str()); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetAllPublicIdsWithLimit(OrthancPluginDatabaseContext* context, + void* payload, + OrthancPluginResourceType resourceType, + uint64_t since, + uint64_t limit) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<std::string> ids; + backend->GetAllPublicIds(ids, resourceType, since, limit); + + for (std::list<std::string>::const_iterator + it = ids.begin(); it != ids.end(); ++it) + { + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, + it->c_str()); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetChanges(OrthancPluginDatabaseContext* context, + void* payload, + int64_t since, + uint32_t maxResult) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); + + try + { + bool done; + backend->GetChanges(done, since, maxResult); + + if (done) + { + OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_, + backend->GetOutput().database_); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetChildrenInternalId(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<int64_t> target; + backend->GetChildrenInternalId(target, id); + + for (std::list<int64_t>::const_iterator + it = target.begin(); it != target.end(); ++it) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, *it); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetChildrenPublicId(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<std::string> ids; + backend->GetChildrenPublicId(ids, id); + + for (std::list<std::string>::const_iterator + it = ids.begin(); it != ids.end(); ++it) + { + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, + it->c_str()); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetExportedResources(OrthancPluginDatabaseContext* context, + void* payload, + int64_t since, + uint32_t maxResult) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); + + try + { + bool done; + backend->GetExportedResources(done, since, maxResult); + + if (done) + { + OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_, + backend->GetOutput().database_); + } + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetLastChange(OrthancPluginDatabaseContext* context, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change); + + try + { + backend->GetLastChange(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetLastExportedResource(OrthancPluginDatabaseContext* context, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource); + + try + { + backend->GetLastExportedResource(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetMainDicomTags(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag); + + try + { + backend->GetMainDicomTags(id); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetPublicId(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::string s = backend->GetPublicId(id); + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, + s.c_str()); + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetResourceCount(uint64_t* target, + void* payload, + OrthancPluginResourceType resourceType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *target = backend->GetResourceCount(resourceType); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetResourceType(OrthancPluginResourceType* resourceType, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *resourceType = backend->GetResourceType(id); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetTotalCompressedSize(uint64_t* target, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *target = backend->GetTotalCompressedSize(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetTotalUncompressedSize(uint64_t* target, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *target = backend->GetTotalUncompressedSize(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode IsExistingResource(int32_t* existing, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *existing = backend->IsExistingResource(id); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode IsProtectedPatient(int32_t* isProtected, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + *isProtected = backend->IsProtectedPatient(id); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode ListAvailableMetadata(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<int32_t> target; + backend->ListAvailableMetadata(target, id); + + for (std::list<int32_t>::const_iterator + it = target.begin(); it != target.end(); ++it) + { + OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, + backend->GetOutput().database_, + *it); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode ListAvailableAttachments(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<int32_t> target; + backend->ListAvailableAttachments(target, id); + + for (std::list<int32_t>::const_iterator + it = target.begin(); it != target.end(); ++it) + { + OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_, + backend->GetOutput().database_, + *it); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LogChange(void* payload, + const OrthancPluginChange* change) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->LogChange(*change); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LogExportedResource(void* payload, + const OrthancPluginExportedResource* exported) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->LogExportedResource(*exported); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupAttachment(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id, + int32_t contentType) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment); + + try + { + backend->LookupAttachment(id, contentType); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupGlobalProperty(OrthancPluginDatabaseContext* context, + void* payload, + int32_t property) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::string s; + if (backend->LookupGlobalProperty(s, property)) + { + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, + s.c_str()); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupIdentifier(OrthancPluginDatabaseContext* context, + void* payload, + const OrthancPluginDicomTag* tag) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<int64_t> target; + backend->LookupIdentifier(target, tag->group, tag->element, tag->value); + + for (std::list<int64_t>::const_iterator + it = target.begin(); it != target.end(); ++it) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, *it); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupIdentifier2(OrthancPluginDatabaseContext* context, + void* payload, + const char* value) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::list<int64_t> target; + backend->LookupIdentifier(target, value); + + for (std::list<int64_t>::const_iterator + it = target.begin(); it != target.end(); ++it) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, *it); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupMetadata(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id, + int32_t metadata) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + std::string s; + if (backend->LookupMetadata(s, id, metadata)) + { + OrthancPluginDatabaseAnswerString(backend->GetOutput().context_, + backend->GetOutput().database_, s.c_str()); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupParent(OrthancPluginDatabaseContext* context, + void* payload, + int64_t id) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + int64_t parent; + if (backend->LookupParent(parent, id)) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, parent); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode LookupResource(OrthancPluginDatabaseContext* context, + void* payload, + const char* publicId) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + int64_t id; + OrthancPluginResourceType type; + if (backend->LookupResource(id, type, publicId)) + { + OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_, + backend->GetOutput().database_, + id, type); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SelectPatientToRecycle(OrthancPluginDatabaseContext* context, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + int64_t id; + if (backend->SelectPatientToRecycle(id)) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, id); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SelectPatientToRecycle2(OrthancPluginDatabaseContext* context, + void* payload, + int64_t patientIdToAvoid) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + int64_t id; + if (backend->SelectPatientToRecycle(id, patientIdToAvoid)) + { + OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_, + backend->GetOutput().database_, id); + } + + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SetGlobalProperty(void* payload, + int32_t property, + const char* value) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->SetGlobalProperty(property, value); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SetMainDicomTag(void* payload, + int64_t id, + const OrthancPluginDicomTag* tag) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->SetMainDicomTag(id, tag->group, tag->element, tag->value); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SetIdentifierTag(void* payload, + int64_t id, + const OrthancPluginDicomTag* tag) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->SetIdentifierTag(id, tag->group, tag->element, tag->value); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SetMetadata(void* payload, + int64_t id, + int32_t metadata, + const char* value) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->SetMetadata(id, metadata, value); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode SetProtectedPatient(void* payload, + int64_t id, + int32_t isProtected) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->SetProtectedPatient(id, (isProtected != 0)); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode StartTransaction(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->StartTransaction(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode RollbackTransaction(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->RollbackTransaction(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode CommitTransaction(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->CommitTransaction(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode Open(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->Open(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode Close(void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None); + + try + { + backend->Close(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode GetDatabaseVersion(uint32_t* version, + void* payload) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + + try + { + *version = backend->GetDatabaseVersion(); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + static OrthancPluginErrorCode UpgradeDatabase(void* payload, + uint32_t targetVersion, + OrthancPluginStorageArea* storageArea) + { + IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload); + + try + { + backend->UpgradeDatabase(targetVersion, storageArea); + return OrthancPluginErrorCode_Success; + } + catch (std::runtime_error& e) + { + LogError(backend, e); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (DatabaseException& e) + { + return e.GetErrorCode(); + } + } + + + public: + /** + * Register a custom database back-end written in C++. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param backend Your custom database engine. + **/ + + static void Register(OrthancPluginContext* context, + IDatabaseBackend& backend) + { + OrthancPluginDatabaseBackend params; + memset(¶ms, 0, sizeof(params)); + + OrthancPluginDatabaseExtensions extensions; + memset(&extensions, 0, sizeof(extensions)); + + params.addAttachment = AddAttachment; + params.attachChild = AttachChild; + params.clearChanges = ClearChanges; + params.clearExportedResources = ClearExportedResources; + params.createResource = CreateResource; + params.deleteAttachment = DeleteAttachment; + params.deleteMetadata = DeleteMetadata; + params.deleteResource = DeleteResource; + params.getAllPublicIds = GetAllPublicIds; + params.getChanges = GetChanges; + params.getChildrenInternalId = GetChildrenInternalId; + params.getChildrenPublicId = GetChildrenPublicId; + params.getExportedResources = GetExportedResources; + params.getLastChange = GetLastChange; + params.getLastExportedResource = GetLastExportedResource; + params.getMainDicomTags = GetMainDicomTags; + params.getPublicId = GetPublicId; + params.getResourceCount = GetResourceCount; + params.getResourceType = GetResourceType; + params.getTotalCompressedSize = GetTotalCompressedSize; + params.getTotalUncompressedSize = GetTotalUncompressedSize; + params.isExistingResource = IsExistingResource; + params.isProtectedPatient = IsProtectedPatient; + params.listAvailableMetadata = ListAvailableMetadata; + params.listAvailableAttachments = ListAvailableAttachments; + params.logChange = LogChange; + params.logExportedResource = LogExportedResource; + params.lookupAttachment = LookupAttachment; + params.lookupGlobalProperty = LookupGlobalProperty; + params.lookupIdentifier = LookupIdentifier; + params.lookupIdentifier2 = LookupIdentifier2; + params.lookupMetadata = LookupMetadata; + params.lookupParent = LookupParent; + params.lookupResource = LookupResource; + params.selectPatientToRecycle = SelectPatientToRecycle; + params.selectPatientToRecycle2 = SelectPatientToRecycle2; + params.setGlobalProperty = SetGlobalProperty; + params.setMainDicomTag = SetMainDicomTag; + params.setIdentifierTag = SetIdentifierTag; + params.setMetadata = SetMetadata; + params.setProtectedPatient = SetProtectedPatient; + params.startTransaction = StartTransaction; + params.rollbackTransaction = RollbackTransaction; + params.commitTransaction = CommitTransaction; + params.open = Open; + params.close = Close; + + extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit; + extensions.getDatabaseVersion = GetDatabaseVersion; + extensions.upgradeDatabase = UpgradeDatabase; + + OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, ¶ms, &extensions, &backend); + if (!context) + { + throw std::runtime_error("Unable to register the database backend"); + } + + backend.RegisterOutput(new DatabaseBackendOutput(context, database)); + } + }; +}
--- a/Plugins/Samples/Basic/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/Basic/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -2,16 +2,7 @@ project(Basic) -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Werror") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror") -endif() +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) -include_directories(${CMAKE_SOURCE_DIR}/../../OrthancCPlugin/) add_library(PluginTest SHARED Plugin.c) - -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 - target_link_libraries(PluginTest pthread dl) -endif()
--- a/Plugins/Samples/Basic/Plugin.c Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/Basic/Plugin.c Wed Sep 30 13:23:31 2015 +0200 @@ -3,35 +3,30 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * 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 <OrthancCPlugin.h> +#include <orthanc/OrthancCPlugin.h> #include <string.h> #include <stdio.h> static OrthancPluginContext* context = NULL; +static OrthancPluginErrorCode customError; + ORTHANC_PLUGINS_API int32_t Callback1(OrthancPluginRestOutput* output, const char* url, @@ -217,9 +212,9 @@ } -ORTHANC_PLUGINS_API int32_t CallbackCreateDicom(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode CallbackCreateDicom(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { const char* pathLocator = "\"Path\" : \""; char info[1024]; @@ -257,12 +252,12 @@ OrthancPluginAnswerBuffer(context, output, "OK\n", 3, "text/plain"); } - return 0; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t OnStoredCallback(OrthancPluginDicomInstance* instance, - const char* instanceId) +ORTHANC_PLUGINS_API OrthancPluginErrorCode OnStoredCallback(OrthancPluginDicomInstance* instance, + const char* instanceId) { char buffer[256]; FILE* fp; @@ -298,13 +293,13 @@ OrthancPluginLogError(context, "Instance has no reception date, should never happen!"); } - return 0; + return OrthancPluginErrorCode_Success; } -ORTHANC_PLUGINS_API int32_t OnChangeCallback(OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId) +ORTHANC_PLUGINS_API OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) { char info[1024]; OrthancPluginMemoryBuffer tmp; @@ -324,7 +319,7 @@ } } - return 0; + return OrthancPluginErrorCode_Success; } @@ -363,9 +358,10 @@ OrthancPluginLogWarning(context, info); OrthancPluginFreeString(context, s); - s = OrthancPluginGetConfigurationPath(context); - sprintf(info, " Path to configuration file: %s", s); + s = OrthancPluginGetConfiguration(context); + sprintf(info, " Content of the configuration file:\n"); OrthancPluginLogWarning(context, info); + OrthancPluginLogWarning(context, s); OrthancPluginFreeString(context, s); /* Print the command-line arguments of Orthanc */ @@ -397,6 +393,7 @@ OrthancPluginExtendOrthancExplorer(context, "alert('Hello Orthanc! From sample plugin with love.');"); /* Make REST requests to the built-in Orthanc API */ + memset(&tmp, 0, sizeof(tmp)); OrthancPluginRestApiGet(context, &tmp, "/changes"); OrthancPluginFreeMemoryBuffer(context, &tmp); OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1"); @@ -406,16 +403,10 @@ sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]"); OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info)); - /* Play with global properties: A global counter is incremented - each time the plugin starts. */ - s = OrthancPluginGetGlobalProperty(context, 1024, "0"); - sscanf(s, "%d", &counter); - sprintf(info, "Number of times this plugin was started: %d", counter); - OrthancPluginLogWarning(context, info); - counter++; - sprintf(info, "%d", counter); - OrthancPluginSetGlobalProperty(context, 1024, info); - OrthancPluginFreeString(context, s); + customError = OrthancPluginRegisterErrorCode(context, 4, 402, "Hello world"); + + OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA, + "ValidationExpiryDate", 1, 1); return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/ExportedSymbols.list Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,7 @@ +# This is the list of the symbols that must be exported by Orthanc +# plugins, if targeting OS X + +_OrthancPluginInitialize +_OrthancPluginFinalize +_OrthancPluginGetName +_OrthancPluginGetVersion
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/OrthancPlugins.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,50 @@ +include(CheckIncludeFiles) +include(CheckLibraryExists) + + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + link_libraries(uuid) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread") + +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + link_libraries(rpcrt4 ws2_32 secur32) + if (CMAKE_COMPILER_IS_GNUCXX) + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++") + endif() + + CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD) + if (HAVE_WIN_PTHREAD) + # This line is necessary to compile with recent versions of MinGW, + # otherwise "libwinpthread-1.dll" is not statically linked. + SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic") + endif() +endif () + + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${SAMPLES_ROOT}/Common/VersionScript.map -Wl,--no-undefined") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_SOURCE_DIR}/Plugins/Samples/Common/ExportedSymbols.list") +endif() + + +if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") +endif() + + +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(dl rt) +endif() + +include_directories(${SAMPLES_ROOT}/../Include/) + +if (MSVC) + include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/) +endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/Common/VersionScript.map Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,12 @@ +# This is a version-script for Orthanc plugins + +{ +global: + OrthancPluginInitialize; + OrthancPluginFinalize; + OrthancPluginGetName; + OrthancPluginGetVersion; + +local: + *; +};
--- a/Plugins/Samples/GdcmDecoding/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -2,21 +2,20 @@ project(GdcmDecoding) -SET(ALLOW_DOWNLOADS ON CACHE BOOL "Allow CMake to download packages") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost") -SET(USE_SYSTEM_GOOGLE_LOG OFF CACHE BOOL "Use the system version of Google Log") +SET(USE_SYSTEM_GOOGLE_LOG ON CACHE BOOL "Use the system version of Google Log") set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..) +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") -endif() - +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) find_package(GDCM REQUIRED) if (GDCM_FOUND) @@ -26,11 +25,6 @@ message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?") endif(GDCM_FOUND) -include_directories( - ${ORTHANC_ROOT}/Plugins/OrthancCPlugin/ - ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw - ) - add_library(GdcmDecoding SHARED Plugin.cpp OrthancContext.cpp @@ -39,18 +33,14 @@ ${GOOGLE_LOG_SOURCES} ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp ${ORTHANC_ROOT}/Core/Enumerations.cpp - ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp - ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp - ${ORTHANC_ROOT}/Core/ImageFormats/ImageProcessing.cpp - ${ORTHANC_ROOT}/Core/OrthancException.cpp + ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp + ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp + ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp ${ORTHANC_ROOT}/Core/Toolbox.cpp ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c + ${JSONCPP_SOURCES} ${THIRD_PARTY_SOURCES} ) target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES}) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - target_link_libraries(GdcmDecoding pthread dl rt) -endif()
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/GdcmDecoding/OrthancContext.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/
--- a/Plugins/Samples/GdcmDecoding/OrthancContext.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/GdcmDecoding/OrthancContext.h Wed Sep 30 13:23:31 2015 +0200 @@ -3,33 +3,26 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * 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 <OrthancCPlugin.h> +#include <orthanc/OrthancCPlugin.h> -#include "../../../Core/ImageFormats/ImageBuffer.h" +#include "../../../Core/Images/ImageBuffer.h" #include <map> #include <string>
--- a/Plugins/Samples/GdcmDecoding/Plugin.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/GdcmDecoding/Plugin.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ @@ -32,7 +25,7 @@ #include <boost/lexical_cast.hpp> #include "OrthancContext.h" -#include "../../../Core/ImageFormats/ImageProcessing.h" +#include "../../../Core/Images/ImageProcessing.h" #include <gdcmReader.h> #include <gdcmImageReader.h> @@ -92,9 +85,9 @@ } -ORTHANC_PLUGINS_API int32_t DecodeImage(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +ORTHANC_PLUGINS_API OrthancPluginErrorCode DecodeImage(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { std::string instance(request->groups[0]); std::string outputFormat(request->groups[1]); @@ -114,7 +107,7 @@ { OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance"); AnswerUnsupportedImage(output); - return 0; + return OrthancPluginErrorCode_Success; } gdcm::Image& image = imageReader.GetImage(); @@ -145,7 +138,7 @@ { OrthancContext::GetInstance().LogError("This sample plugin does not support this image format"); AnswerUnsupportedImage(output); - return 0; + return OrthancPluginErrorCode_Success; } Orthanc::ImageAccessor decodedImage; @@ -197,7 +190,7 @@ { // Do not convert color images to grayscale values (this is Orthanc convention) AnswerUnsupportedImage(output); - return 0; + return OrthancPluginErrorCode_Success; } if (outputFormat == "image-uint8") @@ -216,7 +209,7 @@ { OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat); AnswerUnsupportedImage(output); - return 0; + return OrthancPluginErrorCode_Success; } } @@ -226,7 +219,7 @@ // Compress the converted image as a PNG file OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor); - return 0; // Success + return OrthancPluginErrorCode_Success; // Success }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ServeFolders/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 2.8) + +project(ServeFolders) + +SET(SERVE_FOLDERS_VERSION "0.0" CACHE STRING "Version of the plugin") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") +SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost") + +set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../) +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) + +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) + +add_library(ServeFolders SHARED + Plugin.cpp + ${JSONCPP_SOURCES} + ${BOOST_SOURCES} + ) + + +message("Setting the version of the plugin to ${SERVE_FOLDERS_VERSION}") +add_definitions(-DSERVE_FOLDERS_VERSION="${SERVE_FOLDERS_VERSION}") + +set_target_properties(ServeFolders PROPERTIES + VERSION ${SERVE_FOLDERS_VERSION} + SOVERSION ${SERVE_FOLDERS_VERSION}) + +install( + TARGETS ServeFolders + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ServeFolders/Plugin.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,385 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 <orthanc/OrthancCPlugin.h> + +#include <json/reader.h> +#include <json/value.h> +#include <boost/filesystem.hpp> + + +static OrthancPluginContext* context_ = NULL; +static std::map<std::string, std::string> folders_; +static const char* INDEX_URI = "/app/plugin-serve-folders.html"; + + +static const char* GetMimeType(const std::string& path) +{ + size_t dot = path.find_last_of('.'); + + std::string extension = (dot == std::string::npos) ? "" : path.substr(dot); + std::transform(extension.begin(), extension.end(), extension.begin(), tolower); + + if (extension == ".html") + { + return "text/html"; + } + else if (extension == ".css") + { + return "text/css"; + } + else if (extension == ".js") + { + return "application/javascript"; + } + else if (extension == ".gif") + { + return "image/gif"; + } + else if (extension == ".svg") + { + return "image/svg+xml"; + } + else if (extension == ".json") + { + return "application/json"; + } + else if (extension == ".xml") + { + return "application/xml"; + } + else if (extension == ".png") + { + return "image/png"; + } + else if (extension == ".jpg" || extension == ".jpeg") + { + return "image/jpeg"; + } + else if (extension == ".woff") + { + return "application/x-font-woff"; + } + else + { + std::string s = "Unknown MIME type for extension: " + extension; + OrthancPluginLogWarning(context_, s.c_str()); + return "application/octet-stream"; + } +} + + +static bool ReadFile(std::string& target, + const std::string& path) +{ + OrthancPluginMemoryBuffer buffer; + if (OrthancPluginReadFile(context_, &buffer, path.c_str())) + { + return false; + } + else + { + target.assign(reinterpret_cast<const char*>(buffer.data), buffer.size); + OrthancPluginFreeMemoryBuffer(context_, &buffer); + return true; + } +} + + +static bool ReadConfiguration(Json::Value& configuration, + OrthancPluginContext* context) +{ + std::string s; + + { + char* tmp = OrthancPluginGetConfiguration(context); + if (tmp == NULL) + { + OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc"); + return false; + } + + s.assign(tmp); + OrthancPluginFreeString(context, tmp); + } + + Json::Reader reader; + if (reader.parse(s, configuration)) + { + return true; + } + else + { + OrthancPluginLogError(context, "Unable to parse the configuration"); + return false; + } +} + + + +static bool LookupFolder(std::string& folder, + OrthancPluginRestOutput* output, + const OrthancPluginHttpRequest* request) +{ + const std::string uri = request->groups[0]; + + std::map<std::string, std::string>::const_iterator found = folders_.find(uri); + if (found == folders_.end()) + { + std::string s = "Unknown URI in plugin server-folders: " + uri; + OrthancPluginLogError(context_, s.c_str()); + OrthancPluginSendHttpStatusCode(context_, output, 404); + return false; + } + else + { + folder = found->second; + return true; + } +} + + +static OrthancPluginErrorCode FolderCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + namespace fs = boost::filesystem; + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return OrthancPluginErrorCode_Success; + } + + std::string folder; + + if (LookupFolder(folder, output, request)) + { + const fs::path item(request->groups[1]); + const fs::path parent((fs::path(folder) / item).parent_path()); + + if (item.filename().string() == "index.html" && + fs::is_directory(parent) && + !fs::is_regular_file(fs::path(folder) / item)) + { + // On-the-fly generation of an "index.html" + std::string s; + s += "<html>\n"; + s += " <body>\n"; + s += " <ul>\n"; + + fs::directory_iterator end; + + for (fs::directory_iterator it(parent) ; it != end; ++it) + { + if (fs::is_directory(it->status())) + { + std::string f = it->path().filename().string(); + s += " <li><a href=\"" + f + "/index.html\">" + f + "/</a></li>\n"; + } + } + + for (fs::directory_iterator it(parent) ; it != end; ++it) + { + if (fs::is_regular_file(it->status())) + { + std::string f = it->path().filename().string(); + s += " <li><a href=\"" + f + "\">" + f + "</a></li>\n"; + } + } + + s += " </ul>\n"; + s += " </body>\n"; + s += "</html>\n"; + + OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "text/html"); + } + else + { + std::string path = folder + "/" + item.string(); + const char* mime = GetMimeType(path); + + std::string s; + if (ReadFile(s, path)) + { + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime); + } + else + { + std::string s = "Inexistent file in served folder: " + path; + OrthancPluginLogError(context_, s.c_str()); + OrthancPluginSendHttpStatusCode(context_, output, 404); + } + } + } + + return OrthancPluginErrorCode_Success; +} + + +static OrthancPluginErrorCode ListServedFolders(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context_, output, "GET"); + return OrthancPluginErrorCode_Success; + } + + std::string s = "<html><body><h1>Additional folders served by Orthanc</h1>\n"; + + if (folders_.empty()) + { + s += "<p>Empty section <tt>ServeFolders</tt> in your configuration file: No additional folder is served.</p>\n"; + } + else + { + s += "<ul>\n"; + for (std::map<std::string, std::string>::const_iterator + it = folders_.begin(); it != folders_.end(); ++it) + { + // The URI is relative to INDEX_URI ("/app/plugin-serve-folders.html") + s += "<li><a href=\"../" + it->first + "/index.html\">" + it->first + "</li>\n"; + } + + s += "</ul>\n"; + } + + s += "</body></html>\n"; + + OrthancPluginAnswerBuffer(context_, output, s.c_str(), s.size(), "text/html"); + + return OrthancPluginErrorCode_Success; +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + context_ = context; + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context_) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context_->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context_, info); + return -1; + } + + OrthancPluginSetDescription(context_, "Serve additional folders with the HTTP server of Orthanc."); + + Json::Value configuration; + if (!ReadConfiguration(configuration, context_)) + { + return -1; + } + + if (configuration.isMember("ServeFolders")) + { + if (configuration["ServeFolders"].type() != Json::objectValue) + { + OrthancPluginLogError(context_, "The \"ServeFolders\" configuration section is badly formatted (must be a JSON object)"); + return -1; + } + } + else + { + OrthancPluginLogWarning(context_, "No section \"ServeFolders\" in your configuration file: " + "No additional folder will be served!"); + configuration["ServeFolders"] = Json::objectValue; + } + + + Json::Value::Members members = configuration["ServeFolders"].getMemberNames(); + + // Register the callback for each base URI + for (Json::Value::Members::const_iterator + it = members.begin(); it != members.end(); ++it) + { + std::string baseUri = *it; + + // Remove the heading and trailing slashes in the root URI, if any + while (!baseUri.empty() && + *baseUri.begin() == '/') + { + baseUri = baseUri.substr(1); + } + + while (!baseUri.empty() && + *baseUri.rbegin() == '/') + { + baseUri.resize(baseUri.size() - 1); + } + + if (baseUri.empty()) + { + OrthancPluginLogError(context_, "The URI of a folder to be served cannot be empty"); + return -1; + } + + // Check whether the source folder exists and is indeed a directory + const std::string folder = configuration["ServeFolders"][*it].asString(); + if (!boost::filesystem::is_directory(folder)) + { + std::string msg = "Trying and serve an inexistent folder: " + folder; + OrthancPluginLogError(context_, msg.c_str()); + return -1; + } + + folders_[baseUri] = folder; + + // Register the callback to serve the folder + { + const std::string regex = "/(" + baseUri + ")/(.*)"; + OrthancPluginRegisterRestCallback(context, regex.c_str(), FolderCallback); + } + } + + OrthancPluginRegisterRestCallback(context, INDEX_URI, ListServedFolders); + OrthancPluginSetRootUri(context, INDEX_URI); + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "serve-folders"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return SERVE_FOLDERS_VERSION; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ServeFolders/README Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,52 @@ +Introduction +============ + +This sample plugin enables Orthanc to serve additional folders using +its embedded Web server. + + +Compilation for Linux +===================== + +# mkdir Build +# cd Build +# cmake .. +# make + + +Cross-compilation for Windows using MinGW +========================================= + +# mkdir Build +# cd Build +# cmake .. -DCMAKE_TOOLCHAIN_FILE=../../../Resources/MinGWToolchain.cmake +# make + + +Configuration +============= + +First, generate the default configuration of Orthanc: +https://code.google.com/p/orthanc/wiki/OrthancConfiguration + +Then, modify the "Plugins" option to point to the folder containing +the built plugins. + +Finally, create a section "ServeFolders" in the configuration file to +specify which folder you want to serve, and at which URI. For +instance, the following excerpt would load the plugins from the +working directory, then would branch the content of the folder +"/home/jodogne/WWW/fosdem" as the URI "http://localhost:8042/fosdem": + +{ + "Name" : "MyOrthanc", + [...] + "HttpPort" : 8042, + [...] + "Plugins" : [ + "." + ], + "ServeFolders" : { + "/fosdem" : "/home/jodogne/WWW/fosdem" + } +}
--- a/Plugins/Samples/StorageArea/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/StorageArea/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -2,16 +2,7 @@ project(Basic) -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Werror") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror") -endif() +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) -include_directories(${CMAKE_SOURCE_DIR}/../../OrthancCPlugin/) add_library(PluginTest SHARED Plugin.cpp) - -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 - target_link_libraries(PluginTest pthread dl) -endif()
--- a/Plugins/Samples/StorageArea/Plugin.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/StorageArea/Plugin.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -3,29 +3,22 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * 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 <OrthancCPlugin.h> +#include <orthanc/OrthancCPlugin.h> #include <string.h> #include <stdio.h> @@ -40,43 +33,43 @@ } -static int32_t StorageCreate(const char* uuid, - const void* content, - int64_t size, - OrthancPluginContentType type) +static OrthancPluginErrorCode StorageCreate(const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type) { std::string path = GetPath(uuid); FILE* fp = fopen(path.c_str(), "wb"); if (!fp) { - return -1; + return OrthancPluginErrorCode_StorageAreaPlugin; } bool ok = fwrite(content, size, 1, fp) == 1; fclose(fp); - return ok ? 0 : -1; + return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_StorageAreaPlugin; } -static int32_t StorageRead(void** content, - int64_t* size, - const char* uuid, - OrthancPluginContentType type) +static OrthancPluginErrorCode StorageRead(void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type) { std::string path = GetPath(uuid); FILE* fp = fopen(path.c_str(), "rb"); if (!fp) { - return -1; + return OrthancPluginErrorCode_StorageAreaPlugin; } if (fseek(fp, 0, SEEK_END) < 0) { fclose(fp); - return -1; + return OrthancPluginErrorCode_StorageAreaPlugin; } *size = ftell(fp); @@ -84,7 +77,7 @@ if (fseek(fp, 0, SEEK_SET) < 0) { fclose(fp); - return -1; + return OrthancPluginErrorCode_StorageAreaPlugin; } bool ok = true; @@ -105,15 +98,23 @@ fclose(fp); - return ok ? 0 : -1; + return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_StorageAreaPlugin; } -static int32_t StorageRemove(const char* uuid, - OrthancPluginContentType type) +static OrthancPluginErrorCode StorageRemove(const char* uuid, + OrthancPluginContentType type) { std::string path = GetPath(uuid); - return remove(path.c_str()); + + if (remove(path.c_str()) == 0) + { + return OrthancPluginErrorCode_Success; + } + else + { + return OrthancPluginErrorCode_StorageAreaPlugin; + } }
--- a/Plugins/Samples/WebSkeleton/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -7,7 +7,8 @@ include(Framework/Framework.cmake) -include_directories(${CMAKE_SOURCE_DIR}/../../OrthancCPlugin/) +set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) add_library(WebSkeleton SHARED ${AUTOGENERATED_SOURCES}
--- a/Plugins/Samples/WebSkeleton/Configuration.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Configuration.h Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/
--- a/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py Wed Sep 30 13:23:31 2015 +0200 @@ -7,18 +7,6 @@ # 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 @@ -222,6 +210,10 @@ cpp.write("0x%02x" % c) pos += 1 + # Zero-size array are disallowed, so we put one single void character in it. + if pos == 0: + cpp.write(' 0') + cpp.write(' };\n') cpp.write(' static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
--- a/Plugins/Samples/WebSkeleton/Framework/Framework.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Framework.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -6,18 +6,6 @@ # 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 @@ -28,16 +16,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -if (${CMAKE_COMPILER_IS_GNUCXX}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror -Wno-unused-function") -endif() - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - # Linking with "pthread" is necessary, otherwise the software might crash - # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 - link_libraries(pthread dl) -endif() - if (STANDALONE_BUILD) add_definitions(-DORTHANC_PLUGIN_STANDALONE=1) @@ -73,4 +51,4 @@ list(APPEND AUTOGENERATED_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Framework/Plugin.cpp" - ) \ No newline at end of file + )
--- a/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -3,31 +3,24 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * 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 "../Configuration.h" -#include <OrthancCPlugin.h> +#include <orthanc/OrthancCPlugin.h> #include <string> #include <stdexcept> #include <algorithm> @@ -138,14 +131,14 @@ #if ORTHANC_PLUGIN_STANDALONE == 1 -static int32_t ServeStaticResource(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +static OrthancPluginErrorCode ServeStaticResource(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); - return 0; + return OrthancPluginErrorCode_Success; } std::string path = "/" + std::string(request->groups[0]); @@ -159,29 +152,28 @@ const char* resource = s.size() ? s.c_str() : NULL; OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - - return 0; } catch (std::runtime_error&) { std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]); OrthancPluginLogError(context, s.c_str()); OrthancPluginSendHttpStatusCode(context, output, 404); - return 0; } + + return OrthancPluginErrorCode_Success; } #endif #if ORTHANC_PLUGIN_STANDALONE == 0 -static int32_t ServeFolder(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +static OrthancPluginErrorCode ServeFolder(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { OrthancPluginSendMethodNotAllowed(context, output, "GET"); - return 0; + return OrthancPluginErrorCode_Success; } std::string path = ORTHANC_PLUGIN_RESOURCES_ROOT "/" + std::string(request->groups[0]); @@ -192,23 +184,22 @@ { const char* resource = s.size() ? s.c_str() : NULL; OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - - return 0; } else { std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]); OrthancPluginLogError(context, s.c_str()); OrthancPluginSendHttpStatusCode(context, output, 404); - return 0; } + + return OrthancPluginErrorCode_Success; } #endif -static int32_t RedirectRoot(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +static OrthancPluginErrorCode RedirectRoot(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { @@ -219,7 +210,7 @@ OrthancPluginRedirect(context, output, ORTHANC_PLUGIN_WEB_ROOT "index.html"); } - return 0; + return OrthancPluginErrorCode_Success; }
--- a/README Wed Feb 11 10:40:08 2015 +0100 +++ b/README Wed Sep 30 13:23:31 2015 +0200 @@ -75,7 +75,6 @@ This archive contains the following directories: * Core/ - The core C++ classes (independent of DCMTK) -* OrthancCppClient/ - Code of the C++ client * OrthancExplorer/ - Code of the Web application (HTML5/Javascript) * OrthancServer/ - Code of the Orthanc server (depends on DCMTK) * Plugins/ - Code of the plugin framework
--- a/Resources/CMake/AutoGeneratedCode.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/AutoGeneratedCode.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -6,17 +6,28 @@ macro(EmbedResources) # Convert a semicolon separated list to a whitespace separated string + set(SCRIPT_OPTIONS) set(SCRIPT_ARGUMENTS) set(DEPENDENCIES) set(IS_PATH_NAME false) + + # Loop over the arguments of the function foreach(arg ${ARGN}) - if (${IS_PATH_NAME}) - list(APPEND SCRIPT_ARGUMENTS "${arg}") - list(APPEND DEPENDENCIES "${arg}") - set(IS_PATH_NAME false) + # Extract the first character of the argument + string(SUBSTRING "${arg}" 0 1 FIRST_CHAR) + if (${FIRST_CHAR} STREQUAL "-") + # If the argument starts with a dash "-", this is an option to + # EmbedResources.py + list(APPEND SCRIPT_OPTIONS ${arg}) else() - list(APPEND SCRIPT_ARGUMENTS "${arg}") - set(IS_PATH_NAME true) + if (${IS_PATH_NAME}) + list(APPEND SCRIPT_ARGUMENTS "${arg}") + list(APPEND DEPENDENCIES "${arg}") + set(IS_PATH_NAME false) + else() + list(APPEND SCRIPT_ARGUMENTS "${arg}") + set(IS_PATH_NAME true) + endif() endif() endforeach() @@ -26,12 +37,13 @@ "${TARGET_BASE}.h" "${TARGET_BASE}.cpp" COMMAND - python - "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py" + ${PYTHON_EXECUTABLE} + "${ORTHANC_ROOT}/Resources/EmbedResources.py" + ${SCRIPT_OPTIONS} "${AUTOGENERATED_DIR}/EmbeddedResources" ${SCRIPT_ARGUMENTS} DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/Resources/EmbedResources.py" + "${ORTHANC_ROOT}/Resources/EmbedResources.py" ${DEPENDENCIES} )
--- a/Resources/CMake/BoostConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/BoostConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -39,23 +39,21 @@ 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") - + # Parameters for Boost 1.58.0 + set(BOOST_NAME boost_1_58_0) + set(BOOST_BCP_SUFFIX bcpdigest-0.9.2) + set(BOOST_MD5 "704b110917cbda903e07cb53934b47ac") + set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz") + 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}" - ) + + DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}") set(BOOST_SOURCES) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp @@ -82,8 +80,11 @@ # Windows XP seems not to support properly several codepages # (notably "Latin3", "Hebrew", and "Arabic"). - # add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) - include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake) + if (USE_BOOST_ICONV) + include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake) + else() + add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) + endif() else() message(FATAL_ERROR "Support your platform here") @@ -95,16 +96,6 @@ ) endif() - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # This is a patch to compile Boost 1.55.0 with Clang 3.4 and later - # (including XCode 5.1). Fixes issue 14 of Orthanc. - # https://trac.macports.org/ticket/42282#comment:10 - execute_process( - COMMAND patch -p0 -i ${ORTHANC_ROOT}/Resources/Patches/boost-1.55.0-clang-atomic.patch - WORKING_DIRECTORY ${BOOST_SOURCES_DIR} - ) - endif() - aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) list(APPEND BOOST_SOURCES @@ -118,8 +109,6 @@ ${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 @@ -134,7 +123,7 @@ -DBOOST_HAS_FILESYSTEM_V3=1 ) - if (${CMAKE_COMPILER_IS_GNUCXX}) + if (CMAKE_COMPILER_IS_GNUCXX) add_definitions(-isystem ${BOOST_SOURCES_DIR}) endif() @@ -148,3 +137,9 @@ -DBOOST_HAS_LOCALE=1 ) endif() + + +add_definitions( + -DBOOST_HAS_DATE_TIME=1 + -DBOOST_HAS_REGEX=1 + )
--- a/Resources/CMake/BoostConfiguration.sh Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/BoostConfiguration.sh Wed Sep 30 13:23:31 2015 +0200 @@ -12,22 +12,23 @@ ## ## History: ## - Orthanc between 0.6.2 and 0.7.3: Boost 1.54.0 -## - Orthanc above 0.7.4: Boost 1.55.0 +## - Orthanc between 0.7.4 and 0.9.1: Boost 1.55.0 +## - Orthanc >= 0.9.2: Boost 1.58.0 -rm -rf /tmp/boost_1_55_0 -rm -rf /tmp/bcp/boost_1_55_0 +rm -rf /tmp/boost_1_58_0 +rm -rf /tmp/bcp/boost_1_58_0 cd /tmp -echo "Uncompressing the source of Boost 1.55.0..." -tar xfz boost_1_55_0.tar.gz +echo "Uncompressing the sources of Boost 1.58.0..." +tar xfz ./boost_1_58_0.tar.gz echo "Generating the subset..." -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 +mkdir -p /tmp/bcp/boost_1_58_0 +bcp --boost=/tmp/boost_1_58_0 thread system locale date_time filesystem math/special_functions algorithm uuid atomic /tmp/bcp/boost_1_58_0 cd /tmp/bcp echo "Compressing the subset..." -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 +tar cfz boost_1_58_0_bcpdigest-0.9.2.tar.gz boost_1_58_0 +ls -l boost_1_58_0_bcpdigest-0.9.2.tar.gz +md5sum boost_1_58_0_bcpdigest-0.9.2.tar.gz +readlink -f boost_1_58_0_bcpdigest-0.9.2.tar.gz
--- a/Resources/CMake/Compiler.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/Compiler.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,6 +1,12 @@ # This file sets all the compiler-related flags -if (${CMAKE_COMPILER_IS_GNUCXX}) +if (CMAKE_CROSSCOMPILING) + # Cross-compilation necessarily implies standalone and static build + SET(STATIC_BUILD ON) + SET(STANDALONE_BUILD ON) +endif() + +if (CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration") # --std=c99 makes libcurl not to compile # -pedantic gives a lot of warnings on OpenSSL @@ -11,7 +17,7 @@ set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>") endif() -elseif (${MSVC}) +elseif (MSVC) # Use static runtime under Visual Studio # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace # http://stackoverflow.com/a/6510446 @@ -41,39 +47,69 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") - if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}") - endif() - - add_definitions( - -D_LARGEFILE64_SOURCE=1 - -D_FILE_OFFSET_BITS=64 - ) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map") # Remove the "-rdynamic" option # http://www.mail-archive.com/cmake@cmake.org/msg08837.html set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") - link_libraries(uuid pthread rt dl) + link_libraries(uuid pthread rt) + + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed") + add_definitions( + -D_LARGEFILE64_SOURCE=1 + -D_FILE_OFFSET_BITS=64 + ) + link_libraries(dl) + endif() elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if (MSVC) + message("MSVC compiler version = " ${MSVC_VERSION} "\n") + # Starting Visual Studio 2013 (version 1800), it is not possible + # to target Windows XP anymore + if (MSVC_VERSION LESS 1800) + add_definitions( + -DWINVER=0x0501 + -D_WIN32_WINNT=0x0501 + ) + endif() + else() + add_definitions( + -DWINVER=0x0501 + -D_WIN32_WINNT=0x0501 + ) + endif() + add_definitions( - -DWINVER=0x0501 -D_CRT_SECURE_NO_WARNINGS=1 ) link_libraries(rpcrt4 ws2_32) - if (${CMAKE_COMPILER_IS_GNUCXX}) + if (CMAKE_COMPILER_IS_GNUCXX) # This is a patch for MinGW64 SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++") + + CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD) + if (HAVE_WIN_PTHREAD) + # This line is necessary to compile with recent versions of MinGW, + # otherwise "libwinpthread-1.dll" is not statically linked. + SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic") + add_definitions(-DHAVE_WIN_PTHREAD=1) + else() + add_definitions(-DHAVE_WIN_PTHREAD=0) + endif() endif() elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list") + add_definitions( -D_XOPEN_SOURCE=1 ) @@ -82,6 +118,22 @@ endif() +if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}") +endif() + + +if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include") + SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib") +endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") CHECK_INCLUDE_FILES(rpc.h HAVE_UUID_H) else() @@ -92,7 +144,8 @@ message(FATAL_ERROR "Please install the uuid-dev package") endif() -if (${STATIC_BUILD}) + +if (STATIC_BUILD) add_definitions(-DORTHANC_STATIC=1) else() add_definitions(-DORTHANC_STATIC=0)
--- a/Resources/CMake/DcmtkConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -3,6 +3,7 @@ find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic /usr/share/dcmtk /usr/share/libdcmtk2 + /usr/local/share/dcmtk ) message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}") @@ -14,16 +15,23 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK) SET(DCMTK_VERSION_NUMBER 360) + set(DCMTK_PACKAGE_VERSION "3.6.0") 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}") + SET(DCMTK_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/dcmtk-3.6.0.zip") + SET(DCMTK_MD5 "219ad631b82031806147e4abbfba4fa4") - IF(CMAKE_CROSSCOMPILING) + if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + + DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${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) + SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR}) include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake) include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake) @@ -40,9 +48,8 @@ set(HAVE_PROTOTYPE_GETSOCKNAME 1) endif() - set(DCMTK_PACKAGE_VERSION "3.6.0") set(DCMTK_PACKAGE_VERSION_SUFFIX "") - set(DCMTK_PACKAGE_VERSION_NUMBER 360) + set(DCMTK_PACKAGE_VERSION_NUMBER ${DCMTK_VERSION_NUMBER}) CONFIGURE_FILE( ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in @@ -92,24 +99,52 @@ AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc ) + + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${ORTHANC_ROOT}/Resources/Patches/dcmtk-linux-speed.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") list(REMOVE_ITEM DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc ) - if (${CMAKE_COMPILER_IS_GNUCXX}) + if (CMAKE_COMPILER_IS_GNUCXX) # This is a patch for MinGW64 execute_process( - COMMAND patch -p0 -i ${ORTHANC_ROOT}/Resources/Patches/dcmtk-mingw64.patch + COMMAND ${PATCH_EXECUTABLE} -p0 -N -i ${ORTHANC_ROOT}/Resources/Patches/dcmtk-mingw64.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() endif() + # This patch improves speed, even for Windows + execute_process( + COMMAND ${PATCH_EXECUTABLE} -p0 -N + INPUT_FILE ${ORTHANC_ROOT}/Resources/Patches/dcmtk-linux-speed.patch + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() endif() list(REMOVE_ITEM DCMTK_SOURCES @@ -120,13 +155,13 @@ #set_source_files_properties(${DCMTK_SOURCES} # PROPERTIES COMPILE_DEFINITIONS - # "PACKAGE_VERSION=\"3.6.0\";PACKAGE_VERSION_NUMBER=\"360\"") + # "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"") # 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 + -DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER} ) include_directories( @@ -162,7 +197,6 @@ list(APPEND DCMTK_LIBRARIES "${tmp}") include_directories(${DCMTK_INCLUDE_DIR}) - link_libraries(${DCMTK_LIBRARIES}) add_definitions( -DHAVE_CONFIG_H=1
--- a/Resources/CMake/DownloadPackage.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/DownloadPackage.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -10,6 +10,22 @@ endmacro() + +## +## Setup the patch command-line tool +## + +if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + set(PATCH_EXECUTABLE ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/patch/patch.exe) +else () + find_program(PATCH_EXECUTABLE patch) + if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'patch' standard command-line tool") + endif() +endif() + + + ## ## Check the existence of the required decompression tools ##
--- a/Resources/CMake/GoogleLogConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/GoogleLogConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,10 +1,15 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_LOG) SET(GOOGLE_LOG_SOURCES_DIR ${CMAKE_BINARY_DIR}/glog-0.3.2) - DownloadPackage( - "897fbff90d91ea2b6d6e78c8cea641cc" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz" - "${GOOGLE_LOG_SOURCES_DIR}") + SET(GOOGLE_LOG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/glog-0.3.2.tar.gz") + SET(GOOGLE_LOG_MD5 "897fbff90d91ea2b6d6e78c8cea641cc") + if (IS_DIRECTORY "${GOOGLE_LOG_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + + DownloadPackage(${GOOGLE_LOG_MD5} ${GOOGLE_LOG_URL} "${GOOGLE_LOG_SOURCES_DIR}") # Glog 0.3.3 fails to build with old versions of MinGW, such as the # one installed on our Continuous Integration Server that runs @@ -30,6 +35,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") set(ac_cv_have_unistd_h 1) set(ac_cv_have_stdint_h 1) @@ -65,26 +71,45 @@ if (CMAKE_COMPILER_IS_GNUCXX) if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") execute_process( - COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff + COMMAND ${PATCH_EXECUTABLE} -N utilities.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src + RESULT_VARIABLE Failure ) else() execute_process( - COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff + COMMAND ${PATCH_EXECUTABLE} -N utilities.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src + RESULT_VARIABLE Failure ) endif() + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + + # Patches for MinGW execute_process( - COMMAND patch port.h ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff + #COMMAND ${PATCH_EXECUTABLE} -N port.h -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff + COMMAND ${PATCH_EXECUTABLE} -N port.h -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-h-v2.diff WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows - ) - execute_process( - COMMAND patch port.cc ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff - WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows + RESULT_VARIABLE Failure ) - else(${MSVC}) + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + + execute_process( + COMMAND ${PATCH_EXECUTABLE} -N port.cc -i ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff + WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows + RESULT_VARIABLE Failure + ) + + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + + elseif (MSVC) # https://code.google.com/p/google-glog/issues/detail?id=117 configure_file( ${ORTHANC_ROOT}/Resources/Patches/glog-visual-studio-port.h @@ -96,6 +121,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") # Install the specific configuration for LSB SDK @@ -145,15 +171,12 @@ -DGOOGLE_GLOG_DLL_DECL= ) - if (${CMAKE_COMPILER_IS_GNUCXX}) + if (CMAKE_COMPILER_IS_GNUCXX) # This is a patch for MinGW64 add_definitions(-D_TIME_H__S=1) endif() endif() - add_library(GoogleLog STATIC ${GOOGLE_LOG_SOURCES}) - set(STATIC_GOOGLE_LOG GoogleLog) - else() CHECK_INCLUDE_FILE_CXX(glog/logging.h HAVE_GOOGLE_LOG_H) if (NOT HAVE_GOOGLE_LOG_H) @@ -162,3 +185,13 @@ link_libraries(glog) endif() + + +CHECK_INCLUDE_FILES(sec_api/string_s.h HAVE_SECURE_STRING_EXTENSIONS) +if (HAVE_SECURE_STRING_EXTENSIONS) + add_definitions(-DHAVE_SECURE_STRING_EXTENSIONS=1) +else() + add_definitions(-DHAVE_SECURE_STRING_EXTENSIONS=0) +endif() + +add_definitions(-DORTHANC_ENABLE_GOOGLE_LOG=1)
--- a/Resources/CMake/GoogleTestConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/GoogleTestConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -9,10 +9,10 @@ elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST) set(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0) - DownloadPackage( - "2d6ec8ccdf5c46b05ba54a9fd1d130d7" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip" - "${GTEST_SOURCES_DIR}") + set(GTEST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip") + set(GTEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7") + + DownloadPackage(${GTEST_MD5} ${GTEST_URL} "${GTEST_SOURCES_DIR}") include_directories( ${GTEST_SOURCES_DIR}/include
--- a/Resources/CMake/JsonCppConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/JsonCppConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,11 +1,11 @@ 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}") + set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5) + set(JSONCPP_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-0.10.5.tar.gz") + set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b") - list(APPEND THIRD_PARTY_SOURCES + DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}") + + set(JSONCPP_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 @@ -18,12 +18,18 @@ source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*) else() - CHECK_INCLUDE_FILE_CXX(jsoncpp/json/reader.h HAVE_JSONCPP_H) + find_path(JSONCPP_INCLUDE_DIR json/reader.h + /usr/include/jsoncpp + /usr/local/include/jsoncpp + ) + + message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}") + include_directories(${JSONCPP_INCLUDE_DIR}) + link_libraries(jsoncpp) + + CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/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 Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/LibCurlConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,24 +1,23 @@ 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}") + SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.44.0) + SET(CURL_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/curl-7.44.0.tar.gz") + SET(CURL_MD5 "cf46112b5151e2f1a3fd38439bdade23") + + DownloadPackage(${CURL_MD5} ${CURL_URL} "${CURL_SOURCES_DIR}") - include_directories(${CURL_SOURCES_DIR}/include) + include_directories( + ${CURL_SOURCES_DIR}/include + ) + AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES) + AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES) source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*) - #add_library(Curl STATIC ${CURL_SOURCES}) - #link_libraries(Curl) - add_definitions( + -DBUILDING_LIBCURL=1 -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 @@ -32,7 +31,7 @@ -DCURL_DISABLE_TFTP=1 ) - if (${ENABLE_SSL}) + if (ENABLE_SSL) add_definitions( #-DHAVE_LIBSSL=1 -DUSE_OPENSSL=1 @@ -40,8 +39,17 @@ ) endif() + file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "") + + file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h) + foreach (header IN LISTS CURL_LIBS_HEADERS) + get_filename_component(filename ${header} NAME) + file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n") + endforeach() + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") SET(TMP_OS "x86_64") @@ -51,7 +59,8 @@ 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}\"") + 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;HAVE_LONGLONG;OS=\"${TMP_OS}\"" + ) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") add_definitions(
--- a/Resources/CMake/LibIconvConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/LibIconvConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,8 +1,8 @@ set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.14) -DownloadPackage( - "e34509b1623cec449dfeb73d7ce9c6c6" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libiconv-1.14.tar.gz" - "${LIBICONV_SOURCES_DIR}") +set(LIBICONV_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libiconv-1.14.tar.gz") +set(LIBICONV_MD5 "e34509b1623cec449dfeb73d7ce9c6c6") + +DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}") # https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ add_definitions( @@ -35,6 +35,9 @@ unset(EILSEQ) unset(HAVE_WCHAR_T) +# Create an empty "config.h" for libiconv +file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "") + include_directories( ${LIBICONV_SOURCES_DIR}/include )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibJpegConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,93 @@ +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG) + set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a) + DownloadPackage( + "3353992aecaee1805ef4109aadd433e7" + "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jpegsrc.v9a.tar.gz" + "${LIBJPEG_SOURCES_DIR}") + + include_directories( + ${LIBJPEG_SOURCES_DIR} + ) + + list(APPEND LIBJPEG_SOURCES + ${LIBJPEG_SOURCES_DIR}/jaricom.c + ${LIBJPEG_SOURCES_DIR}/jcapimin.c + ${LIBJPEG_SOURCES_DIR}/jcapistd.c + ${LIBJPEG_SOURCES_DIR}/jcarith.c + ${LIBJPEG_SOURCES_DIR}/jccoefct.c + ${LIBJPEG_SOURCES_DIR}/jccolor.c + ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c + ${LIBJPEG_SOURCES_DIR}/jchuff.c + ${LIBJPEG_SOURCES_DIR}/jcinit.c + ${LIBJPEG_SOURCES_DIR}/jcmarker.c + ${LIBJPEG_SOURCES_DIR}/jcmaster.c + ${LIBJPEG_SOURCES_DIR}/jcomapi.c + ${LIBJPEG_SOURCES_DIR}/jcparam.c + ${LIBJPEG_SOURCES_DIR}/jcprepct.c + ${LIBJPEG_SOURCES_DIR}/jcsample.c + ${LIBJPEG_SOURCES_DIR}/jctrans.c + ${LIBJPEG_SOURCES_DIR}/jdapimin.c + ${LIBJPEG_SOURCES_DIR}/jdapistd.c + ${LIBJPEG_SOURCES_DIR}/jdarith.c + ${LIBJPEG_SOURCES_DIR}/jdatadst.c + ${LIBJPEG_SOURCES_DIR}/jdatasrc.c + ${LIBJPEG_SOURCES_DIR}/jdcoefct.c + ${LIBJPEG_SOURCES_DIR}/jdcolor.c + ${LIBJPEG_SOURCES_DIR}/jddctmgr.c + ${LIBJPEG_SOURCES_DIR}/jdhuff.c + ${LIBJPEG_SOURCES_DIR}/jdinput.c + ${LIBJPEG_SOURCES_DIR}/jcmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmainct.c + ${LIBJPEG_SOURCES_DIR}/jdmarker.c + ${LIBJPEG_SOURCES_DIR}/jdmaster.c + ${LIBJPEG_SOURCES_DIR}/jdmerge.c + ${LIBJPEG_SOURCES_DIR}/jdpostct.c + ${LIBJPEG_SOURCES_DIR}/jdsample.c + ${LIBJPEG_SOURCES_DIR}/jdtrans.c + ${LIBJPEG_SOURCES_DIR}/jerror.c + ${LIBJPEG_SOURCES_DIR}/jfdctflt.c + ${LIBJPEG_SOURCES_DIR}/jfdctfst.c + ${LIBJPEG_SOURCES_DIR}/jfdctint.c + ${LIBJPEG_SOURCES_DIR}/jidctflt.c + ${LIBJPEG_SOURCES_DIR}/jidctfst.c + ${LIBJPEG_SOURCES_DIR}/jidctint.c + #${LIBJPEG_SOURCES_DIR}/jmemansi.c + #${LIBJPEG_SOURCES_DIR}/jmemdos.c + #${LIBJPEG_SOURCES_DIR}/jmemmac.c + ${LIBJPEG_SOURCES_DIR}/jmemmgr.c + #${LIBJPEG_SOURCES_DIR}/jmemname.c + ${LIBJPEG_SOURCES_DIR}/jmemnobs.c + ${LIBJPEG_SOURCES_DIR}/jquant1.c + ${LIBJPEG_SOURCES_DIR}/jquant2.c + ${LIBJPEG_SOURCES_DIR}/jutils.c + + # ${LIBJPEG_SOURCES_DIR}/rdbmp.c + # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c + # ${LIBJPEG_SOURCES_DIR}/rdgif.c + # ${LIBJPEG_SOURCES_DIR}/rdppm.c + # ${LIBJPEG_SOURCES_DIR}/rdrle.c + # ${LIBJPEG_SOURCES_DIR}/rdswitch.c + # ${LIBJPEG_SOURCES_DIR}/rdtarga.c + # ${LIBJPEG_SOURCES_DIR}/transupp.c + # ${LIBJPEG_SOURCES_DIR}/wrbmp.c + # ${LIBJPEG_SOURCES_DIR}/wrgif.c + # ${LIBJPEG_SOURCES_DIR}/wrppm.c + # ${LIBJPEG_SOURCES_DIR}/wrrle.c + # ${LIBJPEG_SOURCES_DIR}/wrtarga.c + ) + + configure_file( + ${LIBJPEG_SOURCES_DIR}/jconfig.txt + ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY + ) + +else() + include(FindJPEG) + + if (NOT ${JPEG_FOUND}) + message(FATAL_ERROR "Unable to find libjpeg") + endif() + + include_directories(${JPEG_INCLUDE_DIR}) + link_libraries(${JPEG_LIBRARIES}) +endif()
--- a/Resources/CMake/LibPngConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/LibPngConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,9 +1,9 @@ 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}") + SET(LIBPNG_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/libpng-1.5.12.tar.gz") + SET(LIBPNG_MD5 "8ea7f60347a306c5faf70b977fa80e28") + + DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}") include_directories( ${LIBPNG_SOURCES_DIR} @@ -12,7 +12,7 @@ configure_file( ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt ${LIBPNG_SOURCES_DIR}/pnglibconf.h - COPY_ONLY) + ) set(LIBPNG_SOURCES #${LIBPNG_SOURCES_DIR}/example.c @@ -38,11 +38,12 @@ # 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 + # The following declaration avoids "__declspec(dllexport)" in + # libpng to prevent publicly exposing its symbols by the DLLs + -DPNG_IMPEXP= ) source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) @@ -51,7 +52,7 @@ include(FindPNG) if (NOT ${PNG_FOUND}) - message(FATAL_ERROR "Unable to find LibPNG") + message(FATAL_ERROR "Unable to find libpng") endif() include_directories(${PNG_INCLUDE_DIRS})
--- a/Resources/CMake/LuaConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/LuaConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,9 +1,9 @@ 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}") + SET(LUA_MD5 "2e115fe26e435e33b0d5c022e4490567") + SET(LUA_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz") + + DownloadPackage(${LUA_MD5} ${LUA_URL} "${LUA_SOURCES_DIR}") add_definitions( #-DLUA_LIB=1 @@ -50,9 +50,6 @@ ${LUA_SOURCES_DIR}/src/linit.c ) - add_library(Lua STATIC ${LUA_SOURCES}) - set(STATIC_LUA Lua) - source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*) else()
--- a/Resources/CMake/MongooseConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/MongooseConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,6 +1,12 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_MONGOOSE) SET(MONGOOSE_SOURCES_DIR ${CMAKE_BINARY_DIR}/mongoose) + if (IS_DIRECTORY "${MONGOOSE_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) + endif() + if (0) # Use Mongoose 3.1 DownloadPackage( @@ -24,20 +30,26 @@ # Patch mongoose execute_process( - COMMAND patch mongoose.c ${MONGOOSE_PATCH} + COMMAND ${PATCH_EXECUTABLE} -N mongoose.c + INPUT_FILE ${MONGOOSE_PATCH} WORKING_DIRECTORY ${MONGOOSE_SOURCES_DIR} + RESULT_VARIABLE Failure ) + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() + include_directories( ${MONGOOSE_SOURCES_DIR} ) - list(APPEND THIRD_PARTY_SOURCES + set(MONGOOSE_SOURCES ${MONGOOSE_SOURCES_DIR}/mongoose.c ) - if (${ENABLE_SSL}) + if (ENABLE_SSL) add_definitions( -DNO_SSL_DL=1 ) @@ -54,7 +66,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - if (${CMAKE_COMPILER_IS_GNUCXX}) + if (CMAKE_COMPILER_IS_GNUCXX) # This is a patch for MinGW64 add_definitions(-D_TIMESPEC_DEFINED=1) endif() @@ -81,5 +93,3 @@ link_libraries(mongoose) endif() - -
--- a/Resources/CMake/OpenSslConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/OpenSslConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,25 +1,20 @@ 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}") + # WARNING - We had to repack the upstream ".tar.gz" file to a ZIP + # file, as the upstream distribution ships symbolic links that are + # not always properly handled when uncompressing on Windows. - 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() + SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2d) + SET(OPENSSL_URL "www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/openssl-1.0.2d.zip") + SET(OPENSSL_MD5 "4b2ac15fc6db17f3dadc54482d3eee85") + + if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}") + set(FirstRun OFF) + else() + set(FirstRun ON) endif() + DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}") + add_definitions( -DOPENSSL_THREADS -DOPENSSL_IA32_SSE2 @@ -188,15 +183,16 @@ elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") execute_process( - COMMAND patch ui_openssl.c ${ORTHANC_ROOT}/Resources/Patches/openssl-lsb.diff + COMMAND ${PATCH_EXECUTABLE} -N ui_openssl.c -i ${ORTHANC_ROOT}/Resources/Patches/openssl-lsb.diff WORKING_DIRECTORY ${OPENSSL_SOURCES_DIR}/crypto/ui + RESULT_VARIABLE Failure ) + if (Failure AND FirstRun) + message(FATAL_ERROR "Error while patching a file") + endif() endif() - #add_library(OpenSSL STATIC ${OPENSSL_SOURCES}) - #link_libraries(OpenSSL) - else() include(FindOpenSSL)
--- a/Resources/CMake/PugixmlConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/PugixmlConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -3,21 +3,17 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML) set(PUGIXML_SOURCES_DIR ${CMAKE_BINARY_DIR}/pugixml-1.4) + set(PUGIXML_MD5 "7c56c91cfe3ecdee248a8e4892ef5781") + set(PUGIXML_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/pugixml-1.4.tar.gz") - DownloadPackage( - "7c56c91cfe3ecdee248a8e4892ef5781" - "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/pugixml-1.4.tar.gz" - "${PUGIXML_SOURCES_DIR}") + DownloadPackage(${PUGIXML_MD5} ${PUGIXML_URL} "${PUGIXML_SOURCES_DIR}") include_directories( ${PUGIXML_SOURCES_DIR}/src ) set(PUGIXML_SOURCES - ${PUGIXML_SOURCES_DIR}/src/vlog_is_on.cc - ) - - list(APPEND THIRD_PARTY_SOURCES + #${PUGIXML_SOURCES_DIR}/src/vlog_is_on.cc ${PUGIXML_SOURCES_DIR}/src/pugixml.cpp )
--- a/Resources/CMake/SQLiteConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/SQLiteConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,11 +1,27 @@ -if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) +if (APPLE) + # Under OS X, the binaries must always be linked against the + # system-wide version of SQLite. Otherwise, if some Orthanc plugin + # also uses its own version of SQLite (such as orthanc-webviewer), + # this results in a crash in "sqlite3_mutex_enter(db->mutex);" (the + # mutex is not initialized), probably because the EXE and the DYNLIB + # share the same memory location for this mutex. + set(SQLITE_STATIC OFF) + +elseif (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE) + set(SQLITE_STATIC ON) +else() + set(SQLITE_STATIC OFF) +endif() + + +if (SQLITE_STATIC) 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}") + SET(SQLITE_MD5 "5fbeff9645ab035a1f580e90b279a16d") + SET(SQLITE_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/sqlite-amalgamation-3071300.zip") - list(APPEND THIRD_PARTY_SOURCES + DownloadPackage(${SQLITE_MD5} ${SQLITE_URL} "${SQLITE_SOURCES_DIR}") + + set(SQLITE_SOURCES ${SQLITE_SOURCES_DIR}/sqlite3.c ) @@ -28,8 +44,14 @@ message(FATAL_ERROR "Please install the libsqlite3-dev package") endif() + find_path(SQLITE_INCLUDE_DIR sqlite3.h + /usr/include + /usr/local/include + ) + message("SQLite include dir: ${SQLITE_INCLUDE_DIR}") + # Autodetection of the version of SQLite - file(STRINGS "/usr/include/sqlite3.h" SQLITE_VERSION_NUMBER1 REGEX "#define SQLITE_VERSION_NUMBER.*$") + file(STRINGS "${SQLITE_INCLUDE_DIR}/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}")
--- a/Resources/CMake/VisualStudioPrecompiledHeaders.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/VisualStudioPrecompiledHeaders.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,6 +1,6 @@ macro(ADD_VISUAL_STUDIO_PRECOMPILED_HEADERS PrecompiledHeaders PrecompiledSource Sources) get_filename_component(PrecompiledBasename ${PrecompiledHeaders} NAME_WE) - set(PrecompiledBinary "$(IntDir)/${PrecompiledBasename}.pch") + set(PrecompiledBinary "${PrecompiledBasename}_$(ConfigurationName).pch") set_source_files_properties(${PrecompiledSource} PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeaders}\" /Fp\"${PrecompiledBinary}\""
--- a/Resources/CMake/ZlibConfiguration.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/CMake/ZlibConfiguration.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,21 +1,15 @@ -# This is the minizip distribution to create ZIP files -list(APPEND THIRD_PARTY_SOURCES - ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c - ${ORTHANC_ROOT}/Resources/ThirdParty/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}") + SET(ZLIB_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz") + SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85") + + DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}") include_directories( ${ZLIB_SOURCES_DIR} ) - list(APPEND THIRD_PARTY_SOURCES + list(APPEND ZLIB_SOURCES ${ZLIB_SOURCES_DIR}/adler32.c ${ZLIB_SOURCES_DIR}/compress.c ${ZLIB_SOURCES_DIR}/crc32.c
--- a/Resources/Configuration.json Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Configuration.json Wed Sep 30 13:23:31 2015 +0200 @@ -42,19 +42,41 @@ ], + /** * Configuration of the HTTP server **/ + // 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, + // HTTP port for the REST services and for the GUI "HttpPort" : 8042, + // When the following option is "true", if an error is encountered + // while calling the REST API, a JSON message describing the error + // is put in the HTTP answer. This feature can be disabled if the + // HTTP client does not properly handles such answers. + "HttpDescribeErrors" : true, + + // Enable HTTP compression to improve network bandwidth utilization, + // at the expense of more computations on the server. Orthanc + // supports the "gzip" and "deflate" HTTP encodings. + "HttpCompressionEnabled" : true, + /** * Configuration of the DICOM server **/ + // 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, + // The DICOM Application Entity Title "DicomAet" : "ORTHANC", @@ -67,8 +89,8 @@ // The default encoding that is assumed for DICOM files without // "SpecificCharacterSet" DICOM tag. The allowed values are "Ascii", // "Utf8", "Latin1", "Latin2", "Latin3", "Latin4", "Latin5", - // "Cyrillic", "Arabic", "Greek", "Hebrew", "Thai", "Japanese", - // and "Chinese". + // "Cyrillic", "Windows1251", "Arabic", "Greek", "Hebrew", "Thai", + // "Japanese", and "Chinese". "DefaultEncoding" : "Latin1", // The transfer syntaxes that are accepted by Orthanc C-Store SCP @@ -81,6 +103,7 @@ "RleTransferSyntaxAccepted" : true, + /** * Security-related options for the HTTP server **/ @@ -123,8 +146,8 @@ * A fourth parameter is available to enable patches for a * specific PACS manufacturer. The allowed values are currently * "Generic" (default value), "StoreScp" (storescp tool from - * DCMTK), "ClearCanvas", "MedInria" and "Dcm4Chee". This - * parameter is case-sensitive. + * DCMTK), "ClearCanvas", "MedInria", "Dcm4Chee" and + * "SyngoVia". This parameter is case-sensitive. **/ // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ] }, @@ -146,6 +169,21 @@ // "HttpProxy" : "proxyUser:proxyPassword@192.168.0.1:3128" "HttpProxy" : "", + // Set the timeout for HTTP requests issued by Orthanc (in seconds). + "HttpTimeout" : 10, + + // Enable the verification of the peers during HTTPS requests. + // Reference: http://curl.haxx.se/docs/sslcerts.html + "HttpsVerifyPeers" : true, + + // Path to the CA (certification authority) certificates to validate + // peers in HTTPS requests. From curl documentation ("--cacert" + // option): "Tells curl to use the specified certificate file to + // verify the peers. The file may contain multiple CA + // certificates. The certificate(s) must be in PEM format." + "HttpsCACertificates" : "", + + /** * Advanced options @@ -169,16 +207,6 @@ // 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. @@ -222,5 +250,15 @@ // are issued. This option sets the number of seconds of inactivity // to wait before automatically closing a DICOM association. If set // to 0, the connection is closed immediately. - "DicomAssociationCloseDelay" : 5 + "DicomAssociationCloseDelay" : 5, + + // Maximum number of query/retrieve DICOM requests that are + // maintained by Orthanc. The least recently used requests get + // deleted as new requests are issued. + "QueryRetrieveSize" : 10, + + // When handling a C-Find SCP request, setting this flag to "false" + // will enable case-insensitive match for PN value representation + // (such as PatientName). By default, the search is case-insensitive. + "CaseSensitivePN" : false }
--- a/Resources/DicomConformanceStatement.py Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/DicomConformanceStatement.py Wed Sep 30 13:23:31 2015 +0200 @@ -1,5 +1,37 @@ #!/usr/bin/python +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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/>. + + + + # This file injects the UID information into the DICOM conformance # statement of Orthanc
--- a/Resources/DicomConformanceStatement.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/DicomConformanceStatement.txt Wed Sep 30 13:23:31 2015 +0200 @@ -186,7 +186,16 @@ FINDPatientRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.1.1 FINDStudyRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.2.1 - FINDStudyRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.2.1 + + +-------------------- +Move SCU Conformance +-------------------- + +Orthanc supports the following SOP Classes as an SCU for C-Move: + + MOVEPatientRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.1.2 + MOVEStudyRootQueryRetrieveInformationModel | 1.2.840.10008.5.1.4.1.2.2.2 -----------------
--- a/Resources/EmbedResources.py Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/EmbedResources.py Wed Sep 30 13:23:31 2015 +0200 @@ -1,3 +1,5 @@ +#!/usr/bin/python + # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics # Department, University Hospital of Liege, Belgium @@ -35,16 +37,29 @@ import re UPCASE_CHECK = True +USE_SYSTEM_EXCEPTION = False +EXCEPTION_CLASS = 'OrthancException' +OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)' +INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)' +NAMESPACE = 'Orthanc' + ARGS = [] for i in range(len(sys.argv)): if not sys.argv[i].startswith('--'): ARGS.append(sys.argv[i]) elif sys.argv[i].lower() == '--no-upcase-check': UPCASE_CHECK = False + elif sys.argv[i].lower() == '--system-exception': + USE_SYSTEM_EXCEPTION = True + EXCEPTION_CLASS = '::std::runtime_error' + OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS + INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS + elif sys.argv[i].startswith('--namespace='): + NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ] if len(ARGS) < 2 or len(ARGS) % 2 != 0: print ('Usage:') - print ('python %s [--no-upcase-check] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0]) + print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0]) exit(-1) TARGET_BASE_FILENAME = ARGS[1] @@ -144,13 +159,13 @@ #include <string> #include <list> -namespace Orthanc +namespace %s { namespace EmbeddedResources { enum FileResourceId { -""") +""" % NAMESPACE) isFirst = True for name in resources: @@ -229,25 +244,32 @@ cpp.write("0x%02x" % c) pos += 1 + # Zero-size array are disallowed, so we put one single void character in it. + if pos == 0: + cpp.write(' 0') + cpp.write(' };\n') cpp.write(' static const size_t resource%dSize = %d;\n' % (item['Index'], pos)) cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w') +cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME)) + +if USE_SYSTEM_EXCEPTION: + cpp.write('#include <stdexcept>') +else: + cpp.write('#include "%s/Core/OrthancException.h"' % os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + cpp.write(""" -#include "%s.h" -#include "%s/Core/OrthancException.h" - #include <stdint.h> #include <string.h> -namespace Orthanc +namespace %s { namespace EmbeddedResources { -""" % (os.path.basename(TARGET_BASE_FILENAME), - os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))) +""" % NAMESPACE) for name in resources: @@ -276,7 +298,7 @@ cpp.write(""" default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw %s; } } @@ -284,7 +306,7 @@ { switch (id) { -""") +""" % OUT_OF_RANGE_EXCEPTION) for name in resources: if resources[name]['Type'] == 'File': @@ -293,10 +315,10 @@ cpp.write(""" default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw %s; } } -""") +""" % OUT_OF_RANGE_EXCEPTION) @@ -318,10 +340,10 @@ for path in resources[name]['Files']: cpp.write(' if (!strcmp(path, "%s"))\n' % path) cpp.write(' return resource%dBuffer;\n' % resources[name]['Files'][path]['Index']) - cpp.write(' throw OrthancException("Unknown path in a directory resource");\n\n') + cpp.write(' throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION) cpp.write(""" default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw %s; } } @@ -329,7 +351,7 @@ { switch (id) { -""") +""" % OUT_OF_RANGE_EXCEPTION) for name in resources: if resources[name]['Type'] == 'Directory': @@ -338,13 +360,13 @@ for path in resources[name]['Files']: cpp.write(' if (!strcmp(path, "%s"))\n' % path) cpp.write(' return resource%dSize;\n' % resources[name]['Files'][path]['Index']) - cpp.write(' throw OrthancException("Unknown path in a directory resource");\n\n') + cpp.write(' throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION) cpp.write(""" default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw %s; } } -""") +""" % OUT_OF_RANGE_EXCEPTION) @@ -370,10 +392,10 @@ cpp.write(' break;\n\n') cpp.write(""" default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw %s; } } -""") +""" % OUT_OF_RANGE_EXCEPTION)
--- a/Resources/EncodingTests.h Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/EncodingTests.h Wed Sep 30 13:23:31 2015 +0200 @@ -1,4 +1,4 @@ -static const unsigned int testEncodingsCount = 14; +static const unsigned int testEncodingsCount = 18; static const ::Orthanc::Encoding testEncodings[] = { ::Orthanc::Encoding_Latin5, ::Orthanc::Encoding_Hebrew, @@ -13,9 +13,13 @@ ::Orthanc::Encoding_Thai, ::Orthanc::Encoding_Japanese, ::Orthanc::Encoding_Ascii, - ::Orthanc::Encoding_Chinese + ::Orthanc::Encoding_Windows1251, + ::Orthanc::Encoding_Chinese, + ::Orthanc::Encoding_Windows1251, + ::Orthanc::Encoding_Windows1251, + ::Orthanc::Encoding_Windows1251 }; -static const char *testEncodingsEncoded[14] = { +static const char *testEncodingsEncoded[18] = { "\x54\x65\x73\x74\xe9\xe4\xf6\xf2\xdd", "\x54\x65\x73\x74\xe3", "\x54\x65\x73\x74\xc8", @@ -29,9 +33,13 @@ "\x54\x65\x73\x74\xfb", "\x54\x65\x73\x74\x84\x44\x83\xa6\xc8", "\x54\x65\x73\x74", - "\x81\x30\x89\x37\x81\x30\x89\x38\xA8\xA4\xA8\xA2\x81\x30\x89\x39\x81\x30\x8A\x30" + "\x54\x65\x73\x74\xc4\x9e", + "\x81\x30\x89\x37\x81\x30\x89\x38\xA8\xA4\xA8\xA2\x81\x30\x89\x39\x81\x30\x8A\x30", + "\xd0\xe5\xed\xf2\xe3\xe5\xed\xee\xe3\xf0\xe0\xf4\xe8\xff", + "\xD2\xE0\xE7", + "\xcf\xf0\xff\xec\xe0\xff" }; -static const char *testEncodingsExpected[14] = { +static const char *testEncodingsExpected[18] = { "\x54\x65\x73\x74\xc3\xa9\xc3\xa4\xc3\xb6\xc3\xb2\xc4\xb0", "\x54\x65\x73\x74\xd7\x93", "\x54\x65\x73\x74\xce\x98", @@ -45,5 +53,9 @@ "\x54\x65\x73\x74\xe0\xb9\x9b", "\x54\x65\x73\x74\xd0\x94\xce\x98\xef\xbe\x88", "\x54\x65\x73\x74", - "\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3" + "\x54\x65\x73\x74\xd0\x94\xd1\x9b", + "\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3", + "\xd0\xa0\xd0\xb5\xd0\xbd\xd1\x82\xd0\xb3\xd0\xb5\xd0\xbd\xd0\xbe\xd0\xb3\xd1\x80\xd0\xb0\xd1\x84\xd0\xb8\xd1\x8f", + "\xd0\xa2\xd0\xb0\xd0\xb7", + "\xd0\x9f\xd1\x80\xd1\x8f\xd0\xbc\xd0\xb0\xd1\x8f" };
--- a/Resources/EncodingTests.py Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/EncodingTests.py Wed Sep 30 13:23:31 2015 +0200 @@ -12,6 +12,7 @@ 'ISO-8859-4' : 'Latin4', 'ISO-8859-9' : 'Latin5', 'ISO-8859-5' : 'Cyrillic', + 'WINDOWS-1251' : 'Windows1251', 'ISO-8859-6' : 'Arabic', 'ISO-8859-7' : 'Greek', 'ISO-8859-8' : 'Hebrew', @@ -49,6 +50,18 @@ expected.append(ToArray('Þßàáâã')) encoded.append('"\\x81\\x30\\x89\\x37\\x81\\x30\\x89\\x38\\xA8\\xA4\\xA8\\xA2\\x81\\x30\\x89\\x39\\x81\\x30\\x8A\\x30"') +# Issue 32 +# "encoded" is the copy/paste from "dcm2xml +Ca cyrillic Issue32.dcm" +l.append('::Orthanc::Encoding_Windows1251') +encoded.append('"\\xd0\\xe5\\xed\\xf2\\xe3\\xe5\\xed\\xee\\xe3\\xf0\\xe0\\xf4\\xe8\\xff"') +expected.append(ToArray('Рентгенография')) +l.append('::Orthanc::Encoding_Windows1251') +encoded.append('"\\xD2\\xE0\\xE7"') +expected.append(ToArray('Таз')) +l.append('::Orthanc::Encoding_Windows1251') +encoded.append('"\\xcf\\xf0\\xff\\xec\\xe0\\xff"') +expected.append(ToArray('Прямая')) + if True: print 'static const unsigned int testEncodingsCount = %d;' % len(l)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ErrorCodes.json Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,496 @@ +[ + /** Generic error codes **/ + + { + "Code": -1, + "Name": "InternalError", + "Description": "Internal error" + }, + { + "Code": 0, + "HttpStatus": 200, + "Name": "Success", + "Description": "Success" + }, + { + "Code": 1, + "Name": "Plugin", + "Description": "Error encountered within the plugin engine" + }, + { + "Code": 2, + "Name": "NotImplemented", + "Description": "Not implemented yet" + }, + { + "Code": 3, + "HttpStatus": 400, + "Name": "ParameterOutOfRange", + "Description": "Parameter out of range", + "SQLite": true + }, + { + "Code": 4, + "Name": "NotEnoughMemory", + "Description": "Not enough memory" + }, + { + "Code": 5, + "HttpStatus": 400, + "Name": "BadParameterType", + "Description": "Bad type for a parameter", + "SQLite": true + }, + { + "Code": 6, + "Name": "BadSequenceOfCalls", + "Description": "Bad sequence of calls" + }, + { + "Code": 7, + "HttpStatus": 404, + "Name": "InexistentItem", + "Description": "Accessing an inexistent item" + }, + { + "Code": 8, + "HttpStatus": 400, + "Name": "BadRequest", + "Description": "Bad request" + }, + { + "Code": 9, + "Name": "NetworkProtocol", + "Description": "Error in the network protocol" + }, + { + "Code": 10, + "Name": "SystemCommand", + "Description": "Error while calling a system command" + }, + { + "Code": 11, + "Name": "Database", + "Description": "Error with the database engine" + }, + { + "Code": 12, + "HttpStatus": 400, + "Name": "UriSyntax", + "Description": "Badly formatted URI" + }, + { + "Code": 13, + "HttpStatus": 404, + "Name": "InexistentFile", + "Description": "Inexistent file" + }, + { + "Code": 14, + "Name": "CannotWriteFile", + "Description": "Cannot write to file" + }, + { + "Code": 15, + "HttpStatus": 400, + "Name": "BadFileFormat", + "Description": "Bad file format" + }, + { + "Code": 16, + "Name": "Timeout", + "Description": "Timeout" + }, + { + "Code": 17, + "HttpStatus": 404, + "Name": "UnknownResource", + "Description": "Unknown resource" + }, + { + "Code": 18, + "Name": "IncompatibleDatabaseVersion", + "Description": "Incompatible version of the database" + }, + { + "Code": 19, + "Name": "FullStorage", + "Description": "The file storage is full" + }, + { + "Code": 20, + "Name": "CorruptedFile", + "Description": "Corrupted file (e.g. inconsistent MD5 hash)" + }, + { + "Code": 21, + "HttpStatus": 404, + "Name": "InexistentTag", + "Description": "Inexistent tag" + }, + { + "Code": 22, + "Name": "ReadOnly", + "Description": "Cannot modify a read-only data structure" + }, + { + "Code": 23, + "Name": "IncompatibleImageFormat", + "Description": "Incompatible format of the images" + }, + { + "Code": 24, + "Name": "IncompatibleImageSize", + "Description": "Incompatible size of the images" + }, + { + "Code": 25, + "Name": "SharedLibrary", + "Description": "Error while using a shared library (plugin)" + }, + { + "Code": 26, + "Name": "UnknownPluginService", + "Description": "Plugin invoking an unknown service" + }, + { + "Code": 27, + "Name": "UnknownDicomTag", + "Description": "Unknown DICOM tag" + }, + { + "Code": 28, + "HttpStatus": 400, + "Name": "BadJson", + "Description": "Cannot parse a JSON document" + }, + { + "Code": 29, + "HttpStatus": 401, + "Name": "Unauthorized", + "Description": "Bad credentials were provided to an HTTP request" + }, + { + "Code": 30, + "Name": "BadFont", + "Description": "Badly formatted font file" + }, + { + "Code": 31, + "Name": "DatabasePlugin", + "Description": "The plugin implementing a custom database back-end does not fulfill the proper interface" + }, + { + "Code": 32, + "Name": "StorageAreaPlugin", + "Description": "Error in the plugin implementing a custom storage area" + }, + + + + /** SQLite **/ + + + { + "Code": 1000, + "Name": "SQLiteNotOpened", + "Description": "SQLite: The database is not opened", + "SQLite": true + }, + { + "Code": 1001, + "Name": "SQLiteAlreadyOpened", + "Description": "SQLite: Connection is already open", + "SQLite": true + }, + { + "Code": 1002, + "Name": "SQLiteCannotOpen", + "Description": "SQLite: Unable to open the database", + "SQLite": true + }, + { + "Code": 1003, + "Name": "SQLiteStatementAlreadyUsed", + "Description": "SQLite: This cached statement is already being referred to", + "SQLite": true + }, + { + "Code": 1004, + "Name": "SQLiteExecute", + "Description": "SQLite: Cannot execute a command", + "SQLite": true + }, + { + "Code": 1005, + "Name": "SQLiteRollbackWithoutTransaction", + "Description": "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)", + "SQLite": true + }, + { + "Code": 1006, + "Name": "SQLiteCommitWithoutTransaction", + "Description": "SQLite: Committing a nonexistent transaction", + "SQLite": true + }, + { + "Code": 1007, + "Name": "SQLiteRegisterFunction", + "Description": "SQLite: Unable to register a function", + "SQLite": true + }, + { + "Code": 1008, + "Name": "SQLiteFlush", + "Description": "SQLite: Unable to flush the database", + "SQLite": true + }, + { + "Code": 1009, + "Name": "SQLiteCannotRun", + "Description": "SQLite: Cannot run a cached statement", + "SQLite": true + }, + { + "Code": 1010, + "Name": "SQLiteCannotStep", + "Description": "SQLite: Cannot step over a cached statement", + "SQLite": true + }, + { + "Code": 1011, + "Name": "SQLiteBindOutOfRange", + "Description": "SQLite: Bing a value while out of range (serious error)", + "SQLite": true + }, + { + "Code": 1012, + "Name": "SQLitePrepareStatement", + "Description": "SQLite: Cannot prepare a cached statement", + "SQLite": true + }, + { + "Code": 1013, + "Name": "SQLiteTransactionAlreadyStarted", + "Description": "SQLite: Beginning the same transaction twice", + "SQLite": true + }, + { + "Code": 1014, + "Name": "SQLiteTransactionCommit", + "Description": "SQLite: Failure when committing the transaction", + "SQLite": true + }, + { + "Code": 1015, + "Name": "SQLiteTransactionBegin", + "Description": "SQLite: Cannot start a transaction", + "SQLite": true + }, + + + + + + + + + /** Specific error codes **/ + + + { + "Code": 2000, + "Name": "DirectoryOverFile", + "Description": "The directory to be created is already occupied by a regular file" + }, + { + "Code": 2001, + "Name": "FileStorageCannotWrite", + "Description": "Unable to create a subdirectory or a file in the file storage" + }, + { + "Code": 2002, + "Name": "DirectoryExpected", + "Description": "The specified path does not point to a directory" + }, + { + "Code": 2003, + "Name": "HttpPortInUse", + "Description": "The TCP port of the HTTP server is already in use" + }, + { + "Code": 2004, + "Name": "DicomPortInUse", + "Description": "The TCP port of the DICOM server is already in use" + }, + { + "Code": 2005, + "Name": "BadHttpStatusInRest", + "Description": "This HTTP status is not allowed in a REST API" + }, + { + "Code": 2006, + "Name": "RegularFileExpected", + "Description": "The specified path does not point to a regular file" + }, + { + "Code": 2007, + "Name": "PathToExecutable", + "Description": "Unable to get the path to the executable" + }, + { + "Code": 2008, + "Name": "MakeDirectory", + "Description": "Cannot create a directory" + }, + { + "Code": 2009, + "Name": "BadApplicationEntityTitle", + "Description": "An application entity title (AET) cannot be empty or be longer than 16 characters" + }, + { + "Code": 2010, + "Name": "NoCFindHandler", + "Description": "No request handler factory for DICOM C-FIND SCP" + }, + { + "Code": 2011, + "Name": "NoCMoveHandler", + "Description": "No request handler factory for DICOM C-MOVE SCP" + }, + { + "Code": 2012, + "Name": "NoCStoreHandler", + "Description": "No request handler factory for DICOM C-STORE SCP" + }, + { + "Code": 2013, + "Name": "NoApplicationEntityFilter", + "Description": "No application entity filter" + }, + { + "Code": 2014, + "Name": "NoSopClassOrInstance", + "Description": "DicomUserConnection: Unable to find the SOP class and instance" + }, + { + "Code": 2015, + "Name": "NoPresentationContext", + "Description": "DicomUserConnection: No acceptable presentation context for modality" + }, + { + "Code": 2016, + "Name": "DicomFindUnavailable", + "Description": "DicomUserConnection: The C-FIND command is not supported by the remote SCP" + }, + { + "Code": 2017, + "Name": "DicomMoveUnavailable", + "Description": "DicomUserConnection: The C-MOVE command is not supported by the remote SCP" + }, + { + "Code": 2018, + "Name": "CannotStoreInstance", + "Description": "Cannot store an instance" + }, + { + "Code": 2019, + "Name": "CreateDicomNotString", + "Description": "Only string values are supported when creating DICOM instances" + }, + { + "Code": 2020, + "Name": "CreateDicomOverrideTag", + "Description": "Trying to override a value inherited from a parent module" + }, + { + "Code": 2021, + "Name": "CreateDicomUseContent", + "Description": "Use \\\"Content\\\" to inject an image into a new DICOM instance" + }, + { + "Code": 2022, + "Name": "CreateDicomNoPayload", + "Description": "No payload is present for one instance in the series" + }, + { + "Code": 2023, + "Name": "CreateDicomUseDataUriScheme", + "Description": "The payload of the DICOM instance must be specified according to Data URI scheme" + }, + { + "Code": 2024, + "Name": "CreateDicomBadParent", + "Description": "Trying to attach a new DICOM instance to an inexistent resource" + }, + { + "Code": 2025, + "Name": "CreateDicomParentIsInstance", + "Description": "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)" + }, + { + "Code": 2026, + "Name": "CreateDicomParentEncoding", + "Description": "Unable to get the encoding of the parent resource" + }, + { + "Code": 2027, + "Name": "UnknownModality", + "Description": "Unknown modality" + }, + { + "Code": 2028, + "Name": "BadJobOrdering", + "Description": "Bad ordering of filters in a job" + }, + { + "Code": 2029, + "Name": "JsonToLuaTable", + "Description": "Cannot convert the given JSON object to a Lua table" + }, + { + "Code": 2030, + "Name": "CannotCreateLua", + "Description": "Cannot create the Lua context" + }, + { + "Code": 2031, + "Name": "CannotExecuteLua", + "Description": "Cannot execute a Lua command" + }, + { + "Code": 2032, + "Name": "LuaAlreadyExecuted", + "Description": "Arguments cannot be pushed after the Lua function is executed" + }, + { + "Code": 2033, + "Name": "LuaBadOutput", + "Description": "The Lua function does not give the expected number of outputs" + }, + { + "Code": 2034, + "Name": "NotLuaPredicate", + "Description": "The Lua function is not a predicate (only true/false outputs allowed)" + }, + { + "Code": 2035, + "Name": "LuaReturnsNoString", + "Description": "The Lua function does not return a string" + }, + { + "Code": 2036, + "Name": "StorageAreaAlreadyRegistered", + "Description": "Another plugin has already registered a custom storage area" + }, + { + "Code": 2037, + "Name": "DatabaseBackendAlreadyRegistered", + "Description": "Another plugin has already registered a custom database back-end" + }, + { + "Code": 2038, + "Name": "DatabaseNotInitialized", + "Description": "Plugin trying to call the database during its initialization" + } +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Fonts/GenerateFont.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,113 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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/>. + + + + +# sudo pip install freetype-py + + + +import freetype +import json +import os +import sys +import unicodedata + + +if len(sys.argv) != 3: + print('Usage: %s <Font> <Size>\n' % sys.argv[0]) + print('Example: %s /usr/share/fonts/truetype/ubuntu-font-family/UbuntuMono-B.ttf 16\n' % sys.argv[0]) + sys.exit(-1) + + + +FONT = sys.argv[1] +PIXEL_SIZE = int(sys.argv[2]) +CHARSET = 'latin-1' + + +# Load the font +face = freetype.Face(FONT) +face.set_char_size(PIXEL_SIZE * 64) + +# Generate all the characters between 0 and 255 +characters = ''.join(map(chr, range(0, 256))) + +# Interpret the string using the required charset +characters = characters.decode(CHARSET, 'ignore') + +# Keep only non-control characters +characters = filter(lambda c: unicodedata.category(c)[0] != 'C', characters) + +font = { + 'Name' : os.path.basename(FONT), + 'Size' : PIXEL_SIZE, + 'Characters' : {} +} + + +def PrintCharacter(c): + pos = 0 + for i in range(c['Height']): + s = '' + for j in range(c['Width']): + if c['Bitmap'][pos] > 127: + s += '*' + else: + s += ' ' + pos += 1 + print s + + +for c in characters: + face.load_char(c) + + info = { + 'Width' : face.glyph.bitmap.width, + 'Height' : face.glyph.bitmap.rows, + 'Advance' : face.glyph.metrics.horiAdvance / 64, + 'Top' : -face.glyph.metrics.horiBearingY / 64, + 'Bitmap' : face.glyph.bitmap.buffer, + } + + font['Characters'][ord(c)] = info + + #PrintCharacter(info) + +minTop = min(map(lambda (k, v): v['Top'], font['Characters'].iteritems())) +for c in font['Characters']: + font['Characters'][c]['Top'] -= minTop + +font['MaxAdvance'] = max(map(lambda (k, v): v['Advance'], font['Characters'].iteritems())) +font['MaxHeight'] = max(map(lambda (k, v): v['Height'], font['Characters'].iteritems())) + +print json.dumps(font)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Fonts/README.txt Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,6 @@ +This file contains a precompiled version of several open-source fonts, +that are used to draw text on images within Orthanc. These fonts are +encoded as JSON files through the "./GenerateFont.py" script. + +- UbuntuMonoBold-16.json is the Ubuntu Mono Bold, size 16, licensed + under the Ubuntu Font Licence.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Fonts/UbuntuMonoBold-16.json Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,1 @@ +{"MaxAdvance": 9, "MaxHeight": 15, "Name": "UbuntuMono-B.ttf", "Characters": {"32": {"Width": 0, "Advance": 8, "Bitmap": [], "Top": 14, "Height": 0}, "33": {"Width": 2, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 254, 251, 245, 242, 224, 223, 196, 199, 0, 0, 199, 198, 199, 198], "Top": 4, "Height": 10}, "34": {"Width": 5, "Advance": 8, "Bitmap": [255, 255, 0, 255, 255, 248, 247, 0, 248, 247, 226, 226, 0, 226, 226, 197, 196, 0, 197, 196, 160, 160, 0, 160, 162], "Top": 3, "Height": 5}, "35": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 31, 255, 254, 255, 225, 0, 0, 0, 93, 255, 250, 255, 163, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 234, 255, 255, 255, 21, 0, 0, 25, 255, 253, 255, 231, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 163, 243, 161, 255, 93, 0, 0, 0, 225, 94, 105, 255, 31, 0, 0], "Top": 4, "Height": 10}, "36": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 8, 0, 33, 198, 255, 255, 248, 169, 190, 255, 255, 255, 255, 202, 246, 255, 46, 8, 44, 66, 216, 255, 231, 148, 27, 0, 71, 243, 255, 255, 239, 59, 0, 23, 115, 205, 255, 205, 82, 56, 10, 42, 255, 246, 211, 255, 255, 255, 255, 209, 120, 227, 255, 255, 245, 72, 0, 0, 255, 255, 15, 0, 0, 0, 255, 255, 0, 0], "Top": 3, "Height": 13}, "37": {"Width": 7, "Advance": 8, "Bitmap": [127, 242, 123, 0, 0, 77, 179, 240, 57, 239, 0, 8, 213, 33, 240, 55, 239, 0, 128, 127, 0, 126, 243, 123, 34, 213, 8, 0, 0, 0, 0, 179, 76, 0, 0, 0, 0, 77, 179, 0, 0, 0, 0, 8, 213, 33, 126, 59, 124, 0, 128, 127, 0, 240, 6, 239, 34, 213, 8, 0, 240, 68, 240, 179, 76, 0, 0, 128, 243, 126], "Top": 4, "Height": 10}, "38": {"Width": 9, "Advance": 8, "Bitmap": [0, 39, 190, 245, 206, 57, 0, 0, 0, 0, 201, 255, 255, 255, 219, 0, 0, 0, 0, 242, 255, 55, 255, 233, 0, 0, 0, 0, 184, 255, 116, 255, 110, 0, 0, 0, 0, 128, 255, 255, 130, 6, 188, 52, 0, 102, 255, 103, 202, 235, 70, 255, 70, 0, 225, 255, 11, 26, 231, 249, 255, 45, 0, 240, 255, 82, 17, 174, 255, 255, 17, 0, 162, 255, 255, 255, 255, 255, 255, 125, 0, 14, 152, 229, 240, 188, 114, 255, 235, 7], "Top": 4, "Height": 10}, "39": {"Width": 2, "Advance": 8, "Bitmap": [255, 255, 251, 250, 230, 230, 200, 199, 161, 163], "Top": 3, "Height": 5}, "40": {"Width": 5, "Advance": 8, "Bitmap": [0, 0, 9, 152, 142, 0, 12, 199, 253, 110, 0, 166, 255, 118, 0, 46, 255, 207, 2, 0, 146, 255, 106, 0, 0, 209, 255, 40, 0, 0, 239, 255, 12, 0, 0, 242, 255, 14, 0, 0, 213, 255, 43, 0, 0, 157, 255, 117, 0, 0, 58, 255, 219, 8, 0, 0, 180, 255, 153, 0, 0, 17, 207, 255, 160, 0, 0, 12, 157, 119], "Top": 3, "Height": 14}, "41": {"Width": 6, "Advance": 8, "Bitmap": [120, 194, 49, 0, 0, 0, 82, 230, 250, 103, 0, 0, 0, 23, 219, 255, 93, 0, 0, 0, 32, 242, 243, 24, 0, 0, 0, 129, 255, 131, 0, 0, 0, 45, 255, 208, 0, 0, 0, 8, 255, 245, 0, 0, 0, 9, 255, 247, 0, 0, 0, 50, 255, 216, 0, 0, 0, 140, 255, 146, 0, 0, 50, 247, 250, 37, 0, 47, 235, 255, 117, 0, 121, 250, 253, 123, 0, 0, 80, 201, 60, 0, 0, 0], "Top": 3, "Height": 14}, "42": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 136, 189, 68, 255, 66, 183, 136, 213, 255, 255, 255, 255, 255, 214, 0, 7, 161, 248, 162, 8, 0, 0, 174, 255, 132, 255, 176, 0, 0, 67, 187, 4, 185, 63, 0], "Top": 4, "Height": 7}, "43": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0], "Top": 6, "Height": 8}, "44": {"Width": 4, "Advance": 8, "Bitmap": [0, 140, 243, 125, 0, 246, 255, 240, 0, 166, 255, 234, 0, 40, 255, 177, 49, 203, 240, 42, 204, 174, 39, 0], "Top": 11, "Height": 6}, "45": {"Width": 4, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255], "Top": 9, "Height": 2}, "46": {"Width": 3, "Advance": 9, "Bitmap": [91, 243, 85, 234, 255, 212, 91, 244, 85], "Top": 11, "Height": 3}, "47": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 0, 47, 255, 217, 0, 0, 0, 0, 124, 255, 139, 0, 0, 0, 0, 201, 255, 62, 0, 0, 0, 24, 254, 237, 3, 0, 0, 0, 99, 255, 164, 0, 0, 0, 0, 176, 255, 87, 0, 0, 0, 8, 245, 250, 15, 0, 0, 0, 75, 255, 189, 0, 0, 0, 0, 152, 255, 111, 0, 0, 0, 0, 228, 255, 34, 0, 0, 0, 50, 255, 213, 0, 0, 0, 0, 127, 255, 136, 0, 0, 0, 0, 204, 255, 59, 0, 0, 0, 26, 254, 235, 2, 0, 0, 0], "Top": 3, "Height": 14}, "48": {"Width": 6, "Advance": 8, "Bitmap": [0, 125, 237, 238, 130, 0, 85, 255, 255, 255, 255, 89, 183, 255, 112, 114, 255, 184, 231, 255, 28, 30, 255, 231, 251, 255, 205, 204, 255, 250, 251, 255, 205, 204, 255, 249, 232, 255, 28, 30, 255, 230, 185, 255, 112, 114, 255, 184, 90, 255, 255, 255, 255, 89, 0, 131, 238, 238, 131, 0], "Top": 4, "Height": 10}, "49": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 48, 216, 255, 0, 0, 49, 161, 252, 255, 255, 0, 0, 196, 255, 221, 255, 255, 0, 0, 56, 80, 3, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "50": {"Width": 7, "Advance": 8, "Bitmap": [0, 31, 166, 239, 236, 162, 19, 0, 206, 255, 255, 255, 255, 177, 0, 93, 125, 21, 76, 255, 247, 0, 0, 0, 0, 22, 255, 200, 0, 0, 0, 5, 182, 247, 54, 0, 0, 9, 184, 244, 73, 0, 0, 3, 184, 233, 49, 0, 0, 0, 117, 255, 60, 0, 0, 0, 0, 227, 255, 255, 255, 255, 255, 0, 253, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "51": {"Width": 6, "Advance": 8, "Bitmap": [78, 198, 247, 226, 148, 13, 201, 255, 255, 255, 255, 155, 66, 71, 13, 62, 255, 236, 0, 0, 12, 99, 255, 226, 0, 255, 255, 255, 250, 88, 0, 255, 255, 255, 251, 95, 0, 1, 16, 94, 255, 228, 69, 33, 7, 76, 255, 239, 215, 255, 255, 255, 255, 162, 145, 222, 251, 233, 144, 13], "Top": 4, "Height": 10}, "52": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 64, 249, 255, 0, 0, 0, 34, 238, 255, 255, 0, 0, 3, 198, 241, 255, 255, 0, 0, 119, 255, 91, 255, 255, 0, 33, 246, 179, 0, 255, 255, 0, 165, 252, 37, 0, 255, 255, 0, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0], "Top": 4, "Height": 10}, "53": {"Width": 6, "Advance": 8, "Bitmap": [0, 171, 255, 255, 255, 255, 0, 184, 255, 255, 255, 255, 0, 198, 236, 0, 0, 0, 0, 221, 255, 215, 118, 2, 0, 244, 255, 255, 255, 118, 0, 4, 34, 154, 255, 218, 0, 0, 0, 12, 255, 246, 60, 25, 8, 96, 255, 216, 213, 255, 255, 255, 255, 108, 156, 231, 251, 222, 106, 1], "Top": 4, "Height": 10}, "54": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 42, 155, 221, 227, 0, 91, 249, 255, 255, 249, 42, 249, 237, 115, 42, 11, 156, 255, 140, 100, 31, 0, 223, 255, 255, 255, 244, 68, 249, 254, 158, 193, 255, 204, 247, 252, 1, 13, 255, 246, 211, 255, 78, 70, 255, 222, 121, 255, 255, 255, 255, 126, 4, 144, 237, 244, 147, 4], "Top": 4, "Height": 10}, "55": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 77, 255, 161, 0, 0, 3, 218, 241, 18, 0, 0, 89, 255, 135, 0, 0, 0, 205, 253, 26, 0, 0, 45, 255, 193, 0, 0, 0, 132, 255, 111, 0, 0, 0, 201, 255, 52, 0, 0, 0, 242, 255, 14, 0, 0], "Top": 4, "Height": 10}, "56": {"Width": 6, "Advance": 8, "Bitmap": [7, 142, 237, 243, 186, 38, 123, 255, 255, 255, 255, 203, 184, 255, 52, 50, 255, 244, 137, 255, 104, 72, 255, 153, 16, 237, 255, 255, 213, 7, 136, 255, 86, 183, 255, 130, 231, 255, 9, 19, 255, 233, 241, 255, 68, 67, 255, 239, 174, 255, 255, 255, 255, 159, 21, 168, 238, 242, 158, 14], "Top": 4, "Height": 10}, "57": {"Width": 6, "Advance": 8, "Bitmap": [3, 141, 242, 235, 131, 1, 125, 255, 255, 255, 255, 93, 224, 255, 60, 85, 255, 198, 246, 255, 12, 2, 253, 236, 205, 255, 198, 166, 255, 249, 69, 244, 255, 255, 255, 228, 0, 29, 93, 127, 255, 174, 0, 15, 80, 227, 255, 74, 0, 253, 255, 255, 154, 0, 0, 236, 203, 101, 1, 0], "Top": 4, "Height": 10}, "58": {"Width": 3, "Advance": 9, "Bitmap": [91, 243, 85, 234, 255, 212, 91, 244, 85, 0, 0, 0, 0, 0, 0, 91, 243, 85, 234, 255, 212, 91, 244, 85], "Top": 6, "Height": 8}, "59": {"Width": 4, "Advance": 8, "Bitmap": [0, 91, 243, 85, 0, 234, 255, 212, 0, 91, 244, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 243, 125, 0, 246, 255, 240, 0, 166, 255, 234, 0, 40, 255, 177, 49, 203, 240, 42, 204, 174, 39, 0], "Top": 6, "Height": 11}, "60": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 0, 0, 94, 131, 0, 0, 0, 32, 185, 255, 203, 0, 2, 112, 244, 255, 255, 192, 27, 201, 255, 255, 234, 98, 1, 72, 255, 252, 148, 16, 0, 0, 72, 255, 252, 147, 16, 0, 0, 25, 195, 255, 255, 234, 97, 1, 0, 1, 108, 243, 255, 255, 192, 0, 0, 0, 30, 183, 255, 203, 0, 0, 0, 0, 0, 93, 131], "Top": 3, "Height": 10}, "61": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 8, "Height": 5}, "62": {"Width": 6, "Advance": 8, "Bitmap": [132, 83, 0, 0, 0, 0, 206, 255, 166, 18, 0, 0, 195, 255, 255, 231, 78, 0, 2, 108, 241, 255, 255, 161, 0, 0, 26, 172, 255, 255, 0, 0, 26, 171, 255, 255, 2, 107, 241, 255, 255, 155, 195, 255, 255, 229, 73, 0, 206, 255, 165, 17, 0, 0, 132, 82, 0, 0, 0, 0], "Top": 3, "Height": 10}, "63": {"Width": 6, "Advance": 8, "Bitmap": [98, 200, 241, 241, 170, 22, 200, 255, 255, 255, 255, 174, 68, 61, 11, 67, 255, 243, 0, 0, 0, 65, 255, 196, 0, 1, 119, 250, 232, 40, 0, 132, 255, 220, 35, 0, 0, 245, 255, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197, 195, 0, 0, 0, 0, 198, 198, 0, 0, 0], "Top": 4, "Height": 10}, "64": {"Width": 7, "Advance": 8, "Bitmap": [0, 30, 175, 244, 229, 135, 6, 10, 211, 81, 14, 124, 255, 132, 111, 139, 0, 0, 10, 255, 224, 190, 59, 31, 209, 247, 255, 252, 232, 20, 172, 255, 247, 255, 255, 249, 5, 235, 255, 47, 255, 255, 248, 4, 251, 255, 2, 255, 255, 229, 21, 231, 255, 45, 255, 255, 179, 70, 152, 255, 253, 255, 255, 88, 176, 14, 159, 237, 229, 129, 1, 181, 163, 45, 7, 0, 0, 0, 5, 120, 209, 246, 254, 216], "Top": 4, "Height": 12}, "65": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 4, "Height": 10}, "66": {"Width": 6, "Advance": 8, "Bitmap": [212, 240, 251, 232, 160, 21, 255, 255, 255, 255, 255, 182, 255, 255, 5, 53, 255, 244, 255, 255, 1, 70, 255, 222, 255, 255, 255, 255, 247, 70, 255, 255, 255, 255, 251, 125, 255, 255, 1, 66, 255, 237, 255, 255, 2, 62, 255, 246, 255, 255, 255, 255, 255, 169, 206, 242, 251, 228, 151, 14], "Top": 4, "Height": 10}, "67": {"Width": 7, "Advance": 8, "Bitmap": [0, 15, 140, 225, 248, 209, 91, 10, 208, 255, 255, 255, 255, 223, 124, 255, 216, 58, 9, 72, 133, 210, 255, 64, 0, 0, 0, 0, 247, 255, 8, 0, 0, 0, 0, 249, 255, 15, 0, 0, 0, 0, 219, 255, 67, 0, 0, 0, 0, 148, 255, 211, 51, 8, 64, 134, 27, 235, 255, 255, 255, 255, 225, 0, 37, 171, 238, 249, 205, 87], "Top": 4, "Height": 10}, "68": {"Width": 7, "Advance": 8, "Bitmap": [214, 244, 252, 229, 159, 29, 0, 255, 255, 255, 255, 255, 229, 23, 255, 255, 5, 42, 212, 255, 143, 255, 255, 0, 0, 65, 255, 218, 255, 255, 0, 0, 12, 255, 247, 255, 255, 0, 0, 15, 255, 246, 255, 255, 0, 0, 72, 255, 214, 255, 255, 8, 61, 219, 255, 135, 255, 255, 255, 255, 255, 219, 17, 217, 248, 250, 221, 145, 19, 0], "Top": 4, "Height": 10}, "69": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "70": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 4, "Height": 10}, "71": {"Width": 7, "Advance": 8, "Bitmap": [0, 20, 151, 231, 254, 255, 234, 13, 215, 255, 255, 255, 255, 190, 130, 255, 219, 61, 11, 72, 122, 212, 255, 68, 0, 0, 0, 0, 247, 255, 13, 0, 0, 0, 0, 248, 255, 13, 0, 0, 255, 255, 219, 255, 58, 0, 0, 255, 255, 148, 255, 200, 39, 5, 255, 255, 28, 236, 255, 255, 255, 255, 255, 0, 40, 178, 242, 255, 255, 255], "Top": 4, "Height": 10}, "72": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255], "Top": 4, "Height": 10}, "73": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "74": {"Width": 6, "Advance": 8, "Bitmap": [0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 8, 255, 252, 115, 93, 9, 109, 255, 223, 216, 255, 255, 255, 255, 128, 72, 209, 249, 232, 131, 4], "Top": 4, "Height": 10}, "75": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 0, 0, 74, 255, 182, 255, 255, 0, 12, 223, 245, 35, 255, 255, 0, 163, 255, 107, 0, 255, 255, 114, 255, 172, 0, 0, 255, 255, 253, 236, 11, 0, 0, 255, 255, 216, 255, 127, 0, 0, 255, 255, 30, 235, 253, 62, 0, 255, 255, 0, 77, 255, 217, 3, 255, 255, 0, 0, 179, 255, 97, 255, 255, 0, 0, 56, 255, 205], "Top": 4, "Height": 10}, "76": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "77": {"Width": 7, "Advance": 8, "Bitmap": [119, 255, 43, 16, 255, 255, 17, 142, 255, 131, 44, 255, 255, 53, 163, 228, 217, 72, 255, 227, 88, 180, 191, 230, 149, 236, 183, 120, 195, 200, 158, 246, 164, 193, 151, 209, 210, 85, 255, 89, 204, 177, 221, 220, 0, 0, 0, 216, 199, 232, 230, 0, 0, 0, 227, 219, 242, 240, 0, 0, 0, 239, 234, 251, 250, 0, 0, 0, 250, 248], "Top": 4, "Height": 10}, "78": {"Width": 7, "Advance": 8, "Bitmap": [255, 246, 31, 0, 0, 255, 255, 255, 255, 171, 0, 0, 255, 255, 255, 255, 255, 53, 0, 255, 255, 255, 255, 229, 185, 0, 255, 255, 255, 255, 106, 255, 57, 255, 255, 255, 255, 6, 223, 178, 255, 255, 255, 255, 0, 97, 254, 255, 255, 255, 255, 0, 5, 229, 255, 255, 255, 255, 0, 0, 120, 255, 255, 255, 255, 0, 0, 17, 244, 255], "Top": 4, "Height": 10}, "79": {"Width": 7, "Advance": 8, "Bitmap": [0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 4, "Height": 10}, "80": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 255, 236, 199, 95, 1, 255, 255, 255, 255, 255, 255, 124, 255, 255, 5, 12, 120, 255, 226, 255, 255, 0, 0, 12, 255, 250, 255, 255, 0, 15, 122, 255, 223, 255, 255, 255, 255, 255, 255, 118, 255, 255, 255, 234, 195, 89, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0], "Top": 4, "Height": 10}, "81": {"Width": 7, "Advance": 8, "Bitmap": [0, 76, 212, 249, 214, 81, 0, 53, 251, 255, 255, 255, 252, 55, 166, 255, 146, 19, 145, 255, 166, 225, 255, 32, 0, 34, 255, 224, 250, 255, 5, 0, 7, 255, 248, 246, 255, 5, 0, 7, 255, 245, 223, 255, 31, 0, 36, 255, 221, 168, 255, 143, 19, 150, 255, 165, 63, 253, 255, 255, 255, 252, 60, 0, 94, 231, 255, 227, 92, 0, 0, 0, 52, 255, 181, 70, 18, 0, 0, 0, 175, 255, 255, 210, 0, 0, 0, 4, 101, 192, 149], "Top": 4, "Height": 13}, "82": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 249, 214, 121, 2, 255, 255, 255, 255, 255, 122, 255, 255, 7, 106, 255, 222, 255, 255, 0, 10, 255, 249, 255, 255, 6, 105, 255, 212, 255, 255, 255, 255, 252, 77, 255, 255, 255, 247, 51, 0, 255, 255, 142, 255, 133, 0, 255, 255, 7, 214, 252, 47, 255, 255, 0, 73, 255, 189], "Top": 4, "Height": 10}, "83": {"Width": 6, "Advance": 8, "Bitmap": [11, 142, 225, 248, 211, 92, 153, 255, 255, 255, 255, 216, 234, 255, 58, 7, 74, 128, 241, 255, 144, 31, 0, 0, 139, 255, 255, 253, 157, 10, 3, 118, 233, 255, 255, 149, 0, 0, 5, 111, 255, 240, 135, 69, 7, 61, 255, 238, 223, 255, 255, 255, 255, 167, 97, 212, 249, 231, 160, 19], "Top": 4, "Height": 10}, "84": {"Width": 8, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0], "Top": 4, "Height": 10}, "85": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 249, 255, 7, 8, 255, 249, 225, 255, 71, 73, 255, 225, 148, 255, 255, 255, 255, 145, 14, 168, 245, 244, 164, 13], "Top": 4, "Height": 10}, "86": {"Width": 7, "Advance": 8, "Bitmap": [232, 250, 3, 0, 3, 249, 230, 182, 255, 35, 0, 39, 255, 178, 123, 255, 79, 0, 84, 255, 123, 62, 255, 124, 0, 131, 255, 63, 7, 247, 175, 0, 182, 249, 9, 0, 188, 228, 0, 233, 190, 0, 0, 120, 255, 65, 255, 120, 0, 0, 50, 255, 194, 255, 48, 0, 0, 1, 232, 255, 225, 0, 0, 0, 0, 156, 255, 147, 0, 0], "Top": 4, "Height": 10}, "87": {"Width": 7, "Advance": 8, "Bitmap": [252, 248, 0, 0, 0, 253, 251, 245, 234, 0, 0, 0, 246, 241, 236, 221, 0, 0, 0, 237, 231, 225, 209, 32, 120, 29, 223, 221, 215, 196, 120, 255, 109, 210, 210, 201, 182, 189, 241, 177, 197, 197, 186, 182, 248, 108, 241, 191, 181, 170, 237, 227, 2, 230, 237, 165, 148, 255, 149, 0, 153, 255, 146, 124, 255, 71, 0, 72, 255, 126], "Top": 4, "Height": 10}, "88": {"Width": 8, "Advance": 8, "Bitmap": [178, 255, 160, 0, 0, 137, 255, 178, 32, 246, 252, 37, 29, 247, 246, 32, 0, 124, 255, 164, 159, 255, 124, 0, 0, 7, 218, 253, 253, 218, 7, 0, 0, 0, 82, 255, 255, 87, 0, 0, 0, 0, 152, 255, 255, 173, 0, 0, 0, 45, 253, 212, 213, 255, 68, 0, 0, 184, 255, 89, 92, 255, 206, 1, 68, 255, 221, 3, 3, 225, 255, 81, 197, 255, 109, 0, 0, 119, 255, 200], "Top": 4, "Height": 10}, "89": {"Width": 8, "Advance": 8, "Bitmap": [201, 255, 76, 0, 0, 57, 255, 203, 92, 255, 172, 0, 0, 158, 255, 98, 6, 232, 249, 22, 17, 245, 233, 8, 0, 118, 255, 130, 120, 255, 120, 0, 0, 9, 227, 236, 234, 233, 11, 0, 0, 0, 98, 255, 255, 101, 0, 0, 0, 0, 2, 255, 255, 2, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0], "Top": 4, "Height": 10}, "90": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 177, 0, 0, 0, 148, 245, 31, 0, 0, 39, 251, 123, 0, 0, 0, 177, 222, 8, 0, 0, 62, 255, 86, 0, 0, 0, 200, 201, 0, 0, 0, 79, 255, 67, 0, 0, 0, 204, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "91": {"Width": 4, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 3, "Height": 14}, "92": {"Width": 6, "Advance": 8, "Bitmap": [220, 255, 44, 0, 0, 0, 147, 255, 116, 0, 0, 0, 75, 255, 189, 0, 0, 0, 10, 248, 249, 12, 0, 0, 0, 186, 255, 78, 0, 0, 0, 113, 255, 150, 0, 0, 0, 40, 255, 223, 0, 0, 0, 0, 223, 255, 40, 0, 0, 0, 151, 255, 112, 0, 0, 0, 79, 255, 185, 0, 0, 0, 12, 249, 247, 9, 0, 0, 0, 190, 255, 74, 0, 0, 0, 117, 255, 146, 0, 0, 0, 44, 255, 219], "Top": 3, "Height": 14}, "93": {"Width": 4, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 3, "Height": 14}, "94": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 49, 250, 251, 54, 0, 0, 0, 7, 211, 255, 255, 218, 11, 0, 0, 139, 255, 171, 169, 255, 155, 0, 60, 253, 235, 22, 19, 231, 255, 80, 74, 201, 88, 0, 0, 76, 208, 90], "Top": 4, "Height": 5}, "95": {"Width": 8, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 15, "Height": 2}, "96": {"Width": 4, "Advance": 8, "Bitmap": [64, 187, 34, 0, 175, 255, 245, 113, 0, 65, 179, 136], "Top": 3, "Height": 3}, "97": {"Width": 7, "Advance": 8, "Bitmap": [0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 6, "Height": 8}, "98": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 250, 172, 10, 255, 255, 255, 255, 255, 124, 255, 255, 0, 94, 255, 210, 255, 255, 0, 10, 255, 242, 255, 255, 0, 12, 255, 238, 255, 255, 0, 95, 255, 198, 255, 255, 243, 255, 255, 87, 183, 232, 250, 226, 101, 0], "Top": 3, "Height": 11}, "99": {"Width": 6, "Advance": 8, "Bitmap": [0, 71, 198, 246, 240, 172, 60, 253, 255, 255, 255, 201, 187, 255, 160, 26, 10, 32, 236, 255, 19, 0, 0, 0, 239, 255, 20, 0, 0, 0, 197, 255, 157, 23, 7, 34, 84, 255, 255, 255, 255, 218, 0, 86, 206, 248, 240, 182], "Top": 6, "Height": 8}, "100": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 118, 226, 254, 255, 255, 255, 90, 255, 255, 255, 255, 255, 255, 199, 255, 128, 8, 0, 255, 255, 239, 255, 14, 0, 0, 255, 255, 238, 255, 21, 0, 0, 255, 255, 194, 255, 154, 17, 6, 255, 255, 70, 255, 255, 255, 255, 255, 255, 0, 86, 206, 248, 246, 226, 185], "Top": 3, "Height": 11}, "101": {"Width": 8, "Advance": 8, "Bitmap": [0, 71, 203, 249, 225, 119, 0, 0, 63, 253, 255, 255, 255, 255, 93, 0, 190, 255, 85, 8, 76, 255, 203, 0, 244, 255, 255, 255, 255, 255, 241, 0, 246, 255, 255, 255, 255, 255, 255, 4, 196, 255, 107, 20, 1, 0, 0, 0, 64, 251, 255, 255, 255, 255, 203, 0, 0, 57, 179, 236, 251, 234, 178, 0], "Top": 6, "Height": 8}, "102": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 19, 159, 232, 248, 223, 147, 0, 0, 186, 255, 255, 255, 255, 208, 0, 0, 250, 255, 50, 6, 34, 54, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 3, "Height": 11}, "103": {"Width": 7, "Advance": 8, "Bitmap": [0, 67, 191, 243, 247, 224, 174, 72, 253, 255, 255, 255, 255, 255, 195, 255, 162, 23, 6, 255, 255, 245, 255, 23, 0, 0, 255, 255, 242, 255, 13, 0, 0, 255, 255, 206, 255, 129, 10, 34, 255, 255, 101, 255, 255, 255, 255, 255, 255, 1, 129, 230, 249, 210, 255, 244, 0, 0, 0, 6, 77, 255, 209, 27, 255, 255, 255, 255, 255, 106, 45, 198, 239, 250, 220, 122, 2], "Top": 6, "Height": 11}, "104": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 228, 246, 171, 16, 255, 255, 255, 255, 255, 153, 255, 255, 16, 82, 255, 228, 255, 255, 0, 8, 255, 251, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255], "Top": 3, "Height": 11}, "105": {"Width": 7, "Advance": 8, "Bitmap": [0, 201, 199, 0, 0, 0, 0, 0, 201, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 1, 0, 0, 0, 0, 238, 255, 63, 0, 0, 0, 0, 180, 255, 255, 255, 173, 0, 0, 33, 189, 244, 234, 136], "Top": 3, "Height": 11}, "106": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 0, 0, 201, 199, 0, 0, 0, 0, 201, 199, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 2, 255, 254, 48, 156, 25, 64, 255, 233, 122, 255, 255, 255, 255, 157, 48, 175, 234, 238, 163, 16], "Top": 3, "Height": 14}, "107": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 1, 182, 255, 141, 255, 255, 0, 129, 255, 158, 1, 255, 255, 94, 255, 161, 2, 0, 255, 255, 252, 216, 2, 0, 0, 255, 255, 179, 255, 136, 0, 0, 255, 255, 9, 205, 255, 100, 0, 255, 255, 0, 34, 241, 248, 49, 255, 255, 0, 0, 97, 196, 148], "Top": 3, "Height": 11}, "108": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 243, 255, 40, 0, 0, 0, 199, 255, 255, 223, 0, 0, 59, 220, 247, 187], "Top": 3, "Height": 11}, "109": {"Width": 7, "Advance": 8, "Bitmap": [185, 234, 244, 179, 232, 228, 76, 255, 255, 255, 255, 255, 255, 208, 255, 255, 35, 244, 37, 255, 245, 255, 255, 0, 255, 1, 255, 255, 255, 255, 0, 255, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255], "Top": 6, "Height": 8}, "110": {"Width": 7, "Advance": 8, "Bitmap": [175, 217, 238, 251, 229, 144, 8, 255, 255, 255, 255, 255, 255, 143, 255, 255, 7, 9, 115, 255, 222, 255, 255, 0, 0, 12, 255, 250, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255], "Top": 6, "Height": 8}, "111": {"Width": 7, "Advance": 8, "Bitmap": [0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 6, "Height": 8}, "112": {"Width": 6, "Advance": 8, "Bitmap": [178, 228, 249, 227, 118, 0, 255, 255, 255, 255, 255, 90, 255, 255, 8, 119, 255, 200, 255, 255, 0, 16, 255, 239, 255, 255, 0, 9, 255, 241, 255, 255, 20, 92, 255, 209, 255, 255, 255, 255, 255, 122, 255, 255, 210, 247, 164, 6, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 6, "Height": 11}, "113": {"Width": 6, "Advance": 8, "Bitmap": [0, 98, 225, 251, 232, 181, 85, 255, 255, 255, 255, 255, 197, 255, 117, 8, 255, 255, 239, 255, 15, 0, 255, 255, 243, 255, 9, 0, 255, 255, 212, 255, 92, 0, 255, 255, 126, 255, 255, 255, 255, 255, 7, 168, 250, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255], "Top": 6, "Height": 11}, "114": {"Width": 6, "Advance": 8, "Bitmap": [122, 195, 239, 253, 239, 187, 255, 255, 255, 255, 255, 200, 255, 255, 28, 3, 17, 34, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 6, "Height": 8}, "115": {"Width": 6, "Advance": 8, "Bitmap": [28, 163, 237, 249, 227, 158, 196, 255, 255, 255, 255, 213, 239, 255, 54, 8, 40, 65, 144, 255, 239, 158, 58, 0, 0, 79, 171, 244, 255, 125, 94, 48, 7, 49, 255, 236, 227, 255, 255, 255, 255, 209, 123, 217, 250, 241, 185, 42], "Top": 6, "Height": 8}, "116": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 250, 255, 48, 7, 48, 0, 0, 209, 255, 255, 255, 221, 0, 0, 62, 210, 249, 237, 170], "Top": 4, "Height": 10}, "117": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 251, 255, 14, 0, 0, 255, 255, 226, 255, 120, 10, 12, 255, 255, 139, 255, 255, 255, 255, 255, 255, 10, 148, 231, 251, 238, 215, 168], "Top": 6, "Height": 8}, "118": {"Width": 7, "Advance": 8, "Bitmap": [166, 255, 49, 0, 1, 236, 220, 97, 255, 110, 0, 53, 255, 149, 26, 254, 182, 0, 133, 255, 75, 0, 203, 247, 11, 218, 241, 6, 0, 123, 255, 131, 255, 162, 0, 0, 35, 255, 251, 255, 67, 0, 0, 0, 198, 255, 224, 2, 0, 0, 0, 98, 255, 123, 0, 0], "Top": 6, "Height": 8}, "119": {"Width": 8, "Advance": 8, "Bitmap": [245, 255, 0, 0, 0, 0, 255, 244, 224, 255, 0, 0, 0, 0, 255, 221, 201, 255, 64, 255, 116, 0, 255, 195, 174, 255, 113, 255, 217, 1, 255, 164, 143, 255, 166, 235, 255, 75, 255, 130, 105, 255, 227, 84, 242, 199, 255, 89, 63, 255, 245, 8, 123, 255, 255, 45, 13, 253, 179, 0, 8, 222, 246, 3], "Top": 6, "Height": 8}, "120": {"Width": 8, "Advance": 8, "Bitmap": [154, 255, 212, 9, 0, 132, 255, 168, 6, 199, 255, 153, 55, 252, 230, 18, 0, 26, 231, 255, 234, 255, 74, 0, 0, 0, 58, 253, 255, 160, 0, 0, 0, 0, 109, 255, 255, 210, 8, 0, 0, 64, 251, 238, 204, 255, 138, 0, 22, 231, 255, 83, 32, 243, 252, 46, 174, 255, 180, 0, 0, 112, 255, 188], "Top": 6, "Height": 8}, "121": {"Width": 8, "Advance": 8, "Bitmap": [0, 222, 255, 34, 0, 30, 255, 222, 0, 153, 255, 105, 0, 89, 255, 156, 0, 80, 255, 180, 0, 152, 255, 88, 0, 9, 244, 249, 16, 219, 253, 19, 0, 0, 165, 255, 141, 255, 200, 0, 0, 0, 66, 255, 254, 255, 120, 0, 0, 0, 1, 214, 255, 255, 37, 0, 0, 0, 0, 123, 255, 196, 0, 0, 0, 0, 44, 228, 255, 86, 0, 0, 174, 255, 255, 255, 185, 0, 0, 0, 198, 249, 234, 156, 13, 0, 0, 0], "Top": 6, "Height": 11}, "122": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 153, 0, 0, 29, 237, 201, 6, 0, 3, 195, 240, 31, 0, 0, 126, 255, 90, 0, 0, 49, 250, 175, 0, 0, 0, 206, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 6, "Height": 8}, "123": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 77, 220, 252, 255, 0, 0, 221, 255, 255, 255, 0, 0, 253, 255, 36, 0, 0, 0, 255, 255, 0, 0, 0, 1, 255, 255, 0, 0, 0, 59, 255, 228, 0, 0, 255, 255, 218, 76, 0, 0, 255, 255, 219, 76, 0, 0, 0, 61, 255, 228, 0, 0, 0, 1, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 253, 255, 36, 0, 0, 0, 221, 255, 255, 255, 0, 0, 77, 221, 253, 255], "Top": 3, "Height": 14}, "124": {"Width": 2, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 3, "Height": 14}, "125": {"Width": 6, "Advance": 8, "Bitmap": [255, 252, 218, 74, 0, 0, 255, 255, 255, 221, 0, 0, 0, 38, 255, 253, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 1, 0, 0, 0, 229, 255, 59, 0, 0, 0, 78, 220, 255, 255, 0, 0, 74, 217, 255, 255, 0, 0, 228, 255, 61, 0, 0, 0, 255, 255, 1, 0, 0, 0, 255, 255, 0, 0, 0, 37, 255, 253, 0, 0, 255, 255, 255, 222, 0, 0, 255, 253, 219, 74, 0, 0], "Top": 3, "Height": 14}, "126": {"Width": 7, "Advance": 8, "Bitmap": [31, 211, 234, 134, 22, 146, 178, 174, 255, 255, 255, 255, 255, 168, 180, 143, 24, 137, 235, 212, 29], "Top": 8, "Height": 3}, "160": {"Width": 0, "Advance": 8, "Bitmap": [], "Top": 14, "Height": 0}, "161": {"Width": 2, "Advance": 8, "Bitmap": [199, 198, 199, 198, 0, 0, 202, 193, 226, 219, 245, 240, 254, 251, 255, 255, 255, 255, 255, 255], "Top": 7, "Height": 10}, "162": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 2, 0, 78, 206, 255, 255, 217, 70, 252, 255, 255, 255, 188, 191, 255, 151, 21, 0, 0, 240, 255, 18, 0, 0, 0, 240, 255, 19, 0, 0, 0, 193, 255, 154, 22, 7, 33, 73, 253, 255, 255, 255, 219, 0, 80, 207, 255, 255, 227, 0, 0, 0, 255, 255, 3, 0, 0, 0, 255, 255, 0], "Top": 4, "Height": 12}, "163": {"Width": 6, "Advance": 8, "Bitmap": [0, 24, 171, 238, 236, 148, 0, 172, 255, 255, 255, 162, 0, 240, 255, 56, 81, 54, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 0, 255, 249, 0, 0, 0, 0, 255, 224, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255], "Top": 4, "Height": 10}, "164": {"Width": 8, "Advance": 8, "Bitmap": [52, 190, 17, 0, 0, 16, 189, 54, 156, 255, 226, 242, 242, 225, 255, 153, 0, 217, 255, 255, 255, 255, 216, 0, 0, 249, 255, 60, 60, 255, 248, 0, 0, 216, 255, 255, 255, 255, 216, 0, 153, 255, 226, 245, 245, 227, 255, 155, 55, 189, 16, 0, 0, 17, 190, 52], "Top": 6, "Height": 7}, "165": {"Width": 8, "Advance": 8, "Bitmap": [185, 255, 52, 0, 0, 52, 255, 182, 46, 253, 156, 0, 0, 155, 254, 44, 0, 161, 246, 24, 24, 245, 172, 0, 0, 38, 252, 146, 147, 255, 55, 0, 0, 0, 162, 248, 248, 188, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0], "Top": 4, "Height": 10}, "166": {"Width": 2, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 3, "Height": 14}, "167": {"Width": 6, "Advance": 8, "Bitmap": [27, 167, 232, 247, 218, 148, 190, 255, 255, 255, 255, 154, 241, 255, 74, 1, 0, 0, 162, 255, 255, 224, 113, 2, 183, 255, 255, 255, 255, 148, 250, 255, 47, 133, 255, 241, 240, 255, 125, 44, 255, 228, 149, 255, 255, 255, 255, 121, 4, 119, 231, 255, 255, 175, 133, 91, 14, 82, 255, 243, 218, 255, 255, 255, 255, 202, 105, 207, 247, 241, 188, 41], "Top": 4, "Height": 12}, "168": {"Width": 5, "Advance": 8, "Bitmap": [199, 198, 0, 199, 199, 201, 199, 0, 201, 201], "Top": 3, "Height": 2}, "169": {"Width": 8, "Advance": 8, "Bitmap": [0, 58, 185, 244, 243, 184, 56, 0, 57, 241, 113, 23, 23, 113, 240, 55, 189, 108, 78, 227, 245, 38, 112, 186, 244, 14, 229, 58, 10, 0, 15, 244, 244, 14, 232, 61, 11, 0, 15, 244, 188, 105, 84, 230, 242, 42, 109, 184, 43, 236, 107, 21, 21, 108, 235, 39, 0, 43, 181, 244, 244, 179, 42, 0], "Top": 6, "Height": 8}, "170": {"Width": 5, "Advance": 8, "Bitmap": [0, 213, 249, 225, 83, 0, 21, 53, 255, 226, 7, 72, 90, 255, 254, 191, 255, 184, 255, 255, 240, 255, 22, 255, 255, 100, 224, 250, 238, 205], "Top": 4, "Height": 6}, "171": {"Width": 8, "Advance": 8, "Bitmap": [0, 41, 193, 16, 0, 46, 191, 14, 1, 194, 218, 1, 1, 198, 216, 1, 99, 255, 110, 0, 101, 255, 108, 0, 216, 255, 27, 0, 216, 255, 27, 0, 93, 255, 111, 0, 96, 255, 110, 0, 0, 188, 220, 2, 0, 193, 218, 1, 0, 37, 191, 15, 0, 41, 190, 14], "Top": 6, "Height": 7}, "172": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255], "Top": 8, "Height": 6}, "174": {"Width": 8, "Advance": 8, "Bitmap": [0, 58, 185, 244, 243, 184, 56, 0, 57, 241, 113, 23, 23, 113, 240, 55, 189, 108, 0, 239, 245, 142, 112, 186, 244, 14, 0, 255, 45, 234, 15, 244, 244, 14, 0, 255, 255, 82, 15, 244, 188, 105, 0, 255, 126, 198, 114, 184, 43, 236, 107, 21, 21, 108, 235, 39, 0, 43, 181, 244, 244, 179, 42, 0], "Top": 6, "Height": 8}, "175": {"Width": 5, "Advance": 8, "Bitmap": [255, 255, 255, 255, 255], "Top": 4, "Height": 1}, "176": {"Width": 4, "Advance": 8, "Bitmap": [88, 232, 230, 81, 233, 53, 51, 229, 234, 54, 52, 230, 94, 234, 231, 83], "Top": 3, "Height": 4}, "177": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 5, "Height": 9}, "178": {"Width": 4, "Advance": 8, "Bitmap": [115, 232, 239, 110, 139, 41, 255, 241, 0, 90, 255, 211, 42, 240, 238, 42, 193, 255, 83, 0, 250, 255, 255, 255], "Top": 4, "Height": 6}, "179": {"Width": 5, "Advance": 8, "Bitmap": [146, 233, 237, 128, 0, 133, 47, 255, 241, 0, 0, 255, 255, 174, 0, 0, 1, 62, 254, 16, 136, 32, 52, 252, 15, 179, 244, 230, 108, 0], "Top": 4, "Height": 6}, "180": {"Width": 4, "Advance": 8, "Bitmap": [0, 35, 187, 63, 113, 245, 255, 174, 136, 179, 64, 0], "Top": 3, "Height": 3}, "181": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 8, 0, 255, 255, 255, 255, 84, 6, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251, 230, 248, 225, 172, 255, 246, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 6, "Height": 11}, "182": {"Width": 7, "Advance": 8, "Bitmap": [1, 104, 203, 240, 248, 230, 190, 129, 255, 255, 255, 254, 255, 255, 238, 255, 255, 255, 1, 255, 255, 243, 255, 255, 255, 0, 255, 255, 188, 255, 255, 255, 0, 255, 255, 36, 200, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255, 0, 0, 255, 255, 0, 255, 255], "Top": 4, "Height": 13}, "183": {"Width": 3, "Advance": 9, "Bitmap": [91, 243, 85, 234, 255, 212, 91, 244, 85], "Top": 8, "Height": 3}, "184": {"Width": 3, "Advance": 8, "Bitmap": [0, 176, 122, 0, 61, 235, 207, 247, 150], "Top": 14, "Height": 3}, "185": {"Width": 5, "Advance": 8, "Bitmap": [27, 122, 236, 255, 0, 208, 224, 255, 255, 0, 26, 2, 255, 255, 0, 0, 0, 255, 255, 0, 255, 255, 255, 255, 255], "Top": 4, "Height": 5}, "186": {"Width": 6, "Advance": 8, "Bitmap": [5, 136, 238, 239, 137, 5, 122, 255, 255, 255, 255, 122, 217, 255, 75, 75, 255, 216, 246, 255, 6, 7, 255, 246, 218, 255, 73, 73, 255, 218, 125, 255, 255, 255, 255, 126, 5, 140, 231, 231, 140, 6], "Top": 4, "Height": 7}, "187": {"Width": 7, "Advance": 8, "Bitmap": [165, 77, 0, 159, 90, 0, 0, 153, 233, 21, 145, 237, 25, 0, 32, 250, 176, 22, 243, 181, 0, 0, 187, 255, 55, 162, 255, 56, 31, 250, 179, 21, 242, 183, 0, 152, 235, 24, 144, 240, 28, 0, 164, 84, 0, 159, 97, 0, 0], "Top": 6, "Height": 7}, "188": {"Width": 8, "Advance": 8, "Bitmap": [85, 247, 0, 0, 0, 60, 191, 0, 236, 255, 0, 0, 0, 188, 63, 0, 50, 255, 0, 0, 61, 190, 0, 0, 0, 255, 0, 0, 189, 62, 0, 0, 0, 0, 0, 62, 189, 0, 0, 0, 0, 0, 0, 190, 61, 0, 0, 0, 0, 0, 63, 188, 1, 186, 255, 0, 0, 0, 191, 60, 110, 149, 255, 0, 0, 64, 188, 0, 241, 255, 255, 255, 0, 191, 60, 0, 0, 0, 255, 0], "Top": 4, "Height": 10}, "189": {"Width": 8, "Advance": 8, "Bitmap": [69, 246, 0, 0, 0, 35, 213, 6, 198, 255, 0, 0, 0, 174, 81, 0, 0, 255, 0, 0, 63, 191, 0, 0, 0, 255, 0, 2, 204, 49, 0, 0, 0, 0, 0, 97, 158, 0, 0, 0, 0, 0, 12, 218, 25, 0, 0, 0, 0, 0, 132, 123, 0, 143, 245, 176, 0, 30, 216, 9, 0, 0, 40, 234, 0, 166, 89, 0, 0, 75, 205, 65, 56, 198, 1, 0, 0, 229, 255, 255], "Top": 4, "Height": 10}, "190": {"Width": 8, "Advance": 8, "Bitmap": [156, 246, 126, 0, 0, 3, 206, 46, 11, 91, 199, 0, 0, 102, 153, 0, 25, 255, 236, 0, 14, 219, 22, 0, 209, 250, 169, 0, 136, 119, 0, 0, 0, 0, 0, 33, 215, 7, 0, 0, 0, 0, 0, 171, 85, 0, 0, 0, 0, 0, 60, 194, 0, 171, 255, 88, 0, 1, 201, 52, 94, 164, 240, 88, 0, 94, 161, 0, 225, 255, 255, 219, 10, 217, 27, 0, 0, 0, 240, 88], "Top": 4, "Height": 10}, "191": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 199, 193, 0, 0, 0, 0, 200, 196, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 255, 48, 0, 0, 67, 254, 244, 10, 0, 29, 236, 255, 115, 0, 0, 170, 255, 173, 2, 0, 0, 240, 255, 19, 0, 0, 0, 233, 255, 79, 11, 103, 129, 148, 255, 255, 255, 255, 217, 13, 160, 240, 247, 196, 73], "Top": 6, "Height": 11}, "192": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 64, 187, 34, 0, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 0, "Height": 14}, "193": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 0, "Height": 14}, "194": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 1, 139, 139, 1, 0, 0, 0, 4, 162, 255, 255, 161, 4, 0, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 0, "Height": 14}, "195": {"Width": 8, "Advance": 8, "Bitmap": [0, 75, 226, 203, 49, 86, 167, 0, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 1, "Height": 13}, "196": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 199, 198, 0, 199, 199, 0, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 255, 92, 0, 0, 0, 0, 122, 255, 255, 172, 0, 0, 0, 0, 204, 234, 207, 244, 7, 0, 0, 32, 255, 165, 135, 255, 71, 0, 0, 113, 255, 98, 72, 255, 146, 0, 0, 191, 255, 42, 18, 255, 217, 0, 16, 251, 255, 255, 255, 255, 255, 30, 85, 255, 255, 255, 255, 255, 255, 98, 154, 255, 147, 0, 0, 130, 255, 161, 222, 255, 89, 0, 0, 70, 255, 224], "Top": 1, "Height": 13}, "197": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 27, 133, 134, 30, 0, 0, 0, 0, 197, 137, 137, 209, 0, 0, 0, 0, 213, 114, 113, 221, 0, 0, 0, 0, 134, 255, 255, 149, 0, 0, 0, 0, 195, 255, 255, 211, 0, 0, 0, 16, 252, 206, 208, 255, 28, 0, 0, 85, 255, 113, 115, 255, 100, 0, 0, 158, 255, 31, 33, 255, 172, 0, 0, 227, 214, 0, 0, 218, 237, 1, 39, 255, 255, 255, 255, 255, 255, 47, 104, 255, 255, 255, 255, 255, 255, 111, 165, 255, 70, 0, 0, 76, 255, 169, 226, 255, 18, 0, 0, 20, 255, 227], "Top": 1, "Height": 13}, "198": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 45, 255, 255, 255, 255, 255, 0, 0, 135, 243, 255, 255, 255, 255, 0, 0, 222, 183, 255, 255, 0, 0, 0, 52, 255, 118, 255, 255, 0, 0, 0, 134, 255, 57, 255, 255, 255, 0, 0, 210, 247, 6, 255, 255, 255, 0, 28, 255, 255, 255, 255, 255, 0, 0, 98, 255, 255, 255, 255, 255, 0, 0, 163, 255, 104, 0, 255, 255, 255, 255, 225, 255, 48, 0, 255, 255, 255, 255], "Top": 4, "Height": 10}, "199": {"Width": 8, "Advance": 8, "Bitmap": [0, 14, 140, 225, 248, 209, 91, 0, 9, 206, 255, 255, 255, 255, 223, 0, 122, 255, 216, 58, 9, 72, 133, 0, 209, 255, 64, 0, 0, 0, 0, 0, 246, 255, 8, 0, 0, 0, 0, 0, 248, 255, 16, 0, 0, 0, 0, 0, 213, 255, 74, 0, 0, 0, 0, 0, 126, 255, 221, 65, 11, 29, 155, 2, 9, 191, 255, 255, 255, 255, 255, 1, 0, 4, 116, 205, 255, 240, 123, 0, 0, 0, 0, 10, 236, 121, 0, 0, 0, 0, 0, 16, 46, 239, 0, 0, 0, 0, 0, 209, 244, 133, 0, 0], "Top": 4, "Height": 13}, "200": {"Width": 6, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "201": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 35, 187, 63, 0, 0, 113, 245, 255, 174, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "202": {"Width": 6, "Advance": 8, "Bitmap": [0, 1, 139, 139, 1, 0, 4, 162, 255, 255, 161, 4, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "203": {"Width": 6, "Advance": 8, "Bitmap": [199, 198, 0, 199, 199, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 1, "Height": 13}, "204": {"Width": 6, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "205": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 35, 187, 63, 0, 0, 113, 245, 255, 174, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "206": {"Width": 6, "Advance": 8, "Bitmap": [0, 1, 139, 139, 1, 0, 4, 162, 255, 255, 161, 4, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 0, "Height": 14}, "207": {"Width": 6, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "Top": 1, "Height": 13}, "208": {"Width": 8, "Advance": 8, "Bitmap": [0, 213, 243, 252, 231, 161, 31, 0, 0, 255, 255, 255, 255, 255, 231, 25, 0, 255, 255, 4, 42, 212, 255, 144, 0, 255, 255, 0, 0, 65, 255, 218, 255, 255, 255, 255, 0, 12, 255, 247, 255, 255, 255, 255, 0, 15, 255, 246, 0, 255, 255, 0, 0, 72, 255, 214, 0, 255, 255, 9, 62, 220, 255, 135, 0, 255, 255, 255, 255, 255, 219, 17, 0, 219, 247, 250, 223, 146, 19, 0], "Top": 4, "Height": 10}, "209": {"Width": 7, "Advance": 8, "Bitmap": [0, 75, 226, 203, 49, 86, 167, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 255, 246, 31, 0, 0, 255, 255, 255, 255, 171, 0, 0, 255, 255, 255, 255, 255, 53, 0, 255, 255, 255, 255, 229, 185, 0, 255, 255, 255, 255, 106, 255, 57, 255, 255, 255, 255, 6, 223, 178, 255, 255, 255, 255, 0, 97, 254, 255, 255, 255, 255, 0, 5, 229, 255, 255, 255, 255, 0, 0, 120, 255, 255, 255, 255, 0, 0, 17, 244, 255], "Top": 1, "Height": 13}, "210": {"Width": 7, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 0, "Height": 14}, "211": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 35, 187, 63, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 0, "Height": 14}, "212": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 1, 139, 139, 1, 0, 0, 4, 162, 255, 255, 161, 4, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 0, "Height": 14}, "213": {"Width": 7, "Advance": 8, "Bitmap": [0, 75, 226, 203, 49, 86, 167, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 1, "Height": 13}, "214": {"Width": 7, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 213, 249, 215, 83, 0, 55, 252, 255, 255, 255, 252, 57, 168, 255, 146, 19, 145, 255, 169, 226, 255, 32, 0, 34, 255, 226, 250, 255, 5, 0, 7, 255, 249, 250, 255, 5, 0, 7, 255, 249, 227, 255, 31, 0, 34, 255, 225, 170, 255, 145, 19, 147, 255, 168, 59, 253, 255, 255, 255, 252, 56, 0, 84, 215, 250, 215, 82, 0], "Top": 1, "Height": 13}, "215": {"Width": 6, "Advance": 8, "Bitmap": [102, 139, 0, 0, 141, 99, 156, 255, 132, 133, 255, 154, 1, 144, 255, 255, 140, 0, 0, 129, 255, 255, 132, 0, 145, 255, 124, 131, 255, 148, 98, 123, 0, 0, 129, 92], "Top": 7, "Height": 6}, "216": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 0, 0, 0, 150, 18, 0, 34, 169, 237, 249, 214, 253, 52, 25, 233, 255, 255, 236, 255, 255, 0, 145, 255, 181, 20, 143, 255, 255, 0, 216, 255, 44, 57, 245, 255, 255, 0, 247, 255, 14, 216, 146, 255, 255, 0, 250, 255, 137, 247, 31, 255, 248, 0, 226, 255, 252, 143, 30, 255, 223, 0, 165, 255, 254, 46, 136, 255, 165, 0, 49, 254, 255, 255, 255, 252, 54, 0, 46, 246, 213, 249, 215, 81, 0, 0, 17, 139, 0, 0, 0, 0, 0, 0], "Top": 3, "Height": 12}, "217": {"Width": 6, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 249, 255, 7, 8, 255, 249, 225, 255, 71, 73, 255, 225, 148, 255, 255, 255, 255, 145, 14, 168, 245, 244, 164, 13], "Top": 0, "Height": 14}, "218": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 35, 187, 63, 0, 0, 113, 245, 255, 174, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 249, 255, 7, 8, 255, 249, 225, 255, 71, 73, 255, 225, 148, 255, 255, 255, 255, 145, 14, 168, 245, 244, 164, 13], "Top": 0, "Height": 14}, "219": {"Width": 6, "Advance": 8, "Bitmap": [0, 1, 139, 139, 1, 0, 4, 162, 255, 255, 161, 4, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 249, 255, 7, 8, 255, 249, 225, 255, 71, 73, 255, 225, 148, 255, 255, 255, 255, 145, 14, 168, 245, 244, 164, 13], "Top": 0, "Height": 14}, "220": {"Width": 6, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 249, 255, 7, 8, 255, 249, 225, 255, 71, 73, 255, 225, 148, 255, 255, 255, 255, 145, 14, 168, 245, 244, 164, 13], "Top": 1, "Height": 13}, "221": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 255, 76, 0, 0, 57, 255, 203, 92, 255, 172, 0, 0, 158, 255, 98, 6, 232, 249, 22, 17, 245, 233, 8, 0, 118, 255, 130, 120, 255, 120, 0, 0, 9, 227, 236, 234, 233, 11, 0, 0, 0, 98, 255, 255, 101, 0, 0, 0, 0, 2, 255, 255, 2, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0], "Top": 0, "Height": 14}, "222": {"Width": 6, "Advance": 8, "Bitmap": [255, 255, 2, 0, 0, 0, 255, 255, 254, 226, 136, 8, 255, 255, 249, 255, 255, 133, 255, 255, 0, 84, 255, 220, 255, 255, 0, 9, 255, 246, 255, 255, 3, 100, 255, 217, 255, 255, 255, 255, 255, 121, 255, 255, 252, 221, 125, 4, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0], "Top": 4, "Height": 10}, "223": {"Width": 7, "Advance": 8, "Bitmap": [9, 146, 238, 249, 193, 44, 0, 140, 255, 255, 255, 255, 206, 0, 225, 255, 88, 48, 255, 249, 0, 252, 255, 5, 44, 255, 194, 0, 255, 255, 0, 176, 255, 60, 0, 255, 255, 0, 244, 255, 20, 0, 255, 255, 0, 184, 255, 191, 17, 255, 255, 0, 7, 141, 255, 181, 255, 255, 0, 115, 40, 255, 245, 255, 255, 0, 231, 255, 255, 215, 255, 255, 0, 177, 248, 219, 70], "Top": 3, "Height": 11}, "224": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 64, 187, 34, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 2, "Height": 12}, "225": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 2, "Height": 12}, "226": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 1, 139, 139, 1, 0, 0, 4, 162, 255, 255, 161, 4, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 2, "Height": 12}, "227": {"Width": 7, "Advance": 8, "Bitmap": [0, 75, 226, 203, 49, 86, 167, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 3, "Height": 11}, "228": {"Width": 7, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 3, "Height": 11}, "229": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 141, 244, 141, 0, 0, 0, 0, 244, 72, 244, 0, 0, 0, 0, 143, 245, 143, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 232, 250, 235, 164, 19, 0, 211, 255, 255, 255, 255, 163, 0, 35, 15, 6, 71, 255, 235, 22, 160, 230, 250, 234, 255, 255, 198, 255, 255, 255, 255, 255, 255, 249, 255, 76, 6, 8, 255, 255, 195, 255, 255, 255, 255, 255, 255, 27, 168, 234, 251, 241, 223, 186], "Top": 2, "Height": 12}, "230": {"Width": 9, "Advance": 8, "Bitmap": [0, 182, 243, 205, 99, 226, 207, 37, 0, 0, 218, 255, 255, 255, 255, 255, 170, 0, 0, 36, 14, 183, 255, 144, 81, 234, 0, 47, 203, 249, 247, 255, 255, 255, 255, 2, 206, 255, 255, 255, 255, 255, 255, 255, 6, 245, 255, 55, 123, 255, 150, 7, 26, 0, 189, 255, 255, 255, 255, 255, 255, 214, 0, 34, 194, 244, 196, 102, 214, 246, 186, 0], "Top": 6, "Height": 8}, "231": {"Width": 6, "Advance": 8, "Bitmap": [0, 70, 198, 246, 242, 179, 57, 253, 255, 255, 255, 190, 183, 255, 160, 26, 0, 0, 235, 255, 19, 0, 0, 0, 241, 255, 20, 0, 0, 0, 192, 255, 157, 23, 0, 0, 64, 249, 255, 255, 255, 211, 0, 59, 185, 252, 253, 206, 0, 0, 0, 211, 114, 0, 0, 0, 0, 44, 238, 0, 0, 0, 207, 245, 136, 0], "Top": 6, "Height": 11}, "232": {"Width": 8, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 203, 249, 225, 119, 0, 0, 63, 253, 255, 255, 255, 255, 93, 0, 190, 255, 85, 8, 76, 255, 203, 0, 244, 255, 255, 255, 255, 255, 241, 0, 246, 255, 255, 255, 255, 255, 255, 4, 196, 255, 107, 20, 1, 0, 0, 0, 64, 251, 255, 255, 255, 255, 203, 0, 0, 57, 179, 236, 251, 234, 178, 0], "Top": 2, "Height": 12}, "233": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 203, 249, 225, 119, 0, 0, 63, 253, 255, 255, 255, 255, 93, 0, 190, 255, 85, 8, 76, 255, 203, 0, 244, 255, 255, 255, 255, 255, 241, 0, 246, 255, 255, 255, 255, 255, 255, 4, 196, 255, 107, 20, 1, 0, 0, 0, 64, 251, 255, 255, 255, 255, 203, 0, 0, 57, 179, 236, 251, 234, 178, 0], "Top": 2, "Height": 12}, "234": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 1, 139, 139, 1, 0, 0, 0, 4, 162, 255, 255, 161, 4, 0, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 203, 249, 225, 119, 0, 0, 63, 253, 255, 255, 255, 255, 93, 0, 190, 255, 85, 8, 76, 255, 203, 0, 244, 255, 255, 255, 255, 255, 241, 0, 246, 255, 255, 255, 255, 255, 255, 4, 196, 255, 107, 20, 1, 0, 0, 0, 64, 251, 255, 255, 255, 255, 203, 0, 0, 57, 179, 236, 251, 234, 178, 0], "Top": 2, "Height": 12}, "235": {"Width": 8, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 203, 249, 225, 119, 0, 0, 63, 253, 255, 255, 255, 255, 93, 0, 190, 255, 85, 8, 76, 255, 203, 0, 244, 255, 255, 255, 255, 255, 241, 0, 246, 255, 255, 255, 255, 255, 255, 4, 196, 255, 107, 20, 1, 0, 0, 0, 64, 251, 255, 255, 255, 255, 203, 0, 0, 57, 179, 236, 251, 234, 178, 0], "Top": 3, "Height": 11}, "236": {"Width": 6, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 241, 255, 47, 103, 0, 0, 193, 255, 255, 239, 0, 0, 53, 215, 241, 155], "Top": 2, "Height": 12}, "237": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 35, 187, 63, 0, 0, 113, 245, 255, 174, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 241, 255, 47, 103, 0, 0, 193, 255, 255, 239, 0, 0, 53, 215, 241, 155], "Top": 2, "Height": 12}, "238": {"Width": 6, "Advance": 8, "Bitmap": [0, 1, 139, 139, 1, 0, 4, 162, 255, 255, 161, 4, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 241, 255, 47, 103, 0, 0, 193, 255, 255, 239, 0, 0, 53, 215, 241, 155], "Top": 2, "Height": 12}, "239": {"Width": 6, "Advance": 8, "Bitmap": [199, 198, 0, 199, 199, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 241, 255, 47, 103, 0, 0, 193, 255, 255, 239, 0, 0, 53, 215, 241, 155], "Top": 3, "Height": 11}, "240": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 109, 207, 59, 38, 74, 0, 0, 113, 255, 253, 255, 195, 0, 0, 196, 197, 217, 255, 53, 0, 0, 1, 0, 58, 255, 146, 0, 85, 203, 237, 247, 255, 208, 97, 255, 255, 255, 255, 255, 244, 213, 255, 116, 21, 13, 255, 247, 245, 255, 10, 0, 32, 255, 222, 212, 255, 108, 15, 149, 255, 164, 104, 255, 255, 255, 255, 254, 59, 0, 106, 223, 249, 215, 88, 0], "Top": 3, "Height": 11}, "241": {"Width": 7, "Advance": 8, "Bitmap": [75, 226, 203, 49, 86, 167, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 0, 175, 217, 238, 251, 229, 144, 8, 255, 255, 255, 255, 255, 255, 143, 255, 255, 7, 9, 115, 255, 222, 255, 255, 0, 0, 12, 255, 250, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255], "Top": 3, "Height": 11}, "242": {"Width": 7, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 2, "Height": 12}, "243": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 2, "Height": 12}, "244": {"Width": 7, "Advance": 8, "Bitmap": [0, 1, 139, 139, 1, 0, 0, 4, 162, 255, 255, 161, 4, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 2, "Height": 12}, "245": {"Width": 7, "Advance": 8, "Bitmap": [0, 75, 226, 203, 49, 86, 167, 0, 167, 85, 52, 206, 225, 75, 0, 0, 0, 0, 0, 0, 0, 0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 3, "Height": 11}, "246": {"Width": 7, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 214, 249, 215, 89, 0, 64, 255, 255, 255, 255, 255, 66, 195, 255, 130, 13, 126, 255, 194, 246, 255, 16, 0, 15, 255, 246, 245, 255, 14, 0, 17, 255, 245, 192, 255, 123, 13, 133, 255, 192, 64, 254, 255, 255, 255, 254, 63, 0, 75, 215, 250, 216, 76, 0], "Top": 3, "Height": 11}, "247": {"Width": 6, "Advance": 8, "Bitmap": [0, 0, 199, 200, 0, 0, 0, 0, 201, 202, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 199, 200, 0, 0, 0, 0, 199, 200, 0, 0], "Top": 6, "Height": 8}, "248": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 0, 0, 21, 9, 0, 86, 214, 248, 212, 206, 158, 64, 255, 255, 255, 255, 255, 86, 195, 255, 127, 45, 246, 255, 187, 246, 252, 11, 195, 131, 240, 237, 226, 239, 129, 195, 12, 253, 237, 86, 254, 245, 44, 128, 255, 196, 0, 146, 255, 255, 255, 255, 87, 0, 200, 189, 238, 235, 113, 0, 0, 29, 1, 0, 0, 0, 0], "Top": 5, "Height": 10}, "249": {"Width": 7, "Advance": 8, "Bitmap": [0, 64, 187, 34, 0, 0, 0, 0, 175, 255, 245, 113, 0, 0, 0, 0, 65, 179, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 251, 255, 14, 0, 0, 255, 255, 226, 255, 120, 10, 12, 255, 255, 139, 255, 255, 255, 255, 255, 255, 10, 148, 231, 251, 238, 215, 168], "Top": 2, "Height": 12}, "250": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 251, 255, 14, 0, 0, 255, 255, 226, 255, 120, 10, 12, 255, 255, 139, 255, 255, 255, 255, 255, 255, 10, 148, 231, 251, 238, 215, 168], "Top": 2, "Height": 12}, "251": {"Width": 7, "Advance": 8, "Bitmap": [0, 0, 1, 139, 139, 1, 0, 0, 4, 162, 255, 255, 161, 4, 0, 8, 186, 91, 89, 188, 8, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 251, 255, 14, 0, 0, 255, 255, 226, 255, 120, 10, 12, 255, 255, 139, 255, 255, 255, 255, 255, 255, 10, 148, 231, 251, 238, 215, 168], "Top": 2, "Height": 12}, "252": {"Width": 7, "Advance": 8, "Bitmap": [0, 199, 198, 0, 199, 199, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 251, 255, 14, 0, 0, 255, 255, 226, 255, 120, 10, 12, 255, 255, 139, 255, 255, 255, 255, 255, 255, 10, 148, 231, 251, 238, 215, 168], "Top": 3, "Height": 11}, "253": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 0, 35, 187, 63, 0, 0, 0, 0, 113, 245, 255, 174, 0, 0, 0, 0, 136, 179, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 222, 255, 34, 0, 30, 255, 222, 0, 153, 255, 105, 0, 89, 255, 156, 0, 80, 255, 180, 0, 152, 255, 88, 0, 9, 244, 249, 16, 219, 253, 19, 0, 0, 165, 255, 141, 255, 200, 0, 0, 0, 66, 255, 254, 255, 120, 0, 0, 0, 1, 214, 255, 255, 37, 0, 0, 0, 0, 123, 255, 196, 0, 0, 0, 0, 44, 228, 255, 86, 0, 0, 174, 255, 255, 255, 185, 0, 0, 0, 198, 249, 234, 156, 13, 0, 0, 0], "Top": 2, "Height": 15}, "254": {"Width": 7, "Advance": 8, "Bitmap": [255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 240, 246, 198, 73, 0, 255, 255, 255, 255, 255, 254, 71, 255, 255, 8, 19, 163, 255, 196, 255, 255, 0, 0, 24, 255, 246, 255, 255, 0, 0, 14, 255, 238, 255, 255, 28, 10, 131, 255, 197, 255, 255, 255, 255, 255, 255, 90, 255, 255, 203, 248, 228, 106, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0], "Top": 3, "Height": 14}, "255": {"Width": 8, "Advance": 8, "Bitmap": [0, 0, 199, 198, 0, 199, 199, 0, 0, 0, 201, 199, 0, 201, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 222, 255, 34, 0, 30, 255, 222, 0, 153, 255, 105, 0, 89, 255, 156, 0, 80, 255, 180, 0, 152, 255, 88, 0, 9, 244, 249, 16, 219, 253, 19, 0, 0, 165, 255, 141, 255, 200, 0, 0, 0, 66, 255, 254, 255, 120, 0, 0, 0, 1, 214, 255, 255, 37, 0, 0, 0, 0, 123, 255, 196, 0, 0, 0, 0, 44, 228, 255, 86, 0, 0, 174, 255, 255, 255, 185, 0, 0, 0, 198, 249, 234, 156, 13, 0, 0, 0], "Top": 3, "Height": 14}}, "Size": 16}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/GenerateErrorCodes.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,143 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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/>. + + +import json +import os +import re +import sys + +START_PLUGINS = 1000000 +BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + + + +## +## Read all the available error codes and HTTP status +## + +with open(os.path.join(BASE, 'Resources', 'ErrorCodes.json'), 'r') as f: + ERRORS = json.loads(re.sub('/\*.*?\*/', '', f.read())) + +for error in ERRORS: + if error['Code'] >= START_PLUGINS: + print('ERROR: Error code must be below %d, but "%s" is set to %d' % (START_PLUGINS, error['Name'], error['Code'])) + sys.exit(-1) + +with open(os.path.join(BASE, 'Core', 'Enumerations.h'), 'r') as f: + a = f.read() + +HTTP = {} +for i in re.findall('(HttpStatus_([0-9]+)_\w+)', a): + HTTP[int(i[1])] = i[0] + + + +## +## Generate the "ErrorCode" enumeration in "Enumerations.h" +## + +path = os.path.join(BASE, 'Core', 'Enumerations.h') +with open(path, 'r') as f: + a = f.read() + +s = ',\n'.join(map(lambda x: ' ErrorCode_%s = %d /*!< %s */' % (x['Name'], int(x['Code']), x['Description']), ERRORS)) + +s += ',\n ErrorCode_START_PLUGINS = %d' % START_PLUGINS +a = re.sub('(enum ErrorCode\s*{)[^}]*?(\s*};)', r'\1\n%s\2' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a) + + + +## +## Generate the "OrthancPluginErrorCode" enumeration in "OrthancCPlugin.h" +## + +path = os.path.join(BASE, 'Plugins', 'Include', 'orthanc', 'OrthancCPlugin.h') +with open(path, 'r') as f: + a = f.read() + +s = ',\n'.join(map(lambda x: ' OrthancPluginErrorCode_%s = %d /*!< %s */' % (x['Name'], int(x['Code']), x['Description']), ERRORS)) +s += ',\n\n _OrthancPluginErrorCode_INTERNAL = 0x7fffffff\n ' +a = re.sub('(typedef enum\s*{)[^}]*?(} OrthancPluginErrorCode;)', r'\1\n%s\2' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a) + + + +## +## Generate the "EnumerationToString(ErrorCode)" and +## "ConvertErrorCodeToHttpStatus(ErrorCode)" functions in +## "Enumerations.cpp" +## + +path = os.path.join(BASE, 'Core', 'Enumerations.cpp') +with open(path, 'r') as f: + a = f.read() + +s = '\n\n'.join(map(lambda x: ' case ErrorCode_%s:\n return "%s";' % (x['Name'], x['Description']), ERRORS)) +a = re.sub('(EnumerationToString\(ErrorCode.*?\)\s*{\s*switch \([^)]*?\)\s*{)[^}]*?(\s*default:)', + r'\1\n%s\2' % s, a, re.DOTALL) + +def GetHttpStatus(x): + s = HTTP[x['HttpStatus']] + return ' case ErrorCode_%s:\n return %s;' % (x['Name'], s) + +s = '\n\n'.join(map(GetHttpStatus, filter(lambda x: 'HttpStatus' in x, ERRORS))) +a = re.sub('(ConvertErrorCodeToHttpStatus\(ErrorCode.*?\)\s*{\s*switch \([^)]*?\)\s*{)[^}]*?(\s*default:)', + r'\1\n%s\2' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a) + + + +## +## Generate the "ErrorCode" enumeration in "OrthancSQLiteException.h" +## + +path = os.path.join(BASE, 'Core', 'SQLite', 'OrthancSQLiteException.h') +with open(path, 'r') as f: + a = f.read() + +e = filter(lambda x: 'SQLite' in x and x['SQLite'], ERRORS) +s = ',\n'.join(map(lambda x: ' ErrorCode_%s' % x['Name'], e)) +a = re.sub('(enum ErrorCode\s*{)[^}]*?(\s*};)', r'\1\n%s\2' % s, a, re.DOTALL) + +s = '\n\n'.join(map(lambda x: ' case ErrorCode_%s:\n return "%s";' % (x['Name'], x['Description']), e)) +a = re.sub('(EnumerationToString\(ErrorCode.*?\)\s*{\s*switch \([^)]*?\)\s*{)[^}]*?(\s*default:)', + r'\1\n%s\2' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/MinGW-W64-Toolchain32.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,17 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Windows) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) +set(CMAKE_RC_COMPILER i686-w64-mingw32-windres) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/MinGW-W64-Toolchain64.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,17 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Windows) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/MinGW64Toolchain.cmake Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# http://sourceforge.net/apps/trac/mingw-w64/wiki/GeneralUsageInstructions - -# the name of the target operating system -set(CMAKE_SYSTEM_NAME Windows) - -# Detect the prefix of the mingw-w64 compiler -execute_process( - COMMAND uname -p - OUTPUT_VARIABLE MINGW64_ARCHITECTURE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - -if (${MINGW64_ARCHITECTURE} STREQUAL "x86_64") - set(MINGW64_PREFIX "x86_64") -else() - set(MINGW64_PREFIX "i686") -endif() - -# which compilers to use for C and C++ -set(CMAKE_C_COMPILER ${MINGW64_PREFIX}-w64-mingw32-gcc) -set(CMAKE_CXX_COMPILER ${MINGW64_PREFIX}-w64-mingw32-g++) -set(CMAKE_RC_COMPILER ${MINGW64_PREFIX}-w64-mingw32-windres) - -# here is the target environment located -set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) - -# adjust the default behaviour of the FIND_XXX() commands: -# search headers and libraries in the target environment, search -# programs in the host environment -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- a/Resources/MinGWToolchain.cmake Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/MinGWToolchain.cmake Wed Sep 30 13:23:31 2015 +0200 @@ -1,5 +1,3 @@ -# http://www.vtk.org/Wiki/CmakeMingw - # the name of the target operating system set(CMAKE_SYSTEM_NAME Windows)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/OldBuildInstructions.txt Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,108 @@ +This file contains old build instructions that are not tested anymore. + + +Debian Squeeze (6.x) +-------------------- + +# sudo apt-get install build-essential unzip cmake mercurial \ + uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ + libgoogle-glog-dev libpng-dev libgtest-dev \ + libsqlite3-dev libssl-dev zlib1g-dev + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_BOOST=OFF \ + -DUSE_SYSTEM_DCMTK=OFF \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_SYSTEM_PUGIXML=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + + +Debian Wheezy (7.x) +------------------- + +# sudo apt-get install build-essential unzip cmake mercurial \ + uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ + libgtest-dev libpng-dev libsqlite3-dev \ + libssl-dev zlib1g-dev libdcmtk2-dev \ + libboost-all-dev libwrap0-dev libjsoncpp-dev + +# cmake -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_GOOGLE_LOG=OFF \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + + + +SUPPORTED - Ubuntu 12.10 +------------------------ + +# 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 libcharls-dev + +With JPEG: + +# cmake "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ + ~/Orthanc + + +Without JPEG: + +# cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DALLOW_DOWNLOADS=ON \ + -DUSE_SYSTEM_MONGOOSE=OFF \ + -DUSE_SYSTEM_JSONCPP=OFF \ + -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE=ON \ + -DUSE_SYSTEM_PUGIXML=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + + +SUPPORTED - Ubuntu 13.10 +------------------------ + +# 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 \ + -DUSE_SYSTEM_PUGIXML=OFF \ + -DENABLE_JPEG=OFF \ + -DENABLE_JPEG_LOSSLESS=OFF \ + ~/Orthanc + + +SUPPORTED - Fedora 19 +--------------------- + +# sudo yum install unzip make automake gcc gcc-c++ python cmake \ + boost-devel curl-devel dcmtk-devel glog-devel \ + gtest-devel libpng-devel libsqlite3x-devel libuuid-devel \ + mongoose-devel openssl-devel jsoncpp-devel lua-devel pugixml-devel + +# cmake "-DDCMTK_LIBRARIES=CharLS" \ + -DSYSTEM_MONGOOSE_USE_CALLBACKS=OFF \ + ~/Orthanc + +Note: Have also a look at the official package: +http://pkgs.fedoraproject.org/cgit/orthanc.git/tree/?h=f18
--- a/Resources/Orthanc.doxygen Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Orthanc.doxygen Wed Sep 30 13:23:31 2015 +0200 @@ -656,8 +656,7 @@ # with spaces. INPUT = @CMAKE_SOURCE_DIR@/Core \ - @CMAKE_SOURCE_DIR@/OrthancServer \ - @CMAKE_SOURCE_DIR@/OrthancCppClient + @CMAKE_SOURCE_DIR@/OrthancServer # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -941,7 +940,7 @@ # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the
--- a/Resources/OrthancClient.doxygen Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1792 +0,0 @@ -# Doxyfile 1.8.1.2 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" "). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or sequence of words) that should -# identify the project. Note that if you do not use Doxywizard you need -# to put quotes around the project name if it contains spaces. - -PROJECT_NAME = "Orthanc Client" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer -# a quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = "Documentation of the client library of Orthanc" - -# With the PROJECT_LOGO tag one can specify an logo or icon that is -# included in the documentation. The maximum height of the logo should not -# exceed 55 pixels and the maximum width should not exceed 200 pixels. -# Doxygen will copy the logo to the output directory. - -PROJECT_LOGO = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = OrthancClientDocumentation - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = NO - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = NO - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful if your file system -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding -# "class=itcl::class" will allow you to use the command class in the -# itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this -# tag. The format is ext=language, where ext is a file extension, and language -# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, -# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all -# comments according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you -# can mix doxygen, HTML, and XML commands with Markdown formatting. -# Disable only in case of backward compatibilities issues. - -MARKDOWN_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also makes the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and -# unions with only public data fields will be shown inline in the documentation -# of the scope in which they are defined (i.e. file, namespace, or group -# documentation), provided this scope is documented. If set to NO (the default), -# structs, classes, and unions are shown on a separate page (for HTML and Man -# pages) or section (for LaTeX and RTF). - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = NO - -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penalty. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will roughly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -SYMBOL_CACHE_SIZE = 0 - -# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be -# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given -# their name and scope. Since this can be an expensive process and often the -# same symbol appear multiple times in the code, doxygen keeps a cache of -# pre-resolved symbols. If the cache is too small doxygen will become slower. -# If the cache is too large, memory is wasted. The cache size is given by this -# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespaces are hidden. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = YES - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to -# do proper type resolution of all parameters of a function it will reject a -# match between the prototype and the implementation of a member function even -# if there is only one candidate or it is obvious which candidate to choose -# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen -# will still accept a match between prototype and implementation in such cases. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or macro consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and macros in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. -# You can optionally specify a file name after the option, if omitted -# DoxygenLayout.xml will be used as the name of the layout file. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files -# containing the references data. This must be a list of .bib files. The -# .bib extension is automatically appended if omitted. Using this command -# requires the bibtex tool to be installed. See also -# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style -# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this -# feature you need bibtex and perl available in the search path. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# The WARN_NO_PARAMDOC option can be enabled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = YES - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = @CMAKE_SOURCE_DIR@/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh -# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py -# *.f90 *.f *.for *.vhd *.vhdl - -FILE_PATTERNS = *.h - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = OrthancClient::Internals - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty or if -# non of the patterns match the file name, INPUT_FILTER is applied. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) -# and it is also possible to disable source filtering for a specific pattern -# using *.ext= (so without naming a filter). This option only has effect when -# FILTER_SOURCE_FILES is enabled. - -FILTER_SOURCE_PATTERNS = - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C, C++ and Fortran comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = YES - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = doc - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen -# needs, which is dependent on the configuration options used. -# It is advised to generate a default header using "doxygen -w html -# header.html footer.html stylesheet.css YourConfigFile" and then modify -# that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when -# changing the value of configuration settings such as GENERATE_TREEVIEW! - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# style sheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that -# the files will be copied as-is; there are no commands or markers available. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the style sheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of -# entries shown in the various tree structured indices initially; the user -# can expand and collapse entries dynamically later on. Doxygen will expand -# the tree to such a level that at most the specified number of entries are -# visible (unless a fully collapsed tree already exceeds this amount). -# So setting the number of entries 1 will produce a full collapsed tree by -# default. 0 is a special value representing an infinite number of entries -# and will result in a full expanded tree by default. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated -# that can be used as input for Qt's qhelpgenerator to generate a -# Qt Compressed Help (.qch) of the generated HTML documentation. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders - -QHP_VIRTUAL_FOLDER = doc - -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to -# add. For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see -# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> -# Qt Help Project / Custom Filters</a>. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's -# filter section matches. -# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> -# Qt Help Project / Filter Attributes</a>. - -QHP_SECT_FILTER_ATTRS = - -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) -# at top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. Since the tabs have the same information as the -# navigation tree you can set this option to NO if you already set -# GENERATE_TREEVIEW to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. -# Since the tree basically has the same information as the tab index you -# could consider to set DISABLE_INDEX to NO when enabling this option. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. - -ENUM_VALUES_PER_LINE = 1 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax -# (see http://www.mathjax.org) which uses client side Javascript for the -# rendering instead of using prerendered bitmaps. Use this if you do not -# have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you may also need to install MathJax separately and -# configure the path to it using the MATHJAX_RELPATH option. - -USE_MATHJAX = NO - -# When MathJax is enabled you need to specify the location relative to the -# HTML output directory using the MATHJAX_RELPATH option. The destination -# directory should contain the MathJax.js script. For instance, if the mathjax -# directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to -# the MathJax Content Delivery Network so you can quickly see the result without -# installing MathJax. -# However, it is strongly recommended to install a local -# copy of MathJax from http://www.mathjax.org before deployment. - -MATHJAX_RELPATH = http://www.mathjax.org/mathjax - -# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension -# names that should be enabled during MathJax rendering. - -MATHJAX_EXTENSIONS = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. - -SEARCHENGINE = NO - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a PHP enabled web server instead of at the web client -# using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server -# based approach is that it scales better to large projects and allows -# full text search. The disadvantages are that it is more difficult to setup -# and does not have live searching capabilities. - -SERVER_BASED_SEARCH = NO - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for -# the generated latex document. The footer should contain everything after -# the last chapter. If it is left blank doxygen will generate a -# standard footer. Notice: only use this tag if you know what you are doing! - -LATEX_FOOTER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include -# source code with syntax highlighting in the LaTeX output. -# Note that which sources are shown also depends on other settings -# such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See -# http://en.wikipedia.org/wiki/BibTeX for more info. - -LATEX_BIB_STYLE = plain - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load style sheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# pointed to by INCLUDE_PATH will be searched when a #include is found. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition that -# overrules the definition found in the source code. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all references to function-like macros -# that are alone on a line, have an all uppercase name, and do not end with a -# semicolon, because these will confuse the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. For each -# tag file the location of the external documentation should be added. The -# format of a tag file without this location is as follows: -# -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths -# or URLs. Note that each tag file must have a unique name (where the name does -# NOT include the path). If a tag file is not located in the directory in which -# doxygen is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option also works with HAVE_DOT disabled, but it is recommended to -# install and use dot, since it yields more powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. - -DOT_NUM_THREADS = 0 - -# By default doxygen will use the Helvetica font for all dot files that -# doxygen generates. When you want a differently looking font you can specify -# the font name using DOT_FONTNAME. You need to make sure dot is able to find -# the font, which can be done by putting it in a standard location or by setting -# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the -# directory containing the font. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the Helvetica font. -# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to -# set the path where dot can find it. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If the UML_LOOK tag is enabled, the fields and methods are shown inside -# the class node. If there are many fields or methods and many nodes the -# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS -# threshold limits the number of items for each type to make the size more -# managable. Set this to 0 for no limit. Note that the threshold may be -# exceeded by 50% before the limit is enforced. - -UML_LIMIT_NUM_FIELDS = 10 - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will generate a graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are svg, png, jpg, or gif. -# If left blank png will be used. If you choose svg you need to set -# HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible in IE 9+ (other browsers do not have this requirement). - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# Note that this requires a modern browser other than Internet Explorer. -# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you -# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible. Older versions of IE do not have SVG support. - -INTERACTIVE_SVG = NO - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the -# \mscfile command). - -MSCFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES
--- a/Resources/OrthancPlugin.doxygen Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/OrthancPlugin.doxygen Wed Sep 30 13:23:31 2015 +0200 @@ -655,7 +655,9 @@ # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = @CMAKE_SOURCE_DIR@/Plugins/Include/ +INPUT = @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCPlugin.h \ + @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCDatabasePlugin.h \ + @CMAKE_SOURCE_DIR@/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -939,7 +941,7 @@ # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the
--- a/Resources/Patches/boost-1.55.0-clang-atomic.patch Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ ---- boost/atomic/detail/cas128strong.hpp -+++ boost/atomic/detail/cas128strong.hpp -@@ -196,15 +196,17 @@ class base_atomic<T, void, 16, Sign> - - public: - BOOST_DEFAULTED_FUNCTION(base_atomic(void), {}) -- explicit base_atomic(value_type const& v) BOOST_NOEXCEPT : v_(0) -+ explicit base_atomic(value_type const& v) BOOST_NOEXCEPT - { -+ memset(&v_, 0, sizeof(v_)); - memcpy(&v_, &v, sizeof(value_type)); - } - - void - store(value_type const& value, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT - { -- storage_type value_s = 0; -+ storage_type value_s; -+ memset(&value_s, 0, sizeof(value_s)); - memcpy(&value_s, &value, sizeof(value_type)); - platform_fence_before_store(order); - platform_store128(value_s, &v_); -@@ -247,7 +249,9 @@ class base_atomic<T, void, 16, Sign> - memory_order success_order, - memory_order failure_order) volatile BOOST_NOEXCEPT - { -- storage_type expected_s = 0, desired_s = 0; -+ storage_type expected_s, desired_s; -+ memset(&expected_s, 0, sizeof(expected_s)); -+ memset(&desired_s, 0, sizeof(desired_s)); - memcpy(&expected_s, &expected, sizeof(value_type)); - memcpy(&desired_s, &desired, sizeof(value_type)); - ---- boost/atomic/detail/gcc-atomic.hpp -+++ boost/atomic/detail/gcc-atomic.hpp -@@ -958,14 +958,16 @@ class base_atomic<T, void, 16, Sign> - - public: - BOOST_DEFAULTED_FUNCTION(base_atomic(void), {}) -- explicit base_atomic(value_type const& v) BOOST_NOEXCEPT : v_(0) -+ explicit base_atomic(value_type const& v) BOOST_NOEXCEPT - { -+ memset(&v_, 0, sizeof(v_)); - memcpy(&v_, &v, sizeof(value_type)); - } - - void store(value_type const& v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT - { -- storage_type tmp = 0; -+ storage_type tmp; -+ memset(&tmp, 0, sizeof(tmp)); - memcpy(&tmp, &v, sizeof(value_type)); - __atomic_store_n(&v_, tmp, atomics::detail::convert_memory_order_to_gcc(order)); - } -@@ -980,7 +982,8 @@ class base_atomic<T, void, 16, Sign> - - value_type exchange(value_type const& v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT - { -- storage_type tmp = 0; -+ storage_type tmp; -+ memset(&tmp, 0, sizeof(tmp)); - memcpy(&tmp, &v, sizeof(value_type)); - tmp = __atomic_exchange_n(&v_, tmp, atomics::detail::convert_memory_order_to_gcc(order)); - value_type res; -@@ -994,7 +997,9 @@ class base_atomic<T, void, 16, Sign> - memory_order success_order, - memory_order failure_order) volatile BOOST_NOEXCEPT - { -- storage_type expected_s = 0, desired_s = 0; -+ storage_type expected_s, desired_s; -+ memset(&expected_s, 0, sizeof(expected_s)); -+ memset(&desired_s, 0, sizeof(desired_s)); - memcpy(&expected_s, &expected, sizeof(value_type)); - memcpy(&desired_s, &desired, sizeof(value_type)); - const bool success = __atomic_compare_exchange_n(&v_, &expected_s, desired_s, false, -@@ -1010,7 +1015,9 @@ class base_atomic<T, void, 16, Sign> - memory_order success_order, - memory_order failure_order) volatile BOOST_NOEXCEPT - { -- storage_type expected_s = 0, desired_s = 0; -+ storage_type expected_s, desired_s; -+ memset(&expected_s, 0, sizeof(expected_s)); -+ memset(&desired_s, 0, sizeof(desired_s)); - memcpy(&expected_s, &expected, sizeof(value_type)); - memcpy(&desired_s, &desired, sizeof(value_type)); - const bool success = __atomic_compare_exchange_n(&v_, &expected_s, desired_s, true, ---
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/dcmtk-linux-speed.patch Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,24 @@ +diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc dcmtk-3.6.0/dcmnet/libsrc/dul.cc +--- dcmtk-3.6.0.orig/dcmnet/libsrc/dul.cc 2010-12-01 09:26:36.000000000 +0100 ++++ dcmtk-3.6.0/dcmnet/libsrc/dul.cc 2015-05-15 17:03:50.762451757 +0200 +@@ -1840,7 +1840,7 @@ + } + #endif + #endif +- setTCPBufferLength(sock); ++ //setTCPBufferLength(sock); + + #ifndef DONT_DISABLE_NAGLE_ALGORITHM + /* +diff -urEb dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc +--- dcmtk-3.6.0.orig/dcmnet/libsrc/dulfsm.cc 2010-12-01 09:26:36.000000000 +0100 ++++ dcmtk-3.6.0/dcmnet/libsrc/dulfsm.cc 2015-05-15 17:03:55.570451952 +0200 +@@ -2417,7 +2417,7 @@ + return makeDcmnetCondition(DULC_TCPINITERROR, OF_error, msg.c_str()); + } + #endif +- setTCPBufferLength(s); ++ //setTCPBufferLength(s); + + #ifndef DONT_DISABLE_NAGLE_ALGORITHM + /*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Patches/glog-port-h-v2.diff Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,52 @@ +124,130c124,146 +< // ----------------------------------- THREADS +< typedef DWORD pthread_t; +< typedef DWORD pthread_key_t; +< typedef LONG pthread_once_t; +< enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock +< #define pthread_self GetCurrentThreadId +< #define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) +--- +> // ----------------------------------- SECURE STRINGS +> +> #if HAVE_SECURE_STRING_EXTENSIONS == 0 +> // Emulation of "localtime_s" and "strerror_s" for old versions of MinGW +> inline int localtime_s(tm * _tm, const time_t * time) +> { +> tm * posix_local_time_struct = localtime(time); +> if (posix_local_time_struct == NULL) +> { +> return 1; +> } +> +> *_tm = *posix_local_time_struct; +> +> return 0; +> } +> +> inline char* strerror_s(char* buf, size_t buflen, int errnum) +> { +> const char* str = strerror(errnum); +> return strncpy(buf, str, buflen - 1); +> } +> #endif +131a148,149 +> +> #if !defined(__MINGW32__) || HAVE_SECURE_STRING_EXTENSIONS == 0 +135a154,155 +> #endif +> +140a161,173 +> +> +> // ----------------------------------- THREADS +> +> #if !defined(__MINGW32__) || HAVE_WIN_PTHREAD == 0 +> typedef DWORD pthread_t; +> typedef DWORD pthread_key_t; +> typedef LONG pthread_once_t; +> enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock +> #define pthread_self GetCurrentThreadId +> #define pthread_equal(pthread_t_1, pthread_t_2) ((pthread_t_1)==(pthread_t_2)) +> #endif +>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/RetrieveCACertificates.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,70 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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/>. + + +import re +import sys +import subprocess +import urllib2 + + +if len(sys.argv) <= 2: + print('Download a set of CA certificates, convert them to PEM, then format them as a C macro') + print('Usage: %s [Macro] [Certificate1] <Certificate2>...' % sys.argv[0]) + print('') + print('Example: %s BITBUCKET_CERTIFICATES https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt' % sys.argv[0]) + print('') + sys.exit(-1) + +MACRO = sys.argv[1] + +sys.stdout.write('#define %s ' % MACRO) + +for url in sys.argv[2:]: + # Download the certificate from the CA authority, in the DES format + des = urllib2.urlopen(url).read() + + # Convert DES to PEM + p = subprocess.Popen([ 'openssl', 'x509', '-inform', 'DES', '-outform', 'PEM' ], + stdin = subprocess.PIPE, + stdout = subprocess.PIPE) + pem = p.communicate(input = des)[0] + pem = re.sub(r'\r', '', pem) # Remove any carriage return + pem = re.sub(r'\\', r'\\\\', pem) # Escape any backslash + pem = re.sub(r'"', r'\\"', pem) # Escape any quote + + # Write the PEM data into the macro + for line in pem.split('\n'): + sys.stdout.write(' \\\n') + sys.stdout.write('"%s\\n" ' % line) + +sys.stdout.write('\n') +sys.stderr.write('Done!\n')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/AutomatedJpeg2kCompression.lua Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,38 @@ +-- This sample shows how to use Orthanc to compress on-the-fly any +-- incoming DICOM file, as a JPEG2k file. + +function OnStoredInstance(instanceId, tags, metadata, origin) + -- Do not compress twice the same file + if origin['RequestOrigin'] ~= 'Lua' then + + -- Retrieve the incoming DICOM instance from Orthanc + local dicom = RestApiGet('/instances/' .. instanceId .. '/file') + + -- Write the DICOM content to some temporary file + local uncompressed = instanceId .. '-uncompressed.dcm' + local target = assert(io.open(uncompressed, 'wb')) + target:write(dicom) + target:close() + + -- Compress to JPEG2000 using gdcm + local compressed = instanceId .. '-compressed.dcm' + os.execute('gdcmconv --j2k ' .. uncompressed .. ' ' .. compressed) + + -- Generate a new SOPInstanceUID for the JPEG2000 file, as + -- gdcmconv does not do this by itself + os.execute('dcmodify --no-backup -gin ' .. compressed) + + -- Read the JPEG2000 file + local source = assert(io.open(compressed, 'rb')) + local jpeg2k = source:read("*all") + source:close() + + -- Upload the JPEG2000 file and remove the uncompressed file + RestApiPost('/instances', jpeg2k) + RestApiDelete('/instances/' .. instanceId) + + -- Remove the temporary DICOM files + os.remove(uncompressed) + os.remove(compressed) + end +end
--- a/Resources/Samples/Lua/AutoroutingConditional.lua Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/Lua/AutoroutingConditional.lua Wed Sep 30 13:23:31 2015 +0200 @@ -1,9 +1,6 @@ -function OnStoredInstance(instanceId, tags, metadata, remoteAet, calledAet) - -- The "remoteAet" and "calledAet" arguments are only available - -- since Orthanc 0.8.6 - if remoteAet ~=nil and calledAet ~= nil then - print ("Source AET: " .. remoteAet .. " => Called AET: " .. calledAet) - end +function OnStoredInstance(instanceId, tags, metadata, origin) + -- The "origin" is only available since Orthanc 0.9.4 + PrintRecursive(origin) -- Extract the value of the "PatientName" DICOM tag local patientName = string.lower(tags['PatientName'])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/CallImageJ.lua Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,35 @@ +-- This sample shows how to invoke an ImageJ script on every DICOM +-- image received by Orthanc. The ImageJ script is generated by the +-- "Initialize()" function at the startup of Orthanc. Whenever a new +-- instance is received, its DICOM file is stored into a temporary +-- file, and a system call to ImageJ is triggered. + +SCRIPT = 'ImageJScript.txt' + +function Initialize() + local target = assert(io.open(SCRIPT, 'w')) + + -- This is a sample ImageJ script that display the size of the DICOM image + target:write('if (getArgument=="") exit ("No argument!");\n') + target:write('open(getArgument);\n') + target:write('print(getTitle + ": " + getWidth + "x" + getHeight);\n') + + target:close() +end + +function OnStoredInstance(instanceId) + -- Retrieve the DICOM instance from Orthanc + local dicom = RestApiGet('/instances/' .. instanceId .. '/file') + + -- Write the DICOM content to some temporary file + local path = instanceId .. '.dcm' + local target = assert(io.open(path, 'wb')) + target:write(dicom) + target:close() + + -- Call ImageJ + os.execute('imagej -b ' .. SCRIPT .. ' ' .. path) + + -- Remove the temporary DICOM file + os.remove(path) +end
--- a/Resources/Samples/Lua/CallWebService.js Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/Lua/CallWebService.js Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/OnStableStudy.lua Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,46 @@ +function Initialize() + print('Number of stored studies at initialization: ' .. + table.getn(ParseJson(RestApiGet('/studies')))) +end + + +function Finalize() + print('Number of stored studies at finalization: ' .. + table.getn(ParseJson(RestApiGet('/studies')))) +end + + +function OnStoredInstance(instanceId, tags, metadata) + patient = ParseJson(RestApiGet('/instances/' .. instanceId .. '/patient')) + print('Received an instance for patient: ' .. + patient['MainDicomTags']['PatientID'] .. ' - ' .. + patient['MainDicomTags']['PatientName']) +end + + +function OnStableStudy(studyId, tags, metadata) + if (metadata['ModifiedFrom'] == nil and + metadata['AnonymizedFrom'] == nil) then + + print('This study is now stable: ' .. studyId) + + -- The tags to be replaced + local replace = {} + replace['StudyDescription'] = 'Modified study' + replace['StationName'] = 'My Medical Device' + replace['0031-1020'] = 'Some private tag' + + -- The tags to be removed + local remove = { 'MilitaryRank' } + + -- The modification command + local command = {} + command['Remove'] = remove + command['Replace'] = replace + + -- Modify the entire study in one single call + local m = RestApiPost('/studies/' .. studyId .. '/modify', + DumpJson(command)) + print('Modified study: ' .. m) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/ParseDoseReport.lua Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,66 @@ +-- Sample Lua script that demonstrates how to extract DICOM tags +-- related to dose reports. In this example, the value of the DLP +-- (Dose Length Product), the value of the mean CTDI volume (Computed +-- Tomography Dose Index), and the number of "IntervalsRejected" are +-- extracted and printed. Furthermore, these values are saved as +-- metadata that is attached to their parent DICOM instance for +-- further processing by external software. + + +function ExploreContentSequence(instanceId, tags) + if tags then + for key, value in pairs(tags) do + -- Recursive exploration + ExploreContentSequence(instanceId, value['ContentSequence']) + + local concept = value['ConceptNameCodeSequence'] + local measure = value['MeasuredValueSequence'] + if concept and measure then + + local value = measure[1]['NumericValue'] + local code = concept[1]['CodeValue'] + + if type(value) == 'string' and type(code) == 'string' then + -- If the field contains the DLP, stores it as a metadata. + -- "DLP" is associated with CodeValue 113838. + -- ftp://medical.nema.org/medical/dicom/final/sup127_ft.pdf + if code == '113838' then + print('DLP = ' .. value) + RestApiPut('/instances/' .. instanceId .. '/metadata/2001', value) + end + + -- Extract the mean CTDI volume + if code == '113830' then + print('CTDI = ' .. value) + RestApiPut('/instances/' .. instanceId .. '/metadata/2002', value) + end + + -- Other values can be extracted here + end + end + end + end +end + + +function StoreTagToMetadata(instanceId, tags, name, metadata) + if tags then + for key, value in pairs(tags) do + if type(value) ~= 'string' then + -- Recursive exploration + StoreTagToMetadata(instanceId, value, name, metadata) + elseif key == name then + print(name .. ' = ' .. value) + if metadata then + RestApiPut('/instances/' .. instanceId .. '/metadata/' .. metadata, value) + end + end + end + end +end + + +function OnStoredInstance(instanceId, tags) + StoreTagToMetadata(instanceId, tags, 'IntervalsRejected', 2000) + ExploreContentSequence(instanceId, tags['ContentSequence']) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/WriteToDisk.lua Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,34 @@ +TARGET = '/tmp/lua' + +function ToAscii(s) + -- http://www.lua.org/manual/5.1/manual.html#pdf-string.gsub + return s:gsub('[^a-zA-Z0-9-/ ]', '_') +end + +function OnStableSeries(seriesId, tags, metadata) + print('This series is now stable, writing its instances on the disk: ' .. seriesId) + + local instances = ParseJson(RestApiGet('/series/' .. seriesId)) ['Instances'] + local patient = ParseJson(RestApiGet('/series/' .. seriesId .. '/patient')) ['MainDicomTags'] + local study = ParseJson(RestApiGet('/series/' .. seriesId .. '/study')) ['MainDicomTags'] + local series = ParseJson(RestApiGet('/series/' .. seriesId)) ['MainDicomTags'] + + for i, instance in pairs(instances) do + local path = ToAscii(TARGET .. '/' .. + patient['PatientID'] .. ' - ' .. patient['PatientName'] .. '/' .. + study['StudyDate'] .. ' - ' .. study['StudyDescription'] .. '/' .. + series['SeriesDescription']) + + -- Retrieve the DICOM file from Orthanc + local dicom = RestApiGet('/instances/' .. instance .. '/file') + + -- Create the subdirectory (CAUTION: For Linux demo only, this is insecure!) + -- http://stackoverflow.com/a/16029744/881731 + os.execute('mkdir -p "' .. path .. '"') + + -- Write to the file + local target = assert(io.open(path .. '/' .. instance .. '.dcm', 'wb')) + target:write(dicom) + target:close() + end +end
--- a/Resources/Samples/OrthancClient/Basic/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(Basic) - -add_executable(Test main.cpp) - -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 - target_link_libraries(Test pthread dl) -endif()
--- a/Resources/Samples/OrthancClient/Basic/main.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include <iostream> -#include <orthanc/OrthancCppClient.h> - -int main() -{ - try - { - // The following explicit initialization is not required, except - // if you wish to specify the full path to the shared library - OrthancClient::Initialize(); - - // Display the content of the local Orthanc instance - OrthancClient::OrthancConnection orthanc("http://localhost:8042"); - - for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) - { - OrthancClient::Patient patient(orthanc.GetPatient(i)); - std::cout << "Patient: " << patient.GetId() << std::endl; - - for (unsigned int j = 0; j < patient.GetStudyCount(); j++) - { - OrthancClient::Study study(patient.GetStudy(j)); - std::cout << " Study: " << study.GetId() << std::endl; - - for (unsigned int k = 0; k < study.GetSeriesCount(); k++) - { - OrthancClient::Series series(study.GetSeries(k)); - std::cout << " Series: " << series.GetId() << std::endl; - - if (series.Is3DImage()) - { - std::cout << " This is a 3D image whose voxel size is " - << series.GetVoxelSizeX() << " x " - << series.GetVoxelSizeY() << " x " - << series.GetVoxelSizeZ() << ", and slice thickness is " - << series.GetSliceThickness() << std::endl; - } - - for (unsigned int l = 0; l < series.GetInstanceCount(); l++) - { - std::cout << " Instance: " << series.GetInstance(l).GetId() << std::endl; - - // Load and display some raw DICOM tag - series.GetInstance(l).LoadTagContent("0020-000d"); - std::cout << " SOP instance UID: " << series.GetInstance(l).GetLoadedTagContent() << std::endl; - } - } - } - } - - OrthancClient::Finalize(); - - return 0; - } - catch (OrthancClient::OrthancClientException& e) - { - std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl; - return -1; - } -}
--- a/Resources/Samples/OrthancClient/Vtk/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -project(Vtk) - -find_package(VTK REQUIRED) -include(${VTK_USE_FILE}) - -add_executable(Test - main.cpp - ) - -# Linking with "pthread" is necessary, otherwise the software crashes -# http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17 -target_link_libraries(Test pthread dl) - -if(VTK_LIBRARIES) - target_link_libraries(Test ${VTK_LIBRARIES}) -else() - target_link_libraries(Test vtkHybrid vtkVolumeRendering) -endif()
--- a/Resources/Samples/OrthancClient/Vtk/main.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,181 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ - - -#include <iostream> - -#include <vtkRenderWindow.h> -#include <vtkImageData.h> -#include <vtkPiecewiseFunction.h> -#include <vtkFixedPointVolumeRayCastMapper.h> -#include <vtkColorTransferFunction.h> -#include <vtkVolumeProperty.h> -#include <vtkRenderWindowInteractor.h> -#include <vtkRenderer.h> -#include <vtkSmartPointer.h> -#include <vtkOpenGLRenderer.h> -#include <vtkInteractorStyleTrackballCamera.h> - -#include <orthanc/OrthancCppClient.h> - - -void Display(OrthancClient::Series& series) -{ - /** - * Load the 3D image from Orthanc into VTK. - **/ - - vtkSmartPointer<vtkImageData> image = vtkSmartPointer<vtkImageData>::New(); - image->SetDimensions(series.GetWidth(), series.GetHeight(), series.GetInstanceCount()); - image->SetScalarType(VTK_SHORT); - image->AllocateScalars(); - - if (series.GetWidth() != 0 && - series.GetHeight() != 0 && - series.GetInstanceCount() != 0) - { - series.Load3DImage(image->GetScalarPointer(0, 0, 0), Orthanc::PixelFormat_SignedGrayscale16, - 2 * series.GetWidth(), 2 * series.GetHeight() * series.GetWidth()); - } - - image->SetSpacing(series.GetVoxelSizeX(), - series.GetVoxelSizeY(), - series.GetVoxelSizeZ()); - - - /** - * The following code is based on the VTK sample for MIP - * http://www.vtk.org/Wiki/VTK/Examples/Cxx/VolumeRendering/MinIntensityRendering - **/ - - // Create a transfer function mapping scalar value to opacity - double range[2]; - image->GetScalarRange(range); - - vtkSmartPointer<vtkPiecewiseFunction> opacityTransfer = - vtkSmartPointer<vtkPiecewiseFunction>::New(); - opacityTransfer->AddSegment(range[0], 0.0, range[1], 1.0); - - vtkSmartPointer<vtkColorTransferFunction> colorTransfer = - vtkSmartPointer<vtkColorTransferFunction>::New(); - colorTransfer->AddRGBPoint(0, 1.0, 1.0, 1.0); - colorTransfer->AddRGBPoint(range[1], 1.0, 1.0, 1.0); - - vtkSmartPointer<vtkVolumeProperty> property = - vtkSmartPointer<vtkVolumeProperty>::New(); - property->SetScalarOpacity(opacityTransfer); - property->SetColor(colorTransfer); - property->SetInterpolationTypeToLinear(); - - // Create a Maximum Intensity Projection rendering - vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> mapper = - vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New(); - mapper->SetBlendModeToMaximumIntensity(); - mapper->SetInput(image); - - vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New(); - volume->SetMapper(mapper); - volume->SetProperty(property); - - vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkOpenGLRenderer>::New(); - renderer->AddViewProp(volume); - renderer->SetBackground(0.1, 0.2, 0.3); // Background color dark blue - - vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = - vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New(); - - vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New(); - window->AddRenderer(renderer); - - vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); - interactor->SetRenderWindow(window); - interactor->SetInteractorStyle(style); - interactor->Start(); -} - - -int main() -{ - try - { - // The following explicit initialization is not required, except - // if you wish to specify the full path to the shared library - OrthancClient::Initialize(); - - // Use the commented code below if you know the identifier of a - // series that corresponds to a 3D image. - - /* - { - OrthancClient::OrthancConnection orthanc("http://localhost:8042"); - OrthancClient::Series series(orthanc, "dc5ec3d9-6e1a7b2c-73a829f0-64c609f6-ef976a97"); - Display(series); - return 0; - } - */ - - - // Try and find a 3D image inside the local store - OrthancClient::OrthancConnection orthanc("http://localhost:8042"); - - for (unsigned int i = 0; i < orthanc.GetPatientCount(); i++) - { - OrthancClient::Patient patient(orthanc.GetPatient(i)); - std::cout << "Patient: " << patient.GetId() << std::endl; - - for (unsigned int j = 0; j < patient.GetStudyCount(); j++) - { - OrthancClient::Study study(patient.GetStudy(j)); - std::cout << " Study: " << study.GetId() << std::endl; - - for (unsigned int k = 0; k < study.GetSeriesCount(); k++) - { - OrthancClient::Series series(study.GetSeries(k)); - std::cout << " Series: " << series.GetId() << std::endl; - - if (series.Is3DImage()) - { - Display(series); - return 0; - } - else - { - std::cout << " => Not a 3D image..." << std::endl; - } - } - } - } - - std::cout << "Unable to find a 3D image in the local Orthanc store" << std::endl; - - return 0; - } - catch (OrthancClient::OrthancClientException& e) - { - std::cerr << "EXCEPTION: [" << e.What() << "]" << std::endl; - return -1; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/ArchiveStudiesInTimeRange.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,78 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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 os.path +import sys +import RestToolbox + +def PrintHelp(): + print('Download ZIP archives for all the studies generated ' + 'during a given time range (according to the StudyDate tag)\n') + print('Usage: %s <URL> <StartDate> <EndDate> <TargetFolder>\n' % sys.argv[0]) + print('Example: %s http://localhost:8042/ 20150101 20151231 /tmp/\n' % sys.argv[0]) + exit(-1) + +def CheckIsDate(date): + if len(date) != 8 or not date.isdigit(): + print '"%s" is not a valid date!\n' % date + exit(-1) + + +if len(sys.argv) != 5: + PrintHelp() + +URL = sys.argv[1] +START = sys.argv[2] +END = sys.argv[3] +TARGET = sys.argv[4] + +CheckIsDate(START) +CheckIsDate(END) + +# Loop over the studies +for studyId in RestToolbox.DoGet('%s/studies' % URL): + # Retrieve the DICOM tags of the current study + study = RestToolbox.DoGet('%s/studies/%s' % (URL, studyId))['MainDicomTags'] + + # Retrieve the DICOM tags of the parent patient of this study + patient = RestToolbox.DoGet('%s/studies/%s/patient' % (URL, studyId))['MainDicomTags'] + + # Check that the StudyDate tag lies within the given range + studyDate = study['StudyDate'][:8] + if studyDate >= START and studyDate <= END: + # Create a filename + filename = '%s - %s %s - %s.zip' % (study['StudyDate'], + patient['PatientID'], + patient['PatientName'], + study['StudyDescription']) + + # Remove any non-ASCII character in the filename + filename = filename.encode('ascii', errors = 'replace') + + # Download the ZIP archive of the study + print('Downloading %s' % filename) + zipContent = RestToolbox.DoGet('%s/studies/%s/archive' % (URL, studyId)) + + # Write the ZIP archive at the proper location + with open(os.path.join(TARGET, filename), 'wb') as f: + f.write(zipContent)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/ContinuousPatientAnonymization.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,99 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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 +import md5 + + +## +## Print help message +## + +if len(sys.argv) != 3: + print(""" +Sample script that anonymizes patients in real-time. A patient gets +anonymized as soon as she gets stable (i.e. when no DICOM instance has +been received for this patient for a sufficient amount of time - cf. +the configuration option "StableAge"). + +Usage: %s [hostname] [HTTP port] +For instance: %s localhost 8042 +""" % (sys.argv[0], sys.argv[0])) + exit(-1) + +URL = 'http://%s:%d' % (sys.argv[1], int(sys.argv[2])) + + + +## +## The following function is called whenever a patient gets stable +## + +COUNT = 1 + +def AnonymizePatient(path): + global URL + global COUNT + + patient = RestToolbox.DoGet(URL + path) + patientID = patient['MainDicomTags']['PatientID'] + + # Ignore anonymized patients + if not 'AnonymizedFrom' in patient: + print('Patient with ID "%s" is stabilized: anonymizing it...' % (patientID)) + + # The PatientID after anonymization is taken as the 8 first + # characters from the MD5 hash of the original PatientID + anonymizedID = md5.new(patientID).hexdigest()[:8] + anonymizedName = 'Anonymized patient %d' % COUNT + COUNT += 1 + + RestToolbox.DoPost(URL + path + '/anonymize', + { 'Replace' : { 'PatientID' : anonymizedID, + 'PatientName' : anonymizedName } }) + + # Delete the source patient after the anonymization + RestToolbox.DoDelete(URL + change['Path']) + + + +## +## Main loop that listens to the changes API. +## + +current = 0 +while True: + r = RestToolbox.DoGet(URL + '/changes', { + 'since' : current, + 'limit' : 4 # Retrieve at most 4 changes at once + }) + + for change in r['Changes']: + if change['ChangeType'] == 'StablePatient': + AnonymizePatient(change['Path']) + + current = r['Last'] + + if r['Done']: + print('Everything has been processed: Waiting...') + time.sleep(1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Python/Replicate.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,109 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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 base64 +import httplib2 +import json +import re +import sys + +URL_REGEX = re.compile('(http|https)://((.+?):(.+?)@|)(.*)') + + +if len(sys.argv) != 3: + print(""" +Script to copy the content of one Orthanc server to another Orthanc +server through their REST API. + +Usage: %s [SourceURI] [TargetURI] +For instance: %s http://orthanc:password@localhost:8042/ http://localhost:8043/ +""" % (sys.argv[0], sys.argv[0])) + exit(-1) + + + +def CreateHeaders(parsedUrl): + headers = { } + username = parsedUrl.group(3) + password = parsedUrl.group(4) + + if username != None and password != None: + # This is a custom reimplementation of the + # "Http.add_credentials()" method for Basic HTTP Access + # Authentication (for some weird reason, this method does not + # always work) + # http://en.wikipedia.org/wiki/Basic_access_authentication + headers['authorization'] = 'Basic ' + base64.b64encode(username + ':' + password) + + return headers + + +def GetBaseUrl(parsedUrl): + return '%s://%s' % (parsedUrl.group(1), parsedUrl.group(5)) + + +def DoGetString(url): + global URL_REGEX + parsedUrl = URL_REGEX.match(url) + headers = CreateHeaders(parsedUrl) + + h = httplib2.Http() + resp, content = h.request(GetBaseUrl(parsedUrl), 'GET', headers = headers) + + if resp.status == 200: + return content + else: + raise Exception('Unable to contact Orthanc at: ' + url) + + +def DoPostDicom(url, body): + global URL_REGEX + parsedUrl = URL_REGEX.match(url) + headers = CreateHeaders(parsedUrl) + headers['content-type'] = 'application/dicom' + + h = httplib2.Http() + resp, content = h.request(GetBaseUrl(parsedUrl), 'POST', + body = body, + headers = headers) + + if resp.status != 200: + raise Exception('Unable to contact Orthanc at: ' + url) + + +def _DecodeJson(s): + if (sys.version_info >= (3, 0)): + return json.loads(s.decode()) + else: + return json.loads(s) + + +def DoGetJson(url): + return _DecodeJson(DoGetString(url)) + + +SOURCE = sys.argv[1] +TARGET = sys.argv[2] + +for study in DoGetJson('%s/studies' % SOURCE): + print('Sending study %s...' % study) + for instance in DoGetJson('%s/studies/%s/instances' % (SOURCE, study)): + dicom = DoGetString('%s/instances/%s/file' % (SOURCE, instance['ID'])) + DoPostDicom('%s/instances' % TARGET, dicom)
--- a/Resources/Samples/Tools/CMakeLists.txt Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/Tools/CMakeLists.txt Wed Sep 30 13:23:31 2015 +0200 @@ -20,6 +20,7 @@ include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/ZlibConfiguration.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake) add_library(CommonLibraries ${BOOST_SOURCES} @@ -27,8 +28,8 @@ ${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 + ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c + ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp ) add_executable(RecoverCompressedFile @@ -37,4 +38,4 @@ ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp ) -target_link_libraries(RecoverCompressedFile CommonLibraries) +target_link_libraries(RecoverCompressedFile CommonLibraries GoogleLog)
--- a/Resources/Samples/WebApplications/DrawingDicomizer.js Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer.js Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/
--- a/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js Wed Sep 30 13:23:31 2015 +0200 @@ -3,27 +3,21 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ + function guid4Block() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16)
--- a/Resources/Samples/WebApplications/NodeToolbox.js Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Samples/WebApplications/NodeToolbox.js Wed Sep 30 13:23:31 2015 +0200 @@ -3,25 +3,18 @@ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * 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. * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. **/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/patch/NOTES.txt Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,70 @@ +=========== +INFORMATION +=========== + +This is a precompiled version of the "patch" standard tool for +Windows. It was compiled using the MSYS framework. + +The binaries originate from the "Git for Windows 1.9.5" package +(https://msysgit.github.io/). The build instructions have been +provided on the discussion group of Git for Windows [1]. They are +copied/pasted below for reference. + + + +================ +UPSTREAM PROJECT +================ + +URL to the upstream project: +http://savannah.gnu.org/projects/patch/ + +License of patch: GPLv2 (GNU General Public License v2) + +Copyright (C) 1988 Larry Wall "with lots o' patches by Paul Eggert" +Copyright (C) 1997 Free Software Foundation, Inc. + + + +====================== +BUILD INSTRUCTIONS [1] +====================== + +The easiest way to find out about this is to install the Git SDK, then +run + + pacman -Qu $(which patch.exe) + +to find out which package contains the `patch.exe` binary. It so happens +to be patch.2.7.5-1 at the moment. Since this is an MSys2 package (not a +MinGW one, otherwise the patch utility would be in /mingw64/bin/, not +/usr/bin/), this package is built from the recipes in + + https://github.com/msys2/MSYS2-packages + +The `patch` package is obviously built from the subdirectory + + https://github.com/Alexpux/MSYS2-packages/tree/master/patch + +and the PKGBUILD file specifies that the source is fetched from +ftp://ftp.gnu.org/gnu/patch/patch-2.7.5.tar.xz: + +https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/PKGBUILD#L14 + +and then these two patches are applied before building: + +https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/msys2-patch-2.7.1.patch + +and + +https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/msys2-patch-manifest.patch + +As you can see, some light changes are applied, i.e. `patch.exe` will +always write in binary mode with MSys2, and the executable will have a +manifest embedded that allows it to run as non-administrator. + +Ciao, +Johannes Schindelin + + +[1] https://groups.google.com/d/msg/git-for-windows/xWyVr4z6Ri0/6RKeV028EAAJ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/patch/patch.exe.manifest Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity version="7.95.0.0" + processorArchitecture="X86" + name="patch.exe" + type="win32"/> + + <!-- Identify the application security requirements. --> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel + level="asInvoker" + uiAccess="false"/> + </requestedPrivileges> + </security> + </trustInfo> +</assembly> +
--- a/Resources/Toolbox.lua Wed Feb 11 10:40:08 2015 +0100 +++ b/Resources/Toolbox.lua Wed Sep 30 13:23:31 2015 +0200 @@ -32,80 +32,92 @@ end -function SendToModality(instanceId, modality) - if instanceId == nil then - error('Cannot send a nonexistent instance') +function SendToModality(resourceId, modality, localAet) + if resourceId == nil then + error('Cannot send a nonexistent resource') end table.insert(_job, { Operation = 'store-scu', - Instance = instanceId, - Modality = modality + Resource = resourceId, + Modality = modality, + LocalAet = localAet }) - return instanceId + return resourceId end -function SendToPeer(instanceId, peer) - if instanceId == nil then - error('Cannot send a nonexistent instance') +function SendToPeer(resourceId, peer) + if resourceId == nil then + error('Cannot send a nonexistent resource') end table.insert(_job, { Operation = 'store-peer', - Instance = instanceId, + Resource = resourceId, Peer = peer }) - return instanceId + return resourceId end -function Delete(instanceId) - if instanceId == nil then - error('Cannot delete a nonexistent instance') +function Delete(resourceId) + if resourceId == nil then + error('Cannot delete a nonexistent resource') end table.insert(_job, { Operation = 'delete', - Instance = instanceId + Resource = resourceId }) return nil -- Forbid chaining end -function ModifyInstance(instanceId, replacements, removals, removePrivateTags) - if instanceId == nil then - error('Cannot modify a nonexistent instance') +function ModifyResource(resourceId, replacements, removals, removePrivateTags) + if resourceId == nil then + error('Cannot modify a nonexistent resource') end - if instanceId == '' then - error('Cannot modify twice an instance'); + if resourceId == '' then + error('Cannot modify twice an resource'); end table.insert(_job, { Operation = 'modify', - Instance = instanceId, + Resource = resourceId, Replace = replacements, Remove = removals, RemovePrivateTags = removePrivateTags }) + return '' -- Chain with another operation end -function CallSystem(instanceId, command, args) - if instanceId == nil then - error('Cannot modify a nonexistent instance') +function ModifyInstance(resourceId, replacements, removals, removePrivateTags) + return ModifyResource(resourceId, replacements, removals, removePrivateTags) +end + + +-- This function is only applicable to individual instances +function CallSystem(resourceId, command, args) + if resourceId == nil then + error('Cannot execute a system call on a nonexistent resource') + end + + if command == nil then + error('No command was specified for system call') end table.insert(_job, { Operation = 'call-system', - Instance = instanceId, + Resource = resourceId, Command = command, Arguments = args }) - return instanceId + return resourceId end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WindowsResources.py Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,89 @@ +#!/usr/bin/python + +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics +# Department, University Hospital 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/>. + + +import os +import sys +import datetime + +if len(sys.argv) != 5: + sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0]) + sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0]) + sys.exit(-1) + +SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc') + +VERSION = sys.argv[1] +PRODUCT = sys.argv[2] +FILENAME = sys.argv[3] +DESCRIPTION = sys.argv[4] + +if VERSION == 'mainline': + VERSION = '999.999.999' + RELEASE = 'This is a mainline build, not an official release' +else: + RELEASE = 'Release %s' % VERSION + +v = VERSION.split('.') +if len(v) != 2 and len(v) != 3: + sys.stderr.write('Bad version number: %s\n' % VERSION) + sys.exit(-1) + +if len(v) == 2: + v.append('0') + +extension = os.path.splitext(FILENAME)[1] +if extension.lower() == '.dll': + BLOCK = '040904E4' + TYPE = 'VFT_DLL' +elif extension.lower() == '.exe': + #BLOCK = '040904B0' # LANG_ENGLISH/SUBLANG_ENGLISH_US, + BLOCK = '040904E4' # Lang=US English, CharSet=Windows Multilingual + TYPE = 'VFT_APP' +else: + sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension) + sys.exit(-1) + + +with open(SOURCE, 'r') as source: + content = source.read() + content = content.replace('${VERSION_MAJOR}', v[0]) + content = content.replace('${VERSION_MINOR}', v[1]) + content = content.replace('${VERSION_PATCH}', v[2]) + content = content.replace('${RELEASE}', RELEASE) + content = content.replace('${DESCRIPTION}', DESCRIPTION) + content = content.replace('${PRODUCT}', PRODUCT) + content = content.replace('${FILENAME}', FILENAME) + content = content.replace('${YEAR}', str(datetime.datetime.now().year)) + content = content.replace('${BLOCK}', BLOCK) + content = content.replace('${TYPE}', TYPE) + + sys.stdout.write(content)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/WindowsResources.rc Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,30 @@ +#include <winver.h> + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH} + PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0 + FILEOS VOS_NT_WINDOWS32 + FILETYPE ${TYPE} + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "${BLOCK}" + BEGIN + VALUE "Comments", "${RELEASE}" + VALUE "CompanyName", "University Hospital of Liege, Belgium" + VALUE "FileDescription", "${DESCRIPTION}" + VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}" + VALUE "InternalName", "${PRODUCT}" + VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium" + VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/" + VALUE "OriginalFilename", "${FILENAME}" + VALUE "ProductName", "${PRODUCT}" + VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 // U.S. English + END + END
--- a/THANKS Wed Feb 11 10:40:08 2015 +0100 +++ b/THANKS Wed Sep 30 13:23:31 2015 +0200 @@ -28,6 +28,7 @@ * Vincent Kersten <vincent1234567@gmail.com>, for DICOMDIR in the GUI. * Emsy Chan <emlscs@yahoo.com>, for various contributions and sample DICOM files. +* Mikhail <mp39590@gmail.com>, for FreeBSD support. Thanks also to all the contributors active in our Google Group:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/BitbucketCACertificates.h Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,25 @@ +#define BITBUCKET_CERTIFICATES \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" \ +"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \ +"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" \ +"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" \ +"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" \ +"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" \ +"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" \ +"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" \ +"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" \ +"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" \ +"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" \ +"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" \ +"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" \ +"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" \ +"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" \ +"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" \ +"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" \ +"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" \ +"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" \ +"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" \ +"+OkuE6N36B9K\n" \ +"-----END CERTIFICATE-----\n" \ +"\n"
--- a/UnitTestsSources/DicomMapTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -83,22 +83,38 @@ TEST(DicomMap, Tags) { + std::set<DicomTag> s; + DicomMap m; + m.GetTags(s); + ASSERT_EQ(0u, s.size()); + 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)); + m.GetTags(s); + ASSERT_EQ(1u, s.size()); + ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); + 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.GetTags(s); + ASSERT_EQ(2u, s.size()); + m.Remove(DICOM_TAG_PATIENT_ID); ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); + m.GetTags(s); + ASSERT_EQ(1u, s.size()); + ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); + std::auto_ptr<DicomMap> mm(m.Clone()); ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); @@ -137,7 +153,7 @@ DicomModule module) { std::set<DicomTag> moduleTags, main; - DicomTag::GetTagsForModule(moduleTags, module); + DicomTag::AddTagsForModule(moduleTags, module); DicomMap::GetMainDicomTags(main, level); // The main dicom tags are a subset of the module
--- a/UnitTestsSources/FileStorageTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/FileStorageTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,17 +34,16 @@ #include "gtest/gtest.h" #include <ctype.h> -#include <glog/logging.h> #include "../Core/FileStorage/FilesystemStorage.h" -#include "../OrthancServer/ServerIndex.h" +#include "../Core/FileStorage/StorageAccessor.h" +#include "../Core/HttpServer/BufferHttpSender.h" +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/Logging.h" +#include "../Core/OrthancException.h" #include "../Core/Toolbox.h" -#include "../Core/OrthancException.h" #include "../Core/Uuid.h" -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/HttpServer/BufferHttpSender.h" -#include "../Core/FileStorage/FileStorageAccessor.h" -#include "../Core/FileStorage/CompressedFileStorageAccessor.h" +#include "../OrthancServer/ServerIndex.h" using namespace Orthanc; @@ -123,111 +122,69 @@ } -TEST(FileStorageAccessor, Simple) +TEST(StorageAccessor, NoCompression) { FilesystemStorage s("UnitTestsStorage"); - FileStorageAccessor accessor(s); + StorageAccessor accessor(s); std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); + FileInfo info = accessor.Write(data, FileContentType_Dicom, CompressionType_None, true); std::string r; - accessor.Read(r, info.GetUuid(), FileContentType_Unknown); - - ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_None, info.GetCompressionType()); - ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(11u, info.GetCompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression) -{ - FilesystemStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); - - accessor.SetCompressionForNextOperations(CompressionType_None); - std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); - - std::string r; - accessor.Read(r, info.GetUuid(), FileContentType_Unknown); + accessor.Read(r, info); ASSERT_EQ(data, r); ASSERT_EQ(CompressionType_None, info.GetCompressionType()); ASSERT_EQ(11u, info.GetUncompressedSize()); ASSERT_EQ(11u, info.GetCompressedSize()); ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); -} - - -TEST(FileStorageAccessor, NoCompression2) -{ - FilesystemStorage s("UnitTestsStorage"); - 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(), FileContentType_Unknown); - - 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()); + ASSERT_EQ("3e25960a79dbc69b674cd4ec67a72c62", info.GetUncompressedMD5()); + ASSERT_EQ(info.GetUncompressedMD5(), info.GetCompressedMD5()); } -TEST(FileStorageAccessor, Compression) +TEST(StorageAccessor, Compression) { FilesystemStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); + StorageAccessor accessor(s); - accessor.SetCompressionForNextOperations(CompressionType_Zlib); std::string data = "Hello world"; - FileInfo info = accessor.Write(data, FileContentType_Dicom); + FileInfo info = accessor.Write(data, FileContentType_DicomAsJson, CompressionType_ZlibWithSize, true); std::string r; - accessor.Read(r, info.GetUuid(), FileContentType_Unknown); + accessor.Read(r, info); ASSERT_EQ(data, r); - ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType()); + ASSERT_EQ(CompressionType_ZlibWithSize, info.GetCompressionType()); ASSERT_EQ(11u, info.GetUncompressedSize()); - ASSERT_EQ(FileContentType_Dicom, info.GetContentType()); + ASSERT_EQ(FileContentType_DicomAsJson, info.GetContentType()); + ASSERT_EQ("3e25960a79dbc69b674cd4ec67a72c62", info.GetUncompressedMD5()); + ASSERT_NE(info.GetUncompressedMD5(), info.GetCompressedMD5()); } -TEST(FileStorageAccessor, Mix) +TEST(StorageAccessor, Mix) { FilesystemStorage s("UnitTestsStorage"); - CompressedFileStorageAccessor accessor(s); + StorageAccessor accessor(s); std::string r; std::string compressedData = "Hello"; std::string uncompressedData = "HelloWorld"; - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom); + FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom, CompressionType_ZlibWithSize, false); + FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom, CompressionType_None, false); - accessor.SetCompressionForNextOperations(CompressionType_None); - FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom); - - accessor.SetCompressionForNextOperations(CompressionType_Zlib); - accessor.Read(r, compressedInfo.GetUuid(), FileContentType_Unknown); + accessor.Read(r, compressedInfo); ASSERT_EQ(compressedData, r); - accessor.SetCompressionForNextOperations(CompressionType_None); - accessor.Read(r, compressedInfo.GetUuid(), FileContentType_Unknown); + accessor.Read(r, uncompressedInfo); + ASSERT_EQ(uncompressedData, r); ASSERT_NE(compressedData, r); /* // This test is too slow on Windows - accessor.SetCompressionForNextOperations(CompressionType_Zlib); + accessor.SetCompressionForNextOperations(CompressionType_ZlibWithSize); ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid(), FileContentType_Unknown), OrthancException); */ }
--- a/UnitTestsSources/FromDcmtkTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -37,9 +37,9 @@ #include "../OrthancServer/OrthancInitialization.h" #include "../OrthancServer/DicomModification.h" #include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Images/ImageBuffer.h" +#include "../Core/Images/PngReader.h" +#include "../Core/Images/PngWriter.h" #include "../Core/Uuid.h" #include "../Resources/EncodingTests.h" @@ -145,33 +145,30 @@ // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image) std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; - std::string m, c; - Toolbox::DecodeDataUriScheme(m, c, s); + std::string m, cc; + Toolbox::DecodeDataUriScheme(m, cc, s); ASSERT_EQ("image/png", m); - ASSERT_EQ(116, c.size()); - std::string cc; - Toolbox::DecodeBase64(cc, c); PngReader reader; reader.ReadFromMemory(cc); - ASSERT_EQ(5, reader.GetHeight()); - ASSERT_EQ(5, reader.GetWidth()); + ASSERT_EQ(5u, reader.GetHeight()); + ASSERT_EQ(5u, reader.GetWidth()); ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); ParsedDicomFile o; - o.EmbedImage(s); + o.EmbedContent(s); o.SaveToFile("UnitTestsResults/png1.dcm"); // Red dot, without alpha channel s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII="; - o.EmbedImage(s); + o.EmbedContent(s); o.SaveToFile("UnitTestsResults/png2.dcm"); // Check box in Graylevel8 s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="; - o.EmbedImage(s); + o.EmbedContent(s); //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing); o.SaveToFile("UnitTestsResults/png3.dcm"); @@ -184,7 +181,7 @@ img.SetHeight(256); img.SetFormat(PixelFormat_Grayscale16); - int v = 0; + uint16_t v = 0; for (unsigned int y = 0; y < img.GetHeight(); y++) { uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y)); @@ -272,6 +269,7 @@ f.SaveToMemoryBuffer(dicom); } + if (testEncodings[i] != Encoding_Windows1251) { ParsedDicomFile g(dicom); @@ -282,9 +280,22 @@ std::string tag; ASSERT_TRUE(g.GetTagValue(tag, DICOM_TAG_PATIENT_NAME)); - - std::string expected(); ASSERT_EQ(std::string(testEncodingsExpected[i]), tag); } } } + + +TEST(FromDcmtkBridge, ValueRepresentation) +{ + ASSERT_EQ(ValueRepresentation_PatientName, + FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ(ValueRepresentation_Date, + FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x0020) /* StudyDate */)); + ASSERT_EQ(ValueRepresentation_Time, + FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x0030) /* StudyTime */)); + ASSERT_EQ(ValueRepresentation_DateTime, + FromDcmtkBridge::GetValueRepresentation(DicomTag(0x0008, 0x002a) /* AcquisitionDateTime */)); + ASSERT_EQ(ValueRepresentation_Other, + FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_ID)); +}
--- a/UnitTestsSources/ImageProcessingTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/ImageProcessingTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,8 +34,8 @@ #include "gtest/gtest.h" #include "../Core/DicomFormat/DicomImageInformation.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/ImageProcessing.h" +#include "../Core/Images/ImageBuffer.h" +#include "../Core/Images/ImageProcessing.h" using namespace Orthanc;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/ImageTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,259 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Images/Font.h" +#include "../Core/Images/Image.h" +#include "../Core/Images/ImageProcessing.h" +#include "../Core/Images/JpegReader.h" +#include "../Core/Images/JpegWriter.h" +#include "../Core/Images/PngReader.h" +#include "../Core/Images/PngWriter.h" +#include "../Core/Toolbox.h" +#include "../Core/Uuid.h" +#include "../OrthancServer/OrthancInitialization.h" + +#include <stdint.h> + + +TEST(PngWriter, ColorPattern) +{ + Orthanc::PngWriter w; + unsigned int width = 17; + unsigned int height = 61; + unsigned int pitch = width * 3; + + std::vector<uint8_t> image(height * pitch); + for (unsigned int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (unsigned int x = 0; x < width; x++, p += 3) + { + p[0] = (y % 3 == 0) ? 255 : 0; + p[1] = (y % 3 == 1) ? 255 : 0; + p[2] = (y % 3 == 2) ? 255 : 0; + } + } + + w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); +} + +TEST(PngWriter, Gray8Pattern) +{ + Orthanc::PngWriter w; + int width = 17; + int height = 256; + int pitch = width; + + std::vector<uint8_t> image(height * pitch); + for (int y = 0; y < height; y++) + { + uint8_t *p = &image[0] + y * pitch; + for (int x = 0; x < width; x++, p++) + { + *p = y; + } + } + + w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); +} + +TEST(PngWriter, Gray16Pattern) +{ + Orthanc::PngWriter w; + int width = 256; + int height = 256; + int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + std::string f, md5; + Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); + Orthanc::Toolbox::ComputeMD5(md5, f); + ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); +} + +TEST(PngWriter, EndToEnd) +{ + Orthanc::PngWriter w; + unsigned int width = 256; + unsigned int height = 256; + unsigned int pitch = width * 2 + 16; + + std::vector<uint8_t> image(height * pitch); + + int v = 0; + for (unsigned int y = 0; y < height; y++) + { + uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + *p = v; + } + } + + std::string s; + w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); + + { + Orthanc::PngReader r; + r.ReadFromMemory(s); + + ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r.GetWidth(), width); + ASSERT_EQ(r.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + ASSERT_EQ(p, r.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } + + { + Orthanc::Toolbox::TemporaryFile tmp; + Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); + + Orthanc::PngReader r2; + r2.ReadFromFile(tmp.GetPath()); + + ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); + ASSERT_EQ(r2.GetWidth(), width); + ASSERT_EQ(r2.GetHeight(), height); + + v = 0; + for (unsigned int y = 0; y < height; y++) + { + const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + ASSERT_EQ(p, r2.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++, p++, v++) + { + ASSERT_EQ(*p, v); + } + } + } +} + + + + +TEST(JpegWriter, Basic) +{ + std::string s; + + { + Orthanc::Image img(Orthanc::PixelFormat_Grayscale8, 16, 16); + for (unsigned int y = 0, value = 0; y < img.GetHeight(); y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(img.GetRow(y)); + for (unsigned int x = 0; x < img.GetWidth(); x++, p++) + { + *p = value++; + } + } + + Orthanc::JpegWriter w; + w.WriteToFile("UnitTestsResults/hello.jpg", img); + + w.WriteToMemory(s, img); + Orthanc::Toolbox::WriteFile(s, "UnitTestsResults/hello2.jpg"); + + std::string t; + Orthanc::Toolbox::ReadFile(t, "UnitTestsResults/hello.jpg"); + ASSERT_EQ(s.size(), t.size()); + ASSERT_EQ(0, memcmp(s.c_str(), t.c_str(), s.size())); + } + + { + Orthanc::JpegReader r1, r2; + r1.ReadFromFile("UnitTestsResults/hello.jpg"); + ASSERT_EQ(16, r1.GetWidth()); + ASSERT_EQ(16, r1.GetHeight()); + + r2.ReadFromMemory(s); + ASSERT_EQ(16, r2.GetWidth()); + ASSERT_EQ(16, r2.GetHeight()); + + for (unsigned int y = 0; y < r1.GetHeight(); y++) + { + const uint8_t* p1 = reinterpret_cast<const uint8_t*>(r1.GetConstRow(y)); + const uint8_t* p2 = reinterpret_cast<const uint8_t*>(r2.GetConstRow(y)); + for (unsigned int x = 0; x < r1.GetWidth(); x++) + { + ASSERT_EQ(*p1, *p2); + } + } + } +} + + +TEST(Font, Basic) +{ + Orthanc::Image s(Orthanc::PixelFormat_RGB24, 640, 480); + memset(s.GetBuffer(), 0, s.GetPitch() * s.GetHeight()); + + ASSERT_GE(1, Orthanc::Configuration::GetFontRegistry().GetSize()); + Orthanc::Configuration::GetFontRegistry().GetFont(0).Draw(s, "Hello world É\n\rComment ça va ?\nq", 50, 60, 255, 0, 0); + + Orthanc::PngWriter w; + w.WriteToFile("UnitTestsResults/font.png", s); +} +
--- a/UnitTestsSources/JpegLosslessTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/JpegLosslessTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -41,8 +41,8 @@ #include "../OrthancServer/ParsedDicomFile.h" #include "../Core/OrthancException.h" -#include "../Core/ImageFormats/ImageBuffer.h" -#include "../Core/ImageFormats/PngWriter.h" +#include "../Core/Images/ImageBuffer.h" +#include "../Core/Images/PngWriter.h" using namespace Orthanc;
--- a/UnitTestsSources/LuaTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/LuaTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,13 +33,14 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/OrthancException.h" #include "../Core/Toolbox.h" #include "../Core/Lua/LuaFunctionCall.h" #include <boost/lexical_cast.hpp> #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) -#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" +#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS to 0 or 1" #endif @@ -79,7 +80,7 @@ { Orthanc::LuaFunctionCall f(lua, "f"); f.PushJson(o); - ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException); + ASSERT_THROW(f.ExecutePredicate(), Orthanc::OrthancException); } o["bool"] = false; @@ -147,8 +148,8 @@ { Json::Value b = Json::objectValue; b["a"] = 42; - b["b"] = 44; - b["c"] = 43; + b["b"] = 44.37; + b["c"] = -43; Json::Value c = Json::arrayValue; c.append("test3"); @@ -170,7 +171,7 @@ Orthanc::LuaFunctionCall f(lua, "identity"); f.PushJson("hello"); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_EQ("hello", v.asString()); } @@ -178,16 +179,24 @@ Orthanc::LuaFunctionCall f(lua, "identity"); f.PushJson(42.25); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_FLOAT_EQ(42.25f, v.asFloat()); } { Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(-42); + Json::Value v; + f.ExecuteToJson(v, false); + ASSERT_EQ(-42, v.asInt()); + } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); Json::Value vv = Json::arrayValue; f.PushJson(vv); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_EQ(Json::arrayValue, v.type()); } @@ -196,7 +205,7 @@ Json::Value vv = Json::objectValue; f.PushJson(vv); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); // Lua does not make the distinction between empty lists and empty objects ASSERT_EQ(Json::arrayValue, v.type()); } @@ -205,18 +214,18 @@ Orthanc::LuaFunctionCall f(lua, "identity"); f.PushJson(b); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat()); - ASSERT_FLOAT_EQ(44.0f, v["b"].asFloat()); - ASSERT_FLOAT_EQ(43.0f, v["c"].asFloat()); + ASSERT_FLOAT_EQ(44.37f, v["b"].asFloat()); + ASSERT_FLOAT_EQ(-43.0f, v["c"].asFloat()); } { Orthanc::LuaFunctionCall f(lua, "identity"); f.PushJson(c); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_EQ(Json::arrayValue, v.type()); ASSERT_EQ("test3", v[0].asString()); ASSERT_EQ("test1", v[1].asString()); @@ -227,29 +236,64 @@ Orthanc::LuaFunctionCall f(lua, "identity"); f.PushJson(a); Json::Value v; - f.ExecuteToJson(v); + f.ExecuteToJson(v, false); ASSERT_EQ("World", v["Hello"].asString()); + ASSERT_EQ(Json::intValue, v["List"][0]["a"].type()); + ASSERT_EQ(Json::realValue, v["List"][0]["b"].type()); + ASSERT_EQ(Json::intValue, v["List"][0]["c"].type()); ASSERT_EQ(42, v["List"][0]["a"].asInt()); + ASSERT_FLOAT_EQ(44.37f, v["List"][0]["b"].asFloat()); ASSERT_EQ(44, v["List"][0]["b"].asInt()); - ASSERT_EQ(43, v["List"][0]["c"].asInt()); + ASSERT_EQ(-43, v["List"][0]["c"].asInt()); ASSERT_EQ("test3", v["List"][1][0].asString()); ASSERT_EQ("test1", v["List"][1][1].asString()); ASSERT_EQ("test2", v["List"][1][2].asString()); } + + { + Orthanc::LuaFunctionCall f(lua, "identity"); + f.PushJson(a); + Json::Value v; + f.ExecuteToJson(v, true); + ASSERT_EQ("World", v["Hello"].asString()); + ASSERT_EQ(Json::stringValue, v["List"][0]["a"].type()); + ASSERT_EQ(Json::stringValue, v["List"][0]["b"].type()); + ASSERT_EQ(Json::stringValue, v["List"][0]["c"].type()); + ASSERT_EQ("42", v["List"][0]["a"].asString()); + ASSERT_EQ("44.37", v["List"][0]["b"].asString()); + ASSERT_EQ("-43", v["List"][0]["c"].asString()); + ASSERT_EQ("test3", v["List"][1][0].asString()); + ASSERT_EQ("test1", v["List"][1][1].asString()); + ASSERT_EQ("test2", v["List"][1][2].asString()); + } + + { + Orthanc::LuaFunctionCall f(lua, "DumpJson"); + f.PushJson(a); + std::string s; + f.ExecuteToString(s); + + Json::FastWriter writer; + std::string t = writer.write(a); + + ASSERT_EQ(s, t); + } } + + TEST(Lua, Http) { Orthanc::LuaContext lua; #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 lua.Execute("JSON = loadstring(HttpGet('http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/JSON.lua')) ()"); - const std::string url("http://orthanc.googlecode.com/hg/OrthancCppClient/SharedLibrary/Product.json"); + const std::string url("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/Product.json"); #endif std::string s; lua.Execute(s, "print(HttpGet({}))"); - ASSERT_EQ("ERROR", Orthanc::Toolbox::StripSpaces(s)); + ASSERT_EQ("nil", Orthanc::Toolbox::StripSpaces(s)); #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 lua.Execute(s, "print(string.len(HttpGet(\"" + url + "\")))");
--- a/UnitTestsSources/MemoryCacheTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/MemoryCacheTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,12 +33,14 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include <glog/logging.h> #include <memory> #include <boost/thread.hpp> #include <boost/lexical_cast.hpp> + +#include "../Core/Cache/MemoryCache.h" +#include "../Core/Cache/SharedArchive.h" #include "../Core/IDynamicObject.h" -#include "../Core/Cache/MemoryCache.h" +#include "../Core/Logging.h" TEST(LRU, Basic) @@ -188,11 +190,6 @@ LOG(INFO) << "Removing cache entry for " << value_; log_ += boost::lexical_cast<std::string>(value_) + " "; } - - int GetValue() const - { - return value_; - } }; class IntegerProvider : public Orthanc::ICachePageProvider @@ -228,3 +225,59 @@ ASSERT_EQ("45 42 43 47 44 42 ", provider.log_); } + + + + + +namespace +{ + class S : public Orthanc::IDynamicObject + { + private: + std::string value_; + + public: + S(const std::string& value) : value_(value) + { + } + + const std::string& GetValue() const + { + return value_; + } + }; +} + + +TEST(LRU, SharedArchive) +{ + std::string first, second; + Orthanc::SharedArchive a(3); + first = a.Add(new S("First item")); + second = a.Add(new S("Second item")); + + for (int i = 1; i < 100; i++) + { + a.Add(new S("Item " + boost::lexical_cast<std::string>(i))); + // Continuously protect the two first items + try { Orthanc::SharedArchive::Accessor(a, first); } catch (Orthanc::OrthancException&) {} + try { Orthanc::SharedArchive::Accessor(a, second); } catch (Orthanc::OrthancException&) {} + } + + std::list<std::string> i; + a.List(i); + + size_t count = 0; + for (std::list<std::string>::const_iterator + it = i.begin(); it != i.end(); it++) + { + if (*it == first || + *it == second) + { + count++; + } + } + + ASSERT_EQ(2u, count); +}
--- a/UnitTestsSources/MultiThreadingTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -32,23 +32,19 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include <glog/logging.h> #include "../OrthancServer/Scheduler/ServerScheduler.h" - #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" -#include "../Core/MultiThreading/ArrayFilledByThreads.h" #include "../Core/MultiThreading/Locker.h" #include "../Core/MultiThreading/Mutex.h" #include "../Core/MultiThreading/ReaderWriterLock.h" -#include "../Core/MultiThreading/ThreadedCommandProcessor.h" using namespace Orthanc; namespace { - class DynamicInteger : public ICommand + class DynamicInteger : public IDynamicObject { private: int value_; @@ -64,56 +60,10 @@ { 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; @@ -146,7 +96,7 @@ SharedMessageQueue q; q.Enqueue(new DynamicInteger(10, s)); q.Enqueue(new DynamicInteger(20, s)); - throw OrthancException("Nope"); + throw OrthancException(ErrorCode_InternalError); } catch (OrthancException&) { @@ -154,78 +104,6 @@ } -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()); - } -} - - TEST(MultiThreading, Mutex) { Mutex mutex; @@ -258,7 +136,8 @@ printf("START\n"); fflush(stdout); { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281"); } @@ -267,7 +146,8 @@ printf("**\n"); fflush(stdout); { - ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + RemoteModalityParameters remote("STORESCP", "localhost", 2000, ModalityManufacturer_Generic); + ReusableDicomUserConnection::Locker lock(c, "ORTHANC", remote); lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277"); } @@ -357,7 +237,7 @@ IServerCommand::ListOfStrings l; scheduler.SubmitAndWait(l, job); - ASSERT_EQ(2, l.size()); + ASSERT_EQ(2u, l.size()); ASSERT_EQ(42 * 2 * 3, boost::lexical_cast<int>(l.front())); ASSERT_EQ(42 * 2 * 3 * 4 * 5, boost::lexical_cast<int>(l.back())); @@ -369,6 +249,11 @@ //Toolbox::ServerBarrier(); //Toolbox::USleep(3000000); + scheduler.Stop(); + done = true; - t.join(); + if (t.joinable()) + { + t.join(); + } }
--- a/UnitTestsSources/PluginsTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/PluginsTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,12 +33,21 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include <glog/logging.h> - #include "../Plugins/Engine/PluginsManager.h" using namespace Orthanc; + +#if ORTHANC_PLUGINS_ENABLED == 1 + +TEST(SharedLibrary, Enumerations) +{ + // The plugin engine cannot work if the size of an enumeration does + // not correspond to the size of "int32_t" + ASSERT_EQ(sizeof(int32_t), sizeof(OrthancPluginErrorCode)); +} + + TEST(SharedLibrary, Basic) { #if defined(_WIN32) @@ -55,6 +64,16 @@ ASSERT_TRUE(l.HasFunction("dlclose")); ASSERT_FALSE(l.HasFunction("world")); +#elif defined(__FreeBSD__) + // dlopen() in FreeBSD is supplied by libc, libc.so is + // a ldscript, so we can't actually use it. Use thread + // library instead - if it works - dlopen() is good. + SharedLibrary l("libpthread.so"); + ASSERT_THROW(l.GetFunction("world"), OrthancException); + ASSERT_TRUE(l.GetFunction("pthread_create") != NULL); + ASSERT_TRUE(l.HasFunction("pthread_cancel")); + ASSERT_FALSE(l.HasFunction("world")); + #elif defined(__APPLE__) && defined(__MACH__) SharedLibrary l("libdl.dylib"); ASSERT_THROW(l.GetFunction("world"), OrthancException); @@ -66,3 +85,5 @@ #error Support your platform here #endif } + +#endif
--- a/UnitTestsSources/PngTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics - * Department, University Hospital 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 "PrecompiledHeadersUnitTests.h" -#include "gtest/gtest.h" - -#include <stdint.h> -#include "../Core/ImageFormats/PngReader.h" -#include "../Core/ImageFormats/PngWriter.h" -#include "../Core/Toolbox.h" -#include "../Core/Uuid.h" - - -TEST(PngWriter, ColorPattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 61; - int pitch = width * 3; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p += 3) - { - p[0] = (y % 3 == 0) ? 255 : 0; - p[1] = (y % 3 == 1) ? 255 : 0; - p[2] = (y % 3 == 2) ? 255 : 0; - } - } - - w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5); -} - -TEST(PngWriter, Gray8Pattern) -{ - Orthanc::PngWriter w; - int width = 17; - int height = 256; - int pitch = width; - - std::vector<uint8_t> image(height * pitch); - for (int y = 0; y < height; y++) - { - uint8_t *p = &image[0] + y * pitch; - for (int x = 0; x < width; x++, p++) - { - *p = y; - } - } - - w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5); -} - -TEST(PngWriter, Gray16Pattern) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - std::string f, md5; - Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png"); - Orthanc::Toolbox::ComputeMD5(md5, f); - ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5); -} - -TEST(PngWriter, EndToEnd) -{ - Orthanc::PngWriter w; - int width = 256; - int height = 256; - int pitch = width * 2 + 16; - - std::vector<uint8_t> image(height * pitch); - - int v = 0; - for (int y = 0; y < height; y++) - { - uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch); - for (int x = 0; x < width; x++, p++, v++) - { - *p = v; - } - } - - std::string s; - w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]); - - { - Orthanc::PngReader r; - r.ReadFromMemory(s); - - ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r.GetWidth(), width); - ASSERT_EQ(r.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); - ASSERT_EQ(p, r.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } - - { - Orthanc::Toolbox::TemporaryFile tmp; - Orthanc::Toolbox::WriteFile(s, tmp.GetPath()); - - Orthanc::PngReader r2; - r2.ReadFromFile(tmp.GetPath()); - - ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16); - ASSERT_EQ(r2.GetWidth(), width); - ASSERT_EQ(r2.GetHeight(), height); - - v = 0; - for (int y = 0; y < height; y++) - { - const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); - ASSERT_EQ(p, r2.GetConstRow(y)); - for (int x = 0; x < width; x++, p++, v++) - { - ASSERT_EQ(*p, v); - } - } - } -}
--- a/UnitTestsSources/RestApiTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/RestApiTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -34,10 +34,10 @@ #include "gtest/gtest.h" #include <ctype.h> -#include <glog/logging.h> #include "../Core/ChunkedBuffer.h" #include "../Core/HttpClient.h" +#include "../Core/Logging.h" #include "../Core/RestApi/RestApi.h" #include "../Core/Uuid.h" #include "../Core/OrthancException.h" @@ -50,6 +50,8 @@ #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" #endif + + TEST(HttpClient, Basic) { HttpClient c; @@ -61,30 +63,82 @@ #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 Json::Value v; - c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json"); + c.SetUrl("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/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 } + +#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 && ORTHANC_SSL_ENABLED == 1 + +/** + The HTTPS CA certificates for BitBucket were extracted as follows: + + (1) We retrieve the certification chain of BitBucket: + + # echo | openssl s_client -showcerts -connect www.bitbucket.org:443 + + (2) We see that the certification authority (CA) is + "www.digicert.com", and the root certificate is "DigiCert High + Assurance EV Root CA". As a consequence, we navigate to DigiCert to + find the URL to this CA certificate: + + firefox https://www.digicert.com/digicert-root-certificates.htm + + (3) Once we get the URL to the CA certificate, we convert it to a C + macro that can be used by libcurl: + + # cd UnitTestsSources + # ../Resources/RetrieveCACertificates.py BITBUCKET_CERTIFICATES https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt > BitbucketCACertificates.h +**/ + +#include "BitbucketCACertificates.h" + +TEST(HttpClient, Ssl) +{ + Toolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert"); + + /*{ + std::string s; + Toolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt"); + Toolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert"); + }*/ + + HttpClient c; + c.SetHttpsVerifyPeers(true); + c.SetHttpsCACertificates("UnitTestsResults/bitbucket.cert"); + c.SetUrl("https://bitbucket.org/sjodogne/orthanc/raw/Orthanc-0.9.3/Resources/Configuration.json"); + + Json::Value v; + c.Apply(v); + ASSERT_TRUE(v.isMember("LuaScripts")); +} + +TEST(HttpClient, SslNoVerification) +{ + HttpClient c; + c.SetHttpsVerifyPeers(false); + c.SetUrl("https://bitbucket.org/sjodogne/orthanc/raw/Orthanc-0.9.3/Resources/Configuration.json"); + + Json::Value v; + c.Apply(v); + ASSERT_TRUE(v.isMember("LuaScripts")); +} + +#endif + + TEST(RestApi, ChunkedBuffer) { ChunkedBuffer b; - ASSERT_EQ(0, b.GetNumBytes()); + ASSERT_EQ(0u, b.GetNumBytes()); b.AddChunk("hello", 5); - ASSERT_EQ(5, b.GetNumBytes()); + ASSERT_EQ(5u, b.GetNumBytes()); b.AddChunk("world", 5); - ASSERT_EQ(10, b.GetNumBytes()); + ASSERT_EQ(10u, b.GetNumBytes()); std::string s; b.Flatten(s); @@ -93,11 +147,11 @@ TEST(RestApi, ParseCookies) { - HttpHandler::Arguments headers; - HttpHandler::Arguments cookies; + IHttpHandler::Arguments headers; + IHttpHandler::Arguments cookies; headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; - HttpHandler::ParseCookies(cookies, headers); + HttpToolbox::ParseCookies(cookies, headers); ASSERT_EQ(4u, cookies.size()); ASSERT_EQ("b", cookies["a"]); ASSERT_EQ("d", cookies["c"]); @@ -105,24 +159,24 @@ ASSERT_EQ("h", cookies["g"]); headers["cookie"] = " name = value ; name2=value2"; - HttpHandler::ParseCookies(cookies, headers); + HttpToolbox::ParseCookies(cookies, headers); ASSERT_EQ(2u, cookies.size()); ASSERT_EQ("value", cookies["name"]); ASSERT_EQ("value2", cookies["name2"]); headers["cookie"] = " ;;; "; - HttpHandler::ParseCookies(cookies, headers); + HttpToolbox::ParseCookies(cookies, headers); ASSERT_EQ(0u, cookies.size()); headers["cookie"] = " ; n=v ;; "; - HttpHandler::ParseCookies(cookies, headers); + HttpToolbox::ParseCookies(cookies, headers); ASSERT_EQ(1u, cookies.size()); ASSERT_EQ("v", cookies["n"]); } TEST(RestApi, RestApiPath) { - HttpHandler::Arguments args; + IHttpHandler::Arguments args; UriComponents trail; { @@ -220,7 +274,7 @@ public: virtual bool Visit(const RestApiHierarchy::Resource& resource, const UriComponents& uri, - const HttpHandler::Arguments& components, + const IHttpHandler::Arguments& components, const UriComponents& trailing) { return resource.Handle(*reinterpret_cast<RestApiGetCall*>(NULL));
--- a/UnitTestsSources/SQLiteTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/SQLiteTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -45,7 +45,14 @@ TEST(SQLite, Configuration) { - ASSERT_EQ(1, sqlite3_threadsafe()); + /** + * The system-wide version of SQLite under OS X uses + * SQLITE_THREADSAFE==2 (SQLITE_CONFIG_SERIALIZED), whereas the + * static builds of Orthanc use SQLITE_THREADSAFE==1 + * (SQLITE_CONFIG_MULTITHREAD). In any case, we wish to ensure that + * SQLITE_THREADSAFE!=0 (SQLITE_CONFIG_SINGLETHREAD). + **/ + ASSERT_NE(0, sqlite3_threadsafe()); }
--- a/UnitTestsSources/ServerIndexTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/ServerIndexTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -33,15 +33,15 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" +#include "../Core/DicomFormat/DicomNullValue.h" +#include "../Core/FileStorage/FilesystemStorage.h" +#include "../Core/Logging.h" +#include "../Core/Uuid.h" #include "../OrthancServer/DatabaseWrapper.h" #include "../OrthancServer/ServerContext.h" #include "../OrthancServer/ServerIndex.h" -#include "../Core/Uuid.h" -#include "../Core/DicomFormat/DicomNullValue.h" -#include "../Core/FileStorage/FilesystemStorage.h" #include <ctype.h> -#include <glog/logging.h> #include <algorithm> using namespace Orthanc; @@ -54,7 +54,7 @@ }; - class ServerIndexListener : public IServerIndexListener + class TestDatabaseListener : public IDatabaseListener { public: std::vector<std::string> deletedFiles_; @@ -100,7 +100,7 @@ class DatabaseWrapperTest : public ::testing::TestWithParam<DatabaseWrapperClass> { protected: - std::auto_ptr<ServerIndexListener> listener_; + std::auto_ptr<TestDatabaseListener> listener_; std::auto_ptr<IDatabaseWrapper> index_; DatabaseWrapperTest() @@ -109,7 +109,7 @@ virtual void SetUp() { - listener_.reset(new ServerIndexListener); + listener_.reset(new TestDatabaseListener); switch (GetParam()) { @@ -193,7 +193,7 @@ { DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get()); sqlite->GetChildren(j, id); - ASSERT_EQ(0, j.size()); + ASSERT_EQ(0u, j.size()); break; } @@ -212,7 +212,7 @@ { DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get()); sqlite->GetChildren(j, id); - ASSERT_EQ(1, j.size()); + ASSERT_EQ(1u, j.size()); ASSERT_EQ(expected, j.front()); break; } @@ -234,7 +234,7 @@ { DatabaseWrapper* sqlite = dynamic_cast<DatabaseWrapper*>(index_.get()); sqlite->GetChildren(j, id); - ASSERT_EQ(2, j.size()); + ASSERT_EQ(2u, j.size()); ASSERT_TRUE((expected1 == j.front() && expected2 == j.back()) || (expected1 == j.back() && expected2 == j.front())); break; @@ -350,7 +350,7 @@ ASSERT_EQ(0u, md.size()); index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", - CompressionType_Zlib, 21, "compressedMD5")); + CompressionType_ZlibWithSize, 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"); @@ -409,7 +409,7 @@ ASSERT_EQ("md5", att.GetUncompressedMD5()); ASSERT_EQ("compressedMD5", att.GetCompressedMD5()); ASSERT_EQ(42u, att.GetUncompressedSize()); - ASSERT_EQ(CompressionType_Zlib, att.GetCompressionType()); + ASSERT_EQ(CompressionType_ZlibWithSize, att.GetCompressionType()); ASSERT_TRUE(index_->LookupAttachment(att, a[6], FileContentType_Dicom)); ASSERT_EQ("world", att.GetUuid()); @@ -660,14 +660,15 @@ Toolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory - ServerContext context(db); - context.SetStorageArea(storage); + ServerContext context(db, storage); ServerIndex& index = context.GetIndex(); 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)); + + context.Stop(); } @@ -727,8 +728,7 @@ Toolbox::RemoveFile(path + "/index"); FilesystemStorage storage(path); DatabaseWrapper db; // The SQLite DB is in memory - ServerContext context(db); - context.SetStorageArea(storage); + ServerContext context(db, storage); ServerIndex& index = context.GetIndex(); index.SetMaximumStorageSize(10); @@ -753,7 +753,7 @@ std::map<MetadataType, std::string> instanceMetadata; ServerIndex::MetadataMap metadata; ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, instance, attachments, "", metadata)); - ASSERT_EQ(2, instanceMetadata.size()); + ASSERT_EQ(2u, instanceMetadata.size()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end()); @@ -779,4 +779,6 @@ // Because the DB is in memory, the SQLite index must not have been created ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException); + + context.Stop(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/StreamTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -0,0 +1,329 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital 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 "PrecompiledHeadersUnitTests.h" +#include "gtest/gtest.h" + +#include "../Core/Toolbox.h" +#include "../Core/OrthancException.h" +#include "../Core/Uuid.h" +#include "../Core/HttpServer/BufferHttpSender.h" +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/HttpServer/HttpStreamTranscoder.h" +#include "../Core/Compression/ZlibCompressor.h" +#include "../Core/Compression/GzipCompressor.h" + + +using namespace Orthanc; + + +TEST(Gzip, Basic) +{ + std::string s = "Hello world"; + + std::string compressed; + GzipCompressor c; + ASSERT_FALSE(c.HasPrefixWithUncompressedSize()); + IBufferCompressor::Compress(compressed, c, s); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); +} + + +TEST(Gzip, Empty) +{ + std::string s; + + std::string compressed; + GzipCompressor c; + ASSERT_FALSE(c.HasPrefixWithUncompressedSize()); + c.SetPrefixWithUncompressedSize(false); + IBufferCompressor::Compress(compressed, c, s); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + ASSERT_EQ(0, uncompressed.size()); +} + + +TEST(Gzip, BasicWithPrefix) +{ + std::string s = "Hello world"; + + std::string compressed; + GzipCompressor c; + c.SetPrefixWithUncompressedSize(true); + ASSERT_TRUE(c.HasPrefixWithUncompressedSize()); + IBufferCompressor::Compress(compressed, c, s); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + ASSERT_EQ(s.size(), uncompressed.size()); + ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size())); +} + + +TEST(Gzip, EmptyWithPrefix) +{ + std::string s; + + std::string compressed; + GzipCompressor c; + c.SetPrefixWithUncompressedSize(true); + ASSERT_TRUE(c.HasPrefixWithUncompressedSize()); + IBufferCompressor::Compress(compressed, c, s); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + ASSERT_EQ(0, uncompressed.size()); +} + + +TEST(Zlib, Basic) +{ + std::string s = Toolbox::GenerateUuid(); + s = s + s + s + s; + + std::string compressed, compressed2; + ZlibCompressor c; + ASSERT_TRUE(c.HasPrefixWithUncompressedSize()); + IBufferCompressor::Compress(compressed, c, s); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + 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); + IBufferCompressor::Compress(compressed, c, s); + + c.SetCompressionLevel(0); + IBufferCompressor::Compress(compressed2, c, 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; + IBufferCompressor::Compress(compressed, c, s); + + compressed[compressed.size() - 1] = 'a'; + std::string u; + + ASSERT_THROW(IBufferCompressor::Uncompress(u, c, compressed), OrthancException); +} + + +TEST(Zlib, Empty) +{ + std::string s = ""; + + std::string compressed, compressed2; + ZlibCompressor c; + IBufferCompressor::Compress(compressed, c, s); + ASSERT_EQ(compressed, compressed2); + + std::string uncompressed; + IBufferCompressor::Uncompress(uncompressed, c, compressed); + ASSERT_EQ(0u, uncompressed.size()); +} + + +static bool ReadAllStream(std::string& result, + IHttpStreamAnswer& stream, + bool allowGzip = false, + bool allowDeflate = false) +{ + stream.SetupHttpCompression(allowGzip, allowDeflate); + + result.resize(static_cast<size_t>(stream.GetContentLength())); + + size_t pos = 0; + while (stream.ReadNextChunk()) + { + size_t s = stream.GetChunkSize(); + if (pos + s > result.size()) + { + return false; + } + + memcpy(&result[pos], stream.GetChunkContent(), s); + pos += s; + } + + return pos == result.size(); +} + + +TEST(BufferHttpSender, Basic) +{ + const std::string s = "Hello world"; + std::string t; + + { + BufferHttpSender sender; + sender.SetChunkSize(1); + ASSERT_TRUE(ReadAllStream(t, sender)); + ASSERT_EQ(0u, t.size()); + } + + for (int cs = 0; cs < 5; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + sender.GetBuffer() = s; + ASSERT_TRUE(ReadAllStream(t, sender)); + ASSERT_EQ(s, t); + } +} + + +TEST(FilesystemHttpSender, Basic) +{ + const std::string& path = "UnitTestsResults/stream"; + const std::string s = "Hello world"; + std::string t; + + { + Toolbox::WriteFile(s, path); + FilesystemHttpSender sender(path); + ASSERT_TRUE(ReadAllStream(t, sender)); + ASSERT_EQ(s, t); + } + + { + Toolbox::WriteFile("", path); + FilesystemHttpSender sender(path); + ASSERT_TRUE(ReadAllStream(t, sender)); + ASSERT_EQ(0u, t.size()); + } +} + + +TEST(HttpStreamTranscoder, Basic) +{ + ZlibCompressor compressor; + + const std::string s = "Hello world " + Toolbox::GenerateUuid(); + + std::string t; + IBufferCompressor::Compress(t, compressor, s); + + for (int cs = 0; cs < 5; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + sender.GetBuffer() = t; + std::string u; + ASSERT_TRUE(ReadAllStream(u, sender)); + + std::string v; + IBufferCompressor::Uncompress(v, compressor, u); + ASSERT_EQ(s, v); + } + + // Pass-through test, no decompression occurs + for (int cs = 0; cs < 5; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + sender.GetBuffer() = t; + + HttpStreamTranscoder transcode(sender, CompressionType_None); + + std::string u; + ASSERT_TRUE(ReadAllStream(u, transcode)); + + ASSERT_EQ(t, u); + } + + // Pass-through test, decompression occurs + for (int cs = 0; cs < 5; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + sender.GetBuffer() = t; + + HttpStreamTranscoder transcode(sender, CompressionType_ZlibWithSize); + + std::string u; + ASSERT_TRUE(ReadAllStream(u, transcode, false, false)); + + ASSERT_EQ(s, u); + } + + // Pass-through test with zlib, no decompression occurs but deflate is sent + for (int cs = 0; cs < 16; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + sender.GetBuffer() = t; + + HttpStreamTranscoder transcode(sender, CompressionType_ZlibWithSize); + + std::string u; + ASSERT_TRUE(ReadAllStream(u, transcode, false, true)); + + ASSERT_EQ(t.size() - sizeof(uint64_t), u.size()); + ASSERT_EQ(t.substr(sizeof(uint64_t)), u); + } + + for (int cs = 0; cs < 3; cs++) + { + BufferHttpSender sender; + sender.SetChunkSize(cs); + + HttpStreamTranscoder transcode(sender, CompressionType_ZlibWithSize); + std::string u; + ASSERT_TRUE(ReadAllStream(u, transcode, false, true)); + + ASSERT_EQ(0u, u.size()); + } +}
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -37,14 +37,15 @@ #include <ctype.h> -#include "../Core/Compression/ZlibCompressor.h" #include "../Core/DicomFormat/DicomTag.h" -#include "../Core/HttpServer/HttpHandler.h" +#include "../Core/HttpServer/HttpToolbox.h" +#include "../Core/Logging.h" #include "../Core/OrthancException.h" #include "../Core/Toolbox.h" #include "../Core/Uuid.h" #include "../OrthancServer/OrthancInitialization.h" + using namespace Orthanc; @@ -77,6 +78,17 @@ ASSERT_FALSE(Toolbox::IsSHA1("012345678901234567890123456789012345678901234")); ASSERT_TRUE(Toolbox::IsSHA1("b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9")); + std::string sha = " b5ed549f-956400ce-69a8c063-bf5b78be-2732a4b9 "; + ASSERT_TRUE(Toolbox::IsSHA1(sha)); + sha[3] = '\0'; + sha[53] = '\0'; + ASSERT_TRUE(Toolbox::IsSHA1(sha)); + sha[40] = '\0'; + ASSERT_FALSE(Toolbox::IsSHA1(sha)); + ASSERT_FALSE(Toolbox::IsSHA1(" ")); + + ASSERT_TRUE(Toolbox::IsSHA1("16738bc3-e47ed42a-43ce044c-a3414a45-cb069bd0")); + std::string s; Toolbox::ComputeSHA1(s, "The quick brown fox jumps over the lazy dog"); ASSERT_TRUE(Toolbox::IsSHA1(s)); @@ -85,99 +97,15 @@ 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(ParseGetArguments, Basic) { - HttpHandler::Arguments a; - HttpHandler::ParseGetArguments(a, "aaa=baaa&bb=a&aa=c"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetArguments(b, "aaa=baaa&bb=a&aa=c"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(3u, a.size()); ASSERT_EQ(a["aaa"], "baaa"); ASSERT_EQ(a["bb"], "a"); @@ -186,8 +114,12 @@ TEST(ParseGetArguments, BasicEmpty) { - HttpHandler::Arguments a; - HttpHandler::ParseGetArguments(a, "aaa&bb=aa&aa"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetArguments(b, "aaa&bb=aa&aa"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(3u, a.size()); ASSERT_EQ(a["aaa"], ""); ASSERT_EQ(a["bb"], "aa"); @@ -196,16 +128,24 @@ TEST(ParseGetArguments, Single) { - HttpHandler::Arguments a; - HttpHandler::ParseGetArguments(a, "aaa=baaa"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetArguments(b, "aaa=baaa"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(1u, a.size()); ASSERT_EQ(a["aaa"], "baaa"); } TEST(ParseGetArguments, SingleEmpty) { - HttpHandler::Arguments a; - HttpHandler::ParseGetArguments(a, "aaa"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetArguments(b, "aaa"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(1u, a.size()); ASSERT_EQ(a["aaa"], ""); } @@ -213,8 +153,12 @@ TEST(ParseGetQuery, Test1) { UriComponents uri; - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(uri, a, "/instances/test/world?aaa=baaa&bb=a&aa=c"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetQuery(uri, b, "/instances/test/world?aaa=baaa&bb=a&aa=c"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(3u, uri.size()); ASSERT_EQ("instances", uri[0]); ASSERT_EQ("test", uri[1]); @@ -228,8 +172,12 @@ TEST(ParseGetQuery, Test2) { UriComponents uri; - HttpHandler::Arguments a; - HttpHandler::ParseGetQuery(uri, a, "/instances/test/world"); + IHttpHandler::GetArguments b; + HttpToolbox::ParseGetQuery(uri, b, "/instances/test/world"); + + IHttpHandler::Arguments a; + HttpToolbox::CompileGetArguments(a, b); + ASSERT_EQ(3u, uri.size()); ASSERT_EQ("instances", uri[0]); ASSERT_EQ("test", uri[1]); @@ -452,8 +400,6 @@ } -#include <glog/logging.h> - TEST(Logger, Basic) { LOG(INFO) << "I say hello"; @@ -576,6 +522,13 @@ RegisterUserMetadata(2047, "Ceci est un test"); ASSERT_EQ(2047, StringToMetadata("2047")); ASSERT_EQ(2047, StringToMetadata("Ceci est un test")); + + ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("Generic"))); + ASSERT_STREQ("StoreScp", EnumerationToString(StringToModalityManufacturer("StoreScp"))); + ASSERT_STREQ("ClearCanvas", EnumerationToString(StringToModalityManufacturer("ClearCanvas"))); + ASSERT_STREQ("MedInria", EnumerationToString(StringToModalityManufacturer("MedInria"))); + ASSERT_STREQ("Dcm4Chee", EnumerationToString(StringToModalityManufacturer("Dcm4Chee"))); + ASSERT_STREQ("SyngoVia", EnumerationToString(StringToModalityManufacturer("SyngoVia"))); } @@ -624,15 +577,15 @@ std::vector<std::string> t; Toolbox::TokenizeString(t, "", ','); - ASSERT_EQ(1, t.size()); + ASSERT_EQ(1u, t.size()); ASSERT_EQ("", t[0]); Toolbox::TokenizeString(t, "abc", ','); - ASSERT_EQ(1, t.size()); + ASSERT_EQ(1u, t.size()); ASSERT_EQ("abc", t[0]); Toolbox::TokenizeString(t, "ab,cd,ef,", ','); - ASSERT_EQ(4, t.size()); + ASSERT_EQ(4u, t.size()); ASSERT_EQ("ab", t[0]); ASSERT_EQ("cd", t[1]); ASSERT_EQ("ef", t[2]); @@ -668,19 +621,29 @@ #if defined(__linux) #include <endian.h> +#elif defined(__FreeBSD__) +#include <machine/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) + + /** + * Windows and OS X are assumed to always little-endian. + **/ + +#if defined(_WIN32) || defined(__APPLE__) ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); -#elif defined(__APPLE__) - ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); + /** + * Linux. + **/ + #elif defined(__linux) || defined(__FreeBSD_kernel__) #if !defined(__BYTE_ORDER) @@ -693,6 +656,18 @@ ASSERT_EQ(Endianness_Little, Toolbox::DetectEndianness()); # endif + + /** + * FreeBSD. + **/ + +#elif defined(__FreeBSD__) +# 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 @@ -742,24 +717,31 @@ } +TEST(Toolbox, StartsWith) +{ + ASSERT_TRUE(Toolbox::StartsWith("hello world", "")); + ASSERT_TRUE(Toolbox::StartsWith("hello world", "hello")); + ASSERT_TRUE(Toolbox::StartsWith("hello world", "h")); + ASSERT_FALSE(Toolbox::StartsWith("hello world", "H")); + ASSERT_FALSE(Toolbox::StartsWith("h", "hello")); + ASSERT_TRUE(Toolbox::StartsWith("h", "h")); + ASSERT_FALSE(Toolbox::StartsWith("", "h")); +} + + 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; + Logging::Initialize(); + Logging::EnableInfoLevel(true); + Toolbox::DetectEndianness(); + Toolbox::MakeDirectory("UnitTestsResults"); + OrthancInitialize(); - Toolbox::DetectEndianness(); - - google::InitGoogleLogging("Orthanc"); - - Toolbox::CreateDirectory("UnitTestsResults"); - - OrthancInitialize(); ::testing::InitGoogleTest(&argc, argv); int result = RUN_ALL_TESTS(); + OrthancFinalize(); + Logging::Finalize(); + return result; }
--- a/UnitTestsSources/VersionsTests.cpp Wed Feb 11 10:40:08 2015 +0100 +++ b/UnitTestsSources/VersionsTests.cpp Wed Sep 30 13:23:31 2015 +0200 @@ -42,7 +42,11 @@ #include <boost/version.hpp> #include <sqlite3.h> #include <lua.h> +#include <jpeglib.h> + +#if ORTHANC_SSL_ENABLED == 1 #include <openssl/opensslv.h> +#endif TEST(Versions, Zlib) @@ -59,15 +63,21 @@ TEST(Versions, Png) { ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE, - png_access_version_number()); + static_cast<int>(png_access_version_number())); } TEST(Versions, SQLite) { +#if defined(__APPLE__) + // Under OS X, there might exist minor differences between the + // version of the headers and the version of the library, for the + // system-wide SQLite. Ignore these differences. +#else // http://www.sqlite.org/capi3ref.html#sqlite3_libversion - assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER ); - assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0); - assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0); + EXPECT_EQ(sqlite3_libversion_number(), SQLITE_VERSION_NUMBER); + EXPECT_STREQ(sqlite3_sourceid(), SQLITE_SOURCE_ID); + EXPECT_STREQ(sqlite3_libversion(), SQLITE_VERSION); +#endif // Ensure that the SQLite version is above 3.7.0. // "sqlite3_create_function_v2" is not defined in previous versions. @@ -84,6 +94,7 @@ #if ORTHANC_STATIC == 1 + TEST(Versions, ZlibStatic) { ASSERT_STREQ("1.2.7", zlibVersion()); @@ -91,13 +102,13 @@ TEST(Versions, BoostStatic) { - ASSERT_STREQ("1_55", BOOST_LIB_VERSION); + ASSERT_STREQ("1_58", BOOST_LIB_VERSION); } TEST(Versions, CurlStatic) { curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("7.26.0", v->version); + ASSERT_STREQ("7.44.0", v->version); } TEST(Versions, PngStatic) @@ -106,12 +117,18 @@ ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING); } +TEST(Versions, JpegStatic) +{ + ASSERT_EQ(9, JPEG_LIB_VERSION_MAJOR); + ASSERT_EQ(1, JPEG_LIB_VERSION_MINOR); +} + TEST(Versions, CurlSslStatic) { curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW); // Check that SSL support is enabled when required - bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL; + bool curlSupportsSsl = (vinfo->features & CURL_VERSION_SSL) != 0; #if ORTHANC_SSL_ENABLED == 0 ASSERT_FALSE(curlSupportsSsl); @@ -125,9 +142,20 @@ ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE); } + +#if ORTHANC_SSL_ENABLED == 1 TEST(Version, OpenSslStatic) { - ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER); + ASSERT_EQ(0x1000204fL /* openssl-1.0.2d */, OPENSSL_VERSION_NUMBER); +} +#endif + + +#include <json/version.h> + +TEST(Version, JsonCpp) +{ + ASSERT_STREQ("0.10.5", JSONCPP_VERSION_STRING); } #endif