comparison OrthancFramework/Sources/Toolbox.cpp @ 5807:8279eaab0d1d attach-custom-data

merged default -> attach-custom-data
author Alain Mazy <am@orthanc.team>
date Tue, 24 Sep 2024 11:39:52 +0200
parents 16df20505710
children
comparison
equal deleted inserted replaced
5085:79f98ee4f04b 5807:8279eaab0d1d
1 /** 1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store 2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium 4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium 5 * Copyright (C) 2017-2023 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium 6 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
7 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 * 8 *
8 * This program is free software: you can redistribute it and/or 9 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License 10 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation, either version 3 of 11 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version. 12 * the License, or (at your option) any later version.
34 35
35 #if !defined(JSONCPP_VERSION_MAJOR) || !defined(JSONCPP_VERSION_MINOR) 36 #if !defined(JSONCPP_VERSION_MAJOR) || !defined(JSONCPP_VERSION_MINOR)
36 # error Cannot access the version of JsonCpp 37 # error Cannot access the version of JsonCpp
37 #endif 38 #endif
38 39
40 #if !defined(ORTHANC_ENABLE_ICU)
41 # define ORTHANC_ENABLE_ICU 1
42 #endif
43
39 44
40 /** 45 /**
41 * We use deprecated "Json::Reader", "Json::StyledWriter" and 46 * We use deprecated "Json::Reader", "Json::StyledWriter" and
42 * "Json::FastWriter" if JsonCpp < 1.7.0. This choice is rather 47 * "Json::FastWriter" if JsonCpp < 1.7.0. This choice is rather
43 * arbitrary, but if Json >= 1.9.0, gcc generates explicit deprecation 48 * arbitrary, but if Json >= 1.9.0, gcc generates explicit deprecation
141 #endif 146 #endif
142 } 147 }
143 148
144 149
145 #if defined(ORTHANC_STATIC_ICU) 150 #if defined(ORTHANC_STATIC_ICU)
151
152 # if (ORTHANC_STATIC_ICU == 1) && (ORTHANC_ENABLE_ICU == 1)
153 # if !defined(ORTHANC_FRAMEWORK_INCLUDE_RESOURCES) || (ORTHANC_FRAMEWORK_INCLUDE_RESOURCES == 1)
154 # include <OrthancFrameworkResources.h>
155 # endif
156 # endif
157
146 # if (ORTHANC_STATIC_ICU == 1 && ORTHANC_ENABLE_LOCALE == 1) 158 # if (ORTHANC_STATIC_ICU == 1 && ORTHANC_ENABLE_LOCALE == 1)
147 # include <OrthancFrameworkResources.h>
148 # include <unicode/udata.h> 159 # include <unicode/udata.h>
149 # include <unicode/uloc.h> 160 # include <unicode/uloc.h>
150 # include "Compression/GzipCompressor.h" 161 # include "Compression/GzipCompressor.h"
151 162
152 static std::string globalIcuData_; 163 static std::string globalIcuData_;
412 423
413 return r; 424 return r;
414 } 425 }
415 } 426 }
416 427
428 std::string Toolbox::JoinUri(const std::string& base, const std::string& uri)
429 {
430 if (uri.size() > 0 && base.size() > 0)
431 {
432 if (base[base.size() - 1] == '/' && uri[0] == '/')
433 {
434 return base + uri.substr(1, uri.size() - 1);
435 }
436 else if (base[base.size() - 1] != '/' && uri[0] != '/')
437 {
438 return base + "/" + uri;
439 }
440 }
441
442 return base + uri;
443 }
444
417 445
418 #if ORTHANC_ENABLE_MD5 == 1 446 #if ORTHANC_ENABLE_MD5 == 1
419 static char GetHexadecimalCharacter(uint8_t value) 447 static char GetHexadecimalCharacter(uint8_t value)
420 { 448 {
421 assert(value < 16); 449 assert(value < 16);
467 { 495 {
468 result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16)); 496 result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
469 result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16)); 497 result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
470 } 498 }
471 } 499 }
500
501 void Toolbox::ComputeMD5(std::string& result,
502 const std::set<std::string>& data)
503 {
504 std::string s;
505
506 for (std::set<std::string>::const_iterator it = data.begin(); it != data.end(); ++it)
507 {
508 s += *it;
509 }
510
511 ComputeMD5(result, s);
512 }
513
472 #endif 514 #endif
473 515
474 516
475 #if ORTHANC_ENABLE_BASE64 == 1 517 #if ORTHANC_ENABLE_BASE64 == 1
476 void Toolbox::EncodeBase64(std::string& result, 518 void Toolbox::EncodeBase64(std::string& result,
607 std::string Toolbox::ConvertToUtf8(const std::string& source, 649 std::string Toolbox::ConvertToUtf8(const std::string& source,
608 Encoding sourceEncoding, 650 Encoding sourceEncoding,
609 bool hasCodeExtensions) 651 bool hasCodeExtensions)
610 { 652 {
611 #if ORTHANC_STATIC_ICU == 1 653 #if ORTHANC_STATIC_ICU == 1
654 # if ORTHANC_ENABLE_ICU == 0
655 throw OrthancException(ErrorCode_NotImplemented, "ICU is disabled for this target");
656 # else
612 if (globalIcuData_.empty()) 657 if (globalIcuData_.empty())
613 { 658 {
614 throw OrthancException(ErrorCode_BadSequenceOfCalls, 659 throw OrthancException(ErrorCode_BadSequenceOfCalls,
615 "Call Toolbox::InitializeGlobalLocale()"); 660 "Call Toolbox::InitializeGlobalLocale()");
616 } 661 }
662 # endif
617 #endif 663 #endif
618 664
619 // The "::skip" flag makes boost skip invalid UTF-8 665 // The "::skip" flag makes boost skip invalid UTF-8
620 // characters. This can occur in badly-encoded DICOM files. 666 // characters. This can occur in badly-encoded DICOM files.
621 667
666 #if ORTHANC_ENABLE_LOCALE == 1 712 #if ORTHANC_ENABLE_LOCALE == 1
667 std::string Toolbox::ConvertFromUtf8(const std::string& source, 713 std::string Toolbox::ConvertFromUtf8(const std::string& source,
668 Encoding targetEncoding) 714 Encoding targetEncoding)
669 { 715 {
670 #if ORTHANC_STATIC_ICU == 1 716 #if ORTHANC_STATIC_ICU == 1
717 # if ORTHANC_ENABLE_ICU == 0
718 throw OrthancException(ErrorCode_NotImplemented, "ICU is disabled for this target");
719 # else
671 if (globalIcuData_.empty()) 720 if (globalIcuData_.empty())
672 { 721 {
673 throw OrthancException(ErrorCode_BadSequenceOfCalls, 722 throw OrthancException(ErrorCode_BadSequenceOfCalls,
674 "Call Toolbox::InitializeGlobalLocale()"); 723 "Call Toolbox::InitializeGlobalLocale()");
675 } 724 }
725 # endif
676 #endif 726 #endif
677 727
678 // The "::skip" flag makes boost skip invalid UTF-8 728 // The "::skip" flag makes boost skip invalid UTF-8
679 // characters. This can occur in badly-encoded DICOM files. 729 // characters. This can occur in badly-encoded DICOM files.
680 730
749 } 799 }
750 800
751 return result; 801 return result;
752 } 802 }
753 803
754
755 void Toolbox::ComputeSHA1(std::string& result, 804 void Toolbox::ComputeSHA1(std::string& result,
756 const void* data, 805 const void* data,
757 size_t size) 806 size_t size)
758 { 807 {
759 boost::uuids::detail::sha1 sha1; 808 boost::uuids::detail::sha1 sha1;
762 { 811 {
763 sha1.process_bytes(data, size); 812 sha1.process_bytes(data, size);
764 } 813 }
765 814
766 unsigned int digest[5]; 815 unsigned int digest[5];
767
768 // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide 816 // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
769 assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 817 assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8));
818 assert(sizeof(boost::uuids::detail::sha1::digest_type) == 20);
770 819
771 sha1.get_digest(digest); 820 // From Boost 1.86, digest_type is "unsigned char[20]" while it was "unsigned int[5]"" in previous versions.
821 // Always perform the cast even if it is useless for Boost < 1.86
822 sha1.get_digest(*(reinterpret_cast<boost::uuids::detail::sha1::digest_type*>(digest)));
772 823
773 result.resize(8 * 5 + 4); 824 result.resize(8 * 5 + 4);
774 sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x", 825 sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
775 digest[0], 826 digest[0],
776 digest[1], 827 digest[1],
998 boost::replace_all(result, "*", ".*"); 1049 boost::replace_all(result, "*", ".*");
999 1050
1000 return result; 1051 return result;
1001 } 1052 }
1002 1053
1054 static void TokenizeStringInternal(std::vector<std::string>& result,
1055 const std::string& value,
1056 char separator,
1057 bool includeEmptyStrings)
1058 {
1059 size_t countSeparators = 0;
1060
1061 for (size_t i = 0; i < value.size(); i++)
1062 {
1063 if (value[i] == separator)
1064 {
1065 countSeparators++;
1066 }
1067 }
1068
1069 result.clear();
1070 result.reserve(countSeparators + 1);
1071
1072 std::string currentItem;
1073
1074 for (size_t i = 0; i < value.size(); i++)
1075 {
1076 if (value[i] == separator)
1077 {
1078 result.push_back(currentItem);
1079 currentItem.clear();
1080 }
1081 else
1082 {
1083 currentItem.push_back(value[i]);
1084 }
1085 }
1086
1087 if (includeEmptyStrings || !currentItem.empty())
1088 {
1089 result.push_back(currentItem);
1090 }
1091 }
1092
1003 1093
1004 void Toolbox::TokenizeString(std::vector<std::string>& result, 1094 void Toolbox::TokenizeString(std::vector<std::string>& result,
1005 const std::string& value, 1095 const std::string& value,
1006 char separator) 1096 char separator)
1007 { 1097 {
1008 size_t countSeparators = 0; 1098 TokenizeStringInternal(result, value, separator, true);
1009 1099 }
1010 for (size_t i = 0; i < value.size(); i++) 1100
1011 { 1101
1012 if (value[i] == separator) 1102 void Toolbox::SplitString(std::set<std::string>& result,
1013 { 1103 const std::string& value,
1014 countSeparators++; 1104 char separator)
1015 } 1105 {
1016 }
1017
1018 result.clear(); 1106 result.clear();
1019 result.reserve(countSeparators + 1); 1107
1020 1108 std::vector<std::string> temp;
1021 std::string currentItem; 1109 TokenizeStringInternal(temp, value, separator, false);
1022 1110 for (size_t i = 0; i < temp.size(); ++i)
1023 for (size_t i = 0; i < value.size(); i++) 1111 {
1024 { 1112 result.insert(temp[i]);
1025 if (value[i] == separator) 1113 }
1026 { 1114 }
1027 result.push_back(currentItem); 1115
1028 currentItem.clear(); 1116
1029 } 1117 void Toolbox::SplitString(std::vector<std::string>& result,
1030 else 1118 const std::string& value,
1031 { 1119 char separator)
1032 currentItem.push_back(value[i]); 1120 {
1033 } 1121 TokenizeStringInternal(result, value, separator, false);
1034 }
1035
1036 result.push_back(currentItem);
1037 } 1122 }
1038 1123
1039 1124
1040 void Toolbox::JoinStrings(std::string& result, 1125 void Toolbox::JoinStrings(std::string& result,
1041 std::set<std::string>& source, 1126 const std::set<std::string>& source,
1042 const char* separator) 1127 const char* separator)
1043 { 1128 {
1044 result = boost::algorithm::join(source, separator); 1129 result = boost::algorithm::join(source, separator);
1045 } 1130 }
1046 1131
1047 void JoinStrings(std::string& result, 1132 void Toolbox::JoinStrings(std::string& result,
1048 std::vector<std::string>& source, 1133 const std::vector<std::string>& source,
1049 const char* separator) 1134 const char* separator)
1050 { 1135 {
1051 result = boost::algorithm::join(source, separator); 1136 result = boost::algorithm::join(source, separator);
1052 } 1137 }
1053 1138
1054 1139
1506 } 1591 }
1507 1592
1508 1593
1509 static void InitializeIcu() 1594 static void InitializeIcu()
1510 { 1595 {
1511 #if ORTHANC_STATIC_ICU == 1 1596 #if (ORTHANC_STATIC_ICU == 1) && (ORTHANC_ENABLE_ICU == 1)
1512 if (globalIcuData_.empty()) 1597 if (globalIcuData_.empty())
1513 { 1598 {
1514 LOG(INFO) << "Setting up the ICU common data"; 1599 LOG(INFO) << "Setting up the ICU common data";
1515 1600
1516 GzipCompressor compressor; 1601 GzipCompressor compressor;
1674 std::string Toolbox::ToUpperCaseWithAccents(const std::string& source) 1759 std::string Toolbox::ToUpperCaseWithAccents(const std::string& source)
1675 { 1760 {
1676 bool error = (globalLocale_.get() == NULL); 1761 bool error = (globalLocale_.get() == NULL);
1677 1762
1678 #if ORTHANC_STATIC_ICU == 1 1763 #if ORTHANC_STATIC_ICU == 1
1764 # if ORTHANC_ENABLE_ICU == 0
1765 throw OrthancException(ErrorCode_NotImplemented, "ICU is disabled for this target");
1766 # else
1679 if (globalIcuData_.empty()) 1767 if (globalIcuData_.empty())
1680 { 1768 {
1681 error = true; 1769 error = true;
1682 } 1770 }
1771 # endif
1683 #endif 1772 #endif
1684 1773
1685 if (error) 1774 if (error)
1686 { 1775 {
1687 throw OrthancException(ErrorCode_BadSequenceOfCalls, 1776 throw OrthancException(ErrorCode_BadSequenceOfCalls,
1795 1884
1796 1885
1797 1886
1798 std::string Toolbox::GenerateUuid() 1887 std::string Toolbox::GenerateUuid()
1799 { 1888 {
1800 #ifdef WIN32 1889 #ifdef _WIN32
1801 UUID uuid; 1890 UUID uuid;
1802 UuidCreate ( &uuid ); 1891 UuidCreate ( &uuid );
1803 1892
1804 unsigned char * str; 1893 unsigned char * str;
1805 UuidToStringA ( &uuid, &str ); 1894 UuidToStringA ( &uuid, &str );
2424 value[value.size() - 1] == '\"') 2513 value[value.size() - 1] == '\"')
2425 { 2514 {
2426 value = value.substr(1, value.size() - 2); 2515 value = value.substr(1, value.size() - 2);
2427 } 2516 }
2428 } 2517 }
2518
2519 Toolbox::ElapsedTimer::ElapsedTimer()
2520 {
2521 Restart();
2522 }
2523
2524 void Toolbox::ElapsedTimer::Restart()
2525 {
2526 start_ = boost::posix_time::microsec_clock::universal_time();
2527 }
2528
2529 uint64_t Toolbox::ElapsedTimer::GetElapsedMilliseconds()
2530 {
2531 return GetElapsedNanoseconds() / 1000000;
2532 }
2533
2534 uint64_t Toolbox::ElapsedTimer::GetElapsedMicroseconds()
2535 {
2536 return GetElapsedNanoseconds() / 1000;
2537 }
2538
2539 uint64_t Toolbox::ElapsedTimer::GetElapsedNanoseconds()
2540 {
2541 boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
2542 boost::posix_time::time_duration diff = now - start_;
2543 return static_cast<uint64_t>(diff.total_nanoseconds());
2544 }
2545
2546 std::string Toolbox::ElapsedTimer::GetHumanElapsedDuration()
2547 {
2548 return Toolbox::GetHumanDuration(GetElapsedNanoseconds());
2549 }
2550
2551 // in "full" mode, returns " 26.45MB in 2.25s = 94.04Mbps"
2552 // else, returns "94.04Mbps"
2553 std::string Toolbox::ElapsedTimer::GetHumanTransferSpeed(bool full, uint64_t sizeInBytes)
2554 {
2555 return Toolbox::GetHumanTransferSpeed(full, sizeInBytes, GetElapsedNanoseconds());
2556 }
2557
2558 Toolbox::ElapsedTimeLogger::ElapsedTimeLogger(const std::string& message)
2559 : message_(message),
2560 logged_(false)
2561 {
2562 Restart();
2563 }
2564
2565 Toolbox::ElapsedTimeLogger::~ElapsedTimeLogger()
2566 {
2567 if (!logged_)
2568 {
2569 StopAndLog();
2570 }
2571 }
2572
2573 void Toolbox::ElapsedTimeLogger::Restart()
2574 {
2575 timer_.Restart();
2576 }
2577
2578 void Toolbox::ElapsedTimeLogger::StopAndLog()
2579 {
2580 LOG(WARNING) << "ELAPSED TIMER: " << message_ << " (" << timer_.GetElapsedMicroseconds() << " us)";
2581 logged_ = true;
2582 }
2583
2584 std::string Toolbox::GetHumanFileSize(uint64_t sizeInBytes)
2585 {
2586 if (sizeInBytes < 1024)
2587 {
2588 std::ostringstream oss;
2589 oss << sizeInBytes << "bytes";
2590 return oss.str();
2591 }
2592 else
2593 {
2594 static const char* suffixes[] = {"KB", "MB", "GB", "TB"};
2595 static const int suffixesCount = sizeof(suffixes) / sizeof(suffixes[0]);
2596
2597 int i = 0;
2598 double size = static_cast<double>(sizeInBytes)/1024.0;
2599
2600 while (size >= 1024.0 && i < suffixesCount - 1)
2601 {
2602 size /= 1024.0;
2603 i++;
2604 }
2605
2606 std::ostringstream oss;
2607 oss << std::fixed << std::setprecision(2) << size << suffixes[i];
2608 return oss.str();
2609 }
2610 }
2611
2612 std::string Toolbox::GetHumanDuration(uint64_t durationInNanoseconds)
2613 {
2614 if (durationInNanoseconds < 1024)
2615 {
2616 std::ostringstream oss;
2617 oss << durationInNanoseconds << "ns";
2618 return oss.str();
2619 }
2620 else
2621 {
2622 static const char* suffixes[] = {"ns", "us", "ms", "s"};
2623 static const int suffixesCount = sizeof(suffixes) / sizeof(suffixes[0]);
2624
2625 int i = 0;
2626 double duration = static_cast<double>(durationInNanoseconds);
2627
2628 while (duration >= 1000.0 && i < suffixesCount - 1)
2629 {
2630 duration /= 1000.0;
2631 i++;
2632 }
2633
2634 std::ostringstream oss;
2635 oss << std::fixed << std::setprecision(2) << duration << suffixes[i];
2636 return oss.str();
2637 }
2638 }
2639
2640 std::string Toolbox::GetHumanTransferSpeed(bool full, uint64_t sizeInBytes, uint64_t durationInNanoseconds)
2641 {
2642 // in "full" mode, returns " 26.45MB in 2.25s = 94.04Mbps"
2643 // else, return "94.04Mbps"
2644
2645 if (full)
2646 {
2647 std::ostringstream oss;
2648 oss << Toolbox::GetHumanFileSize(sizeInBytes) << " in " << Toolbox::GetHumanDuration(durationInNanoseconds) << " = " << GetHumanTransferSpeed(false, sizeInBytes, durationInNanoseconds);
2649 return oss.str();
2650 }
2651
2652 double throughputInBps = 8.0 * 1000000000.0 * static_cast<double>(sizeInBytes) / static_cast<double>(durationInNanoseconds);
2653
2654 if (throughputInBps < 1000.0)
2655 {
2656 std::ostringstream oss;
2657 oss << throughputInBps << "bps";
2658 return oss.str();
2659 }
2660 else
2661 {
2662 throughputInBps /= 1000.0;
2663 static const char* suffixes[] = {"kbps", "Mbps", "Gbps"};
2664 static const int suffixesCount = sizeof(suffixes) / sizeof(suffixes[0]);
2665
2666 int i = 0;
2667
2668 while (throughputInBps >= 1000.0 && i < suffixesCount - 1)
2669 {
2670 throughputInBps /= 1000.0;
2671 i++;
2672 }
2673
2674 std::ostringstream oss;
2675 oss << std::fixed << std::setprecision(2) << throughputInBps << suffixes[i];
2676 return oss.str();
2677 }
2678 }
2679
2680
2681 bool Toolbox::ParseVersion(unsigned int& major,
2682 unsigned int& minor,
2683 unsigned int& revision,
2684 const char* version)
2685 {
2686 if (version == NULL)
2687 {
2688 throw OrthancException(ErrorCode_NullPointer);
2689 }
2690
2691 #ifdef _MSC_VER
2692 #define ORTHANC_SCANF sscanf_s
2693 #else
2694 #define ORTHANC_SCANF sscanf
2695 #endif
2696
2697 int a, b, c;
2698 if (ORTHANC_SCANF(version, "%4d.%4d.%4d", &a, &b, &c) == 3)
2699 {
2700 if (a >= 0 &&
2701 b >= 0 &&
2702 c >= 0)
2703 {
2704 major = static_cast<unsigned int>(a);
2705 minor = static_cast<unsigned int>(b);
2706 revision = static_cast<unsigned int>(c);
2707 return true;
2708 }
2709 else
2710 {
2711 return false;
2712 }
2713 }
2714 else if (ORTHANC_SCANF(version, "%4d.%4d", &a, &b) == 2)
2715 {
2716 if (a >= 0 &&
2717 b >= 0)
2718 {
2719 major = static_cast<unsigned int>(a);
2720 minor = static_cast<unsigned int>(b);
2721 revision = 0;
2722 return true;
2723 }
2724 else
2725 {
2726 return false;
2727 }
2728 }
2729 else if (ORTHANC_SCANF(version, "%4d", &a) == 1 &&
2730 a >= 0)
2731 {
2732 if (a >= 0)
2733 {
2734 major = static_cast<unsigned int>(a);
2735 minor = 0;
2736 revision = 0;
2737 return true;
2738 }
2739 else
2740 {
2741 return false;
2742 }
2743 }
2744 else
2745 {
2746 return false;
2747 }
2748 }
2749
2750
2751 bool Toolbox::IsVersionAbove(const char* version,
2752 unsigned int major,
2753 unsigned int minor,
2754 unsigned int revision)
2755 {
2756 /**
2757 * Note: Similar standalone functions are implemented in
2758 * "OrthancCPlugin.h" and "OrthancPluginCppWrapper.cpp".
2759 **/
2760
2761 unsigned int actualMajor, actualMinor, actualRevision;
2762
2763 if (version == NULL)
2764 {
2765 throw OrthancException(ErrorCode_NullPointer);
2766 }
2767 else if (!strcmp(version, "mainline"))
2768 {
2769 // Assume compatibility with the mainline
2770 return true;
2771 }
2772 else if (ParseVersion(actualMajor, actualMinor, actualRevision, version))
2773 {
2774 if (actualMajor > major)
2775 {
2776 return true;
2777 }
2778
2779 if (actualMajor < major)
2780 {
2781 return false;
2782 }
2783
2784 // Check the minor version number
2785 assert(actualMajor == major);
2786
2787 if (actualMinor > minor)
2788 {
2789 return true;
2790 }
2791
2792 if (actualMinor < minor)
2793 {
2794 return false;
2795 }
2796
2797 // Check the patch level version number
2798 assert(actualMajor == major);
2799
2800 if (actualRevision >= revision)
2801 {
2802 return true;
2803 }
2804 else
2805 {
2806 return false;
2807 }
2808 }
2809 else
2810 {
2811 throw OrthancException(ErrorCode_ParameterOutOfRange, "Not a valid version: " + std::string(version));
2812 }
2813 }
2429 } 2814 }
2430 2815
2431 2816
2432 2817
2433 OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content) 2818 OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content)