From 6eab9e12d74fb90c0bb830c5dbbf9e4c1276a63b Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 4 Feb 2025 15:07:40 +0100 Subject: [PATCH 01/32] Gracefully close the HTTP connection when querying server caps --- CMakeLists.txt | 2 +- src/etp/ssl/HttpsClientSession.h | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7ba54a..a784439 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set (FETPAPI_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) # version mechanism set (Fetpapi_VERSION_MAJOR 0) set (Fetpapi_VERSION_MINOR 3) -set (Fetpapi_VERSION_PATCH 1) +set (Fetpapi_VERSION_PATCH 2) set (Fetpapi_VERSION_TWEAK 0) set (Fetpapi_VERSION ${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}.${Fetpapi_VERSION_PATCH}.${Fetpapi_VERSION_TWEAK}) diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index 067ad1b..744243d 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -271,17 +271,12 @@ namespace ETP_NS return; } - // Force close - boost::system::error_code closeEc; - stream_.next_layer().close(closeEc); - /* // Gracefully close the stream_ stream_.async_shutdown( std::bind( &HttpsClientSession::on_shutdown, shared_from_this(), std::placeholders::_1)); - */ } void From 74e5c6ac72c179404aa282264885879223e9c6f9 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 4 Feb 2025 18:22:48 +0100 Subject: [PATCH 02/32] Discard ssl_stream from FETPAPI headers if Boost version > 1.67 --- src/etp/ssl/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/etp/ssl/CMakeLists.txt b/src/etp/ssl/CMakeLists.txt index 8e63450..bd7310a 100644 --- a/src/etp/ssl/CMakeLists.txt +++ b/src/etp/ssl/CMakeLists.txt @@ -1,4 +1,6 @@ set(FETPAPI_SSL_SOURCES ${CMAKE_CURRENT_LIST_DIR}/SslClientSession.cpp ) set(FETPAPI_SSL_HEADERS ${CMAKE_CURRENT_LIST_DIR}/HttpsClientSession.h - ${CMAKE_CURRENT_LIST_DIR}/ssl_stream.h ${CMAKE_CURRENT_LIST_DIR}/SslClientSession.h ) +if ((Boost_VERSION_MAJOR EQUAL 1) AND (Boost_VERSION_MINOR LESS 68)) + list(APPEND FETPAPI_SSL_HEADERS ${CMAKE_CURRENT_LIST_DIR}/ssl_stream.h) +endif () From eaf87362163dda67dc4f326f0cd97ba5e5a763da Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 20 Feb 2025 16:19:32 +0100 Subject: [PATCH 03/32] Clean etp host string management thanks to c++17 --- src/etp/ssl/HttpsClientSession.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index 744243d..ba5a40c 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -70,18 +70,13 @@ namespace ETP_NS uint16_t proxyPort = 80, const std::string& proxyAuthorization = "") { - size_t hostSizeWithNullTermChar = etpServerHost.size() + 1; - char* copyHost = new char[hostSizeWithNullTermChar]; - std::memcpy(copyHost, etpServerHost.c_str(), hostSizeWithNullTermChar); // Copy host because it must be non const in SSL_set_tlsext_host_name // Set SNI Hostname (many hosts need this to handshake successfully) - if (!SSL_set_tlsext_host_name(stream_.native_handle(), copyHost)) + if (!SSL_set_tlsext_host_name(stream_.native_handle(), etpServerHost.data())) { boost::system::error_code ec{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; std::cerr << ec.message() << "\n"; - delete[] copyHost; return; } - delete[] copyHost; // Set up an HTTP GET request message req_.version(version); From 2a28b294fabbeba470165b8c80ca7ab97898dba8 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 20 Feb 2025 16:21:17 +0100 Subject: [PATCH 04/32] Close and block untill WS session is closed (not only ETP session) --- src/etp/AbstractSession.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index 0b1dd39..50020c4 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -313,7 +313,7 @@ namespace ETP_NS FETPAPI_DLL_IMPORT_OR_EXPORT void closeAndBlock() { close(); auto t_start = std::chrono::high_resolution_clock::now(); - while (!isEtpSessionClosed()) { + while (!webSocketSessionClosed) { if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > _timeOut) { throw std::runtime_error("Time out waiting for closing"); } From c07d603f09716184976c2fd43932eb923dd68697 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 26 Mar 2025 18:44:21 +0100 Subject: [PATCH 05/32] Build fix : CS version does not point to the correct DLL name --- cs/CMakeLists.txt | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/cs/CMakeLists.txt b/cs/CMakeLists.txt index 042fde8..eff2b9c 100644 --- a/cs/CMakeLists.txt +++ b/cs/CMakeLists.txt @@ -9,21 +9,6 @@ message("Generating SWIG C# files...") # Cleaning execute_process(COMMAND powershell "Remove-Item ${CMAKE_SOURCE_DIR}/cs/src/* -recurse -exclude .gitignore") -# The name of the library is different on Windows because it includes the version -if (WIN32) - if (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_RELEASE_POSTFIX}.${Fetpapi_VERSION}) - else (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_DEBUG_POSTFIX}.${Fetpapi_VERSION}) - endif (SWIG_LINKED_TO_RELEASE) -else (WIN32) - if (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_RELEASE_POSTFIX}) - else (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_DEBUG_POSTFIX}) - endif (SWIG_LINKED_TO_RELEASE) -endif (WIN32) - # SWIG execution set (EXECUTE_COMMAND "${SWIG_EXECUTABLE}") list (APPEND EXECUTE_COMMAND -v) @@ -36,7 +21,7 @@ if (WITH_ETP_SSL) list (APPEND EXECUTE_COMMAND -DWITH_ETP_SSL) endif (WITH_ETP_SSL) list (APPEND EXECUTE_COMMAND -dllimport) -list (APPEND EXECUTE_COMMAND ${ASSEMBLY_NAME}.dll) +list (APPEND EXECUTE_COMMAND ${PROJECT_NAME}${CMAKE_RELEASE_POSTFIX}-${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}.dll) list (APPEND EXECUTE_COMMAND -namespace) list (APPEND EXECUTE_COMMAND F2iConsulting.Fetpapi) list (APPEND EXECUTE_COMMAND -o) From 76b23b0b06d79dbadb556fdeb5516b648ce80187 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 27 Mar 2025 10:11:18 +0100 Subject: [PATCH 06/32] Manage SSL short read --- src/etp/AbstractSession.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index 20737ee..379d3d1 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -52,6 +52,12 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr else { // This indicates an unexpected error fesapi_log("on_read : error code number", ec.value(), "->", ec.message()); + // This error may be a common one to ignore in case of SSL short read : https://github.com/boostorg/beast/issues/824 + if (etpSessionClosed) { + fesapi_log("It looks that the other endpoint has closed the websocket session in a non graceful way."); + webSocketSessionClosed = true; + flushReceivingBuffer(); + } } return; From 711bab575bedfd24e9cb951d8f1a6d29908801a6 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 27 Mar 2025 10:11:36 +0100 Subject: [PATCH 07/32] Const correctness --- src/etp/fesapi/FesapiHdfProxy.cpp | 6 +++--- src/etp/fesapi/FesapiHdfProxy.h | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 31ac384..71de26e 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -341,13 +341,13 @@ void FesapiHdfProxy::writeArrayNdSlab( open(); // URI AND PATH - std::string uri{ buildEtp12Uri() }; + const std::string uri{ buildEtp12Uri() }; - std::string pathInResource{ (groupName.back() == '/' ? + const std::string pathInResource{ (groupName.back() == '/' ? groupName : groupName + '/') + datasetName }; // Create Total Count - size_t totalCount = std::accumulate(numValuesInEachDimension, numValuesInEachDimension + numDimensions, 1, std::multiplies()); + const size_t totalCount = std::accumulate(numValuesInEachDimension, numValuesInEachDimension + numDimensions, 1, std::multiplies()); // Determine Value Size (bytes) and Any Array Type size_t valueSize{ 1 }; diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index da7a74b..01dd402 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -491,13 +491,8 @@ namespace ETP_NS } // First get metadata about the data array - std::vector dimensions; - const Energistics::Etp::v12::Datatypes::DataArrayTypes::DataArrayMetadata daMetadata = getDataArrayMetadata(datasetName); - size_t valueCount = 1; - for (int64_t dim : daMetadata.dimensions) { - valueCount *= dim; - } + const size_t valueCount = std::accumulate(daMetadata.dimensions.begin(), daMetadata.dimensions.end(), 1, std::multiplies()); size_t valueSize = 1; switch (daMetadata.transportArrayType) { @@ -509,7 +504,7 @@ namespace ETP_NS case Energistics::Etp::v12::Datatypes::AnyArrayType::arrayOfDouble: valueSize = 8; break; default: throw std::logic_error("Array of strings are not implemented yet"); } - size_t wholeSize = valueCount * valueSize; + const size_t wholeSize = valueCount * valueSize; // maxAllowedDataArraySize is the maximum serialized size of the array (including avro extra longs for array blocks) const size_t maxAllowedDataArraySize = session_->getMaxWebSocketMessagePayloadSize() From f878d59ba10dd46dc13ce11cf6fb080600e15cd0 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 27 Mar 2025 14:27:57 +0100 Subject: [PATCH 08/32] [SWIG] Share DataObjectRepository accros FESAPI and FETPAPI modules --- cmake/swigEtp1_2Include.i.in | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 6b01bb5..7c6d6cd 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -32,6 +32,7 @@ under the License. #ifdef WITH_FESAPI %include "${FESAPI_INCLUDE_DIR}/fesapi/nsDefinitions.h" %import(module="fesapi") "${FESAPI_INCLUDE_DIR}/fesapi/common/DataObjectReference.h" +%import(module="fesapi") "${FESAPI_INCLUDE_DIR}/fesapi/common/DataObjectRepository.h" %import(module="fesapi") "${FESAPI_INCLUDE_DIR}/fesapi/common/HdfProxyFactory.h" %import(module="fesapi") "${FESAPI_INCLUDE_DIR}/fesapi/common/AbstractObject.h" #endif @@ -2281,7 +2282,9 @@ namespace ETP_NS } #ifdef WITH_FESAPI -%pragma(java) moduleimports="import com.f2i_consulting.fesapi.common.AbstractObject;" -%pragma(java) jniclassimports="import com.f2i_consulting.fesapi.common.AbstractObject;" +%pragma(java) moduleimports="import com.f2i_consulting.fesapi.common.AbstractObject; +import com.f2i_consulting.fesapi.common.DataObjectRepository;" +%pragma(java) jniclassimports="import com.f2i_consulting.fesapi.common.AbstractObject; +import com.f2i_consulting.fesapi.common.DataObjectRepository;" %pragma(csharp) moduleimports="using F2iConsulting.Fesapi.common;" #endif \ No newline at end of file From 11923e5f0003ebe8531017c9eaf339baccec867d Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 8 Apr 2025 09:48:55 +0200 Subject: [PATCH 09/32] Typo --- cmake/swigEtp1_2Include.i.in | 7 +++--- src/etp/ProtocolHandlers/DataArrayHandlers.h | 24 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 7c6d6cd..8a180c5 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -1268,10 +1268,9 @@ namespace Energistics { } %fragment("data_array_handler_reference_function", "header", fragment="data_array_handler_reference_init") { - - static PyObject *data_array_handler_reference() { - static PyObject *data_array_handler_reference_string = SWIG_Python_str_FromChar("__data_array_handler_reference"); - return data_array_handler_reference_string; + static PyObject *data_array_handler_reference() { + static PyObject *data_array_handler_reference_string = SWIG_Python_str_FromChar("__data_array_handler_reference"); + return data_array_handler_reference_string; } } diff --git a/src/etp/ProtocolHandlers/DataArrayHandlers.h b/src/etp/ProtocolHandlers/DataArrayHandlers.h index 21398d9..ed7001e 100644 --- a/src/etp/ProtocolHandlers/DataArrayHandlers.h +++ b/src/etp/ProtocolHandlers/DataArrayHandlers.h @@ -31,73 +31,73 @@ namespace ETP_NS void decodeMessageBody(const Energistics::Etp::v12::Datatypes::MessageHeader & mh, avro::DecoderPtr d); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataArrays(const Energistics::Etp::v12::Protocol::DataArray::GetDataArrays& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataArraysResponse(const Energistics::Etp::v12::Protocol::DataArray::GetDataArraysResponse& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutDataArrays(const Energistics::Etp::v12::Protocol::DataArray::PutDataArrays& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutDataArraysResponse(const Energistics::Etp::v12::Protocol::DataArray::PutDataArraysResponse& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataSubarrays(const Energistics::Etp::v12::Protocol::DataArray::GetDataSubarrays& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataSubarraysResponse(const Energistics::Etp::v12::Protocol::DataArray::GetDataSubarraysResponse& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutDataSubarrays(const Energistics::Etp::v12::Protocol::DataArray::PutDataSubarrays& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutDataSubarraysResponse(const Energistics::Etp::v12::Protocol::DataArray::PutDataSubarraysResponse& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataArrayMetadata(const Energistics::Etp::v12::Protocol::DataArray::GetDataArrayMetadata& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_GetDataArrayMetadataResponse(const Energistics::Etp::v12::Protocol::DataArray::GetDataArrayMetadataResponse& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutUninitializedDataArrays(const Energistics::Etp::v12::Protocol::DataArray::PutUninitializedDataArrays& msg, int64_t correlationId); /** - * @param msg The ETP message boday which has been received and which is to be processed. + * @param msg The ETP message body which has been received and which is to be processed. * @param correlationId It is the correlation ID to use if a response is needed to this message. It corresponds to the message ID of the received ETP message. */ virtual void on_PutUninitializedDataArraysResponse(const Energistics::Etp::v12::Protocol::DataArray::PutUninitializedDataArraysResponse& msg, int64_t correlationId); From deb60e3974f9e043b45e8ea3105d1d0fb4c9aa1d Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 8 Apr 2025 10:00:23 +0200 Subject: [PATCH 10/32] Add reading of properties in the python etp client example --- python/example/etp_client_example.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/python/example/etp_client_example.py b/python/example/etp_client_example.py index 46fa2e3..87f1d6d 100644 --- a/python/example/etp_client_example.py +++ b/python/example/etp_client_example.py @@ -86,13 +86,32 @@ def start_etp_server(client_session): print("Cell 0,0,0 corner 0 is at index ", origin_index) print("Cell 0,0,0 corner 0 is ", xyz_points.getitem(origin_index * 3), " ", xyz_points.getitem(origin_index * 3 + 1), " ", xyz_points.getitem(origin_index * 3 + 2)) ijk_grid.unloadSplitInformation() + + if ijk_grid.getValuesPropertyCount() > 0: + prop = ijk_grid.getValuesProperty(0) + print("Prop at index 0 : " + prop.getTitle()) + print(type(prop)) + + if isinstance(prop, fesapi.Resqml2_ContinuousProperty): + prop_values = fesapi.DoubleArray(ijk_grid.getICellCount() * ijk_grid.getJCellCount() * ijk_grid.getKCellCount()) + prop.getDoubleValuesOfPatch(0, prop_values) + print("Cell 0,0,0 has prop value ", prop_values.getitem(0)) + print("Cell 1,0,0 has prop value ", prop_values.getitem(1)) + print("Cell 2,0,0 has prop value ", prop_values.getitem(2)) + print("Cell 3,0,0 has prop value ", prop_values.getitem(3)) + print("Cell 4,0,0 has prop value ", prop_values.getitem(4)) + print("Cell 5,0,0 has prop value ", prop_values.getitem(5)) + else: + print(f"This property is a {type(prop)}") + else: + print("This IJK grid has no property") else: print("This dataspace has no IJK Grid") print("Read data of the first 2d grid"); if repo.getHorizonGrid2dRepresentationCount() > 0: grid2d = repo.getHorizonGrid2dRepresentation(0) - print("2d Grid : " + grid2d.getTitle()) + print("2d grid : " + grid2d.getTitle()) print(f"iCount : {grid2d.getNodeCountAlongIAxis()} jCount : {grid2d.getNodeCountAlongJAxis()}") nb_z_points = grid2d.getNodeCountAlongIAxis() * grid2d.getNodeCountAlongJAxis() print(f"XYZ points count : {nb_z_points}") @@ -101,6 +120,12 @@ def start_etp_server(client_session): print("Z value at index 0 : ", z_points.getitem(0)) print("Z value at index 1 : ", z_points.getitem(1)) + + if grid2d.getValuesPropertyCount() > 0: + prop = repo.getValuesProperty(0) + print("Prop at index 0 : " + prop.getTitle()) + else: + print("This 2d grid has no property") else: print("This dataspace has no 2d Grid") From ec89359e1723f8178f3cd92614485505dff4a42d Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 24 Apr 2025 18:09:04 +0200 Subject: [PATCH 11/32] If the ETP server is supporting TLS, it MUST support v1.2 or greater --- src/etp/ClientSessionLaunchers.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/etp/ClientSessionLaunchers.cpp b/src/etp/ClientSessionLaunchers.cpp index 43f672f..9bef84b 100644 --- a/src/etp/ClientSessionLaunchers.cpp +++ b/src/etp/ClientSessionLaunchers.cpp @@ -67,12 +67,15 @@ std::shared_ptr ETP_NS::ClientSessionLaunchers::createCli #ifdef WITH_ETP_SSL if (initializationParams->getEtpServerPort() == 443 || initializationParams->isTlsForced()) { // The SSL context is required, and holds certificates - boost::asio::ssl::context ctx{ boost::asio::ssl::context::sslv23_client }; + // From official ETP documentation : If the ETP server is supporting TLS, it MUST support v1.2 or greater + boost::asio::ssl::context ctx{ boost::asio::ssl::context::tlsv12_client }; ctx.set_default_verify_paths(); ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 + | boost::asio::ssl::context::no_tlsv1 + | boost::asio::ssl::context::no_tlsv1_1 | boost::asio::ssl::context::single_dh_use ); From d9f4db85f6e11c24d59c5d426025af8151a3188d Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 29 Apr 2025 12:27:56 +0200 Subject: [PATCH 12/32] Prefer IP4 than IP6 when both are available --- python/example/fetpapi.ipynb | 2 ++ src/etp/HttpClientSession.h | 8 ++++++-- src/etp/PlainClientSession.h | 8 ++++++-- src/etp/ssl/HttpsClientSession.h | 8 ++++++-- src/etp/ssl/SslClientSession.h | 8 ++++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/python/example/fetpapi.ipynb b/python/example/fetpapi.ipynb index 85bf62a..48055dd 100644 --- a/python/example/fetpapi.ipynb +++ b/python/example/fetpapi.ipynb @@ -131,6 +131,8 @@ " sleep(0.25)\t\n", "if client_session.isEtpSessionClosed():\n", " print(\"The ETP session could not be established in 5 seconds.\")\n", + " if not etp_server_url.endswith(\"/\"):\n", + " print(\"You may try adding an ending slash to your ETP server URL.\")\n", "else:\n", " print(\"Now connected to ETP Server\")" ] diff --git a/src/etp/HttpClientSession.h b/src/etp/HttpClientSession.h index b3c4369..33661f9 100644 --- a/src/etp/HttpClientSession.h +++ b/src/etp/HttpClientSession.h @@ -95,11 +95,15 @@ namespace ETP_NS return; } + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + // Make the connection on the IP address we get from a lookup boost::asio::async_connect( socket_, - results.begin(), - results.end(), + endpoints.begin(), + endpoints.end(), std::bind( &HttpClientSession::on_connect, shared_from_this(), diff --git a/src/etp/PlainClientSession.h b/src/etp/PlainClientSession.h index ac0e305..39f3114 100644 --- a/src/etp/PlainClientSession.h +++ b/src/etp/PlainClientSession.h @@ -45,11 +45,15 @@ namespace ETP_NS std::cerr << "on_resolve : " << ec.message() << std::endl; } + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + // Make the connection on the IP address we get from a lookup boost::asio::async_connect( ws_.next_layer(), - results.begin(), - results.end(), + endpoints.begin(), + endpoints.end(), std::bind( &AbstractClientSessionCRTP::on_connect, std::static_pointer_cast(shared_from_this()), diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index ba5a40c..938e4a9 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -122,11 +122,15 @@ namespace ETP_NS return; } + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4();}); + // Make the connection on the IP address we get from a lookup boost::asio::async_connect( stream_.next_layer(), - results.begin(), - results.end(), + endpoints.begin(), + endpoints.end(), std::bind( &HttpsClientSession::on_connect, shared_from_this(), diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index 48cf2b6..088d4d0 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -66,11 +66,15 @@ namespace ETP_NS std::cerr << "on_resolve : " << ec.message() << std::endl; } + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + // Make the connection on the IP address we get from a lookup boost::asio::async_connect( ws_.next_layer().next_layer(), - results.begin(), - results.end(), + endpoints.begin(), + endpoints.end(), std::bind( &SslClientSession::on_ssl_connect, std::static_pointer_cast(shared_from_this()), From ac83fc05047ad49fa04ac13579970053cb08254b Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 17 Jun 2025 16:47:22 +0200 Subject: [PATCH 13/32] Clean duplicated on_handshake method --- src/etp/AbstractClientSessionCRTP.h | 23 ++--------------------- src/etp/ssl/HttpsClientSession.h | 14 +++++++------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/etp/AbstractClientSessionCRTP.h b/src/etp/AbstractClientSessionCRTP.h index acce0a3..4b43150 100644 --- a/src/etp/AbstractClientSessionCRTP.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -52,7 +52,7 @@ namespace ETP_NS } }, std::bind( - &AbstractClientSessionCRTP::on_handshake, + &ClientSession::on_handshake, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); #else @@ -74,7 +74,7 @@ namespace ETP_NS derived().ws().async_handshake(responseType, etpServerHost + ":" + etpServerPort, etpServerTarget, std::bind( - &AbstractClientSessionCRTP::on_handshake, + &ClientSession::on_handshake, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); #endif @@ -109,25 +109,6 @@ namespace ETP_NS std::placeholders::_2)); } - void on_handshake(boost::system::error_code ec) - { - if (ec) { - std::cerr << "on_handshake : " << ec.message() << std::endl; - std::cerr << "Sometimes some ETP server require a trailing slash at the end of their URL. Did you also check your optional \"data-partition-id\" additional Header Field?" << std::endl; - return; - } - - if (!responseType.count(boost::beast::http::field::sec_websocket_protocol) || - responseType[boost::beast::http::field::sec_websocket_protocol] != "etp12.energistics.org") - std::cerr << "The client MUST specify the Sec-Websocket-Protocol header value of etp12.energistics.org, and the server MUST reply with the same" << std::endl; - - successfulConnection = true; - webSocketSessionClosed = false; - - send(requestSession, 0, 0x02); - do_read(); - } - void setMaxWebSocketMessagePayloadSize(int64_t value) final { maxWebSocketMessagePayloadSize = value; derived().ws().read_message_max(value); diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index 938e4a9..f253120 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -118,7 +118,7 @@ namespace ETP_NS tcp::resolver::results_type results) { if (ec) { - std::cerr << "resolve : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL resolve : " << ec.message() << std::endl; return; } @@ -141,7 +141,7 @@ namespace ETP_NS on_connect(boost::system::error_code ec) { if (ec) { - std::cerr << "connect : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL connect : " << ec.message() << std::endl; return; } @@ -173,7 +173,7 @@ namespace ETP_NS boost::ignore_unused(bytes_transferred); if (ec) { - std::cerr << "Proxy handshake write : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL Proxy handshake write : " << ec.message() << std::endl; return; } @@ -207,7 +207,7 @@ namespace ETP_NS { boost::ignore_unused(bytes_transferred); if (ec) { - std::cerr << "read : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL read : " << ec.message() << std::endl; return; } @@ -224,7 +224,7 @@ namespace ETP_NS on_handshake(boost::system::error_code ec) { if (ec) { - std::cerr << "handshake : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL handshake : " << ec.message() << std::endl; return; } @@ -245,7 +245,7 @@ namespace ETP_NS boost::ignore_unused(bytes_transferred); if (ec) { - std::cerr << "write : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL write : " << ec.message() << std::endl; return; } @@ -266,7 +266,7 @@ namespace ETP_NS boost::ignore_unused(bytes_transferred); if (ec) { - std::cerr << "read : " << ec.message() << std::endl; + std::cerr << "HTTP over SSL read : " << ec.message() << std::endl; return; } From c57a22779487bad352e6f6a1d423ca3b760d57ff Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Fri, 20 Jun 2025 15:12:50 +0200 Subject: [PATCH 14/32] Set SNI Hostname for WSS connection --- src/etp/AbstractClientSessionCRTP.h | 2 +- src/etp/ClientSession.h | 2 +- src/etp/ssl/HttpsClientSession.h | 2 +- src/etp/ssl/SslClientSession.h | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/etp/AbstractClientSessionCRTP.h b/src/etp/AbstractClientSessionCRTP.h index 4b43150..03277fa 100644 --- a/src/etp/AbstractClientSessionCRTP.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -32,7 +32,7 @@ namespace ETP_NS void on_connect(boost::system::error_code ec) { if (ec) { - std::cerr << "on_connect : " << ec.message() << std::endl; + std::cerr << "Websocket on connect : " << ec.message() << std::endl; } #if BOOST_VERSION < 107000 diff --git a/src/etp/ClientSession.h b/src/etp/ClientSession.h index f11bf40..595f866 100644 --- a/src/etp/ClientSession.h +++ b/src/etp/ClientSession.h @@ -75,7 +75,7 @@ namespace ETP_NS void on_handshake(boost::system::error_code ec) { if (ec) { - std::cerr << "on_handshake : " << ec.message() << std::endl; + std::cerr << "on WS handshake : " << ec.message() << std::endl; std::cerr << "Sometimes some ETP server require a trailing slash at the end of their URL. Did you also check your optional \"data-partition-id\" additional Header Field?" << std::endl; return; } diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index f253120..615a672 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -74,7 +74,7 @@ namespace ETP_NS if (!SSL_set_tlsext_host_name(stream_.native_handle(), etpServerHost.data())) { boost::system::error_code ec{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; - std::cerr << ec.message() << "\n"; + std::cerr << "HTTPS on connect (SNI): " << ec.message() << "\n"; return; } diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index 088d4d0..a095abd 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -66,6 +66,13 @@ namespace ETP_NS std::cerr << "on_resolve : " << ec.message() << std::endl; } + // Set SNI Hostname (many hosts need this to handshake successfully) + if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), etpServerHost.c_str())) + { + boost::system::error_code ecSNI{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; + std::cerr << "Websocket on connect (SNI): " << ecSNI.message() << std::endl; + } + // Reality check: IPv6 is unlikely to be available yet std::vector endpoints(results.begin(), results.end()); std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); From 8e0a2ea4b5dd547ad958849019eddc6c369fa8df Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 24 Jun 2025 10:49:09 +0200 Subject: [PATCH 15/32] Fix infinite loop when a disconnection occurs during DataArray reading. --- cmake/swigEtp1_2Include.i.in | 29 +++------------------------- src/etp/AbstractSession.h | 32 ++++++++++++++++++++++++++++++- src/etp/fesapi/FesapiHdfProxy.cpp | 12 +----------- src/etp/fesapi/FesapiHdfProxy.h | 10 ++-------- 4 files changed, 37 insertions(+), 46 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 8a180c5..d7f5c5e 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -1639,17 +1639,7 @@ namespace ETP_NS void setDataspaceProtocolHandlers(std::shared_ptr dataspaceHandlers); void setDataspaceOSDUProtocolHandlers(std::shared_ptr dataspaceOSDUHandlers); - template int64_t sendWithSpecificHandler(const T & mb, std::shared_ptr specificHandler, int64_t correlationId = 0, int32_t messageFlags = 0) - { - int64_t msgId = encode(mb, correlationId, messageFlags); // put the message to write in the queue - - if (sendingQueue.size() == 1) { - do_write(); - } - specificProtocolHandlers[msgId] = specificHandler; - - return msgId; - } + template int64_t sendWithSpecificHandler(const T & mb, std::shared_ptr specificHandler, int64_t correlationId = 0, int32_t messageFlags = 0) {} %template(sendWithSpecificHandler) sendWithSpecificHandler; %template(sendWithSpecificHandler) sendWithSpecificHandler; %template(sendWithSpecificHandler) sendWithSpecificHandler; @@ -1724,15 +1714,7 @@ namespace ETP_NS %template(sendWithSpecificHandler) sendWithSpecificHandler; %template(sendWithSpecificHandler) sendWithSpecificHandler; - template int64_t send(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) - { - if (protocolHandlers.size() > mb.protocolId) { - return sendWithSpecificHandler(mb, protocolHandlers[mb.protocolId], correlationId, messageFlags); - } - else { - throw std::logic_error("The agent has no registered handler at all for the protocol " + std::to_string(mb.protocolId)); - } - } + template int64_t send(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) {} %template(send) send; %template(send) send; %template(send) send; @@ -1807,12 +1789,7 @@ namespace ETP_NS %template(send) send; %template(send) send; - template void sendAndBlock(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) - { - int64_t msgId = send(mb, correlationId, messageFlags); - while (isMessageStillProcessing(msgId)) {} - } - + template int64_t sendAndBlock(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) {} %template(sendAndBlock) sendAndBlock; %template(sendAndBlock) sendAndBlock; %template(sendAndBlock) sendAndBlock; diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index 50020c4..03c4d3e 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -169,7 +169,7 @@ namespace ETP_NS * @param messageFlags The message flags to be sent within the header * @return The ID of the message that has been put in the sending queue. */ - template void sendAndBlock(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) + template int64_t sendAndBlock(const T & mb, int64_t correlationId = 0, int32_t messageFlags = 0) { int64_t msgId = send(mb, correlationId, messageFlags); // The correlationId of the first message MUST be set to 0 and the correlationId of all successive @@ -184,6 +184,8 @@ namespace ETP_NS throw std::runtime_error("Time out waiting for a response of message id " + std::to_string(msgId)); } } + + return msgId; } /** @@ -223,6 +225,34 @@ namespace ETP_NS return std::get<0>(queueItem); } + /** + * Send a message to the server and register a specific handler for the response and block the thread until the answer of the server has been processed by the handlers + * Please look at setTimeOut if you want to set the default timeout value which is 10 000 ms. + * + * @param mb The ETP message body to send + * @param correlationId The ID of the message which this message is answering to. + * @param messageFlags The message flags to be sent within the header + * @return The ID of the message that has been put in the sending queue. + */ + template int64_t sendWithSpecificHandlerAndBlock(const T& mb, std::shared_ptr specificHandler, int64_t correlationId = 0, int32_t messageFlags = 0) + { + int64_t msgId = sendWithSpecificHandler(mb, specificHandler, correlationId, messageFlags); + // The correlationId of the first message MUST be set to 0 and the correlationId of all successive + // messages in the same multipart request or notification MUST be set to the messageId of the first + // message of the multipart request or notification. + // If the request message is itself multipart, the correlationId of each message of the multipart + // response MUST be set to the messageId of the FIRST message in the multipart request. + + auto t_start = std::chrono::high_resolution_clock::now(); + while (isMessageStillProcessing(correlationId == 0 ? msgId : correlationId)) { + if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > _timeOut) { + throw std::runtime_error("Time out waiting for a response of message id " + std::to_string(msgId)); + } + } + + return msgId; + } + /** * Close the web socket session (without sending any ETP message) */ diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 71de26e..e0c8ff5 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -53,21 +53,11 @@ Energistics::Etp::v12::Datatypes::DataArrayTypes::DataArrayMetadata FesapiHdfPro // We don't care about the template parameter in this particular case auto handlers = std::make_shared>(session_, nullptr); - const int64_t msgId = session_->sendWithSpecificHandler( + const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( buildGetDataArrayMetadataMessage(datasetName), handlers, 0, 0x02); - // Blocking loop - auto t_start = std::chrono::high_resolution_clock::now(); - // Use timeOut value for session. - auto timeOut = session_->getTimeOut(); - while (session_->isMessageStillProcessing(msgId)) { - if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > timeOut) { - throw std::runtime_error("Time out waiting for a response of GetDataArrayMetadata message id " + std::to_string(msgId)); - } - } - return handlers->getDataArrayMetadata(); } diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index 01dd402..3048636 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -515,13 +515,10 @@ namespace ETP_NS auto specializedHandler = std::make_shared>(session_, values); if (wholeSize + (valueCount + 1) * 8 <= maxAllowedDataArraySize) { // There can be valueCount array block and there is the length of the last array block // Get all values at once - const int64_t msgId = session_->sendWithSpecificHandler( + const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( buildGetDataArraysMessage(datasetName), specializedHandler, 0, 0x02); - - // Blocking loop - while (session_->isMessageStillProcessing(msgId)) {} } else { // Get all values using several data subarrays allowing more granular streaming @@ -581,13 +578,10 @@ namespace ETP_NS } // Send message - const int64_t msgId = session_->sendWithSpecificHandler( + const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( msg, specializedHandler, 0, 0x02); - - // Blocking loop - while (session_->isMessageStillProcessing(msgId)) {} } } }; From 66ac4b13789616a55258783d5b1be6a4fe4af861 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Fri, 27 Jun 2025 20:42:32 +0200 Subject: [PATCH 16/32] Now PutDataArray slabs in an async mode. --- src/etp/fesapi/FesapiHdfProxy.cpp | 40 +++++++++++++++++++++++++++---- src/etp/fesapi/FesapiHdfProxy.h | 21 ++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index e0c8ff5..0479c46 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -326,6 +326,33 @@ void FesapiHdfProxy::writeArrayNdSlab( const uint64_t* numValuesInEachDimension, const uint64_t* offsetInEachDimension, unsigned int numDimensions) +{ + std::setstillProcessingMsgIds = async_writeArrayNdSlab(groupName, datasetName, + datatype, values, numValuesInEachDimension, + offsetInEachDimension, numDimensions); + + auto t_start = std::chrono::high_resolution_clock::now(); + while (!stillProcessingMsgIds.empty()) { + for (int64_t msgId : stillProcessingMsgIds) { + if (!session_->isMessageStillProcessing(msgId)) { + stillProcessingMsgIds.erase(msgId); + } + } + if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > session_->getTimeOut()) { + throw std::runtime_error("Time out waiting for a writeArrayNdSlab response"); + } + } + std::cerr << "writeArrayNdSlab Response got in " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; +} + +std::set FesapiHdfProxy::async_writeArrayNdSlab( + const string& groupName, + const string& datasetName, + COMMON_NS::AbstractObject::numericalDatatypeEnum datatype, + const void* values, + const uint64_t* numValuesInEachDimension, + const uint64_t* offsetInEachDimension, + unsigned int numDimensions) { if (!isOpened()) open(); @@ -370,6 +397,7 @@ void FesapiHdfProxy::writeArrayNdSlab( "You need to give a COMMON_NS::AbstractObject::numericalDatatypeEnum as the datatype"); } + std::set sentMessageIds; if (totalCount * valueSize <= maxArraySize_) { std::vector counts; std::vector starts; @@ -389,7 +417,7 @@ void FesapiHdfProxy::writeArrayNdSlab( pdsa.dataSubarrays["0"].data = convertVoidArrayIntoAvroAnyArray(datatype, values, totalCount); // Send putDataSubarrays Message - session_->sendAndBlock(pdsa, 0, 0x02); + sentMessageIds.insert(session_->send(pdsa, 0, 0x02)); } else { std::unique_ptr counts(new uint64_t[numDimensions]); @@ -405,10 +433,11 @@ void FesapiHdfProxy::writeArrayNdSlab( if (numValuesInEachDimension[dimIdx] > 1) { uint64_t previousCount = counts[dimIdx]; counts[dimIdx] /= 2; - - writeArrayNdSlab(groupName, datasetName, + + std::set intermediateResult = async_writeArrayNdSlab(groupName, datasetName, datatype, values, counts.get(), starts.get(), numDimensions); + sentMessageIds.insert(intermediateResult.begin(), intermediateResult.end()); writtenTotalCount = std::accumulate(counts.get(), counts.get() + numDimensions, 1, std::multiplies()); @@ -424,10 +453,13 @@ void FesapiHdfProxy::writeArrayNdSlab( + std::to_string(maxArraySize_) + " bytes."); } - writeArrayNdSlab(groupName, datasetName, datatype, + std::set intermediateResult = async_writeArrayNdSlab(groupName, datasetName, datatype, (int8_t*)values + (writtenTotalCount * valueSize), counts.get(), starts.get(), numDimensions); + sentMessageIds.insert(intermediateResult.begin(), intermediateResult.end()); } + + return sentMessageIds; } void FesapiHdfProxy::readArrayNdOfDoubleValues( diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index 3048636..98d8e73 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -23,6 +23,7 @@ under the License. #include "../AbstractSession.h" #include "../ProtocolHandlers/GetFullDataArrayHandlers.h" +#include #include namespace ETP_NS @@ -184,6 +185,26 @@ namespace ETP_NS const uint64_t* offsetValuesInEachDimension, unsigned int numDimensions) final; + /** + * Find the array associated with @p groupName and @p name and write to it asynchronously. + * @param groupName The name of the group associated with the array. + * @param name The name of the array (potentially with multi dimensions). + * @param datatype The specific datatype of the values to write. + * @param values 1d array of specific datatype ordered firstly by fastest direction. + * @param numValuesInEachDimension Number of values in each dimension of the array to write. They are ordered from fastest index to slowest index. + * @param offsetValuesInEachDimension Offset values in each dimension of the array to write. They are ordered from fastest index to slowest index. + * @param numDimensions The number of the dimensions of the array to write. + * @return All message ids which have been sent to the ETP server + */ + std::set async_writeArrayNdSlab( + const std::string& groupName, + const std::string& name, + COMMON_NS::AbstractObject::numericalDatatypeEnum datatype, + const void* values, + const uint64_t* numValuesInEachDimension, + const uint64_t* offsetValuesInEachDimension, + unsigned int numDimensions); + /** * Write some string attributes into a group */ From cd25ad34e5a71079eb0825d67fba7cd34a8b8ce1 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Fri, 11 Jul 2025 10:16:45 +0200 Subject: [PATCH 17/32] Automatic ETP session resume when disconnection occurs outside of a transaction --- .github/workflows/github-actions.yml | 55 +++--- CMakeLists.txt | 4 +- README.md | 2 +- cmake/fetpapiCsWithFesapi.csproj.template | 4 + cmake/fetpapiCsWithoutFesapi.csproj.template | 4 + cmake/pyproject.toml.in | 4 +- cmake/swigEtp1_2Include.i.in | 2 +- example/withFesapi/etpClient.cpp | 190 ++++++++++++++++++- python/example/PutHorizon.ipynb | 20 +- python/example/etp_client_example.py | 2 +- src/etp/AbstractClientSessionCRTP.h | 45 +++-- src/etp/AbstractSession.cpp | 22 ++- src/etp/AbstractSession.h | 51 +++-- src/etp/ClientSession.h | 46 ++++- src/etp/ClientSessionLaunchers.cpp | 2 +- src/etp/InitializationParameters.h | 2 +- src/etp/PlainClientSession.cpp | 10 +- src/etp/PlainClientSession.h | 23 +-- src/etp/fesapi/FesapiHdfProxy.h | 4 - src/etp/ssl/SslClientSession.cpp | 12 +- src/etp/ssl/SslClientSession.h | 50 ++--- 21 files changed, 399 insertions(+), 155 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index e506522..602e66b 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,13 +1,13 @@ name: github-actions on: [push, pull_request] jobs: - windows-2019: - runs-on: windows-2019 + windows-2022: + runs-on: windows-2022 steps: - uses: actions/checkout@v4 - name: Boost install run: | - (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.87.0/binaries/boost_1_87_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") + (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.88.0/binaries/boost_1_88_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") Start-Process -Wait -FilePath "${{ runner.temp }}\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=${{ runner.temp }}\boost-install" - name: AVRO install run: | @@ -19,7 +19,7 @@ jobs: cd ${{ runner.temp }} mkdir avro-cpp-build cd avro-cpp-build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/avro-cpp-install ${{ runner.temp }}/avro-cpp-1.11.3 + cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/avro-cpp-install ${{ runner.temp }}/avro-cpp-1.11.3 cmake --build . --config Release --target avrocpp_s -j2 cmake --install . - name: CMake build and install @@ -27,15 +27,15 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_ETP_SSL=FALSE ${{ github.workspace }} + cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_ETP_SSL=FALSE ${{ github.workspace }} cmake --build . --config Release -j2 - windows-2019-with-fesapi: - runs-on: windows-2019 + windows-2022-with-fesapi: + runs-on: windows-2022 steps: - uses: actions/checkout@v4 - name: Boost install run: | - (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.87.0/binaries/boost_1_87_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") + (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.88.0/binaries/boost_1_88_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") Start-Process -Wait -FilePath "${{ runner.temp }}\boost.exe" "/SILENT","/SP-","/SUPPRESSMSGBOXES","/DIR=${{ runner.temp }}\boost-install" - name: AVRO install run: | @@ -47,19 +47,19 @@ jobs: cd ${{ runner.temp }} mkdir avro-cpp-build cd avro-cpp-build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/avro-cpp-install ${{ runner.temp }}/avro-cpp-1.11.3 + cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/avro-cpp-install ${{ runner.temp }}/avro-cpp-1.11.3 cmake --build . --config Release --target avrocpp_s -j2 cmake --install . - name: FESAPI install run: | - (New-Object System.Net.WebClient).DownloadFile("https://github.com/F2I-Consulting/fesapi/releases/download/v2.12.1.0/fesapi2_12_1_0-cpp-vs2019-x64.zip", "${{ runner.temp }}\fesapi.zip") + (New-Object System.Net.WebClient).DownloadFile("https://github.com/F2I-Consulting/fesapi/releases/download/v2.13.0.0/fesapi2_13_0_0-cpp-vs2019-x64.zip", "${{ runner.temp }}\fesapi.zip") 7z x ${{ runner.temp }}\fesapi.zip -o${{ runner.temp }} - name: CMake build and install run: | cd ${{ github.workspace }}/.. mkdir build cd build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi2_12_1_0-cpp-vs2019-x64 -DWITH_ETP_SSL=FALSE ${{ github.workspace }} + cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi2_13_0_0-cpp-vs2019-x64 -DWITH_ETP_SSL=FALSE ${{ github.workspace }} cmake --build . --config Release -j2 ubuntu-22: runs-on: ubuntu-22.04 @@ -148,13 +148,16 @@ jobs: sudo apt install -y ${{ matrix.xcc_pkg }} libhdf5-dev libminizip-dev libboost-all-dev - name: FESAPI install run: | - git clone --branch v2.12.1.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src + git clone --branch v2.13.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src cd ${{ runner.temp }} mkdir fesapi-build cd fesapi-build cmake -DMINIZIP_INCLUDE_DIR=/usr/include/minizip -DMINIZIP_LIBRARY_RELEASE=/usr/lib/x86_64-linux-gnu/libminizip.so.1.0.0 -DCMAKE_BUILD_TYPE=Release -DWITH_JAVA_WRAPPING=TRUE -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/fesapi-install -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} ${{ runner.temp }}/fesapi-src cmake --build . -j2 cmake --install . + mkdir -p ${{ runner.temp }}/fesapi-install/include/fesapi + cd ${{ runner.temp }}/fesapi-src/src + find . -name "*.h" -exec cp --parents \{\} ${{ runner.temp }}/fesapi-install/include/fesapi/ \; - name: AVRO INSTALL run: | curl https://archive.apache.org/dist/avro/avro-1.11.3/cpp/avro-cpp-1.11.3.tar.gz -o ${{ runner.temp }}/avro-cpp-1.11.3.tar.gz @@ -172,7 +175,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.12.1.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} + cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.13.0.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} cmake --build . --config Release -j2 build_wheels_windows: name: Build wheels on windows-latest @@ -191,12 +194,12 @@ jobs: CIBW_BEFORE_ALL: > %VCPKG_INSTALLATION_ROOT%\vcpkg install boost-uuid minizip hdf5[zlib] && cd ${{ runner.temp }} && - powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.12.1.0.tar.gz', '${{ runner.temp }}\fesapi-2.12.1.0.tar.gz')" && - 7z x ${{ runner.temp }}\fesapi-2.12.1.0.tar.gz -o${{ runner.temp }} && - 7z x ${{ runner.temp }}\fesapi-2.12.1.0.tar -o${{ runner.temp }} && + powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.tar.gz', '${{ runner.temp }}\fesapi-2.13.0.0.tar.gz')" && + 7z x ${{ runner.temp }}\fesapi-2.13.0.0.tar.gz -o${{ runner.temp }} && + 7z x ${{ runner.temp }}\fesapi-2.13.0.0.tar -o${{ runner.temp }} && mkdir fesapi-build && cd fesapi-build && - cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/fesapi-install ${{ runner.temp }}\fesapi-2.12.1.0 && + cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/fesapi-install ${{ runner.temp }}\fesapi-2.13.0.0 && cmake --build . --config Release -j2 && cmake --build . --config Release --target INSTALL && %VCPKG_INSTALLATION_ROOT%\vcpkg install openssl boost-beast avro-cpp && @@ -239,11 +242,11 @@ jobs: yum install -y epel-release && yum --enablerepo=epel install -y minizip1.2-devel hdf5-devel cmake3 && cd / && - wget https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.12.1.0.zip && - unzip v2.12.1.0.zip && + wget https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.zip && + unzip v2.13.0.0.zip && mkdir fesapi-build && cd fesapi-build && - cmake3 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:STRING=/fesapi-install /fesapi-2.12.1.0 && + cmake3 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:STRING=/fesapi-install /fesapi-2.13.0.0 && cmake3 --build . -j2 --config Release && cmake3 --install . && cd / && @@ -292,9 +295,9 @@ jobs: # Dont use brew for dependencies https://github.com/pypa/cibuildwheel/issues/1251#issuecomment-1234553537 CIBW_BEFORE_ALL: > cd ${{ github.workspace }}/.. && - wget --no-verbose https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz && - tar xf boost_1_87_0.tar.gz && - cd boost_1_87_0 && + wget --no-verbose https://archives.boost.io/release/1.88.0/source/boost_1_88_0.tar.gz && + tar xf boost_1_88_0.tar.gz && + cd boost_1_88_0 && ./bootstrap.sh --prefix=${{ github.workspace }}/../boost-install --with-libraries=filesystem,iostreams,program_options,regex,system && ./b2 -d0 install && git clone https://github.com/F2I-Consulting/Minizip.git ${{ github.workspace }}/../minizip && @@ -312,11 +315,11 @@ jobs: cmake --build . -j2 --config Release && cmake --install . && cd ${{ github.workspace }}/.. && - wget --no-verbose https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.12.1.0.zip && - unzip v2.12.1.0.zip && + wget --no-verbose https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.zip && + unzip v2.13.0.0.zip && mkdir fesapi-build && cd fesapi-build && - cmake -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=${{ github.workspace }}/../boost-install -DMINIZIP_ROOT=${{ github.workspace }}/../minizip-install -DHDF5_ROOT=${{ github.workspace }}/../hdf5-install -DHDF5_USE_STATIC_LIBRARIES=TRUE -DCMAKE_INSTALL_PREFIX:STRING=${{ github.workspace }}/../fesapi-install ${{ github.workspace }}/../fesapi-2.12.1.0 && + cmake -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=${{ github.workspace }}/../boost-install -DMINIZIP_ROOT=${{ github.workspace }}/../minizip-install -DHDF5_ROOT=${{ github.workspace }}/../hdf5-install -DHDF5_USE_STATIC_LIBRARIES=TRUE -DCMAKE_INSTALL_PREFIX:STRING=${{ github.workspace }}/../fesapi-install ${{ github.workspace }}/../fesapi-2.13.0.0 && cmake --build . -j2 --config Release && cmake --install . && cd ${{ github.workspace }}/.. && diff --git a/CMakeLists.txt b/CMakeLists.txt index a784439..db8bd2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,8 @@ set (FETPAPI_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) # version mechanism set (Fetpapi_VERSION_MAJOR 0) -set (Fetpapi_VERSION_MINOR 3) -set (Fetpapi_VERSION_PATCH 2) +set (Fetpapi_VERSION_MINOR 4) +set (Fetpapi_VERSION_PATCH 0) set (Fetpapi_VERSION_TWEAK 0) set (Fetpapi_VERSION ${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}.${Fetpapi_VERSION_PATCH}.${Fetpapi_VERSION_TWEAK}) diff --git a/README.md b/README.md index e75b4d1..4142656 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Download (build and install if necessary) third party libraries: - BOOST : All versions from version 1.66 should be ok but you may experience some [min/max build issues](https://github.com/boostorg/beast/issues/1980) using version 1.72 or 1.73. - AVRO : https://avro.apache.org/releases.html#Download (starting from version 1.9.0 [except 1.11.1](https://issues.apache.org/jira/browse/AVRO-3601), build it with the above boost library.) -- (OPTIONALLY) OpenSSL : version 1.1 is known to work. +- (OPTIONALLY) OpenSSL : version 3.4 is known to work. - (OPTIONALLY) [FESAPI](https://github.com/F2I-Consulting/fesapi/releases) : All versions from version 2.7.0.0 should be ok but a minimal version of 2.11.0.0 is recommended to automatically recognize FESAPI CMake Variables using CMake find Module and build silently the EtpClient example. # Configure the build diff --git a/cmake/fetpapiCsWithFesapi.csproj.template b/cmake/fetpapiCsWithFesapi.csproj.template index 327f39f..8025923 100644 --- a/cmake/fetpapiCsWithFesapi.csproj.template +++ b/cmake/fetpapiCsWithFesapi.csproj.template @@ -10,7 +10,11 @@ Properties F2iConsulting.Fetpapi ${CS_LIBRARY_NAME} + + + v4.8 512 diff --git a/cmake/fetpapiCsWithoutFesapi.csproj.template b/cmake/fetpapiCsWithoutFesapi.csproj.template index 3b01749..d98085a 100644 --- a/cmake/fetpapiCsWithoutFesapi.csproj.template +++ b/cmake/fetpapiCsWithoutFesapi.csproj.template @@ -10,7 +10,11 @@ Properties F2iConsulting.Fetpapi ${CS_LIBRARY_NAME} + + + v4.8 512 diff --git a/cmake/pyproject.toml.in b/cmake/pyproject.toml.in index c8fd895..cda5017 100644 --- a/cmake/pyproject.toml.in +++ b/cmake/pyproject.toml.in @@ -16,7 +16,6 @@ readme = "README.md" classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Operating System :: MacOS', @@ -29,9 +28,10 @@ classifiers=[ 'Programming Language :: Python :: 3.12', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Application Frameworks ', + 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: File Formats', ] +license = {text = "Apache-2.0"} keywords = [ "energistics", "resqml", diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index d7f5c5e..d613257 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -2133,7 +2133,7 @@ namespace ETP_NS * Run the websocket and then the ETP session. * Everything related to this session (including the completion handlers) will operate on the same unique thread in a single event loop. */ - bool run(); + void run(); }; class InitializationParameters diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp index f7d1ffd..445eae8 100644 --- a/example/withFesapi/etpClient.cpp +++ b/example/withFesapi/etpClient.cpp @@ -21,7 +21,11 @@ under the License. #include #include +#include +#include +#include #include +#include #include #include "etp/ClientSessionLaunchers.h" @@ -47,6 +51,7 @@ void printHelp() std::cout << "\tBlockingImport" << std::endl << "\t\tList all dataobjects from the project/study named dataspace (or the first dataspace) and get the first one in a blocking way" << std::endl << std::endl; std::cout << "\tBlockingExport" << std::endl << "\t\tPut a dummy horizon feature into a dummy dataspace which is also created" << std::endl << std::endl; std::cout << "\tPing" << std::endl << "\t\tPing the server" << std::endl << std::endl; + std::cout << "\tPutDummyHorizon" << std::endl << "\t\tPut a dummy horizon to the store" << std::endl << std::endl; std::cout << "\tList" << std::endl << "\t\tList the objects which have been got from ETP to the in-memory Dataobject repository" << std::endl << std::endl; std::cout << "\tPutXmlAndHdfAtOnce" << std::endl << "\t\tPut a dummy point set representation to the store sending XML and HDF5 points at once." << std::endl << std::endl; std::cout << "\tGetDataspaces" << std::endl << "\t\tGet all store dataspaces" << std::endl << std::endl; @@ -68,6 +73,7 @@ void printHelp() std::cout << "\tDeleteDataObject URI" << std::endl << "\t\tDelete a dataobject" << std::endl << std::endl; std::cout << "\tDeleteDataspace URI" << std::endl << "\t\tDelete a dataspace" << std::endl << std::endl; std::cout << "\tGetDeletedResources dataspaceURI" << std::endl << "\t\tGet all deleted resources" << std::endl << std::endl; + std::cout << "\tGetAllRepsAndProps dataspace" << std::endl << "\t\tGet all the representations and all their properties from a dataspace" << std::endl << std::endl; std::cout << "\tquit" << std::endl << "\t\tQuit the session." << std::endl << std::endl; } @@ -80,11 +86,10 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb while (command != "quit") { if (session->isEtpSessionClosed()) { - command = "quit"; - } - else { - std::getline(std::cin, command); + std::cout << "The ETP session has been lost. You should quit if FETPAPI cannot reconnect by itself" << std::endl; } + + std::getline(std::cin, command); auto commandTokens = tokenize(command, ' '); if (commandTokens.empty()) { @@ -239,9 +244,9 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb std::cerr << " The UUID " << uuid << " from URI " << commandTokens[1] << " does not correspond to a representation which is on client side. Please get first this dataobject from the store before to call GetXYZPoints on it." << std::endl; continue; } - auto xyzPointCount = rep->getXyzPointCountOfPatch(0); + auto xyzPointCount = rep->getXyzPointCountOfAllPatches(); std::unique_ptr xyzPoints(new double[xyzPointCount * 3]); - rep->getXyzPointsOfPatch(0, xyzPoints.get()); + rep->getXyzPointsOfAllPatches(xyzPoints.get()); for (auto xyzPointIndex = 0; xyzPointIndex < xyzPointCount && xyzPointIndex < 20; ++xyzPointIndex) { std::cout << "XYZ Point Index " << xyzPointIndex << " : " << xyzPoints[xyzPointIndex * 3] << "," << xyzPoints[xyzPointIndex * 3 + 1] << "," << xyzPoints[xyzPointIndex * 3 + 2] << std::endl; } @@ -270,6 +275,72 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb std::cout << dataspace.uri << std::endl; } } + else if (commandTokens[0] == "GetAllRepsAndProps") { + if (commandTokens.size() == 1) { + std::cerr << "Please provide some ETP URIs of a dataspace" << std::endl; + continue; + } + std::map dataspaceUris; + dataspaceUris["0"] = commandTokens[1]; + + Energistics::Etp::v12::Datatypes::Object::ContextInfo ctxInfo; + ctxInfo.uri = dataspaceUris["0"]; + ctxInfo.depth = 0; + ctxInfo.navigableEdges = Energistics::Etp::v12::Datatypes::Object::RelationshipKind::Both; + ctxInfo.includeSecondaryTargets = false; + ctxInfo.includeSecondarySources = false; + const auto resources = session->getResources(ctxInfo, Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets); + std::cout << "************ GET ALL DATAOBJECTS ************" << std::endl; + if (!resources.empty()) { + std::map< std::string, std::string > query; + size_t index = 0; + for (auto& resource : resources) { + query[std::to_string(index++)] = resource.uri; + } + const auto dataobjects = session->getDataObjects(query); + for (const auto& dataobjectEntry : dataobjects) { + repo.addOrReplaceGsoapProxy(dataobjectEntry.second.data, ETP_NS::EtpHelpers::getDataObjectType(dataobjectEntry.second.resource.uri), ETP_NS::EtpHelpers::getDataspaceUri(dataobjectEntry.second.resource.uri)); + } + // Parse reps + auto global_start = std::chrono::high_resolution_clock::now(); + for (auto* rep : repo.getDataObjects()) { + std::cout << "Representation " << rep->getTitle() << std::endl; + std::unique_ptr ijkGridPoints(new double[rep->getXyzPointCountOfAllPatches() * 3]); + auto t_start = std::chrono::high_resolution_clock::now(); + try { + rep->getXyzPointsOfAllPatches(ijkGridPoints.get()); + std::cout << "XYZ POINTS IN " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; + } + catch (...) { + std::cerr << "Error reading XYZ points." << std::endl; + } + + auto allProps = rep->getValuesPropertySet(); + size_t propIndex = 1; + for (auto* prop : allProps) { + size_t valuesCount = prop->getValuesCountOfPatch(0); + if (dynamic_cast(prop) != nullptr) { + std::cout << "Continuous Prop " << propIndex++ << "/" << allProps.size() << " : " << prop->getTitle() << std::endl; + std::unique_ptr propValues(new double[valuesCount]); + t_start = std::chrono::high_resolution_clock::now(); + prop->getDoubleValuesOfPatch(0, propValues.get()); + std::cout << "Continuous Prop IN " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; + } + else { + std::cout << "Non Continuous Prop " << propIndex++ << "/" << allProps.size() << " : " << prop->getTitle() << std::endl; + std::unique_ptr propValues(new int[valuesCount]); + t_start = std::chrono::high_resolution_clock::now(); + prop->getInt32ValuesOfPatch(0, propValues.get()); + std::cout << "Non Continuous Prop IN " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; + } + } + } + std::cout << "GLOBALLY DONE IN " << std::chrono::duration(std::chrono::high_resolution_clock::now() - global_start).count() << " ms" << std::endl; + } + else { + std::cout << "There is no dataobject in this dataspace" << std::endl; + } + } else if (commandTokens[0] == "PutDataspace") { if (commandTokens.size() == 1) { std::cerr << "Please provide some ETP URI of a dataspace" << std::endl; @@ -418,6 +489,106 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb std::cout << "PING at " << ping.currentDateTime << std::endl; std::cout << "Please Set Verbosity to 1 if you don't see anything" << std::endl; } + else if (commandTokens[0] == "PutDummyHorizon") { + Energistics::Etp::v12::Datatypes::Object::Dataspace dataspace; + dataspace.path = "demo/PutHorizon3"; + dataspace.uri = "eml:///dataspace('" + dataspace.path + "')"; + Energistics::Etp::v12::Datatypes::DataValue dataValue; + Energistics::Etp::v12::Datatypes::ArrayOfString aos; + + aos.values.push_back("data.default.viewers@osdu.example.com"); + //aos.values.push_back("data.default.viewers@opendes.contoso.com"); + dataValue.item.set_ArrayOfString(aos); + dataspace.customData["viewers"] = dataValue; + + aos.values[0] = "data.default.owners@osdu.example.com"; + //aos.values[0] = "data.default.owners@opendes.contoso.com"; + dataValue.item.set_ArrayOfString(aos); + dataspace.customData["owners"] = dataValue; + + aos.values[0] = "osdu-public-usa-dataset"; + //aos.values[0] = "opendes-ReservoirDDMS-Legal-Tag"; + dataValue.item.set_ArrayOfString(aos); + dataspace.customData["legaltags"] = dataValue; + + aos.values[0] = "US"; + dataValue.item.set_ArrayOfString(aos); + dataspace.customData["otherRelevantDataCountries"] = dataValue; + + std::map deleteDs; + deleteDs["0"] = dataspace.uri; + session->deleteDataspaces(deleteDs); + + std::map dataspaces; + dataspaces["0"] = dataspace; + auto successKeys = session->putDataspaces(dataspaces); + if (successKeys.size() == 1) std::cout << "Dataspace has been put" << std::endl; + else std::cout << "Error when putting dataspace" << std::endl; + + COMMON_NS::DataObjectRepository tmpRepo; + tmpRepo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::RESQML2_0_1); + tmpRepo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_0); + auto* local_3d_crs = tmpRepo.createLocalDepth3dCrs("b2129512-b8f9-4721-8a70-1abac53ef406", "Default CRS", + 0.0, 0.0, 0.0, 0.0, + gsoap_resqml2_0_1::eml20__LengthUom::m, 5215, + gsoap_resqml2_0_1::eml20__LengthUom::m, "Unknown", + false); + tmpRepo.setDefaultCrs(local_3d_crs); + tmpRepo.setHdfProxyFactory(new ETP_NS::FesapiHdfProxyFactory(session.get())); + auto* hdf_proxy = tmpRepo.createHdfProxy("f8160b8f-0517-4c55-ab6e-ed8bcdc87111", "Hdf Proxy", + ".", "fake.h5", + COMMON_NS::DataObjectRepository::openingMode::OVERWRITE); + hdf_proxy->setUriSource(dataspace.uri); + tmpRepo.setDefaultHdfProxy(hdf_proxy); + auto* horizon_feature = tmpRepo.createHorizon("c0f12836-41f4-44a8-a3fd-95ac78f6232d", "My horizon feature"); + auto* horizon_interpretation = tmpRepo.createHorizonInterpretation(horizon_feature, "dc217b29-8ceb-4b77-bdcc-6bcfd9cd3baf", "My horizon interpretation"); + auto* horizon_grid_2d_representation = tmpRepo.createGrid2dRepresentation(horizon_interpretation, "7721fb3c-39ba-4d59-ba0b-f9451706a94c", "My horizon representation"); + + std::vector< std::string > dataspacesToLock; + dataspacesToLock.push_back(dataspace.uri); + + auto transaction_start = std::chrono::high_resolution_clock::now(); + + session->startTransaction(dataspacesToLock); + + const size_t ni = 10000; + const size_t nj = 1000; + std::unique_ptr resqml_points(new double[ni * nj]); + for (double i = 0; i < ni * nj; ++i) { + resqml_points[(int)i] = i * 100; + } + horizon_grid_2d_representation->setGeometryAsArray2dOfExplicitZ(resqml_points.get(), ni, nj, hdf_proxy, + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 25.0, + 0.0, 1.0, 0.0, 50.0); + + for (size_t propIndex = 0; propIndex < 1; ++propIndex) { + auto t_start = std::chrono::high_resolution_clock::now(); + auto* prop = tmpRepo.createContinuousProperty(horizon_grid_2d_representation, "", "", 1, gsoap_eml2_3::eml23__IndexableElement::nodes, gsoap_resqml2_0_1::resqml20__ResqmlUom::m, + gsoap_resqml2_0_1::resqml20__ResqmlPropertyKind::length); + std::unique_ptr prop_values(new double[ni * nj]); + prop->pushBackDoubleHdf5Array2dOfValues(prop_values.get(), ni, nj, hdf_proxy); + std::cout << " Pushed prop " << propIndex << " in " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; + std::cout << " Global time " << std::chrono::duration(std::chrono::high_resolution_clock::now() - transaction_start).count() << " s" << std::endl; + } + + tmpRepo.setUriSource(dataspace.uri); + std::map dataobjects; + auto allUuids = tmpRepo.getUuids(); + int index = 0; + for (auto& uuid : allUuids) + dataobjects[std::to_string(index++)] = ETP_NS::FesapiHelpers::buildEtpDataObjectFromEnergisticsObject(tmpRepo, uuid); + successKeys = session->putDataObjects(dataobjects); + for (std::string& str : successKeys) + std::cout << "successKey : " << str << std::endl; + + std::cout << "commit : " << session->commitTransaction() << std::endl; + + if (session != nullptr && !session->isWebSocketSessionClosed()) + horizon_grid_2d_representation->getZValues(resqml_points.get()); + + tmpRepo.clear(); + } else if (commandTokens[0] == "GetDataspaces") { const auto dataspaces = session->getDataspaces(); for (auto& dataspace : dataspaces) { @@ -511,6 +682,7 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb arrayOfInt.values = { 0,1,2,3,4,5,6,7,8,9 }; data.item.set_ArrayOfInt(arrayOfInt); pda.dataArrays["0"].array.data = data; + std::cout << "Start sending the array" << std::endl; session->send(pda, 0, 0x02); } @@ -552,8 +724,6 @@ int main(int argc, char **argv) std::string authorization; std::getline(std::cin, authorization); - bool successfulConnection = false; - COMMON_NS::DataObjectRepository repo; repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::RESQML2_0_1); repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_0); @@ -571,7 +741,6 @@ int main(int argc, char **argv) repo.setHdfProxyFactory(new ETP_NS::FesapiHdfProxyFactory(clientSession.get())); std::thread sessionThread(&ETP_NS::ClientSession::run, clientSession); - sessionThread.detach(); // Wait for the ETP session to be opened auto t_start = std::chrono::high_resolution_clock::now(); @@ -583,8 +752,11 @@ int main(int argc, char **argv) } clientSession->setTimeOut(60000); + clientSession->setVerbose(false); askUser(clientSession, repo); + sessionThread.join(); + #ifdef _WIN32 _CrtDumpMemoryLeaks(); #endif diff --git a/python/example/PutHorizon.ipynb b/python/example/PutHorizon.ipynb index 7961b9d..f05ff93 100644 --- a/python/example/PutHorizon.ipynb +++ b/python/example/PutHorizon.ipynb @@ -140,7 +140,7 @@ "id": "abbc43cb", "metadata": {}, "source": [ - "Let's create a dataspace" + "Let's create a dataspace with the OSDU ACL mandatory information" ] }, { @@ -152,7 +152,23 @@ "source": [ "dataspace = fetpapi.Dataspace()\n", "dataspace.path = \"demo/PutHorizon\"\n", - "dataspace.uri = \"eml:///dataspace('\" + dataspace.path + \"')\"" + "dataspace.uri = \"eml:///dataspace('\" + dataspace.path + \"')\"\n", + "dataspace.customData = fetpapi.MapStringDataValue()\n", + "dataValue = fetpapi.DataValue()\n", + "aos = fetpapi.ArrayOfString\n", + "dataValue.item = fetpapi.DataValueitem_t()\n", + "aos.values = [\"data.default.viewers@osdu.example.com\"]\n", + "dataValue.item.set_ArrayOfString(aos)\n", + "dataspace.customData[\"viewers\"] = dataValue\n", + "aos.values = [\"data.default.owners@osdu.example.com\"]\n", + "dataValue.item.set_ArrayOfString(aos)\n", + "dataspace.customData[\"owners\"] = dataValue\n", + "aos.values = [\"osdu-public-usa-dataset\"]\n", + "dataValue.item.set_ArrayOfString(aos)\n", + "dataspace.customData[\"legaltags\"] = dataValue\n", + "aos.values = [\"US\"]\n", + "dataValue.item.set_ArrayOfString(aos)\n", + "dataspace.customData[\"otherRelevantDataCountries\"] = dataValue" ] }, { diff --git a/python/example/etp_client_example.py b/python/example/etp_client_example.py index 87f1d6d..44a2f62 100644 --- a/python/example/etp_client_example.py +++ b/python/example/etp_client_example.py @@ -122,7 +122,7 @@ def start_etp_server(client_session): print("Z value at index 1 : ", z_points.getitem(1)) if grid2d.getValuesPropertyCount() > 0: - prop = repo.getValuesProperty(0) + prop = grid2d.getValuesProperty(0) print("Prop at index 0 : " + prop.getTitle()) else: print("This 2d grid has no property") diff --git a/src/etp/AbstractClientSessionCRTP.h b/src/etp/AbstractClientSessionCRTP.h index 03277fa..469b1f0 100644 --- a/src/etp/AbstractClientSessionCRTP.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -32,12 +32,13 @@ namespace ETP_NS void on_connect(boost::system::error_code ec) { if (ec) { - std::cerr << "Websocket on connect : " << ec.message() << std::endl; + std::cerr << "ERROR at Websocket connection : " << ec.message() << std::endl; + return; } #if BOOST_VERSION < 107000 // Perform the websocket handshake - derived().ws().async_handshake_ex(responseType, + derived().ws()->async_handshake_ex(responseType, etpServerHost + ":" + etpServerPort, etpServerTarget, [&](websocket::request_type& m) { @@ -56,7 +57,7 @@ namespace ETP_NS std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); #else - derived().ws().set_option(websocket::stream_base::decorator( + derived().ws()->set_option(websocket::stream_base::decorator( [&](websocket::request_type& m) { m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); @@ -71,7 +72,7 @@ namespace ETP_NS }) ); // Perform the websocket handshake - derived().ws().async_handshake(responseType, + derived().ws()->async_handshake(responseType, etpServerHost + ":" + etpServerPort, etpServerTarget, std::bind( &ClientSession::on_handshake, @@ -85,7 +86,7 @@ namespace ETP_NS * The ETP session had to be closed before. */ FETPAPI_DLL_IMPORT_OR_EXPORT void do_close() { - derived().ws().async_close(websocket::close_code::normal, + derived().ws()->async_close(websocket::close_code::normal, std::bind( &AbstractSession::on_close, shared_from_this(), @@ -100,7 +101,7 @@ namespace ETP_NS } // Read a message into our buffer - derived().ws().async_read( + derived().ws()->async_read( receivedBuffer, std::bind( &AbstractSession::on_read, @@ -111,7 +112,7 @@ namespace ETP_NS void setMaxWebSocketMessagePayloadSize(int64_t value) final { maxWebSocketMessagePayloadSize = value; - derived().ws().read_message_max(value); + derived().ws()->read_message_max(value); } protected: @@ -121,12 +122,12 @@ namespace ETP_NS Derived& derived() { return static_cast(*this); } void do_write() { - const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); if (sendingQueue.empty()) { fesapi_log("The sending queue is empty."); return; } + const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); bool previousSentMessageCompleted = specificProtocolHandlers.find(std::get<0>(sendingQueue.front())) == specificProtocolHandlers.end(); if (!previousSentMessageCompleted) { @@ -135,16 +136,26 @@ namespace ETP_NS else { fesapi_log("Sending Message id :", std::to_string(std::get<0>(sendingQueue.front()))); - derived().ws().async_write( + derived().ws()->async_write( boost::asio::buffer(std::get<1>(sendingQueue.front())), - std::bind( - &AbstractSession::on_write, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2)); - - // Register the handler to respond to the sent message - specificProtocolHandlers[std::get<0>(sendingQueue.front())] = std::get<2>(sendingQueue.front()); + [this, self{ this->shared_from_this() }](boost::system::error_code ec, std::size_t) + ->void + { + if (ec) { + std::cerr << "on_write : " << ec.message() << std::endl; + } + else { + // Register the handler to respond to the sent message + const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); + specificProtocolHandlers[std::get<0>(sendingQueue.front())] = std::get<2>(sendingQueue.front()); + } + + // Remove the sent message from the queue + const std::lock_guard sendingQueueLock(sendingQueueMutex); + sendingQueue.pop(); + + do_write(); + }); } } }; diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index 379d3d1..aed3aad 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -42,24 +42,30 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr boost::ignore_unused(bytes_transferred); if (ec) { + // If read completes with an error, it indicates there is an issue with the connection, so async_close would also complete with an error. + // Therefore, there is no need to call async_close; the destructor of websocket::stream will forcefully close the underlying socket. + flushReceivingBuffer(); if (ec == websocket::error::closed) { // This indicates that the web socket (and consequently etp) session was closed - fesapi_log("The other endpoint closed the web socket (and consequently etp) connection."); - webSocketSessionClosed = true; - flushReceivingBuffer(); + std::cerr << "The other endpoint closed the web socket (and consequently etp) connection" << std::endl; } else { // This indicates an unexpected error - fesapi_log("on_read : error code number", ec.value(), "->", ec.message()); + std::cerr << "on_read : error code number " << ec.value() << std::endl; + std::cerr << "on_read : error message " << ec.message() << std::endl; + std::cerr << "on_read : error category " << ec.category().name() << std::endl; // This error may be a common one to ignore in case of SSL short read : https://github.com/boostorg/beast/issues/824 if (etpSessionClosed) { - fesapi_log("It looks that the other endpoint has closed the websocket session in a non graceful way."); - webSocketSessionClosed = true; - flushReceivingBuffer(); + std::cerr << "It looks that the other endpoint has closed the websocket session in a non graceful way" << std::endl; } } + const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); + specificProtocolHandlers.clear(); + webSocketSessionClosed = true; + etpSessionClosed = true; + return; } @@ -147,7 +153,7 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr send(ETP_NS::EtpHelpers::buildSingleMessageProtocolException(19, "The agent is unable to de-serialize the body of the message id " + std::to_string(receivedMh.messageId) + " : " + std::string(e.what())), 0, 0x02); } - if (specificProtocolHandlers.empty() && isCloseRequested) + if (specificProtocolHandlers.empty() && isCloseRequested_) { etpSessionClosed = true; send(Energistics::Etp::v12::Protocol::Core::CloseSession(), 0, 0x02); diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index 03c4d3e..a2e30ce 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -199,12 +199,22 @@ namespace ETP_NS */ template int64_t sendWithSpecificHandler(const T & mb, std::shared_ptr specificHandler, int64_t correlationId = 0, int32_t messageFlags = 0) { + if (mb.protocolId != 0 || mb.messageTypeId != 1) { + // Wait for a reconnection + while (isEtpSessionClosed() && !isCloseRequested()) {} + // Check if reconnection is successful + if (isEtpSessionClosed() && !isCloseRequested()) { + throw std::runtime_error("The ETP session could not be opened in order to send the message."); + } + } + // Encode the message into AVRO format auto queueItem = encode(mb, correlationId, messageFlags); const std::lock_guard sendingQueueLock(sendingQueueMutex); // Set the handlers which are going to be called for the response to this sent message std::get<2>(queueItem) = specificHandler; + // Push the message into the queue sendingQueue.push(queueItem); fesapi_log("*************************************************"); @@ -250,6 +260,19 @@ namespace ETP_NS } } + // If the message has not been answered correctly + if (isEtpSessionClosed() && !isCloseRequested()) { + // Wait for a reconnection + while (isEtpSessionClosed() && !isCloseRequested()) {} + // Check if reconnection is successfull + if (isEtpSessionClosed()) { + throw std::runtime_error("The ETP session could not be opened in order to send again the message."); + } + else { + return sendWithSpecificHandlerAndBlock(mb, specificHandler, correlationId, messageFlags); + } + } + return msgId; } @@ -268,18 +291,6 @@ namespace ETP_NS */ FETPAPI_DLL_IMPORT_OR_EXPORT void on_read(boost::system::error_code ec, std::size_t bytes_transferred); - void on_write(boost::system::error_code ec, std::size_t) { - if(ec) { - std::cerr << "on_write : " << ec.message() << std::endl; - } - - // Remove the sent message from the queue - const std::lock_guard sendingQueueLock(sendingQueueMutex); - sendingQueue.pop(); - - do_write(); - } - void on_close(boost::system::error_code ec) { if(ec) { std::cerr << "on_close : " << ec.message() << std::endl; @@ -292,6 +303,11 @@ namespace ETP_NS webSocketSessionClosed = true; } + /** + * Check if the the closing of the session has been requested by this session or not. + */ + FETPAPI_DLL_IMPORT_OR_EXPORT bool isCloseRequested() const { return isCloseRequested_; } + /** * Check if the websocket session (starting after the HTTP handshake/upgrade) is not opened yet or has been closed. */ @@ -305,7 +321,6 @@ namespace ETP_NS const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); return (!sendingQueue.empty() && std::get<0>(sendingQueue.front()) <= msgId) || specificProtocolHandlers.count(msgId) > 0; } - //FETPAPI_DLL_IMPORT_OR_EXPORT bool isMessageStillProcessing(int64_t msgId) const { return specificProtocolHandlers.count(msgId) > 0; } virtual void setMaxWebSocketMessagePayloadSize(int64_t value) = 0; int64_t getMaxWebSocketMessagePayloadSize() const { return maxWebSocketMessagePayloadSize; } @@ -320,7 +335,7 @@ namespace ETP_NS * This method does not block. */ FETPAPI_DLL_IMPORT_OR_EXPORT void close() { - isCloseRequested = true; + isCloseRequested_ = true; sendingQueueMutex.lock(); specificProtocolHandlersMutex.lock(); if (specificProtocolHandlers.empty() && sendingQueue.empty()) { @@ -355,7 +370,10 @@ namespace ETP_NS */ FETPAPI_DLL_IMPORT_OR_EXPORT bool isEtpSessionClosed() const { return webSocketSessionClosed || etpSessionClosed; } - void setEtpSessionClosed(bool etpSessionClosed_) { etpSessionClosed = etpSessionClosed_; } + void setEtpSessionClosed(bool etpSessionClosed_) { + etpSessionClosed = etpSessionClosed_; + reconnectionTryCount_ = 0; + } /**************** *** DATASPACE *** @@ -617,7 +635,8 @@ namespace ETP_NS /// The identifier of the session boost::uuids::uuid identifier; /// Indicates that the endpoint request to close the websocket session - bool isCloseRequested{ false }; + bool isCloseRequested_{ false }; + size_t reconnectionTryCount_ = 0; AbstractSession() = default; diff --git a/src/etp/ClientSession.h b/src/etp/ClientSession.h index 595f866..093b731 100644 --- a/src/etp/ClientSession.h +++ b/src/etp/ClientSession.h @@ -48,9 +48,7 @@ namespace ETP_NS * Since this is a loop, you may want to operate this method on a dedicated thread not to block your program. * This method returns only when the session is closed. */ - bool run() { - successfulConnection = false; - + void run() { // Look up the domain name before to run the session // It is important to do this before to run the io context. Otherwise running the io context would return immediately if nothing has to be done. resolver.async_resolve( @@ -66,17 +64,45 @@ namespace ETP_NS // Run will return only when there will no more be any uncomplete operations (such as a reading operation for example) getIoContext().run(); - return successfulConnection; + std::cerr << "The IO Context does no more run" << std::endl; + + // Try to reconnect up to 10 times + if (!isCloseRequested_ && reconnectionTryCount_ < 10) { + ++reconnectionTryCount_; + std::cerr << "Session has been disconnected, trying to reconnect... " << reconnectionTryCount_ << "/10" << std::endl; + getIoContext().restart(); + run(); + } + + if (!isCloseRequested_ && reconnectionTryCount_ >= 10) { + std::cerr << "Could not reconnect after 10 retries... Give up and close" << reconnectionTryCount_ << "/10" << std::endl; + isCloseRequested_ = true; + } } - virtual void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) = 0; + void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) { + if (ec) { + std::cerr << "on_resolve : " << ec.message() << std::endl; + } + + // Reality check: IPv6 is unlikely to be available yet + endpoints = std::vector(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + + asyncConnect(); + } + + virtual void asyncConnect() = 0; + virtual bool isTls() const = 0; void on_handshake(boost::system::error_code ec) { if (ec) { - std::cerr << "on WS handshake : " << ec.message() << std::endl; - std::cerr << "Sometimes some ETP server require a trailing slash at the end of their URL. Did you also check your optional \"data-partition-id\" additional Header Field?" << std::endl; + std::cerr << "on WS handshake, error code number : " << ec.value() << std::endl; + std::cerr << "on WS handshake, error message : " << ec.message() << std::endl; + std::cerr << "on WS handshake, error category : " << ec.category().name() << std::endl; + std::cerr << "Sometimes some ETP server require a trailing slash at the end of their URL. Did you also check your optional \"data-partition-id\" additional Header Field? Has your token expired?" << std::endl; return; } @@ -84,7 +110,7 @@ namespace ETP_NS responseType[boost::beast::http::field::sec_websocket_protocol] != "etp12.energistics.org") std::cerr << "The client MUST specify the Sec-Websocket-Protocol header value of etp12.energistics.org, and the server MUST reply with the same" << std::endl; - successfulConnection = true; + fesapi_log("Now connected to Websocket"); webSocketSessionClosed = false; send(requestSession, 0, 0x02); @@ -103,8 +129,8 @@ namespace ETP_NS std::string proxyAuthorization; std::map additionalHandshakeHeaderFields_; websocket::response_type responseType; // In order to check handshake sec_websocket_protocol + std::vector endpoints; // Store the resolved endpoints to prevent calling resolve once again when reconnecting Energistics::Etp::v12::Protocol::Core::RequestSession requestSession; - bool successfulConnection = false; /** * @param initializationParams The initialization parameters of the session including IP host, port, requestedProtocols, supportedDataObjects @@ -114,7 +140,7 @@ namespace ETP_NS */ ClientSession( InitializationParameters const* initializationParams, const std::string& target, const std::string& etpServerAuth, const std::string& proxyAuth = "") : - ioc(4), + ioc(), resolver(ioc), etpServerHost(initializationParams->getEtpServerHost()), etpServerPort(std::to_string(initializationParams->getEtpServerPort())), diff --git a/src/etp/ClientSessionLaunchers.cpp b/src/etp/ClientSessionLaunchers.cpp index 9bef84b..8b777b1 100644 --- a/src/etp/ClientSessionLaunchers.cpp +++ b/src/etp/ClientSessionLaunchers.cpp @@ -98,7 +98,7 @@ std::shared_ptr ETP_NS::ClientSessionLaunchers::createCli std::size_t preferredMaxFrameSize = getNegotiatedMaxWebSocketFramePayloadSize(restClientSession->getResponse().body(), initializationParams->getPreferredMaxFrameSize()); - result = std::make_shared(ctx, initializationParams, "/" + initializationParams->getEtpServerUrlPath(), + result = std::make_shared(std::move(ctx), initializationParams, "/" + initializationParams->getEtpServerUrlPath(), authorization, proxyAuthorization, initializationParams->getAdditionalHandshakeHeaderFields(), preferredMaxFrameSize); } diff --git a/src/etp/InitializationParameters.h b/src/etp/InitializationParameters.h index 52db6f5..a6b367c 100644 --- a/src/etp/InitializationParameters.h +++ b/src/etp/InitializationParameters.h @@ -93,7 +93,7 @@ namespace ETP_NS * where port is optional and is defaulted to 80 if scheme is "ws" or if no scheme is provided. * In "wss" schema cases, port is defaulted to 443. * @param proxyUrl The proxy URL. It must follow the syntax http://: or simply :. - * Leave it empty if your connection to eptServerUrl is direct and does not pass throughr any proxy. + * Leave it empty if your connection to eptServerUrl is direct and does not pass through any proxy. */ FETPAPI_DLL_IMPORT_OR_EXPORT InitializationParameters(const std::string& instanceUuid, const std::string& etpServerUrl, const std::string& proxyUrl = "") diff --git a/src/etp/PlainClientSession.cpp b/src/etp/PlainClientSession.cpp index b834eba..84cec5d 100644 --- a/src/etp/PlainClientSession.cpp +++ b/src/etp/PlainClientSession.cpp @@ -24,15 +24,7 @@ using namespace ETP_NS; PlainClientSession::PlainClientSession( InitializationParameters const* initializationParams, const std::string & target, const std::string & authorization, const std::string& proxyAuthorization, const std::map& additionalHandshakeHeaderFields, std::size_t frameSize) : - AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), - ws_(ioc) + AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), frameSize_(frameSize) { - ws_.binary(true); -#if BOOST_VERSION < 107000 - ws_.write_buffer_size(frameSize); -#else - ws_.write_buffer_bytes(frameSize); -#endif - additionalHandshakeHeaderFields_ = additionalHandshakeHeaderFields; } diff --git a/src/etp/PlainClientSession.h b/src/etp/PlainClientSession.h index 39f3114..6b1804d 100644 --- a/src/etp/PlainClientSession.h +++ b/src/etp/PlainClientSession.h @@ -35,23 +35,23 @@ namespace ETP_NS virtual ~PlainClientSession() = default; // Called by the base class - FETPAPI_DLL_IMPORT_OR_EXPORT websocket::stream& ws() { return ws_; } + FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>& ws() { return ws_; } bool isTls() const final{ return false; } - void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) + void asyncConnect() { - if (ec) { - std::cerr << "on_resolve : " << ec.message() << std::endl; - } - - // Reality check: IPv6 is unlikely to be available yet - std::vector endpoints(results.begin(), results.end()); - std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + ws_.reset(new websocket::stream(ioc)); + ws_->binary(true); +#if BOOST_VERSION < 107000 + ws_->write_buffer_size(frameSize_); +#else + ws_->write_buffer_bytes(frameSize_); +#endif // Make the connection on the IP address we get from a lookup boost::asio::async_connect( - ws_.next_layer(), + ws_->next_layer(), endpoints.begin(), endpoints.end(), std::bind( @@ -61,6 +61,7 @@ namespace ETP_NS } private: - websocket::stream ws_; + std::unique_ptr> ws_; + std::size_t frameSize_; }; } diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index 98d8e73..4bc2703 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -507,10 +507,6 @@ namespace ETP_NS template void readArrayNdOfValues(const std::string & datasetName, T* values) { - if (!isOpened()) { - throw std::runtime_error("The ETP session does not look to be opened. Please reconnect."); - } - // First get metadata about the data array const Energistics::Etp::v12::Datatypes::DataArrayTypes::DataArrayMetadata daMetadata = getDataArrayMetadata(datasetName); const size_t valueCount = std::accumulate(daMetadata.dimensions.begin(), daMetadata.dimensions.end(), 1, std::multiplies()); diff --git a/src/etp/ssl/SslClientSession.cpp b/src/etp/ssl/SslClientSession.cpp index b6b2cab..c9acbf2 100644 --- a/src/etp/ssl/SslClientSession.cpp +++ b/src/etp/ssl/SslClientSession.cpp @@ -20,18 +20,10 @@ under the License. using namespace ETP_NS; -SslClientSession::SslClientSession(boost::asio::ssl::context& ctx, +SslClientSession::SslClientSession(boost::asio::ssl::context&& ctx, InitializationParameters const* initializationParams, const std::string& target, const std::string& authorization, const std::string& proxyAuthorization, const std::map& additionalHandshakeHeaderFields, std::size_t frameSize) - : AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), - ws_(ioc, ctx) + : AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), sslContext_(std::move(ctx)), frameSize_(frameSize) { - ws_.binary(true); -#if BOOST_VERSION < 107000 - ws_.write_buffer_size(frameSize); -#else - ws_.write_buffer_bytes(frameSize); -#endif - additionalHandshakeHeaderFields_ = additionalHandshakeHeaderFields; } diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index a095abd..84a35e8 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -37,49 +37,41 @@ namespace ETP_NS { class SslClientSession : public AbstractClientSessionCRTP { - private: - websocket::stream> ws_; - http::request proxyHandshake; - http::response proxyHandshakeResponse; - // use own response parser - // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body - http::response_parser http_proxy_handshake_parser; - public: /* * @param frameSize Sets the size of the write buffer used by the implementation to send frames : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html. */ - FETPAPI_DLL_IMPORT_OR_EXPORT SslClientSession(boost::asio::ssl::context& ctx, + FETPAPI_DLL_IMPORT_OR_EXPORT SslClientSession(boost::asio::ssl::context&& ctx, InitializationParameters const* initializationParams, const std::string& target, const std::string& authorization, const std::string& proxyAuthorization = "", const std::map& additionalHandshakeHeaderFields = {}, std::size_t frameSize = 4096); virtual ~SslClientSession() {} // Called by the base class - FETPAPI_DLL_IMPORT_OR_EXPORT websocket::stream>& ws() { return ws_; } + FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>>& ws() { return ws_; } bool isTls() const final { return true; } - void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) + void asyncConnect() { - if (ec) { - std::cerr << "on_resolve : " << ec.message() << std::endl; - } + ws_.reset(new websocket::stream>(ioc, sslContext_)); + ws_->binary(true); +#if BOOST_VERSION < 107000 + ws_->write_buffer_size(frameSize_); +#else + ws_->write_buffer_bytes(frameSize_); +#endif // Set SNI Hostname (many hosts need this to handshake successfully) - if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), etpServerHost.c_str())) + if (!SSL_set_tlsext_host_name(ws_->next_layer().native_handle(), etpServerHost.c_str())) { boost::system::error_code ecSNI{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; std::cerr << "Websocket on connect (SNI): " << ecSNI.message() << std::endl; } - // Reality check: IPv6 is unlikely to be available yet - std::vector endpoints(results.begin(), results.end()); - std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); - // Make the connection on the IP address we get from a lookup boost::asio::async_connect( - ws_.next_layer().next_layer(), + ws_->next_layer().next_layer(), endpoints.begin(), endpoints.end(), std::bind( @@ -106,7 +98,7 @@ namespace ETP_NS } // Send the handshake to the proxy - http::async_write(ws_.next_layer().next_layer(), proxyHandshake, + http::async_write(ws_->next_layer().next_layer(), proxyHandshake, std::bind( &SslClientSession::on_proxy_handshake_write, std::static_pointer_cast(shared_from_this()), @@ -115,7 +107,7 @@ namespace ETP_NS } else { // Perform the SSL handshake - ws_.next_layer().async_handshake( + ws_->next_layer().async_handshake( boost::asio::ssl::stream_base::client, std::bind( &AbstractClientSessionCRTP::on_connect, @@ -151,7 +143,7 @@ namespace ETP_NS http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212 // Receive the HTTP response - http::async_read(ws_.next_layer().next_layer(), receivedBuffer, http_proxy_handshake_parser, + http::async_read(ws_->next_layer().next_layer(), receivedBuffer, http_proxy_handshake_parser, std::bind( &SslClientSession::on_proxy_handshake_read, std::static_pointer_cast(shared_from_this()), @@ -171,12 +163,22 @@ namespace ETP_NS } // Perform the SSL handshake - ws_.next_layer().async_handshake( + ws_->next_layer().async_handshake( boost::asio::ssl::stream_base::client, std::bind( &AbstractClientSessionCRTP::on_connect, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); } + + private: + boost::asio::ssl::context sslContext_; + std::unique_ptr>> ws_; + http::request proxyHandshake; + http::response proxyHandshakeResponse; + // use own response parser + // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body + http::response_parser http_proxy_handshake_parser; + std::size_t frameSize_; }; } From 7c92257ffcab5ea48f60a448acccda1cbe1e553a Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 15 Jul 2025 11:35:06 +0200 Subject: [PATCH 18/32] Adopting latest Beast classes (tcp_stream and asio::ssl::stream) Store the identifier/UUID of the session Clean sending queue at disconnection time Update the Jupyter notebook to put a dataspace with ACLs and LegalTags --- example/withFesapi/etpClient.cpp | 2 +- python/example/PutHorizon.ipynb | 18 ++++++-- src/etp/AbstractClientSessionCRTP.h | 2 +- src/etp/AbstractSession.cpp | 9 ++++ src/etp/AbstractSession.h | 29 +++++------- src/etp/ClientSession.h | 9 +--- src/etp/PlainClientSession.h | 42 ++++++++++++++--- src/etp/ProtocolHandlers/CoreHandlers.cpp | 6 ++- .../StoreNotificationHandlers.cpp | 15 +----- src/etp/ssl/SslClientSession.h | 46 +++++++++++++++++-- 10 files changed, 121 insertions(+), 57 deletions(-) diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp index 445eae8..91a5ae4 100644 --- a/example/withFesapi/etpClient.cpp +++ b/example/withFesapi/etpClient.cpp @@ -737,6 +737,7 @@ int main(int argc, char **argv) std::cout << "Creating a client session..." << std::endl; auto clientSession = ETP_NS::ClientSessionLaunchers::createClientSession(&initializationParams, authorization); + clientSession->setVerbose(true); repo.setHdfProxyFactory(new ETP_NS::FesapiHdfProxyFactory(clientSession.get())); @@ -752,7 +753,6 @@ int main(int argc, char **argv) } clientSession->setTimeOut(60000); - clientSession->setVerbose(false); askUser(clientSession, repo); sessionThread.join(); diff --git a/python/example/PutHorizon.ipynb b/python/example/PutHorizon.ipynb index f05ff93..6a32360 100644 --- a/python/example/PutHorizon.ipynb +++ b/python/example/PutHorizon.ipynb @@ -155,18 +155,26 @@ "dataspace.uri = \"eml:///dataspace('\" + dataspace.path + \"')\"\n", "dataspace.customData = fetpapi.MapStringDataValue()\n", "dataValue = fetpapi.DataValue()\n", - "aos = fetpapi.ArrayOfString\n", + "aos = fetpapi.ArrayOfString()\n", "dataValue.item = fetpapi.DataValueitem_t()\n", - "aos.values = [\"data.default.viewers@osdu.example.com\"]\n", + "tmp = fetpapi.StringVector()\n", + "tmp.push_back(\"data.default.viewers@osdu.example.com\")\n", + "aos.values = tmp\n", "dataValue.item.set_ArrayOfString(aos)\n", "dataspace.customData[\"viewers\"] = dataValue\n", - "aos.values = [\"data.default.owners@osdu.example.com\"]\n", + "tmp.clear()\n", + "tmp.push_back(\"data.default.owners@osdu.example.com\")\n", + "aos.values = tmp\n", "dataValue.item.set_ArrayOfString(aos)\n", "dataspace.customData[\"owners\"] = dataValue\n", - "aos.values = [\"osdu-public-usa-dataset\"]\n", + "tmp.clear()\n", + "tmp.push_back(\"osdu-public-usa-dataset\")\n", + "aos.values = tmp\n", "dataValue.item.set_ArrayOfString(aos)\n", "dataspace.customData[\"legaltags\"] = dataValue\n", - "aos.values = [\"US\"]\n", + "tmp.clear()\n", + "tmp.push_back(\"US\")\n", + "aos.values = tmp\n", "dataValue.item.set_ArrayOfString(aos)\n", "dataspace.customData[\"otherRelevantDataCountries\"] = dataValue" ] diff --git a/src/etp/AbstractClientSessionCRTP.h b/src/etp/AbstractClientSessionCRTP.h index 469b1f0..2c23b59 100644 --- a/src/etp/AbstractClientSessionCRTP.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -30,7 +30,7 @@ namespace ETP_NS virtual ~AbstractClientSessionCRTP() = default; - void on_connect(boost::system::error_code ec) { + void on_ssl_handshake(boost::system::error_code ec) { if (ec) { std::cerr << "ERROR at Websocket connection : " << ec.message() << std::endl; return; diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index aed3aad..ccb7895 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -50,6 +50,12 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr // This indicates that the web socket (and consequently etp) session was closed std::cerr << "The other endpoint closed the web socket (and consequently etp) connection" << std::endl; } +#if BOOST_VERSION > 106900 + else if (ec == boost::beast::error::timeout) { + // This indicates that the web socket (and consequently etp) session was closed + std::cerr << "Beast timeout has been reached" << std::endl; + } +#endif else { // This indicates an unexpected error std::cerr << "on_read : error code number " << ec.value() << std::endl; @@ -63,6 +69,9 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); specificProtocolHandlers.clear(); + const std::lock_guard sendingQueueLock(sendingQueueMutex); + std::queue< std::tuple, std::shared_ptr> > empty; + std::swap(sendingQueue, empty); webSocketSessionClosed = true; etpSessionClosed = true; diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index a2e30ce..7938ff0 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -57,10 +57,7 @@ namespace ETP_NS * If the ETP session is not set up, it returns the nil UUID. */ const boost::uuids::uuid& getIdentifier() { - if (isEtpSessionClosed()) { - identifier = boost::uuids::nil_uuid(); - } - + std::lock_guard lock(identifierMutex); return identifier; } @@ -78,11 +75,6 @@ namespace ETP_NS return _timeOut; } - /** - * The list of subscriptions recorded by customers on this session. - */ - std::unordered_map subscriptions; - /** * Set the Core protocol handlers */ @@ -246,14 +238,14 @@ namespace ETP_NS */ template int64_t sendWithSpecificHandlerAndBlock(const T& mb, std::shared_ptr specificHandler, int64_t correlationId = 0, int32_t messageFlags = 0) { - int64_t msgId = sendWithSpecificHandler(mb, specificHandler, correlationId, messageFlags); + const int64_t msgId = sendWithSpecificHandler(mb, specificHandler, correlationId, messageFlags); // The correlationId of the first message MUST be set to 0 and the correlationId of all successive // messages in the same multipart request or notification MUST be set to the messageId of the first // message of the multipart request or notification. // If the request message is itself multipart, the correlationId of each message of the multipart // response MUST be set to the messageId of the FIRST message in the multipart request. - auto t_start = std::chrono::high_resolution_clock::now(); + const auto t_start = std::chrono::high_resolution_clock::now(); while (isMessageStillProcessing(correlationId == 0 ? msgId : correlationId)) { if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > _timeOut) { throw std::runtime_error("Time out waiting for a response of message id " + std::to_string(msgId)); @@ -370,11 +362,6 @@ namespace ETP_NS */ FETPAPI_DLL_IMPORT_OR_EXPORT bool isEtpSessionClosed() const { return webSocketSessionClosed || etpSessionClosed; } - void setEtpSessionClosed(bool etpSessionClosed_) { - etpSessionClosed = etpSessionClosed_; - reconnectionTryCount_ = 0; - } - /**************** *** DATASPACE *** ****************/ @@ -633,7 +620,8 @@ namespace ETP_NS /// The next available message id. std::atomic messageId; /// The identifier of the session - boost::uuids::uuid identifier; + boost::uuids::uuid identifier{ boost::uuids::nil_uuid() }; + std::mutex identifierMutex; /// Indicates that the endpoint request to close the websocket session bool isCloseRequested_{ false }; size_t reconnectionTryCount_ = 0; @@ -646,6 +634,11 @@ namespace ETP_NS receivedBuffer.consume(receivedBuffer.size()); } + void setEtpSessionClosed(bool etpSessionClosed_) { + etpSessionClosed = etpSessionClosed_; + reconnectionTryCount_ = 0; + } + /** * Write the current buffer on the web socket */ @@ -762,5 +755,7 @@ namespace ETP_NS protocolHandlers[protocolId] = coreHandlers; } + + friend void CoreHandlers::decodeMessageBody(const Energistics::Etp::v12::Datatypes::MessageHeader& mh, avro::DecoderPtr d); }; } diff --git a/src/etp/ClientSession.h b/src/etp/ClientSession.h index 093b731..ea5e264 100644 --- a/src/etp/ClientSession.h +++ b/src/etp/ClientSession.h @@ -85,14 +85,10 @@ namespace ETP_NS std::cerr << "on_resolve : " << ec.message() << std::endl; } - // Reality check: IPv6 is unlikely to be available yet - endpoints = std::vector(results.begin(), results.end()); - std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); - - asyncConnect(); + asyncConnect(results); } - virtual void asyncConnect() = 0; + virtual void asyncConnect(const tcp::resolver::results_type& results) = 0; virtual bool isTls() const = 0; @@ -129,7 +125,6 @@ namespace ETP_NS std::string proxyAuthorization; std::map additionalHandshakeHeaderFields_; websocket::response_type responseType; // In order to check handshake sec_websocket_protocol - std::vector endpoints; // Store the resolved endpoints to prevent calling resolve once again when reconnecting Energistics::Etp::v12::Protocol::Core::RequestSession requestSession; /** diff --git a/src/etp/PlainClientSession.h b/src/etp/PlainClientSession.h index 6b1804d..65db0f6 100644 --- a/src/etp/PlainClientSession.h +++ b/src/etp/PlainClientSession.h @@ -35,19 +35,24 @@ namespace ETP_NS virtual ~PlainClientSession() = default; // Called by the base class +#if BOOST_VERSION < 107000 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>& ws() { return ws_; } +#else + FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>& ws() { return ws_; } +#endif bool isTls() const final{ return false; } - void asyncConnect() + void asyncConnect(const tcp::resolver::results_type& results) { - ws_.reset(new websocket::stream(ioc)); - ws_->binary(true); + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints = std::vector(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + #if BOOST_VERSION < 107000 + ws_.reset(new websocket::stream(ioc)); ws_->write_buffer_size(frameSize_); -#else - ws_->write_buffer_bytes(frameSize_); -#endif + ws_->binary(true); // Make the connection on the IP address we get from a lookup boost::asio::async_connect( @@ -55,13 +60,36 @@ namespace ETP_NS endpoints.begin(), endpoints.end(), std::bind( - &AbstractClientSessionCRTP::on_connect, + &AbstractClientSessionCRTP::on_ssl_handshake, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); +#else + ws_.reset(new websocket::stream(ioc)); + ws_->write_buffer_bytes(frameSize_); + ws_->binary(true); + + // Make the connection on the IP address we get from a lookup + boost::beast::get_lowest_layer(*ws_).async_connect( + endpoints, + boost::beast::bind_front_handler( + &PlainClientSession::on_connect, + std::static_pointer_cast(shared_from_this()))); +#endif + } + +#if BOOST_VERSION > 106900 + void on_connect(boost::beast::error_code ec, tcp::resolver::results_type::endpoint_type) + { + on_ssl_handshake(ec); } +#endif private: +#if BOOST_VERSION < 107000 std::unique_ptr> ws_; +#else + std::unique_ptr> ws_; +#endif std::size_t frameSize_; }; } diff --git a/src/etp/ProtocolHandlers/CoreHandlers.cpp b/src/etp/ProtocolHandlers/CoreHandlers.cpp index 2cfdf44..53bd9ed 100644 --- a/src/etp/ProtocolHandlers/CoreHandlers.cpp +++ b/src/etp/ProtocolHandlers/CoreHandlers.cpp @@ -54,6 +54,10 @@ void CoreHandlers::decodeMessageBody(const Energistics::Etp::v12::Datatypes::Mes } session->setEtpSessionClosed(false); + { + std::lock_guard lock(session->identifierMutex); + std::copy(os.sessionId.array.begin(), os.sessionId.array.end(), session->identifier.begin()); + } on_OpenSession(os, mh.correlationId); } else if (mh.messageType == Energistics::Etp::v12::Protocol::Core::CloseSession::messageTypeId) { @@ -104,7 +108,7 @@ void CoreHandlers::on_RequestSession(const Energistics::Etp::v12::Protocol::Core void CoreHandlers::on_OpenSession(const Energistics::Etp::v12::Protocol::Core::OpenSession &, int64_t) { - session->fesapi_log("The session has been opened with the default core protocol handlers."); + session->fesapi_log("The session", session->getIdentifier(), "has been opened with the default core protocol handlers."); } void CoreHandlers::on_CloseSession(const Energistics::Etp::v12::Protocol::Core::CloseSession &, int64_t) diff --git a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp index bc78868..10eb70c 100644 --- a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp +++ b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp @@ -103,20 +103,7 @@ void StoreNotificationHandlers::on_UnsubscribeNotifications(const Energistics::E { session->fesapi_log("on_UnsubscribeNotifications"); - int64_t toRemove = (std::numeric_limits::max)(); - for (const auto& pair : session->subscriptions) { - if (pair.second.requestUuid.array == msg.requestUuid.array) { - toRemove = pair.first; - break; - } - } - - if (toRemove != (std::numeric_limits::max)()) { - session->subscriptions.erase(toRemove); - } - else { - session->send(ETP_NS::EtpHelpers::buildSingleMessageProtocolException(5, "The subscription request UUID is unknown by the store."), messageId, 0x02); - } + session->send(ETP_NS::EtpHelpers::buildSingleMessageProtocolException(7, "The StoreHandlers::on_UnsubscribeNotifications method has not been overriden by the agent."), 0x02); } void StoreNotificationHandlers::on_UnsolicitedStoreNotifications(const Energistics::Etp::v12::Protocol::StoreNotification::UnsolicitedStoreNotifications &, int64_t) diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index 84a35e8..3a6c0c3 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -25,10 +25,13 @@ under the License. #include "ssl_stream.h" #elif BOOST_VERSION < 107000 #include -#else +#elif BOOST_VERSION < 108600 #include #include #include +#else +#include +#include #endif namespace http = boost::beast::http; // from @@ -48,13 +51,26 @@ namespace ETP_NS virtual ~SslClientSession() {} // Called by the base class +#if BOOST_VERSION < 107000 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>>& ws() { return ws_; } +#elif BOOST_VERSION < 108600 + FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr>>& ws() { return ws_; } +#else + FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr< websocket::stream>>& ws() { return ws_; } +#endif bool isTls() const final { return true; } - void asyncConnect() + void asyncConnect(const tcp::resolver::results_type& results) { +#if BOOST_VERSION < 107000 ws_.reset(new websocket::stream>(ioc, sslContext_)); +#elif BOOST_VERSION < 108600 + ws_.reset(new websocket::stream>(ioc, sslContext_)); +#else + ws_.reset(new websocket::stream>(ioc, sslContext_)); +#endif + ws_->binary(true); #if BOOST_VERSION < 107000 ws_->write_buffer_size(frameSize_); @@ -69,7 +85,12 @@ namespace ETP_NS std::cerr << "Websocket on connect (SNI): " << ecSNI.message() << std::endl; } + // Reality check: IPv6 is unlikely to be available yet + std::vector endpoints = std::vector(results.begin(), results.end()); + std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); }); + // Make the connection on the IP address we get from a lookup +#if BOOST_VERSION < 107000 boost::asio::async_connect( ws_->next_layer().next_layer(), endpoints.begin(), @@ -78,9 +99,20 @@ namespace ETP_NS &SslClientSession::on_ssl_connect, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); +#else + boost::beast::get_lowest_layer(*ws_).async_connect( + endpoints, + boost::beast::bind_front_handler( + &SslClientSession::on_ssl_connect, + std::static_pointer_cast(shared_from_this()))); +#endif } +#if BOOST_VERSION < 107000 void on_ssl_connect(boost::system::error_code ec) { +#else + void on_ssl_connect(boost::beast::error_code ec, tcp::resolver::results_type::endpoint_type) { +#endif if (ec) { std::cerr << "on_ssl_connect : " << ec.message() << std::endl; } @@ -110,7 +142,7 @@ namespace ETP_NS ws_->next_layer().async_handshake( boost::asio::ssl::stream_base::client, std::bind( - &AbstractClientSessionCRTP::on_connect, + &AbstractClientSessionCRTP::on_ssl_handshake, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); } @@ -166,14 +198,20 @@ namespace ETP_NS ws_->next_layer().async_handshake( boost::asio::ssl::stream_base::client, std::bind( - &AbstractClientSessionCRTP::on_connect, + &AbstractClientSessionCRTP::on_ssl_handshake, std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); } private: boost::asio::ssl::context sslContext_; +#if BOOST_VERSION < 107000 std::unique_ptr>> ws_; +#elif BOOST_VERSION < 108600 + std::unique_ptr>> ws_; +#else + std::unique_ptr>> ws_; +#endif http::request proxyHandshake; http::response proxyHandshakeResponse; // use own response parser From 29b8cfb8bdf44e4c9e02c3d91e68b954832f28e6 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Mon, 15 Sep 2025 18:09:52 +0200 Subject: [PATCH 19/32] Improve documentation and port copyDataObjectsByValue with SWIG Renamed parameters for clarity in copyToDataspace and copyDataObjectsByValue methods, updated method documentation for accuracy and detail, and added copyDataObjectsByValue in SWIG interface. --- cmake/swigEtp1_2Include.i.in | 56 +++++++++++++++++--------- src/etp/AbstractSession.cpp | 8 ++-- src/etp/AbstractSession.h | 78 +++++++++++++++++++----------------- 3 files changed, 84 insertions(+), 58 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index d613257..3196d17 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -1943,37 +1943,37 @@ namespace ETP_NS std::vector getDataspaceInfo(const std::map& dataspaceUris); /** - * Copy by reference some dataspaces into another one. + * A customer sends to a store to lock or unlock one or more dataspaces. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. + * An OSDU locked dataspace will have its custom data "locked" to true. It will also have its custom datm "read-only" to true for all users. + * As a reminder, a custom data "read-only" can be true with a custom data "locked" to false in case the dataspace is not locked but in read only for the particular current ETP user. * - * @param sourceDataspaceUris ETP general map : One each for each source dataspace to be copied. They are identified by their URI. - * @param targetDataspaceUri The URI of the ETP dataspace where the sourceDataspaces have to be copied by reference. - * @param return The map keys corresponding to the dataspaces which have been successfully copied into the target dataspace. - */ - std::vector copyDataspacesContent(const std::map& sourceDataspaceUris, const std::string& targetDataspaceUri); + * @param dataspaceUris ETP general map where the values must be the URIs for the dataspaces the customer wants to lock or unlock. + * @param lock true for locking the dataspaces, false to unlock the dataspaces + std::vector lockDataspaces(const std::map& dataspaceUris, bool lock); /** - * A customer sends to a store to lock or unlock one or more dataspaces. + * Copy by reference some dataspaces into another one. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * - * @param dataspaceUris ETP general map where the values must be the URIs for the dataspaces the customer wants to lock or unlock. - * @param lock true for locking the dataspaces, false to unlock the dataspaces - * @param return The map keys corresponding to the dataspaces which have been successfully locked or unlocked. + * @param sourceDataspaceUris ETP general map : One each for each source dataspace to be copied. They are identified by their URI. + * @param targetDataspaceUri The URI of the ETP dataspace where the sourceDataspaces have to be copied by reference. + * @param return The map keys corresponding to the dataspaces which have been successfully copied into the target dataspace. */ - std::vector lockDataspaces(const std::map& dataspaceUris, bool lock); + std::vector copyDataspacesContent(const std::map& sourceDataspaceUris, const std::string& targetDataspaceUri); /** * Copy by reference some dataobjects into another dataspace. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * - * @param sourceUris ETP general map : One each for each source dataobject to be copied. They are identified by their URI. - * @param targetDataspaceUri The URI of the ETP dataspace where the source dataobjects have to be copied by reference. - * @param return The map keys corresponding to the dataobjects which have been successfully copied into the target dataspace. + * @param sourceDataobjectUris ETP general map : One for each source dataobject to be copied. They are identified by their URI. + * @param targetDataspaceUri The URI of the ETP dataspace where the source dataobjects have to be copied by reference. + * @param return The map keys corresponding to the dataobjects which have been successfully copied into the target dataspace. */ - std::vector copyToDataspace(const std::map& sourceUris, const std::string& targetDataspaceUri); + std::vector copyToDataspace(const std::map& sourceDataobjectUris, const std::string& targetDataspaceUri); /**************** *** DISCOVERY *** @@ -2051,6 +2051,26 @@ namespace ETP_NS */ std::vector deleteDataObjects(const std::map& uris); + /********************* + ***** STORE OSDU ***** + **********************/ + + /** + * A customer sends to a store to copy by value a dataobject in the same dataspace with potentially some of its sources based on their datatypes. + * This function should be used with caution if Store OSDU Handlers have been overidden. + * It actually sends a message and blocks the current thread until a response has been received from the store. + * + * @param sourceDataobjectUri The URI of the dataobject to be copied. + * @param sourcesDepth The "depth" or how many "levels" (or "jumps") in the data model (graph) from the starting point (specified by the URI) that you want to copy + * Depth MUST always be greater than zero. + * @param dataObjectTypes Optionally, specify the types of data objects that you want to copy. + * The default is an empty array, which means ALL data types negotiated for the current ETP session. + * They ARE case sensitive. EXAMPLES: "witsml20.Well", "witsml20.Wellbore", "prodml21.WellTest", "resqml20.obj_TectonicBoundaryFeature", "eml21.DataAssuranceRecord" + * To indicate that all data objects within a data schema version are supported, you can use a star (*) as a wildcard, EXAMPLE: "witsml20.*", "prodml21.*", "resqml20.*" + * @param return The received dataobjects in a map where the key makes the link between the asked uris and the received dataobjects. + */ + std::vector copyDataObjectsByValue(const std::string& sourceDataobjectUri, int32_t sourcesDepth = 0, const std::vector& dataObjectTypes = {}); + /**************** ** TRANSACTION ** ****************/ diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index ccb7895..6733725 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -276,7 +276,7 @@ std::vector AbstractSession::lockDataspaces(const std::map AbstractSession::copyToDataspace(const std::map& sourceUris, const std::string& targetDataspaceUri) +std::vector AbstractSession::copyToDataspace(const std::map& sourceDataobjectUris, const std::string& targetDataspaceUri) { std::shared_ptr handlers = getDataspaceOSDUProtocolHandlers(); if (handlers == nullptr) { @@ -284,7 +284,7 @@ std::vector AbstractSession::copyToDataspace(const std::map result = handlers->getSuccessKeys(); @@ -441,7 +441,7 @@ std::vector AbstractSession::deleteDataObjects(const std::map AbstractSession::copyDataObjectsByValue(const std::string& uri, int32_t sourcesDepth, const std::vector& dataObjectTypes) +std::vector AbstractSession::copyDataObjectsByValue(const std::string& sourceDataobjectUri, int32_t sourcesDepth, const std::vector& dataObjectTypes) { std::shared_ptr handlers = getStoreOSDUProtocolHandlers(); if (handlers == nullptr) { @@ -449,7 +449,7 @@ std::vector AbstractSession::copyDataObjectsByValue(const std::stri } Energistics::Etp::v12::Protocol::StoreOSDU::CopyDataObjectsByValue msg; - msg.uri = uri; + msg.uri = sourceDataobjectUri; msg.sourcesDepth = sourcesDepth; msg.dataObjectTypes = dataObjectTypes; sendAndBlock(msg, 0, 0x02); diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index 7938ff0..4b17cac 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -369,7 +369,7 @@ namespace ETP_NS /** * A customer sends to a store to discover all dataspaces available on the store. * This function should be used with caution if Dataspace Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param storeLastWriteFilter An optional filter to limit the dataspaces returned by date/time last saved to the store. * The store returns a list of dataspaces whose last changed date/time is greater than the specified date/time. @@ -381,7 +381,7 @@ namespace ETP_NS /** * A customer sends to a store to create one or more dataspaces. * This function should be used with caution if Dataspace Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param dataspaces ETP general map : One each for each dataspace the customer wants to add or update. * @param return The map keys corresponding to the dataspaces which have been put successfully into the store. @@ -391,7 +391,7 @@ namespace ETP_NS /** * A customer sends to a store to delete one or more dataspaces. * This function should be used with caution if Dataspace Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param dataspaceUris ETP general map where the values must be the URIs for the dataspaces the customer wants to delete. * @param return The map keys corresponding to the dataspaces which have been deleted successfully. @@ -405,28 +405,19 @@ namespace ETP_NS /** * A customer sends to a store to discover information of particular dataspaces. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param dataspaceUris ETP general map : One each for each dataspace, identified by their URI, the customer wants to get info about. * @param return The dataspaces the store could return. */ FETPAPI_DLL_IMPORT_OR_EXPORT std::vector getDataspaceInfo(const std::map& dataspaceUris); - /** - * Copy by reference some dataspaces into another one. - * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. - * - * @param sourceDataspaceUris ETP general map : One each for each source dataspace to be copied. They are identified by their URI. - * @param targetDataspaceUri The URI of the ETP dataspace where the sourceDataspaces have to be copied by reference. - * @param return The map keys corresponding to the dataspaces which have been successfully copied into the target dataspace. - */ - FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyDataspacesContent(const std::map& sourceDataspaceUris, const std::string& targetDataspaceUri); - /** * A customer sends to a store to lock or unlock one or more dataspaces. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. + * An OSDU locked dataspace will have its custom data "locked" to true. It will also have its custom datm "read-only" to true for all users. + * As a reminder, a custom data "read-only" can be true with a custom data "locked" to false in case the dataspace is not locked but in read only for the particular current ETP user. * * @param dataspaceUris ETP general map where the values must be the URIs for the dataspaces the customer wants to lock or unlock. * @param lock true for locking the dataspaces, false to unlock the dataspaces @@ -434,16 +425,27 @@ namespace ETP_NS */ FETPAPI_DLL_IMPORT_OR_EXPORT std::vector lockDataspaces(const std::map& dataspaceUris, bool lock); + /** + * Copy by reference some dataspaces into another one. + * This function should be used with caution if Dataspace OSDU Handlers have been overidden. + * It actually sends a message and blocks the current thread until a response has been received from the store. + * + * @param sourceDataspaceUris ETP general map : One each for each source dataspace to be copied. They are identified by their URI. + * @param targetDataspaceUri The URI of the ETP dataspace where the sourceDataspaces have to be copied by reference. + * @param return The map keys corresponding to the dataspaces which have been successfully copied into the target dataspace. + */ + FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyDataspacesContent(const std::map& sourceDataspaceUris, const std::string& targetDataspaceUri); + /** * Copy by reference some dataobjects into another dataspace. * This function should be used with caution if Dataspace OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * - * @param sourceUris ETP general map : One each for each source dataobject to be copied. They are identified by their URI. - * @param targetDataspaceUri The URI of the ETP dataspace where the source dataobjects have to be copied by reference. - * @param return The map keys corresponding to the dataobjects which have been successfully copied into the target dataspace. + * @param sourceDataobjectUris ETP general map : One for each source dataobject to be copied. They are identified by their URI. + * @param targetDataspaceUri The URI of the ETP dataspace where the source dataobjects have to be copied by reference. + * @param return The map keys corresponding to the dataobjects which have been successfully copied into the target dataspace. */ - FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyToDataspace(const std::map& sourceUris, const std::string& targetDataspaceUri); + FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyToDataspace(const std::map& sourceDataobjectUris, const std::string& targetDataspaceUri); /**************** *** DISCOVERY *** @@ -452,7 +454,7 @@ namespace ETP_NS /** * A Customer sends this message to a store to discover data objects in the store. * This function should be used with caution if Discovery Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param context Includes the URI of the dataspace or data object to begin the discovery, what specific types of data objects are of interest, * and how many "levels" of relationships in the model to discover, among others. @@ -473,7 +475,7 @@ namespace ETP_NS /** * A customer sends to a store to discover data objects that have been deleted (which are sometimes called "tombstones"). * This function should be used with caution if Discovery Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param dataspaceUri The URI of the dataspace where the objects were deleted. * @param deleteTimeFilter An optional filter to filter the discovery on a date when the data object was deleted in a particular store. @@ -494,7 +496,7 @@ namespace ETP_NS /** * A customer sends to a store to get one or more data objects, each identified by a URI. * This function should be used with caution if Store Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param uris ETP general map where the values MUST be the URIs of a data object to be retrieved. * @param return The received dataobjects in a map where the key makes the link between the asked uris and the received dataobjects. @@ -504,7 +506,7 @@ namespace ETP_NS /** * A customer sends to a store to add or update one or more data objects. * This function should be used with caution if Store Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param uris ETP general map where the values MUST be the data for each data object in the request, including each one's URI. * @param return The map keys corresponding to the dataObjects which have been put successfully. @@ -514,7 +516,7 @@ namespace ETP_NS /** * A customer sends to a store to delete one or more data objects from the store. * This function should be used with caution if Store Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param uris ETP general map where the values MUST be the URIs of a data object to be deleted. * @param return The map keys corresponding to the dataObjects which have been deleted successfully. @@ -526,16 +528,20 @@ namespace ETP_NS **********************/ /** - * A customer sends to a store to copy by value a dataobject with potentially some of its sources based on their datatypes. + * A customer sends to a store to copy by value a dataobject in the same dataspace with potentially some of its sources based on their datatypes. * This function should be used with caution if Store OSDU Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * - * @param uri The URI of the dataobject to be copied. - * @param sourcesDepth The number of level if sources of the dataobject to be copied as well. - * @param sourcesDepth The number of level if sources of the dataobject to be copied as well. - * @param return The received dataobjects in a map where the key makes the link between the asked uris and the received dataobjects. + * @param sourceDataobjectUri The URI of the dataobject to be copied. + * @param sourcesDepth The "depth" or how many "levels" (or "jumps") in the data model (graph) from the starting point (specified by the URI) that you want to copy + * Depth MUST always be greater than zero. + * @param dataObjectTypes Optionally, specify the types of data objects that you want to copy. + * The default is an empty array, which means ALL data types negotiated for the current ETP session. + * They ARE case sensitive. EXAMPLES: "witsml20.Well", "witsml20.Wellbore", "prodml21.WellTest", "resqml20.obj_TectonicBoundaryFeature", "eml21.DataAssuranceRecord" + * To indicate that all data objects within a data schema version are supported, you can use a star (*) as a wildcard, EXAMPLE: "witsml20.*", "prodml21.*", "resqml20.*" + * @param return The received dataobjects in a map where the key makes the link between the asked uris and the received dataobjects. */ - FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyDataObjectsByValue(const std::string& uri, int32_t sourcesDepth = 0, const std::vector& dataObjectTypes = {}); + FETPAPI_DLL_IMPORT_OR_EXPORT std::vector copyDataObjectsByValue(const std::string& sourceDataobjectUri, int32_t sourcesDepth = 0, const std::vector& dataObjectTypes = {}); /**************** ** TRANSACTION ** @@ -544,7 +550,7 @@ namespace ETP_NS /** * A customer sends to a store to begin a transaction. * This function should be used with caution if Transaction Handlers have been overidden. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * * @param dataspaceUris Indicates the dataspaces involved in the transaction. An empty STRING means the default dataspace. An empty LIST means all dataspaces. * @param readOnly Indicates that the request in the transaction is read-only (i.e., "get" messages). @@ -556,7 +562,7 @@ namespace ETP_NS * A customer sends to a store to commit and end a transaction. This message implies that the customer * has received from or sent to the store all the data required for some purpose. The customer asserts that * the data sent in the scope of this transaction is a consistent unit of work. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * @return Failure message or empty string if success */ FETPAPI_DLL_IMPORT_OR_EXPORT std::string rollbackTransaction(); @@ -565,7 +571,7 @@ namespace ETP_NS * A customer sends to a store to cancel a transaction. The store MUST disregard any requests or data sent * with that transaction. The current transaction (the one being canceled) MUST NOT change the state of * the store. - * It actually sends a message and block the current thread until a response has been received from the store. + * It actually sends a message and blocks the current thread until a response has been received from the store. * @return Failure message or empty string if success */ FETPAPI_DLL_IMPORT_OR_EXPORT std::string commitTransaction(); From f6340a35f53847c3ffe74fd9dfe6f6e8ab427804 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 18 Sep 2025 15:34:15 +0200 Subject: [PATCH 20/32] Change WebSocket payload size type to uint64_t Replaced int64_t with uint64_t for maxWebSocketMessagePayloadSize and related variables and method signatures to better reflect expected value range and prevent negative sizes. --- cmake/swigEtp1_2Include.i.in | 2 +- src/etp/AbstractClientSessionCRTP.h | 2 +- src/etp/AbstractSession.h | 8 ++++---- src/etp/ProtocolHandlers/CoreHandlers.cpp | 2 +- src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp | 8 ++++---- src/etp/ssl/SslClientSession.h | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 3196d17..2c7724c 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -2172,7 +2172,7 @@ namespace ETP_NS InitializationParameters(const std::string& instanceUuid, const std::string & host, unsigned short port, const std::string & urlPath = ""); virtual ~InitializationParameters(); - void setMaxWebSocketMessagePayloadSize(int64_t value); + void setMaxWebSocketMessagePayloadSize(uint64_t value); uint64_t getMaxWebSocketMessagePayloadSize() const; void setPreferredMaxFrameSize(uint64_t value); diff --git a/src/etp/AbstractClientSessionCRTP.h b/src/etp/AbstractClientSessionCRTP.h index 2c23b59..3a6e676 100644 --- a/src/etp/AbstractClientSessionCRTP.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -110,7 +110,7 @@ namespace ETP_NS std::placeholders::_2)); } - void setMaxWebSocketMessagePayloadSize(int64_t value) final { + void setMaxWebSocketMessagePayloadSize(uint64_t value) final { maxWebSocketMessagePayloadSize = value; derived().ws()->read_message_max(value); } diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index 4b17cac..95fa00a 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -314,8 +314,8 @@ namespace ETP_NS return (!sendingQueue.empty() && std::get<0>(sendingQueue.front()) <= msgId) || specificProtocolHandlers.count(msgId) > 0; } - virtual void setMaxWebSocketMessagePayloadSize(int64_t value) = 0; - int64_t getMaxWebSocketMessagePayloadSize() const { return maxWebSocketMessagePayloadSize; } + virtual void setMaxWebSocketMessagePayloadSize(uint64_t value) = 0; + uint64_t getMaxWebSocketMessagePayloadSize() const { return maxWebSocketMessagePayloadSize; } /**************** ***** CORE ****** @@ -611,7 +611,7 @@ namespace ETP_NS /// which should be determined by the limits imposed by the WebSocket library used by each endpoint. /// See https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/using_websocket/messages.html /// and https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/read_message_max/overload1.html - int64_t maxWebSocketMessagePayloadSize{ 16000000 }; + uint64_t maxWebSocketMessagePayloadSize{ 16000000 }; /// Indicates if the websocket session is opened or not. It becomes false after the websocket handshake std::atomic webSocketSessionClosed{ true }; /// Indicates if the ETP1.2 session is opened or not. It becomes false after the requestSession and openSession message @@ -672,7 +672,7 @@ namespace ETP_NS avro::encode(*e, mh); avro::encode(*e, mb); e->flush(); - const int64_t byteCount = e->byteCount(); + const uint64_t byteCount = e->byteCount(); if (byteCount < maxWebSocketMessagePayloadSize) { return std::make_tuple(mh.messageId, *avro::snapshot(*out).get(), nullptr); diff --git a/src/etp/ProtocolHandlers/CoreHandlers.cpp b/src/etp/ProtocolHandlers/CoreHandlers.cpp index 53bd9ed..78c301a 100644 --- a/src/etp/ProtocolHandlers/CoreHandlers.cpp +++ b/src/etp/ProtocolHandlers/CoreHandlers.cpp @@ -46,7 +46,7 @@ void CoreHandlers::decodeMessageBody(const Energistics::Etp::v12::Datatypes::Mes auto search = os.endpointCapabilities.find("MaxWebSocketMessagePayloadSize"); if (search != os.endpointCapabilities.end() && search->second.item.idx() == 3) { - const int64_t maxWebSocketMessagePayloadSize = search->second.item.get_long(); + const uint64_t maxWebSocketMessagePayloadSize = search->second.item.get_long(); if (maxWebSocketMessagePayloadSize > 0 && maxWebSocketMessagePayloadSize < session->getMaxWebSocketMessagePayloadSize()) { session->setMaxWebSocketMessagePayloadSize(maxWebSocketMessagePayloadSize); diff --git a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp index 10eb70c..0b2c79e 100644 --- a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp +++ b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp @@ -99,24 +99,24 @@ void StoreNotificationHandlers::on_SubscribeNotificationsResponse(const Energist session->fesapi_log("Received SubscribeNotificationsResponse"); } -void StoreNotificationHandlers::on_UnsubscribeNotifications(const Energistics::Etp::v12::Protocol::StoreNotification::UnsubscribeNotifications & msg, int64_t messageId, int64_t) +void StoreNotificationHandlers::on_UnsubscribeNotifications(const Energistics::Etp::v12::Protocol::StoreNotification::UnsubscribeNotifications&, int64_t, int64_t) { session->fesapi_log("on_UnsubscribeNotifications"); session->send(ETP_NS::EtpHelpers::buildSingleMessageProtocolException(7, "The StoreHandlers::on_UnsubscribeNotifications method has not been overriden by the agent."), 0x02); } -void StoreNotificationHandlers::on_UnsolicitedStoreNotifications(const Energistics::Etp::v12::Protocol::StoreNotification::UnsolicitedStoreNotifications &, int64_t) +void StoreNotificationHandlers::on_UnsolicitedStoreNotifications(const Energistics::Etp::v12::Protocol::StoreNotification::UnsolicitedStoreNotifications&, int64_t) { session->fesapi_log("Received UnsolicitedStoreNotifications"); } -void StoreNotificationHandlers::on_SubscriptionEnded(const Energistics::Etp::v12::Protocol::StoreNotification::SubscriptionEnded &, int64_t) +void StoreNotificationHandlers::on_SubscriptionEnded(const Energistics::Etp::v12::Protocol::StoreNotification::SubscriptionEnded&, int64_t) { session->fesapi_log("Received SubscriptionEnded "); } -void StoreNotificationHandlers::on_ObjectChanged(const Energistics::Etp::v12::Protocol::StoreNotification::ObjectChanged & msg, int64_t) +void StoreNotificationHandlers::on_ObjectChanged(const Energistics::Etp::v12::Protocol::StoreNotification::ObjectChanged& msg, int64_t) { switch (msg.change.changeKind) { case Energistics::Etp::v12::Datatypes::Object::ObjectChangeKind::authorized: session->fesapi_log("authorized"); break; diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index 3a6c0c3..4c78997 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -79,7 +79,7 @@ namespace ETP_NS #endif // Set SNI Hostname (many hosts need this to handshake successfully) - if (!SSL_set_tlsext_host_name(ws_->next_layer().native_handle(), etpServerHost.c_str())) + if (!SSL_set_tlsext_host_name(ws_->next_layer().native_handle(), etpServerHost.data())) { boost::system::error_code ecSNI{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; std::cerr << "Websocket on connect (SNI): " << ecSNI.message() << std::endl; From 666f95e09c4c4953767909b96d363a11c1bf232a Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 18 Sep 2025 15:50:55 +0200 Subject: [PATCH 21/32] Remove unused msgId variables in FesapiHdfProxy Eliminated unnecessary assignment of return values from sendWithSpecificHandlerAndBlock in FesapiHdfProxy.cpp and FesapiHdfProxy.h. Also updated a cast in async_writeArrayNdSlab to use const int8_t* for improved type safety. --- cmake/swigEtp1_2Include.i.in | 1 + src/etp/fesapi/FesapiHdfProxy.cpp | 4 ++-- src/etp/fesapi/FesapiHdfProxy.h | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 2c7724c..b009eab 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -1951,6 +1951,7 @@ namespace ETP_NS * * @param dataspaceUris ETP general map where the values must be the URIs for the dataspaces the customer wants to lock or unlock. * @param lock true for locking the dataspaces, false to unlock the dataspaces + */ std::vector lockDataspaces(const std::map& dataspaceUris, bool lock); /** diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 0479c46..2658360 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -53,7 +53,7 @@ Energistics::Etp::v12::Datatypes::DataArrayTypes::DataArrayMetadata FesapiHdfPro // We don't care about the template parameter in this particular case auto handlers = std::make_shared>(session_, nullptr); - const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( + session_->sendWithSpecificHandlerAndBlock( buildGetDataArrayMetadataMessage(datasetName), handlers, 0, 0x02); @@ -454,7 +454,7 @@ std::set FesapiHdfProxy::async_writeArrayNdSlab( } std::set intermediateResult = async_writeArrayNdSlab(groupName, datasetName, datatype, - (int8_t*)values + (writtenTotalCount * valueSize), counts.get(), + (const int8_t*)values + (writtenTotalCount * valueSize), counts.get(), starts.get(), numDimensions); sentMessageIds.insert(intermediateResult.begin(), intermediateResult.end()); } diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index 4bc2703..2225343 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -532,7 +532,7 @@ namespace ETP_NS auto specializedHandler = std::make_shared>(session_, values); if (wholeSize + (valueCount + 1) * 8 <= maxAllowedDataArraySize) { // There can be valueCount array block and there is the length of the last array block // Get all values at once - const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( + session_->sendWithSpecificHandlerAndBlock( buildGetDataArraysMessage(datasetName), specializedHandler, 0, 0x02); @@ -595,7 +595,7 @@ namespace ETP_NS } // Send message - const int64_t msgId = session_->sendWithSpecificHandlerAndBlock( + session_->sendWithSpecificHandlerAndBlock( msg, specializedHandler, 0, 0x02); From cc118867051e7bfe63c1eda7b228380b7a877e20 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 18 Sep 2025 16:56:33 +0200 Subject: [PATCH 22/32] Fix Avro build when building Macos wheel --- .github/workflows/github-actions.yml | 4 +++- src/etp/ClientSession.h | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 602e66b..96a068f 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -330,8 +330,10 @@ jobs: make && make install && cd ${{ github.workspace }}/.. && - wget --no-verbose https://archive.apache.org/dist/avro/avro-1.11.3/cpp/avro-cpp-1.11.3.tar.gz && + wget --no-verbose https://downloads.apache.org/avro/avro-1.11.3/cpp/avro-cpp-1.11.3.tar.gz && tar xf avro-cpp-1.11.3.tar.gz && + sed -i '' 's/cmake_minimum_required (VERSION 3.1)/cmake_minimum_required (VERSION 3.5)/' avro-cpp-1.11.3/CMakeLists.txt && + sed -i '' 's/if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.0)/if (APPLE)/' avro-cpp-1.11.3/CMakeLists.txt && sed -i '' 's/install (TARGETS avrocpp avrocpp_s/install (TARGETS avrocpp_s/' avro-cpp-1.11.3/CMakeLists.txt && sed -i '' 's/install (TARGETS avrogencpp RUNTIME DESTINATION bin)//' avro-cpp-1.11.3/CMakeLists.txt && mkdir avro-build && diff --git a/src/etp/ClientSession.h b/src/etp/ClientSession.h index ea5e264..86403ba 100644 --- a/src/etp/ClientSession.h +++ b/src/etp/ClientSession.h @@ -64,8 +64,6 @@ namespace ETP_NS // Run will return only when there will no more be any uncomplete operations (such as a reading operation for example) getIoContext().run(); - std::cerr << "The IO Context does no more run" << std::endl; - // Try to reconnect up to 10 times if (!isCloseRequested_ && reconnectionTryCount_ < 10) { ++reconnectionTryCount_; From 79c1074e9ec733ea76fd9455336fe16118ab7e24 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Thu, 25 Sep 2025 18:47:06 +0200 Subject: [PATCH 23/32] Now validate ETP URI before to get info from it in EtpHelpers --- cmake/swigEtp1_2Include.i.in | 13 ++---- example/withFesapi/etpClient.cpp | 4 +- src/etp/EtpHelpers.cpp | 76 +++++++------------------------- src/etp/EtpHelpers.h | 9 ++-- 4 files changed, 26 insertions(+), 76 deletions(-) diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index b009eab..2b6b090 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -2129,19 +2129,14 @@ namespace ETP_NS std::string getDataspaceUri(const std::string& uri); /** - * @param session Provide this parameter if you want to send a protocol exception in case of non validation. + * Validate an ETP URI */ - Energistics::Etp::v12::Datatypes::ErrorInfo validateUri(const std::string & uri, ETP_NS::AbstractSession* session = nullptr); + bool validateUri(const std::string& uri); /** - * @param session Provide this parameter if you want to send a protocol exception in case of non validation. + * @Validate an ETP dataobject URI */ - Energistics::Etp::v12::Datatypes::ErrorInfo validateDataObjectUri(const std::string & uri, ETP_NS::AbstractSession* session = nullptr); - - /** - * Build a protocol exception message which only contains a single error message (not a messsage map). - */ - Energistics::Etp::v12::Protocol::Core::ProtocolException buildSingleMessageProtocolException(int32_t m_code, const std::string & m_message); + bool validateDataObjectUri(const std::string& uri); } /******************* CLIENT ***************************/ diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp index 91a5ae4..162b36e 100644 --- a/example/withFesapi/etpClient.cpp +++ b/example/withFesapi/etpClient.cpp @@ -290,7 +290,7 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb ctxInfo.includeSecondaryTargets = false; ctxInfo.includeSecondarySources = false; const auto resources = session->getResources(ctxInfo, Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets); - std::cout << "************ GET ALL DATAOBJECTS ************" << std::endl; + std::cout << "************ GET ALL " << resources.size() << " DATAOBJECTS ************" << std::endl; if (!resources.empty()) { std::map< std::string, std::string > query; size_t index = 0; @@ -737,7 +737,7 @@ int main(int argc, char **argv) std::cout << "Creating a client session..." << std::endl; auto clientSession = ETP_NS::ClientSessionLaunchers::createClientSession(&initializationParams, authorization); - clientSession->setVerbose(true); + clientSession->setVerbose(false); repo.setHdfProxyFactory(new ETP_NS::FesapiHdfProxyFactory(clientSession.get())); diff --git a/src/etp/EtpHelpers.cpp b/src/etp/EtpHelpers.cpp index a194952..b6cc811 100644 --- a/src/etp/EtpHelpers.cpp +++ b/src/etp/EtpHelpers.cpp @@ -18,15 +18,16 @@ under the License. -----------------------------------------------------------------------*/ #include "EtpHelpers.h" -#if (defined(_WIN32) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) #include -#endif -#include "AbstractSession.h" #include "EtpException.h" std::string ETP_NS::EtpHelpers::getDataObjectType(const std::string& uri) { + if (!ETP_NS::EtpHelpers::validateDataObjectUri(uri)) { + throw ETP_NS::EtpException(9, "The dataobject ETP URI \"" + uri + "\" is invalid."); + } + const std::size_t lastSlash = uri.find_last_of("/"); const std::size_t lastOpenParenthesis = uri.find_last_of("("); return lastSlash != std::string::npos && lastOpenParenthesis != std::string::npos @@ -36,9 +37,13 @@ std::string ETP_NS::EtpHelpers::getDataObjectType(const std::string& uri) std::string ETP_NS::EtpHelpers::getDataspaceUri(const std::string& uri) { + if (!ETP_NS::EtpHelpers::validateUri(uri)) { + throw ETP_NS::EtpException(9, "The ETP URI \"" + uri + "\" is invalid."); + } + const size_t dataspacePos = uri.find("dataspace('"); if (dataspacePos == std::string::npos) { - return ""; + return "eml:///"; } const size_t closingParenthesisPos = uri.find(')', dataspacePos); @@ -48,72 +53,23 @@ std::string ETP_NS::EtpHelpers::getDataspaceUri(const std::string& uri) return uri.substr(0, closingParenthesisPos+1); } -Energistics::Etp::v12::Datatypes::ErrorInfo ETP_NS::EtpHelpers::validateUri(const std::string & uri, ETP_NS::AbstractSession* session) +bool ETP_NS::EtpHelpers::validateUri(const std::string & uri) { - Energistics::Etp::v12::Datatypes::ErrorInfo errorInfo; - errorInfo.code = -1; // Regular expressions are not handled before GCC 4.9 // https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions -#if (defined(_WIN32) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) - const bool result = + return std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)])?", std::regex::ECMAScript)) || std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(resqml20|eml20)\.obj_[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)) || std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(witsml|resqml|prodml|eml)([0-9]{2})\[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)); - if (!result) { - std::cerr << "The URI \"" + uri + "\" is invalid." << std::endl; - } -#else - const bool result = uri.find("eml:///") == 0; -#endif - - if (!result) { - errorInfo.code = 9; - errorInfo.message = "The URI " + uri + " is invalid."; - - if (session != nullptr) { - Energistics::Etp::v12::Protocol::Core::ProtocolException error; - error.error.emplace(errorInfo); - session->send(error); - } - } - - return errorInfo; - } -Energistics::Etp::v12::Datatypes::ErrorInfo ETP_NS::EtpHelpers::validateDataObjectUri(const std::string & uri, AbstractSession* session) +bool ETP_NS::EtpHelpers::validateDataObjectUri(const std::string & uri) { - Energistics::Etp::v12::Datatypes::ErrorInfo errorInfo; - errorInfo.code = -1; // Regular expressions are not handled before GCC 4.9 // https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions -#if (defined(_WIN32) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) - const bool result = (uri.find("resqml20") != std::string::npos || uri.find("eml20") != std::string::npos) + return (uri.find("resqml20") != std::string::npos || uri.find("eml20") != std::string::npos) ? std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(resqml20|eml20)\.obj_[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)) : std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(witsml|resqml|prodml|eml)([0-9]{2})\.[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)); - if (!result) { - std::cerr << "The data object URI \"" + uri + "\" is invalid." << std::endl; - } -#else - const bool result = uri.find("eml:///") == 0 && - (uri.find("resqml20.obj_") != std::string::npos || uri.find("eml20.obj_") != std::string::npos || - uri.find("witsml20.") != std::string::npos || uri.find("eml21.") != std::string::npos || - uri.find("prodml21.") != std::string::npos || uri.find("eml22.") != std::string::npos || - uri.find("witsml21.") != std::string::npos); -#endif - - if (!result) { - errorInfo.code = 9; - errorInfo.message = "The data object URI " + uri + " is invalid."; - - if (session != nullptr) { - Energistics::Etp::v12::Protocol::Core::ProtocolException error; - error.error.emplace(errorInfo); - session->send(error); - } - } - - return errorInfo; } Energistics::Etp::v12::Protocol::Core::ProtocolException ETP_NS::EtpHelpers::buildSingleMessageProtocolException(int32_t m_code, const std::string & m_message) @@ -131,11 +87,11 @@ std::pair ETP_NS::EtpHelpers::getUuidAndVersionFromUri { std::pair result; - Energistics::Etp::v12::Datatypes::ErrorInfo error = ETP_NS::EtpHelpers::validateDataObjectUri(uri); - if (error.code > -1) { - throw ETP_NS::EtpException(error.code, error.message); + if (!ETP_NS::EtpHelpers::validateDataObjectUri(uri)) { + throw ETP_NS::EtpException(9, "Invalid URI"); } + // old code to be updated if (uri[6] != '/') { throw ETP_NS::EtpException(2, "The URI " + uri + " uses some dataspaces. This agent does not support dataspace."); } diff --git a/src/etp/EtpHelpers.h b/src/etp/EtpHelpers.h index 80b509e..7ebc282 100644 --- a/src/etp/EtpHelpers.h +++ b/src/etp/EtpHelpers.h @@ -40,7 +40,6 @@ namespace COMMON_NS namespace ETP_NS { - class AbstractSession; namespace EtpHelpers { @@ -61,14 +60,14 @@ namespace ETP_NS FETPAPI_DLL_IMPORT_OR_EXPORT std::string getDataspaceUri(const std::string& uri); /** - * @param session Provide this parameter if you want to send a protocol exception in case of non validation. + * Validate an ETP URI */ - FETPAPI_DLL_IMPORT_OR_EXPORT Energistics::Etp::v12::Datatypes::ErrorInfo validateUri(const std::string & uri, AbstractSession* session = nullptr); + FETPAPI_DLL_IMPORT_OR_EXPORT bool validateUri(const std::string& uri); /** - * @param session Provide this parameter if you want to send a protocol exception in case of non validation. + * @Validate an ETP dataobject URI */ - FETPAPI_DLL_IMPORT_OR_EXPORT Energistics::Etp::v12::Datatypes::ErrorInfo validateDataObjectUri(const std::string & uri, AbstractSession* session = nullptr); + FETPAPI_DLL_IMPORT_OR_EXPORT bool validateDataObjectUri(const std::string& uri); /** * Build a protocol exception message which only contains a single error message (not a messsage map). From 21c5293855e5602d062ff0519a1427471fdf0c01 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Tue, 14 Oct 2025 16:17:52 +0200 Subject: [PATCH 24/32] Fix the FETPAPI DLL name to load in Java example --- cmake/FetpapiClientUsingFesapi.java | 2 +- cmake/swigEtp1_2Include.i.in | 6 +++--- java/CMakeLists.txt | 4 ++-- src/etp/EtpHelpers.h | 2 +- src/etp/fesapi/FesapiHdfProxy.cpp | 7 +++++-- src/etp/fesapi/FesapiHdfProxy.h | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cmake/FetpapiClientUsingFesapi.java b/cmake/FetpapiClientUsingFesapi.java index a5effb6..0a05e70 100644 --- a/cmake/FetpapiClientUsingFesapi.java +++ b/cmake/FetpapiClientUsingFesapi.java @@ -16,7 +16,7 @@ Licensed to the Apache Software Foundation (ASF) under one specific language governing permissions and limitations under the License. -----------------------------------------------------------------------*/ -package com.f2i_consulting.fetpapi.client; +package com.f2i_consulting.example; import java.util.Optional; import java.util.UUID; diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index 2b6b090..d64d8f1 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -299,7 +299,7 @@ typedef long long time_t; %include "std_array.i" %typemap(javaimports) SWIGTYPE %{ - import com.f2i_consulting.fetpapi.*; +import com.f2i_consulting.fetpapi.*; %} namespace Energistics { @@ -2134,7 +2134,7 @@ namespace ETP_NS bool validateUri(const std::string& uri); /** - * @Validate an ETP dataobject URI + * Validate an ETP dataobject URI */ bool validateDataObjectUri(const std::string& uri); } @@ -2217,7 +2217,7 @@ namespace ETP_NS #ifdef WITH_FESAPI %typemap(javaimports) FesapiHdfProxyFactory %{ - import com.f2i_consulting.fesapi.common.HdfProxyFactory; +import com.f2i_consulting.fesapi.common.HdfProxyFactory; %} %typemap(csimports) FesapiHdfProxyFactory %{ using F2iConsulting.Fesapi.common; diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt index 61f9996..cd343a5 100644 --- a/java/CMakeLists.txt +++ b/java/CMakeLists.txt @@ -20,9 +20,9 @@ file (REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/java/src/com/f2i_consulting/fetpapi/etp # The assembly name is used to load the debug vs release FETPAPI library in the Java example if (WIN32) if (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_RELEASE_POSTFIX}.${Fetpapi_VERSION}) + set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_RELEASE_POSTFIX}-${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}) else (SWIG_LINKED_TO_RELEASE) - set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_DEBUG_POSTFIX}.${Fetpapi_VERSION}) + set (ASSEMBLY_NAME ${PROJECT_NAME}${CMAKE_DEBUG_POSTFIX}-${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}) endif (SWIG_LINKED_TO_RELEASE) else (WIN32) if (SWIG_LINKED_TO_RELEASE) diff --git a/src/etp/EtpHelpers.h b/src/etp/EtpHelpers.h index 7ebc282..aa30a4a 100644 --- a/src/etp/EtpHelpers.h +++ b/src/etp/EtpHelpers.h @@ -65,7 +65,7 @@ namespace ETP_NS FETPAPI_DLL_IMPORT_OR_EXPORT bool validateUri(const std::string& uri); /** - * @Validate an ETP dataobject URI + * Validate an ETP dataobject URI */ FETPAPI_DLL_IMPORT_OR_EXPORT bool validateDataObjectUri(const std::string& uri); diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 2658360..4b1d04c 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -333,16 +333,19 @@ void FesapiHdfProxy::writeArrayNdSlab( auto t_start = std::chrono::high_resolution_clock::now(); while (!stillProcessingMsgIds.empty()) { + std::vector idsToErase; for (int64_t msgId : stillProcessingMsgIds) { if (!session_->isMessageStillProcessing(msgId)) { - stillProcessingMsgIds.erase(msgId); + idsToErase.push_back(msgId); } } + for (int64_t msgId : idsToErase) { + stillProcessingMsgIds.erase(msgId); + } if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > session_->getTimeOut()) { throw std::runtime_error("Time out waiting for a writeArrayNdSlab response"); } } - std::cerr << "writeArrayNdSlab Response got in " << std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() << " ms" << std::endl; } std::set FesapiHdfProxy::async_writeArrayNdSlab( diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index 2225343..f4d9b5b 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -37,7 +37,7 @@ namespace ETP_NS */ FesapiHdfProxy(AbstractSession* session, COMMON_NS::DataObjectRepository * repo, const std::string & guid, const std::string & title, const std::string & packageDirAbsolutePath, const std::string & externalFilePath, COMMON_NS::DataObjectRepository::openingMode hdfPermissionAccess) : EML2_NS::AbstractHdfProxy(packageDirAbsolutePath, externalFilePath, hdfPermissionAccess), session_(session), compressionLevel(0) { - xmlNs_ = repo->getDefaultEmlVersion() == COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_0 ? "eml20" : "eml23"; + xmlNs_ = repo->getDefaultEmlVersion() == COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_3 ? "eml23" : "eml20"; initGsoapProxy(repo, guid, title, 20); } From 5f18443c8d3615773bbad9ccb15aa10cdcf4eccd Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 15 Oct 2025 16:37:56 +0200 Subject: [PATCH 25/32] Update GitHub Actions to latest versions --- .github/workflows/github-actions.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 96a068f..cb12313 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -4,7 +4,7 @@ jobs: windows-2022: runs-on: windows-2022 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Boost install run: | (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.88.0/binaries/boost_1_88_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") @@ -32,7 +32,7 @@ jobs: windows-2022-with-fesapi: runs-on: windows-2022 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Boost install run: | (New-Object System.Net.WebClient).DownloadFile("https://archives.boost.io/release/1.88.0/binaries/boost_1_88_0-msvc-14.2-64.exe", "${{ runner.temp }}\boost.exe") @@ -64,7 +64,7 @@ jobs: ubuntu-22: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: APT install run: | sudo apt update @@ -91,8 +91,8 @@ jobs: ubuntu-22-java11: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '11' @@ -137,8 +137,8 @@ jobs: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '11' @@ -181,13 +181,13 @@ jobs: name: Build wheels on windows-latest runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Stub `setup.py` check # It will be generated during CMake run # https://github.com/pypa/cibuildwheel/issues/1139 run: touch python/setup.py - name: Build wheels - uses: pypa/cibuildwheel@v2.21.3 + uses: pypa/cibuildwheel@v3.2.1 env: CIBW_BUILD: cp38-win_amd64 cp39-win_amd64 cp310-win_amd64 cp311-win_amd64 cp312-win_amd64 cp313-win_amd64 CIBW_ARCHS: auto64 @@ -224,13 +224,14 @@ jobs: name: Build wheels on ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Stub `setup.py` check # It will be generated during CMake run # https://github.com/pypa/cibuildwheel/issues/1139 run: touch python/setup.py - name: Build wheels - uses: pypa/cibuildwheel@v2.21.3 + # Above cibuildwheel@v2.22.0, GNU 14 is most likely used instead of GNU 12 or 13 which makes AVRO 1.11.3 not compiling + uses: pypa/cibuildwheel@v2.22.0 env: CIBW_BUILD: cp38-manylinux_* cp39-manylinux_* cp310-manylinux_* cp311-manylinux_* cp312-manylinux_* cp313-manylinux_* CIBW_ARCHS: auto64 @@ -280,13 +281,13 @@ jobs: name: Build wheels on macos-14 runs-on: macos-14 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Stub `setup.py` check # It will be generated during CMake run # https://github.com/pypa/cibuildwheel/issues/1139 run: touch python/setup.py - name: Build wheels - uses: pypa/cibuildwheel@v2.21.3 + uses: pypa/cibuildwheel@v3.2.1 env: CIBW_BUILD: cp38-macosx_* cp39-macosx_* cp310-macosx_* cp311-macosx_* cp312-macosx_* cp313-macosx_* CIBW_ARCHS: auto64 From 3a5d1ad68d38a0abcfb9ebb6b86e27aa9ef74360 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 12 Nov 2025 16:35:44 +0100 Subject: [PATCH 26/32] Replace boost::optional with std::optional and boost::any with std::variant Migrated all uses of boost::optional to std::optional and updated related code to use .value() and .has_value() instead of .get() and .is_initialized(). Refactored union types to use std::variant instead of boost::any, and updated Avro codec_traits for std::optional. Also updated usages in example, protocol handlers, and data array handlers to match new APIs. --- .github/workflows/github-actions.yml | 2 +- cmake/pyproject.toml.in | 2 +- cmake/swigEtp1_2Include.i.in | 26 +- example/withFesapi/etpClient.cpp | 6 +- python/CMakeLists.txt | 2 +- src/etp/EtpMessages.h | 757 +++++------------- src/etp/ProtocolHandlers/CoreHandlers.cpp | 2 +- .../GetFullDataArrayHandlers.h | 28 +- src/etp/ProtocolHandlers/ProtocolHandlers.cpp | 4 +- src/etp/fesapi/FesapiHdfProxy.cpp | 12 +- src/etp/fesapi/FesapiHdfProxy.h | 6 +- 11 files changed, 231 insertions(+), 616 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index cb12313..d858544 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -331,7 +331,7 @@ jobs: make && make install && cd ${{ github.workspace }}/.. && - wget --no-verbose https://downloads.apache.org/avro/avro-1.11.3/cpp/avro-cpp-1.11.3.tar.gz && + wget --no-verbose https://archive.apache.org/dist/avro/avro-1.11.3/cpp/avro-cpp-1.11.3.tar.gz && tar xf avro-cpp-1.11.3.tar.gz && sed -i '' 's/cmake_minimum_required (VERSION 3.1)/cmake_minimum_required (VERSION 3.5)/' avro-cpp-1.11.3/CMakeLists.txt && sed -i '' 's/if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.0)/if (APPLE)/' avro-cpp-1.11.3/CMakeLists.txt && diff --git a/cmake/pyproject.toml.in b/cmake/pyproject.toml.in index cda5017..cba1699 100644 --- a/cmake/pyproject.toml.in +++ b/cmake/pyproject.toml.in @@ -43,7 +43,7 @@ keywords = [ ] requires-python = ">=3.8" dependencies = [ - 'fesapi==2.12.1', + 'fesapi==2.13.0', ] [project.urls] diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index d64d8f1..8f99e3a 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -377,22 +377,22 @@ namespace Energistics { std::string get_string(); void set_string(const std::string& v); - Energistics::Etp::v12::Datatypes::ArrayOfBoolean get_ArrayOfBoolean(); + Energistics::Etp::v12::Datatypes::ArrayOfBoolean const& get_ArrayOfBoolean() const; void set_ArrayOfBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& v); - Energistics::Etp::v12::Datatypes::ArrayOfInt get_ArrayOfInt(); + Energistics::Etp::v12::Datatypes::ArrayOfInt const& get_ArrayOfInt() const; void set_ArrayOfInt(const Energistics::Etp::v12::Datatypes::ArrayOfInt& v); - Energistics::Etp::v12::Datatypes::ArrayOfLong get_ArrayOfLong(); + Energistics::Etp::v12::Datatypes::ArrayOfLong const& get_ArrayOfLong() const; void set_ArrayOfLong(const Energistics::Etp::v12::Datatypes::ArrayOfLong& v); - Energistics::Etp::v12::Datatypes::ArrayOfFloat get_ArrayOfFloat(); + Energistics::Etp::v12::Datatypes::ArrayOfFloat const& get_ArrayOfFloat() const; void set_ArrayOfFloat(const Energistics::Etp::v12::Datatypes::ArrayOfFloat& v); - Energistics::Etp::v12::Datatypes::ArrayOfDouble get_ArrayOfDouble(); + Energistics::Etp::v12::Datatypes::ArrayOfDouble const& get_ArrayOfDouble() const; void set_ArrayOfDouble(const Energistics::Etp::v12::Datatypes::ArrayOfDouble& v); - Energistics::Etp::v12::Datatypes::ArrayOfString get_ArrayOfString(); + Energistics::Etp::v12::Datatypes::ArrayOfString const& get_ArrayOfString() const; void set_ArrayOfString(const Energistics::Etp::v12::Datatypes::ArrayOfString& v); std::string get_bytes(); @@ -412,25 +412,25 @@ namespace Energistics { public: size_t idx() const; - Energistics::Etp::v12::Datatypes::ArrayOfBoolean get_ArrayOfBoolean(); + Energistics::Etp::v12::Datatypes::ArrayOfBoolean const& get_ArrayOfBoolean() const; void set_ArrayOfBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& v); - Energistics::Etp::v12::Datatypes::ArrayOfInt get_ArrayOfInt(); + Energistics::Etp::v12::Datatypes::ArrayOfInt const& get_ArrayOfInt() const; void set_ArrayOfInt(const Energistics::Etp::v12::Datatypes::ArrayOfInt& v); - Energistics::Etp::v12::Datatypes::ArrayOfLong get_ArrayOfLong(); + Energistics::Etp::v12::Datatypes::ArrayOfLong const& get_ArrayOfLong() const; void set_ArrayOfLong(const Energistics::Etp::v12::Datatypes::ArrayOfLong& v); - Energistics::Etp::v12::Datatypes::ArrayOfFloat get_ArrayOfFloat(); + Energistics::Etp::v12::Datatypes::ArrayOfFloat const& get_ArrayOfFloat() const; void set_ArrayOfFloat(const Energistics::Etp::v12::Datatypes::ArrayOfFloat& v); - Energistics::Etp::v12::Datatypes::ArrayOfDouble get_ArrayOfDouble(); + Energistics::Etp::v12::Datatypes::ArrayOfDouble const& get_ArrayOfDouble() const; void set_ArrayOfDouble(const Energistics::Etp::v12::Datatypes::ArrayOfDouble& v); - Energistics::Etp::v12::Datatypes::ArrayOfString get_ArrayOfString(); + Energistics::Etp::v12::Datatypes::ArrayOfString const& get_ArrayOfString() const; void set_ArrayOfString(const Energistics::Etp::v12::Datatypes::ArrayOfString& v); - std::string get_bytes(); + std::string const& get_bytes() const; void set_bytes(const std::string& v); }; diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp index 162b36e..3735e98 100644 --- a/example/withFesapi/etpClient.cpp +++ b/example/withFesapi/etpClient.cpp @@ -196,8 +196,8 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb const auto resources = session->getResources(mb.context, mb.scope); for (auto& resource : resources) { std::cout << resource.uri << std::endl; - if (resource.has_sourceCount()) std::cout << "Source count: " << resource.sourceCount.get() << std::endl; - if (resource.has_targetCount()) std::cout << "Target count: " << resource.targetCount.get() << std::endl; + if (resource.has_sourceCount()) std::cout << "Source count: " << resource.sourceCount.value() << std::endl; + if (resource.has_targetCount()) std::cout << "Target count: " << resource.targetCount.value() << std::endl; } continue; } @@ -680,7 +680,7 @@ void askUser(std::shared_ptr session, COMMON_NS::DataOb Energistics::Etp::v12::Datatypes::AnyArray data; Energistics::Etp::v12::Datatypes::ArrayOfInt arrayOfInt; arrayOfInt.values = { 0,1,2,3,4,5,6,7,8,9 }; - data.item.set_ArrayOfInt(arrayOfInt); + data.item.set_ArrayOfInt(std::move(arrayOfInt)); pda.dataArrays["0"].array.data = data; std::cout << "Start sending the array" << std::endl; diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 1455314..8e461ac 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -58,7 +58,7 @@ if (WITH_FESAPI) set (FESAPI_LIBRARY_RELEASE_WLE ",'${FESAPI_LIBRARY_RELEASE_WLE}'") endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set (EXTRA_COMPILE_ARGS "extra-compile-args=['/DSWIG_TYPE_TABLE=FESTAPI', '/utf-8']") + set (EXTRA_COMPILE_ARGS "extra-compile-args=['/DSWIG_TYPE_TABLE=FESTAPI', '/std:c++17', '/utf-8']") else () set (EXTRA_COMPILE_ARGS "extra-compile-args=['-DSWIG_TYPE_TABLE=FESTAPI', '-std=c++17']") endif() diff --git a/src/etp/EtpMessages.h b/src/etp/EtpMessages.h index f60b982..9682a94 100644 --- a/src/etp/EtpMessages.h +++ b/src/etp/EtpMessages.h @@ -1,15 +1,16 @@ #ifndef ETP_MESSAGES__ #define ETP_MESSAGES__ +#include #include +#include #include -#include -#include +#include #include -#include -#include -#include -#include +#include +#include +#include + #include #include #include @@ -347,9 +348,9 @@ namespace Energistics { namespace Protocol { namespace Dataspace { struct GetDataspaces { - boost::optional storeLastWriteFilter; - bool has_storeLastWriteFilter() const { return storeLastWriteFilter.is_initialized(); } - int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.get(); } + std::optional storeLastWriteFilter; + bool has_storeLastWriteFilter() const { return storeLastWriteFilter.has_value(); } + int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.value(); } static constexpr int messageTypeId=1; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::Dataspace); }; @@ -400,9 +401,9 @@ namespace Energistics { namespace Discovery { struct GetDeletedResources { std::string dataspaceUri; - boost::optional deleteTimeFilter; - bool has_deleteTimeFilter() const { return deleteTimeFilter.is_initialized(); } - int64_t get_deleteTimeFilter() const { return deleteTimeFilter.get(); } + std::optional deleteTimeFilter; + bool has_deleteTimeFilter() const { return deleteTimeFilter.has_value(); } + int64_t get_deleteTimeFilter() const { return deleteTimeFilter.value(); } std::vector dataObjectTypes; static constexpr int messageTypeId=5; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::Discovery); @@ -737,7 +738,7 @@ namespace Energistics { namespace v12 { namespace Datatypes { struct ArrayOfNullableBoolean { - std::vector> values; + std::vector> values; }; } } @@ -758,7 +759,7 @@ namespace Energistics { namespace v12 { namespace Datatypes { struct ArrayOfNullableInt { - std::vector> values; + std::vector> values; }; } } @@ -779,7 +780,7 @@ namespace Energistics { namespace v12 { namespace Datatypes { struct ArrayOfNullableLong { - std::vector> values; + std::vector> values; }; } } @@ -847,122 +848,80 @@ namespace Energistics { namespace Datatypes { struct AnyArrayitem_t { private: - size_t idx_=0; - boost::any value_; + std::variant< + Energistics::Etp::v12::Datatypes::ArrayOfBoolean, + Energistics::Etp::v12::Datatypes::ArrayOfInt, + Energistics::Etp::v12::Datatypes::ArrayOfLong, + Energistics::Etp::v12::Datatypes::ArrayOfFloat, + Energistics::Etp::v12::Datatypes::ArrayOfDouble, + Energistics::Etp::v12::Datatypes::ArrayOfString, + std::string + > value_; public: - size_t idx() const { return idx_; } + size_t idx() const { return value_.index(); } Energistics::Etp::v12::Datatypes::ArrayOfBoolean const & get_ArrayOfBoolean() const { - if (idx_ != 0) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBoolean const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfBoolean& get_ArrayOfBoolean() { - if (idx_ != 0) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBoolean& >(value_); + return std::get(value_); } void set_ArrayOfBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& v) { - idx_ = 0; - value_ = v; + value_.emplace(v); } - Energistics::Etp::v12::Datatypes::ArrayOfInt const & get_ArrayOfInt() const { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfInt const & >(value_); + void set_ArrayOfBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfBoolean&& v) { + value_.emplace(std::move(v)); } - Energistics::Etp::v12::Datatypes::ArrayOfInt& get_ArrayOfInt() { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfInt& >(value_); + Energistics::Etp::v12::Datatypes::ArrayOfInt const & get_ArrayOfInt() const { + return std::get(value_); } void set_ArrayOfInt(const Energistics::Etp::v12::Datatypes::ArrayOfInt& v) { - idx_ = 1; - value_ = v; + value_.emplace(v); } - Energistics::Etp::v12::Datatypes::ArrayOfLong const & get_ArrayOfLong() const { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfLong const & >(value_); + void set_ArrayOfInt(const Energistics::Etp::v12::Datatypes::ArrayOfInt&& v) { + value_.emplace(std::move(v)); } - Energistics::Etp::v12::Datatypes::ArrayOfLong& get_ArrayOfLong() { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfLong& >(value_); + Energistics::Etp::v12::Datatypes::ArrayOfLong const & get_ArrayOfLong() const { + return std::get(value_); } void set_ArrayOfLong(const Energistics::Etp::v12::Datatypes::ArrayOfLong& v) { - idx_ = 2; - value_ = v; + value_.emplace(v); } - Energistics::Etp::v12::Datatypes::ArrayOfFloat const & get_ArrayOfFloat() const { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfFloat const & >(value_); + void set_ArrayOfLong(const Energistics::Etp::v12::Datatypes::ArrayOfLong&& v) { + value_.emplace(std::move(v)); } - Energistics::Etp::v12::Datatypes::ArrayOfFloat& get_ArrayOfFloat() { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfFloat& >(value_); + Energistics::Etp::v12::Datatypes::ArrayOfFloat const & get_ArrayOfFloat() const { + return std::get(value_); } void set_ArrayOfFloat(const Energistics::Etp::v12::Datatypes::ArrayOfFloat& v) { - idx_ = 3; - value_ = v; + value_.emplace(v); } - Energistics::Etp::v12::Datatypes::ArrayOfDouble const & get_ArrayOfDouble() const { - if (idx_ != 4) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfDouble const & >(value_); + void set_ArrayOfFloat(const Energistics::Etp::v12::Datatypes::ArrayOfFloat&& v) { + value_.emplace(std::move(v)); } - Energistics::Etp::v12::Datatypes::ArrayOfDouble& get_ArrayOfDouble() { - if (idx_ != 4) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfDouble& >(value_); + Energistics::Etp::v12::Datatypes::ArrayOfDouble const & get_ArrayOfDouble() const { + return std::get(value_); } void set_ArrayOfDouble(const Energistics::Etp::v12::Datatypes::ArrayOfDouble& v) { - idx_ = 4; - value_ = v; + value_.emplace(v); } - Energistics::Etp::v12::Datatypes::ArrayOfString const & get_ArrayOfString() const { - if (idx_ != 5) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfString const & >(value_); + void set_ArrayOfDouble(const Energistics::Etp::v12::Datatypes::ArrayOfDouble&& v) { + value_.emplace(std::move(v)); } - Energistics::Etp::v12::Datatypes::ArrayOfString& get_ArrayOfString() { - if (idx_ != 5) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfString& >(value_); + Energistics::Etp::v12::Datatypes::ArrayOfString const & get_ArrayOfString() const { + return std::get(value_); } void set_ArrayOfString(const Energistics::Etp::v12::Datatypes::ArrayOfString& v) { - idx_ = 5; - value_ = v; + value_.emplace(v); } - std::string const & get_bytes() const { - if (idx_ != 6) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string const & >(value_); + void set_ArrayOfString(const Energistics::Etp::v12::Datatypes::ArrayOfString&& v) { + value_.emplace(std::move(v)); } - std::string& get_bytes() { - if (idx_ != 6) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string& >(value_); + std::string const & get_bytes() const { + return std::get(value_); } void set_bytes(const std::string& v) { - idx_ = 6; - value_ = v; + value_.emplace(v); + } + void set_bytes(const std::string&& v) { + value_.emplace(std::move(v)); } }; } @@ -1015,42 +974,42 @@ namespace avro { { Energistics::Etp::v12::Datatypes::ArrayOfInt vv; avro::decode(d, vv); - v.set_ArrayOfInt(vv); + v.set_ArrayOfInt(std::move(vv)); } break; case 2: { Energistics::Etp::v12::Datatypes::ArrayOfLong vv; avro::decode(d, vv); - v.set_ArrayOfLong(vv); + v.set_ArrayOfLong(std::move(vv)); } break; case 3: { Energistics::Etp::v12::Datatypes::ArrayOfFloat vv; avro::decode(d, vv); - v.set_ArrayOfFloat(vv); + v.set_ArrayOfFloat(std::move(vv)); } break; case 4: { Energistics::Etp::v12::Datatypes::ArrayOfDouble vv; avro::decode(d, vv); - v.set_ArrayOfDouble(vv); + v.set_ArrayOfDouble(std::move(vv)); } break; case 5: { Energistics::Etp::v12::Datatypes::ArrayOfString vv; avro::decode(d, vv); - v.set_ArrayOfString(vv); + v.set_ArrayOfString(std::move(vv)); } break; case 6: { std::string vv; avro::decode(d, vv); - v.set_bytes(vv); + v.set_bytes(std::move(vv)); } break; } @@ -1188,299 +1147,140 @@ namespace Energistics { namespace Datatypes { struct DataValueitem_t { private: - size_t idx_=0; - boost::any value_; + std::variant< + std::nullptr_t, + bool, + int32_t, + int64_t, + float, + double, + std::string, + Energistics::Etp::v12::Datatypes::ArrayOfBoolean, + Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean, + Energistics::Etp::v12::Datatypes::ArrayOfInt, + Energistics::Etp::v12::Datatypes::ArrayOfNullableInt, + Energistics::Etp::v12::Datatypes::ArrayOfLong, + Energistics::Etp::v12::Datatypes::ArrayOfNullableLong, + Energistics::Etp::v12::Datatypes::ArrayOfFloat, + Energistics::Etp::v12::Datatypes::ArrayOfDouble, + Energistics::Etp::v12::Datatypes::ArrayOfString, + Energistics::Etp::v12::Datatypes::ArrayOfBytes, + std::vector, + Energistics::Etp::v12::Datatypes::AnySparseArray + > value_; public: - size_t idx() const { return idx_; } - bool is_null() const { return idx_==0; } - void set_null() { idx_=0; value_ = boost::any(); } - bool const & get_boolean() const { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< bool const & >(value_); - } - bool& get_boolean() { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< bool& >(value_); + size_t idx() const { return value_.index(); } + bool is_null() const { return idx() == 0; } + void set_null() { value_ = nullptr; } + bool get_boolean() const { + return std::get(value_); } void set_boolean(const bool& v) { - idx_ = 1; value_ = v; } - int32_t const & get_int() const { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int32_t const & >(value_); - } - int32_t& get_int() { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int32_t& >(value_); + int32_t get_int() const { + return std::get(value_); } void set_int(const int32_t& v) { - idx_ = 2; value_ = v; } - int64_t const & get_long() const { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int64_t const & >(value_); - } - int64_t& get_long() { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int64_t& >(value_); + int64_t get_long() const { + return std::get(value_); } void set_long(const int64_t& v) { - idx_ = 3; value_ = v; } - float const & get_float() const { - if (idx_ != 4) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< float const & >(value_); - } - float& get_float() { - if (idx_ != 4) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< float& >(value_); + float get_float() const { + return std::get(value_); } void set_float(const float& v) { - idx_ = 4; value_ = v; } - double const & get_double() const { - if (idx_ != 5) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< double const & >(value_); - } - double& get_double() { - if (idx_ != 5) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< double& >(value_); + double get_double() const { + return std::get(value_); } void set_double(const double& v) { - idx_ = 5; value_ = v; } std::string const & get_string() const { - if (idx_ != 6) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string const & >(value_); - } - std::string& get_string() { - if (idx_ != 6) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string& >(value_); + return std::get(value_); } void set_string(const std::string& v) { - idx_ = 6; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfBoolean const & get_ArrayOfBoolean() const { - if (idx_ != 7) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBoolean const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfBoolean& get_ArrayOfBoolean() { - if (idx_ != 7) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBoolean& >(value_); + return std::get(value_); } void set_ArrayOfBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& v) { - idx_ = 7; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean const & get_ArrayOfNullableBoolean() const { - if (idx_ != 8) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean& get_ArrayOfNullableBoolean() { - if (idx_ != 8) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean& >(value_); + return std::get(value_); } void set_ArrayOfNullableBoolean(const Energistics::Etp::v12::Datatypes::ArrayOfNullableBoolean& v) { - idx_ = 8; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfInt const & get_ArrayOfInt() const { - if (idx_ != 9) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfInt const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfInt& get_ArrayOfInt() { - if (idx_ != 9) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfInt& >(value_); + return std::get(value_); } void set_ArrayOfInt(const Energistics::Etp::v12::Datatypes::ArrayOfInt& v) { - idx_ = 9; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfNullableInt const & get_ArrayOfNullableInt() const { - if (idx_ != 10) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableInt const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfNullableInt& get_ArrayOfNullableInt() { - if (idx_ != 10) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableInt& >(value_); + return std::get(value_); } void set_ArrayOfNullableInt(const Energistics::Etp::v12::Datatypes::ArrayOfNullableInt& v) { - idx_ = 10; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfLong const & get_ArrayOfLong() const { - if (idx_ != 11) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfLong const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfLong& get_ArrayOfLong() { - if (idx_ != 11) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfLong& >(value_); + return std::get(value_); } void set_ArrayOfLong(const Energistics::Etp::v12::Datatypes::ArrayOfLong& v) { - idx_ = 11; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfNullableLong const & get_ArrayOfNullableLong() const { - if (idx_ != 12) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableLong const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfNullableLong& get_ArrayOfNullableLong() { - if (idx_ != 12) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfNullableLong& >(value_); + return std::get(value_); } void set_ArrayOfNullableLong(const Energistics::Etp::v12::Datatypes::ArrayOfNullableLong& v) { - idx_ = 12; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfFloat const & get_ArrayOfFloat() const { - if (idx_ != 13) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfFloat const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfFloat& get_ArrayOfFloat() { - if (idx_ != 13) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfFloat& >(value_); + return std::get(value_); } void set_ArrayOfFloat(const Energistics::Etp::v12::Datatypes::ArrayOfFloat& v) { - idx_ = 13; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfDouble const & get_ArrayOfDouble() const { - if (idx_ != 14) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfDouble const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfDouble& get_ArrayOfDouble() { - if (idx_ != 14) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfDouble& >(value_); + return std::get(value_); } void set_ArrayOfDouble(const Energistics::Etp::v12::Datatypes::ArrayOfDouble& v) { - idx_ = 14; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfString const & get_ArrayOfString() const { - if (idx_ != 15) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfString const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfString& get_ArrayOfString() { - if (idx_ != 15) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfString& >(value_); + return std::get(value_); } void set_ArrayOfString(const Energistics::Etp::v12::Datatypes::ArrayOfString& v) { - idx_ = 15; value_ = v; } Energistics::Etp::v12::Datatypes::ArrayOfBytes const & get_ArrayOfBytes() const { - if (idx_ != 16) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBytes const & >(value_); - } - Energistics::Etp::v12::Datatypes::ArrayOfBytes& get_ArrayOfBytes() { - if (idx_ != 16) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ArrayOfBytes& >(value_); + return std::get(value_); } void set_ArrayOfBytes(const Energistics::Etp::v12::Datatypes::ArrayOfBytes& v) { - idx_ = 16; value_ = v; } - std::string const & get_bytes() const { - if (idx_ != 17) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string const & >(value_); - } - std::string& get_bytes() { - if (idx_ != 17) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< std::string& >(value_); + std::string get_bytes() const { + const auto& bytes = std::get>(value_); + return std::string(reinterpret_cast(bytes.data()), bytes.size()); + } void set_bytes(const std::string& v) { - idx_ = 17; value_ = v; } Energistics::Etp::v12::Datatypes::AnySparseArray const & get_AnySparseArray() const { - if (idx_ != 18) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::AnySparseArray const & >(value_); - } - Energistics::Etp::v12::Datatypes::AnySparseArray& get_AnySparseArray() { - if (idx_ != 18) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::AnySparseArray& >(value_); + return std::get(value_); } void set_AnySparseArray(const Energistics::Etp::v12::Datatypes::AnySparseArray& v) { - idx_ = 18; value_ = v; } }; @@ -1492,7 +1292,6 @@ namespace avro { template<> struct codec_traits { static void encode(Encoder& e, Energistics::Etp::v12::Datatypes::DataValueitem_t v) { - e.encodeUnionIndex(v.idx()); switch (v.idx()) { case 0: @@ -1841,9 +1640,9 @@ namespace Energistics { namespace Protocol { namespace Core { struct ProtocolException { - boost::optional error; - bool has_error() const { return error.is_initialized(); } - Energistics::Etp::v12::Datatypes::ErrorInfo get_error() const { return error.get(); } + std::optional error; + bool has_error() const { return error.has_value(); } + Energistics::Etp::v12::Datatypes::ErrorInfo get_error() const { return error.value(); } std::map errors; static constexpr int messageTypeId=1000; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::Core); @@ -2770,59 +2569,33 @@ namespace Energistics { namespace Datatypes { struct IndexValueitem_t { private: - size_t idx_=0; - boost::any value_; + std::variant< + std::nullptr_t, + int64_t, + double, + Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth + > value_; public: - size_t idx() const { return idx_; } - bool is_null() const { return idx_==0; } - void set_null() { idx_=0; value_ = boost::any(); } - int64_t const & get_long() const { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int64_t const & >(value_); - } - int64_t& get_long() { - if (idx_ != 1) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< int64_t& >(value_); + size_t idx() const { return value_.index(); } + bool is_null() const { return idx() == 0; } + void set_null() { value_ = nullptr; } + int64_t get_long() const { + return std::get(value_); } void set_long(const int64_t& v) { - idx_ = 1; value_ = v; } - double const & get_double() const { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< double const & >(value_); - } - double& get_double() { - if (idx_ != 2) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< double& >(value_); + double get_double() const { + return std::get(value_); } void set_double(const double& v) { - idx_ = 2; value_ = v; } Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth const & get_PassIndexedDepth() const { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth const & >(value_); - } - Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth& get_PassIndexedDepth() { - if (idx_ != 3) { - throw avro::Exception("Invalid type for union."); - } - return boost::any_cast< Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth& >(value_); + return std::get(value_); } void set_PassIndexedDepth(const Energistics::Etp::v12::Datatypes::ChannelData::PassIndexedDepth& v) { - idx_ = 3; value_ = v; } }; @@ -2918,9 +2691,9 @@ namespace Energistics { int64_t channelId = 0; Energistics::Etp::v12::Datatypes::IndexValue startIndex; bool dataChanges=false; - boost::optional requestLatestIndexCount; - bool has_requestLatestIndexCount() const { return requestLatestIndexCount.is_initialized(); } - int32_t get_requestLatestIndexCount() const { return requestLatestIndexCount.get(); } + std::optional requestLatestIndexCount; + bool has_requestLatestIndexCount() const { return requestLatestIndexCount.has_value(); } + int32_t get_requestLatestIndexCount() const { return requestLatestIndexCount.value(); } }; } } @@ -4432,12 +4205,12 @@ namespace Energistics { Energistics::Etp::v12::Datatypes::Object::ContextInfo context; Energistics::Etp::v12::Datatypes::Object::ContextScopeKind scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; bool countObjects=false; - boost::optional storeLastWriteFilter; - bool has_storeLastWriteFilter() const { return storeLastWriteFilter.is_initialized(); } - int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.get(); } - boost::optional activeStatusFilter; - bool has_activeStatusFilter() const { return activeStatusFilter.is_initialized(); } - Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.get(); } + std::optional storeLastWriteFilter; + bool has_storeLastWriteFilter() const { return storeLastWriteFilter.has_value(); } + int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.value(); } + std::optional activeStatusFilter; + bool has_activeStatusFilter() const { return activeStatusFilter.has_value(); } + Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.value(); } bool includeEdges=false; static constexpr int messageTypeId=1; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::Discovery); @@ -4475,12 +4248,12 @@ namespace Energistics { struct FindResources { Energistics::Etp::v12::Datatypes::Object::ContextInfo context; Energistics::Etp::v12::Datatypes::Object::ContextScopeKind scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; - boost::optional storeLastWriteFilter; - bool has_storeLastWriteFilter() const { return storeLastWriteFilter.is_initialized(); } - int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.get(); } - boost::optional activeStatusFilter; - bool has_activeStatusFilter() const { return activeStatusFilter.is_initialized(); } - Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.get(); } + std::optional storeLastWriteFilter; + bool has_storeLastWriteFilter() const { return storeLastWriteFilter.has_value(); } + int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.value(); } + std::optional activeStatusFilter; + bool has_activeStatusFilter() const { return activeStatusFilter.has_value(); } + Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.value(); } static constexpr int messageTypeId=1; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::DiscoveryQuery); }; @@ -4513,12 +4286,12 @@ namespace Energistics { struct FindDataObjects { Energistics::Etp::v12::Datatypes::Object::ContextInfo context; Energistics::Etp::v12::Datatypes::Object::ContextScopeKind scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; - boost::optional storeLastWriteFilter; - bool has_storeLastWriteFilter() const { return storeLastWriteFilter.is_initialized(); } - int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.get(); } - boost::optional activeStatusFilter; - bool has_activeStatusFilter() const { return activeStatusFilter.is_initialized(); } - Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.get(); } + std::optional storeLastWriteFilter; + bool has_storeLastWriteFilter() const { return storeLastWriteFilter.has_value(); } + int64_t get_storeLastWriteFilter() const { return storeLastWriteFilter.value(); } + std::optional activeStatusFilter; + bool has_activeStatusFilter() const { return activeStatusFilter.has_value(); } + Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind get_activeStatusFilter() const { return activeStatusFilter.value(); } std::string format; static constexpr int messageTypeId=1; static constexpr uint16_t protocolId = static_cast::type>(Energistics::Etp::v12::Datatypes::Protocol::StoreQuery); @@ -4612,12 +4385,12 @@ namespace Energistics { std::string uri; std::vector alternateUris; std::string name; - boost::optional sourceCount; - bool has_sourceCount() const { return sourceCount.is_initialized(); } - int32_t get_sourceCount() const { return sourceCount.get(); } - boost::optional targetCount; - bool has_targetCount() const { return targetCount.is_initialized(); } - int32_t get_targetCount() const { return targetCount.get(); } + std::optional sourceCount; + bool has_sourceCount() const { return sourceCount.has_value(); } + int32_t get_sourceCount() const { return sourceCount.value(); } + std::optional targetCount; + bool has_targetCount() const { return targetCount.has_value(); } + int32_t get_targetCount() const { return targetCount.value(); } int64_t lastChanged = 0; int64_t storeLastWrite = 0; int64_t storeCreated = 0; @@ -4752,9 +4525,9 @@ namespace Energistics { struct DataObject { Energistics::Etp::v12::Datatypes::Object::Resource resource; std::string format; - boost::optional blobId; - bool has_blobId() const { return blobId.is_initialized(); } - Energistics::Etp::v12::Datatypes::Uuid get_blobId() const { return blobId.get(); } + std::optional blobId; + bool has_blobId() const { return blobId.has_value(); } + Energistics::Etp::v12::Datatypes::Uuid get_blobId() const { return blobId.value(); } std::string data; }; } @@ -5008,9 +4781,9 @@ namespace Energistics { namespace Object { struct SupportedType { std::string dataObjectType; - boost::optional objectCount; - bool has_objectCount() const { return objectCount.is_initialized(); } - int32_t get_objectCount() const { return objectCount.get(); } + std::optional objectCount; + bool has_objectCount() const { return objectCount.has_value(); } + int32_t get_objectCount() const { return objectCount.value(); } Energistics::Etp::v12::Datatypes::Object::RelationshipKind relationshipKind = Energistics::Etp::v12::Datatypes::Object::RelationshipKind::Primary; }; } @@ -5032,203 +4805,45 @@ namespace avro { } }; } -namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { - e.encodeUnionIndex(1); - avro::encode(e, v.get()); - } - else { - e.encodeUnionIndex(0); - e.encodeNull(); - } - } - static void decode(Decoder& d, boost::optional& v) { - size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } - switch (n) { - case 0: - { - d.decodeNull(); - v = boost::none; - } - break; - case 1: - { - bool vv; - avro::decode(d, vv); - v.emplace(vv); - } - break; - } - } - }; -} - -namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { - e.encodeUnionIndex(1); - avro::encode(e, v.get()); - } - else { - e.encodeUnionIndex(0); - e.encodeNull(); - } - } - static void decode(Decoder& d, boost::optional& v) { - size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } - switch (n) { - case 0: - { - d.decodeNull(); - v = boost::none; - } - break; - case 1: - { - int32_t vv; - avro::decode(d, vv); - v.emplace(vv); - } - break; - } - } - }; -} - -namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { - e.encodeUnionIndex(1); - avro::encode(e, v.get()); - } - else { - e.encodeUnionIndex(0); - e.encodeNull(); - } - } - static void decode(Decoder& d, boost::optional& v) { - size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } - switch (n) { - case 0: - { - d.decodeNull(); - v = boost::none; - } - break; - case 1: - { - int64_t vv; - avro::decode(d, vv); - v.emplace(vv); - } - break; - } - } - }; -} - -namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { - e.encodeUnionIndex(1); - avro::encode(e, v.get()); - } - else { - e.encodeUnionIndex(0); - e.encodeNull(); - } - } - static void decode(Decoder& d, boost::optional& v) { - size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } - switch (n) { - case 0: - { - d.decodeNull(); - v = boost::none; - } - break; - case 1: - { - Energistics::Etp::v12::Datatypes::Uuid vv; - avro::decode(d, vv); - v.emplace(vv); - } - break; - } - } - }; -} namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { + /** + * codec_traits for Avro optional. + */ + template + struct codec_traits> { + /** + * Encodes a given value. + */ + static void encode(Encoder& e, const std::optional& b) { + if (b) { e.encodeUnionIndex(1); - avro::encode(e, v.get()); + avro::encode(e, b.value()); } else { e.encodeUnionIndex(0); e.encodeNull(); } } - static void decode(Decoder& d, boost::optional& v) { - size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } - switch (n) { - case 0: - { - d.decodeNull(); - v = boost::none; - } - break; - case 1: - { - Energistics::Etp::v12::Datatypes::ErrorInfo vv; - avro::decode(d, vv); - v.emplace(vv); - } - break; - } - } - }; -} -namespace avro { - template<> struct codec_traits> { - static void encode(Encoder& e, boost::optional v) { - if (v) { - e.encodeUnionIndex(1); - avro::encode(e, v.get()); - } - else { - e.encodeUnionIndex(0); - e.encodeNull(); - } - } - static void decode(Decoder& d, boost::optional& v) { + /** + * Decodes into a given value. + */ + static void decode(Decoder& d, std::optional& s) { size_t n = d.decodeUnionIndex(); if (n >= 2) { throw avro::Exception("Union index too big"); } switch (n) { case 0: { d.decodeNull(); - v = boost::none; + s = std::nullopt; } break; case 1: { - Energistics::Etp::v12::Datatypes::Object::ActiveStatusKind vv; - avro::decode(d, vv); - v.emplace(vv); + T t; + avro::decode(d, t); + s.emplace(t); } break; } diff --git a/src/etp/ProtocolHandlers/CoreHandlers.cpp b/src/etp/ProtocolHandlers/CoreHandlers.cpp index 78c301a..4e950c7 100644 --- a/src/etp/ProtocolHandlers/CoreHandlers.cpp +++ b/src/etp/ProtocolHandlers/CoreHandlers.cpp @@ -121,7 +121,7 @@ void CoreHandlers::on_ProtocolException(const Energistics::Etp::v12::Protocol::C { std::cerr << "EXCEPTION received for message_id " << correlationId << std::endl; if (pe.error) { - std::cerr << "Single error code " << pe.error.get().code << " : " << pe.error.get().message << std::endl; + std::cerr << "Single error code " << pe.error.value().code << " : " << pe.error.value().message << std::endl; } else { std::cerr << "One or more error code : " << std::endl; diff --git a/src/etp/ProtocolHandlers/GetFullDataArrayHandlers.h b/src/etp/ProtocolHandlers/GetFullDataArrayHandlers.h index fc36b0d..9d0103f 100644 --- a/src/etp/ProtocolHandlers/GetFullDataArrayHandlers.h +++ b/src/etp/ProtocolHandlers/GetFullDataArrayHandlers.h @@ -84,45 +84,45 @@ namespace ETP_NS if (msg.dataArrays.size() == 1) { auto dataArray = msg.dataArrays.begin()->second; if (dataArray.data.item.idx() == 0) { - Energistics::Etp::v12::Datatypes::ArrayOfBoolean& avroArray = dataArray.data.item.get_ArrayOfBoolean(); + const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& avroArray = dataArray.data.item.get_ArrayOfBoolean(); for (size_t i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } else if (dataArray.data.item.idx() == 1) { - Energistics::Etp::v12::Datatypes::ArrayOfInt& avroArray = dataArray.data.item.get_ArrayOfInt(); + const Energistics::Etp::v12::Datatypes::ArrayOfInt& avroArray = dataArray.data.item.get_ArrayOfInt(); for (size_t i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } else if (dataArray.data.item.idx() == 2) { - Energistics::Etp::v12::Datatypes::ArrayOfLong& avroArray = dataArray.data.item.get_ArrayOfLong(); + const Energistics::Etp::v12::Datatypes::ArrayOfLong& avroArray = dataArray.data.item.get_ArrayOfLong(); for (size_t i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } else if (dataArray.data.item.idx() == 3) { - Energistics::Etp::v12::Datatypes::ArrayOfFloat& avroArray = dataArray.data.item.get_ArrayOfFloat(); + const Energistics::Etp::v12::Datatypes::ArrayOfFloat& avroArray = dataArray.data.item.get_ArrayOfFloat(); for (size_t i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } else if (dataArray.data.item.idx() == 4) { - Energistics::Etp::v12::Datatypes::ArrayOfDouble& avroArray = dataArray.data.item.get_ArrayOfDouble(); + const Energistics::Etp::v12::Datatypes::ArrayOfDouble& avroArray = dataArray.data.item.get_ArrayOfDouble(); for (size_t i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } /* else if (dataArray.data.item.idx() == 5) { - Energistics::Etp::v12::Datatypes::ArrayOfString& avroArray = dataArray.data.item.get_ArrayOfString(); + const Energistics::Etp::v12::Datatypes::ArrayOfString& avroArray = dataArray.data.item.get_ArrayOfString(); for (auto i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } */ else if (dataArray.data.item.idx() == 6) { - std::string& avroValues = dataArray.data.item.get_bytes(); + const std::string& avroValues = dataArray.data.item.get_bytes(); for (size_t i = 0; i < avroValues.size(); ++i) { values[i] = avroValues[i]; } @@ -162,45 +162,45 @@ namespace ETP_NS // Copy from the ETP subarray to the receiving array if (dataArray.data.item.idx() == 0) { - Energistics::Etp::v12::Datatypes::ArrayOfBoolean& avroArray = dataArray.data.item.get_ArrayOfBoolean(); + const Energistics::Etp::v12::Datatypes::ArrayOfBoolean& avroArray = dataArray.data.item.get_ArrayOfBoolean(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroArray.values[i + subarrayOffset]; } } else if (dataArray.data.item.idx() == 1) { - Energistics::Etp::v12::Datatypes::ArrayOfInt& avroArray = dataArray.data.item.get_ArrayOfInt(); + const Energistics::Etp::v12::Datatypes::ArrayOfInt& avroArray = dataArray.data.item.get_ArrayOfInt(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroArray.values[i + subarrayOffset]; } } else if (dataArray.data.item.idx() == 2) { - Energistics::Etp::v12::Datatypes::ArrayOfLong& avroArray = dataArray.data.item.get_ArrayOfLong(); + const Energistics::Etp::v12::Datatypes::ArrayOfLong& avroArray = dataArray.data.item.get_ArrayOfLong(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroArray.values[i + subarrayOffset]; } } else if (dataArray.data.item.idx() == 3) { - Energistics::Etp::v12::Datatypes::ArrayOfFloat& avroArray = dataArray.data.item.get_ArrayOfFloat(); + const Energistics::Etp::v12::Datatypes::ArrayOfFloat& avroArray = dataArray.data.item.get_ArrayOfFloat(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroArray.values[i + subarrayOffset]; } } else if (dataArray.data.item.idx() == 4) { - Energistics::Etp::v12::Datatypes::ArrayOfDouble& avroArray = dataArray.data.item.get_ArrayOfDouble(); + const Energistics::Etp::v12::Datatypes::ArrayOfDouble& avroArray = dataArray.data.item.get_ArrayOfDouble(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroArray.values[i + subarrayOffset]; } } /* else if (dataArray.data.item.idx() == 5) { - Energistics::Etp::v12::Datatypes::ArrayOfString& avroArray = dataArray.data.item.get_ArrayOfString(); + const Energistics::Etp::v12::Datatypes::ArrayOfString& avroArray = dataArray.data.item.get_ArrayOfString(); for (auto i = 0; i < avroArray.values.size(); ++i) { values[i] = avroArray.values[i]; } } */ else if (dataArray.data.item.idx() == 6) { - std::string& avroValues = dataArray.data.item.get_bytes(); + const std::string& avroValues = dataArray.data.item.get_bytes(); for (auto i = 0; i < iterator->second.counts.back(); ++i) { values[i + arrayOffset] = avroValues[i + subarrayOffset]; } diff --git a/src/etp/ProtocolHandlers/ProtocolHandlers.cpp b/src/etp/ProtocolHandlers/ProtocolHandlers.cpp index 91edb4f..6c44b72 100644 --- a/src/etp/ProtocolHandlers/ProtocolHandlers.cpp +++ b/src/etp/ProtocolHandlers/ProtocolHandlers.cpp @@ -37,10 +37,10 @@ void ProtocolHandlers::printDataObject(const Energistics::Etp::v12::Datatypes::O } std::cout << "name : " << dataObject.resource.name << std::endl; if (dataObject.resource.sourceCount) { - std::cout << "source count : " << dataObject.resource.sourceCount.get() << std::endl; + std::cout << "source count : " << dataObject.resource.sourceCount.value() << std::endl; } if (dataObject.resource.targetCount) { - std::cout << "target count : " << dataObject.resource.targetCount.get() << std::endl; + std::cout << "target count : " << dataObject.resource.targetCount.value() << std::endl; } std::cout << "lastChanged : "; diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 4b1d04c..31343c6 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -118,7 +118,7 @@ namespace { static_cast(values), static_cast(values) + totalCount); - data.item.set_ArrayOfDouble(avroArray); + data.item.set_ArrayOfDouble(std::move(avroArray)); } else if (datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::FLOAT) { Energistics::Etp::v12::Datatypes::ArrayOfFloat avroArray; @@ -127,7 +127,7 @@ namespace { static_cast(values), static_cast(values) + totalCount); - data.item.set_ArrayOfFloat(avroArray); + data.item.set_ArrayOfFloat(std::move(avroArray)); } else if (datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::INT64 || datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::UINT64) { @@ -137,7 +137,7 @@ namespace { static_cast(values), static_cast(values) + totalCount); - data.item.set_ArrayOfLong(avroArray); + data.item.set_ArrayOfLong(std::move(avroArray)); } else if (datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::INT32 || datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::UINT32) { @@ -147,7 +147,7 @@ namespace { static_cast(values), static_cast(values) + totalCount); - data.item.set_ArrayOfInt(avroArray); + data.item.set_ArrayOfInt(std::move(avroArray)); } else if (datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::INT16 || datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::UINT16) { @@ -157,7 +157,7 @@ namespace { avroArray.values.push_back(static_cast(values)[i]); } - data.item.set_ArrayOfInt(avroArray); + data.item.set_ArrayOfInt(std::move(avroArray)); } else if (datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::INT8 || datatype == COMMON_NS::AbstractObject::numericalDatatypeEnum::UINT8) { @@ -167,7 +167,7 @@ namespace { avroArray.push_back(static_cast(values)[i]); } - data.item.set_bytes(avroArray); + data.item.set_bytes(std::move(avroArray)); } else { throw logic_error( diff --git a/src/etp/fesapi/FesapiHdfProxy.h b/src/etp/fesapi/FesapiHdfProxy.h index f4d9b5b..59f1a7d 100644 --- a/src/etp/fesapi/FesapiHdfProxy.h +++ b/src/etp/fesapi/FesapiHdfProxy.h @@ -18,14 +18,14 @@ under the License. -----------------------------------------------------------------------*/ #pragma once +#include +#include + #include #include "../AbstractSession.h" #include "../ProtocolHandlers/GetFullDataArrayHandlers.h" -#include -#include - namespace ETP_NS { class FETPAPI_DLL_IMPORT_OR_EXPORT FesapiHdfProxy : public EML2_NS::AbstractHdfProxy From 2463965f4d1850136eb738d191e64a734fcf1dc9 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Fri, 14 Nov 2025 14:38:57 +0100 Subject: [PATCH 27/32] Bump to FESAPI 2.14.0.0 --- .github/workflows/github-actions.yml | 34 ++++++++++++++-------------- cmake/pyproject.toml.in | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index d858544..3ff3e70 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -52,14 +52,14 @@ jobs: cmake --install . - name: FESAPI install run: | - (New-Object System.Net.WebClient).DownloadFile("https://github.com/F2I-Consulting/fesapi/releases/download/v2.13.0.0/fesapi2_13_0_0-cpp-vs2019-x64.zip", "${{ runner.temp }}\fesapi.zip") + (New-Object System.Net.WebClient).DownloadFile("https://github.com/F2I-Consulting/fesapi/releases/download/v2.14.0.0/fesapi2_14_0_0-cpp-vs2019-x64.zip", "${{ runner.temp }}\fesapi.zip") 7z x ${{ runner.temp }}\fesapi.zip -o${{ runner.temp }} - name: CMake build and install run: | cd ${{ github.workspace }}/.. mkdir build cd build - cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi2_13_0_0-cpp-vs2019-x64 -DWITH_ETP_SSL=FALSE ${{ github.workspace }} + cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi2_14_0_0-cpp-vs2019-x64 -DWITH_ETP_SSL=FALSE ${{ github.workspace }} cmake --build . --config Release -j2 ubuntu-22: runs-on: ubuntu-22.04 @@ -148,7 +148,7 @@ jobs: sudo apt install -y ${{ matrix.xcc_pkg }} libhdf5-dev libminizip-dev libboost-all-dev - name: FESAPI install run: | - git clone --branch v2.13.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src + git clone --branch v2.14.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src cd ${{ runner.temp }} mkdir fesapi-build cd fesapi-build @@ -175,7 +175,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.13.0.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} + cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-install -DAVRO_USE_STATIC_LIBS=TRUE -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.14.0.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} cmake --build . --config Release -j2 build_wheels_windows: name: Build wheels on windows-latest @@ -194,12 +194,12 @@ jobs: CIBW_BEFORE_ALL: > %VCPKG_INSTALLATION_ROOT%\vcpkg install boost-uuid minizip hdf5[zlib] && cd ${{ runner.temp }} && - powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.tar.gz', '${{ runner.temp }}\fesapi-2.13.0.0.tar.gz')" && - 7z x ${{ runner.temp }}\fesapi-2.13.0.0.tar.gz -o${{ runner.temp }} && - 7z x ${{ runner.temp }}\fesapi-2.13.0.0.tar -o${{ runner.temp }} && + powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.14.0.0.tar.gz', '${{ runner.temp }}\fesapi-2.14.0.0.tar.gz')" && + 7z x ${{ runner.temp }}\fesapi-2.14.0.0.tar.gz -o${{ runner.temp }} && + 7z x ${{ runner.temp }}\fesapi-2.14.0.0.tar -o${{ runner.temp }} && mkdir fesapi-build && cd fesapi-build && - cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/fesapi-install ${{ runner.temp }}\fesapi-2.13.0.0 && + cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -G"Visual Studio 17 2022" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/fesapi-install ${{ runner.temp }}\fesapi-2.14.0.0 && cmake --build . --config Release -j2 && cmake --build . --config Release --target INSTALL && %VCPKG_INSTALLATION_ROOT%\vcpkg install openssl boost-beast avro-cpp && @@ -243,11 +243,11 @@ jobs: yum install -y epel-release && yum --enablerepo=epel install -y minizip1.2-devel hdf5-devel cmake3 && cd / && - wget https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.zip && - unzip v2.13.0.0.zip && + wget https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.14.0.0.zip && + unzip v2.14.0.0.zip && mkdir fesapi-build && cd fesapi-build && - cmake3 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:STRING=/fesapi-install /fesapi-2.13.0.0 && + cmake3 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:STRING=/fesapi-install /fesapi-2.14.0.0 && cmake3 --build . -j2 --config Release && cmake3 --install . && cd / && @@ -296,9 +296,9 @@ jobs: # Dont use brew for dependencies https://github.com/pypa/cibuildwheel/issues/1251#issuecomment-1234553537 CIBW_BEFORE_ALL: > cd ${{ github.workspace }}/.. && - wget --no-verbose https://archives.boost.io/release/1.88.0/source/boost_1_88_0.tar.gz && - tar xf boost_1_88_0.tar.gz && - cd boost_1_88_0 && + wget --no-verbose https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz && + tar xf boost_1_87_0.tar.gz && + cd boost_1_87_0 && ./bootstrap.sh --prefix=${{ github.workspace }}/../boost-install --with-libraries=filesystem,iostreams,program_options,regex,system && ./b2 -d0 install && git clone https://github.com/F2I-Consulting/Minizip.git ${{ github.workspace }}/../minizip && @@ -316,11 +316,11 @@ jobs: cmake --build . -j2 --config Release && cmake --install . && cd ${{ github.workspace }}/.. && - wget --no-verbose https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.13.0.0.zip && - unzip v2.13.0.0.zip && + wget --no-verbose https://github.com/F2I-Consulting/fesapi/archive/refs/tags/v2.14.0.0.zip && + unzip v2.14.0.0.zip && mkdir fesapi-build && cd fesapi-build && - cmake -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=${{ github.workspace }}/../boost-install -DMINIZIP_ROOT=${{ github.workspace }}/../minizip-install -DHDF5_ROOT=${{ github.workspace }}/../hdf5-install -DHDF5_USE_STATIC_LIBRARIES=TRUE -DCMAKE_INSTALL_PREFIX:STRING=${{ github.workspace }}/../fesapi-install ${{ github.workspace }}/../fesapi-2.13.0.0 && + cmake -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=${{ github.workspace }}/../boost-install -DMINIZIP_ROOT=${{ github.workspace }}/../minizip-install -DHDF5_ROOT=${{ github.workspace }}/../hdf5-install -DHDF5_USE_STATIC_LIBRARIES=TRUE -DCMAKE_INSTALL_PREFIX:STRING=${{ github.workspace }}/../fesapi-install ${{ github.workspace }}/../fesapi-2.14.0.0 && cmake --build . -j2 --config Release && cmake --install . && cd ${{ github.workspace }}/.. && diff --git a/cmake/pyproject.toml.in b/cmake/pyproject.toml.in index cba1699..ffe9606 100644 --- a/cmake/pyproject.toml.in +++ b/cmake/pyproject.toml.in @@ -43,7 +43,7 @@ keywords = [ ] requires-python = ">=3.8" dependencies = [ - 'fesapi==2.13.0', + 'fesapi==2.14.0', ] [project.urls] From 97bedb6a3118d05d30924a9d7cbea8f37ec2bd61 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 19 Nov 2025 11:01:32 +0100 Subject: [PATCH 28/32] [Python] Cast SWIG arrays when using them as parameters --- python/example/PutHorizon.ipynb | 2 +- python/example/etp_client_example.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/example/PutHorizon.ipynb b/python/example/PutHorizon.ipynb index 6a32360..96ebd41 100644 --- a/python/example/PutHorizon.ipynb +++ b/python/example/PutHorizon.ipynb @@ -266,7 +266,7 @@ "resqml_points = fesapi.DoubleArray(6)\n", "for i in range(6):\n", " resqml_points.setitem(i, i*100)\n", - "horizon_grid_2d_representation.setGeometryAsArray2dOfExplicitZ(resqml_points, 2, 3, hdf_proxy,\n", + "horizon_grid_2d_representation.setGeometryAsArray2dOfExplicitZ(resqml_points.cast(), 2, 3, hdf_proxy,\n", " 0.0, 0.0, 0.0,\n", " 1.0, 0.0, 0.0, 25.0,\n", " 0.0, 1.0, 0.0, 50.0)" diff --git a/python/example/etp_client_example.py b/python/example/etp_client_example.py index 44a2f62..3091caa 100644 --- a/python/example/etp_client_example.py +++ b/python/example/etp_client_example.py @@ -79,7 +79,7 @@ def start_etp_server(client_session): nb_xyz_points = ijk_grid.getXyzPointCountOfAllPatches() print("XYZ points count :", nb_xyz_points) xyz_points = fesapi.DoubleArray(nb_xyz_points * 3) - ijk_grid.getXyzPointsOfAllPatches(xyz_points) + ijk_grid.getXyzPointsOfAllPatches(xyz_points.cast()) ijk_grid.loadSplitInformation() origin_index = ijk_grid.getXyzPointIndexFromCellCorner(0, 0, 0, 0) @@ -94,7 +94,7 @@ def start_etp_server(client_session): if isinstance(prop, fesapi.Resqml2_ContinuousProperty): prop_values = fesapi.DoubleArray(ijk_grid.getICellCount() * ijk_grid.getJCellCount() * ijk_grid.getKCellCount()) - prop.getDoubleValuesOfPatch(0, prop_values) + prop.getDoubleValuesOfPatch(0, prop_values.cast()) print("Cell 0,0,0 has prop value ", prop_values.getitem(0)) print("Cell 1,0,0 has prop value ", prop_values.getitem(1)) print("Cell 2,0,0 has prop value ", prop_values.getitem(2)) @@ -116,7 +116,7 @@ def start_etp_server(client_session): nb_z_points = grid2d.getNodeCountAlongIAxis() * grid2d.getNodeCountAlongJAxis() print(f"XYZ points count : {nb_z_points}") z_points = fesapi.DoubleArray(nb_z_points) - grid2d.getZValues(z_points) + grid2d.getZValues(z_points.cast()) print("Z value at index 0 : ", z_points.getitem(0)) print("Z value at index 1 : ", z_points.getitem(1)) From bd4e72d7d579927922b72b22b78e32dc79153995 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 19 Nov 2025 11:08:18 +0100 Subject: [PATCH 29/32] In place construction when decoding std::optional Add missing header. Improve error message. Document expected corresponding schema. Prefere optional.reset() over optional = std::nullopt --- src/etp/EtpMessages.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/etp/EtpMessages.h b/src/etp/EtpMessages.h index 9682a94..ae35941 100644 --- a/src/etp/EtpMessages.h +++ b/src/etp/EtpMessages.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace Energistics { namespace Etp { @@ -4808,7 +4809,7 @@ namespace avro { namespace avro { /** - * codec_traits for Avro optional. + * codec_traits for Avro optional assumming that the schema is ["null", T]. */ template struct codec_traits> { @@ -4831,19 +4832,18 @@ namespace avro { */ static void decode(Decoder& d, std::optional& s) { size_t n = d.decodeUnionIndex(); - if (n >= 2) { throw avro::Exception("Union index too big"); } + if (n >= 2) { throw avro::Exception("Union index too big for optional (expected 0 or 1, got " + std::to_string(n) + ")"); } switch (n) { case 0: { d.decodeNull(); - s = std::nullopt; + s.reset(); } break; case 1: { - T t; - avro::decode(d, t); - s.emplace(t); + s.emplace(); + avro::decode(d, *s); } break; } From a349ab21ce782f5e2e1d77cd6b13f28ac44fec4b Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 19 Nov 2025 11:41:27 +0100 Subject: [PATCH 30/32] Do no more cerr closing messages by default --- src/etp/AbstractSession.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index 6733725..9409afd 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -48,7 +48,12 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr if (ec == websocket::error::closed) { // This indicates that the web socket (and consequently etp) session was closed - std::cerr << "The other endpoint closed the web socket (and consequently etp) connection" << std::endl; + if (etpSessionClosed) { + fesapi_log("The other endpoint closed the web socket (and consequently etp) connection in a graceful way."); + } + else { + std::cerr << "The other endpoint closed the web socket(and consequently etp) connection in a graceful way." << std::endl; + } } #if BOOST_VERSION > 106900 else if (ec == boost::beast::error::timeout) { @@ -57,13 +62,15 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr } #endif else { - // This indicates an unexpected error - std::cerr << "on_read : error code number " << ec.value() << std::endl; - std::cerr << "on_read : error message " << ec.message() << std::endl; - std::cerr << "on_read : error category " << ec.category().name() << std::endl; - // This error may be a common one to ignore in case of SSL short read : https://github.com/boostorg/beast/issues/824 if (etpSessionClosed) { - std::cerr << "It looks that the other endpoint has closed the websocket session in a non graceful way" << std::endl; + // This error may be a common one to ignore in case of SSL short read : https://github.com/boostorg/beast/issues/824 + fesapi_log("It looks that the other endpoint has closed the websocket session in a non graceful way."); + } + else { + // This indicates an unexpected error + std::cerr << "on_read : error code number " << ec.value() << std::endl; + std::cerr << "on_read : error message " << ec.message() << std::endl; + std::cerr << "on_read : error category " << ec.category().name() << std::endl; } } @@ -149,7 +156,7 @@ void AbstractSession::on_read(boost::system::error_code ec, std::size_t bytes_tr normalProtocolHandlerIt->second->decodeMessageBody(receivedMh, d); } else { - std::cerr << "Received a message with id " << receivedMh.messageId << " for which non protocol handlers is associated. Protocol " << receivedMhProtocol << std::endl; + std::cerr << "Received a message with id " << receivedMh.messageId << " for which no protocol handler is associated. Protocol " << receivedMhProtocol << std::endl; send(ETP_NS::EtpHelpers::buildSingleMessageProtocolException(4, "The agent does not support the protocol " + std::to_string(receivedMhProtocol) + " identified in a message header."), receivedMh.messageId, 0x02); } } From ed4995ed59e6ade50346071b16d38abb95201f3f Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Mon, 24 Nov 2025 11:26:44 +0100 Subject: [PATCH 31/32] Drop dependency on date.h Deleted src/tools/date.h and its CMakeLists.txt, removed references to the custom date utility from build scripts and source files, and replaced date formatting with direct timestamp output in ProtocolHandlers and StoreNotificationHandlers. This simplifies the codebase by removing an external dependency and using raw timestamps instead. --- CMakeLists.txt | 9 - src/CMakeLists.txt | 1 - src/etp/ProtocolHandlers/ProtocolHandlers.cpp | 7 +- .../StoreNotificationHandlers.cpp | 5 +- src/tools/CMakeLists.txt | 1 - src/tools/date.h | 8245 ----------------- 6 files changed, 3 insertions(+), 8265 deletions(-) delete mode 100644 src/tools/CMakeLists.txt delete mode 100644 src/tools/date.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db8bd2c..d2b79bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,7 +200,6 @@ set (ALL_SOURCES_AND_HEADERS ${FETPAPI_HEADERS} ${FETPAPI_PROTOCOL_SOURCES} ${FETPAPI_PROTOCOL_HEADERS} - ${FETPAPI_TOOLS_HEADERS} ${FETPAPI_FESAPI_SOURCES} ${FETPAPI_FESAPI_HEADERS} ${FETPAPI_SSL_SOURCES} @@ -224,8 +223,6 @@ target_include_directories(${PROJECT_NAME} INTERFACE # organizing sources and headers in the Visual Studio Project if (WIN32) - source_group ("tools" FILES ${FETPAPI_TOOLS_HEADERS}) - set (ETP_PREFIX "etp") source_group ("${ETP_PREFIX}" FILES ${FETPAPI_SOURCES} ${FETPAPI_HEADERS}) source_group ("${ETP_PREFIX}\\ProtocolHandlers" FILES ${FETPAPI_PROTOCOL_SOURCES} ${FETPAPI_PROTOCOL_HEADERS}) @@ -307,12 +304,6 @@ INSTALL ( COMPONENT fetpapi_headers ) -INSTALL ( - FILES ${FETPAPI_TOOLS_HEADERS} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fetpapi/tools - COMPONENT fetpapi_headers -) - if (WITH_ETP_SSL) INSTALL ( FILES ${FETPAPI_SSL_HEADERS} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 574b0b3..2657944 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,3 @@ set(FESAPI_TOOLS_HEADERS ${CMAKE_CURRENT_LIST_DIR}/nsDefinitions.h ) include(${CMAKE_CURRENT_LIST_DIR}/etp/CMakeLists.txt) -include(${CMAKE_CURRENT_LIST_DIR}/tools/CMakeLists.txt) diff --git a/src/etp/ProtocolHandlers/ProtocolHandlers.cpp b/src/etp/ProtocolHandlers/ProtocolHandlers.cpp index 6c44b72..66fe4bf 100644 --- a/src/etp/ProtocolHandlers/ProtocolHandlers.cpp +++ b/src/etp/ProtocolHandlers/ProtocolHandlers.cpp @@ -21,8 +21,6 @@ under the License. #include "../AbstractSession.h" #include "../EtpHelpers.h" -#include "../../tools/date.h" - using namespace ETP_NS; void ProtocolHandlers::printDataObject(const Energistics::Etp::v12::Datatypes::Object::DataObject & dataObject) @@ -43,10 +41,9 @@ void ProtocolHandlers::printDataObject(const Energistics::Etp::v12::Datatypes::O std::cout << "target count : " << dataObject.resource.targetCount.value() << std::endl; } - std::cout << "lastChanged : "; + std::cout << "lastChanged timestamp : "; if (dataObject.resource.lastChanged >= 0) { - auto duration = std::chrono::microseconds(dataObject.resource.lastChanged); - std::cout << date::format("%FT%TZ", date::floor(duration)); + std::cout << dataObject.resource.lastChanged; } else { std::cout << "unknown"; diff --git a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp index 0b2c79e..4bf4055 100644 --- a/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp +++ b/src/etp/ProtocolHandlers/StoreNotificationHandlers.cpp @@ -21,8 +21,6 @@ under the License. #include "../AbstractSession.h" #include "../EtpHelpers.h" -#include "../../tools/date.h" - using namespace ETP_NS; void StoreNotificationHandlers::decodeMessageBody(const Energistics::Etp::v12::Datatypes::MessageHeader & mh, avro::DecoderPtr d) @@ -128,8 +126,7 @@ void StoreNotificationHandlers::on_ObjectChanged(const Energistics::Etp::v12::Pr case Energistics::Etp::v12::Datatypes::Object::ObjectChangeKind::unjoinedSubscription: session->fesapi_log("unjoinedSubscription"); break; } - auto duration = std::chrono::microseconds(msg.change.changeTime); - session->fesapi_log("on", date::format("%FT%TZ", date::floor(duration))); + session->fesapi_log("on timestamp ", msg.change.changeTime); printDataObject(msg.change.dataObject); } diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt deleted file mode 100644 index b46449c..0000000 --- a/src/tools/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -set(FETPAPI_TOOLS_HEADERS ${CMAKE_CURRENT_LIST_DIR}/date.h ) diff --git a/src/tools/date.h b/src/tools/date.h deleted file mode 100644 index beb627e..0000000 --- a/src/tools/date.h +++ /dev/null @@ -1,8245 +0,0 @@ -#ifndef DATE_H -#define DATE_H - -// The MIT License (MIT) -// -// Copyright (c) 2015, 2016, 2017 Howard Hinnant -// Copyright (c) 2016 Adrian Colomitchi -// Copyright (c) 2017 Florian Dang -// Copyright (c) 2017 Paul Thompson -// Copyright (c) 2018, 2019 Tomasz Kamiński -// Copyright (c) 2019 Jiangang Zhuang -// -// 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. -// -// Our apologies. When the previous paragraph was written, lowercase had not yet -// been invented (that would involve another several millennia of evolution). -// We did not mean to shout. - -#ifndef HAS_STRING_VIEW -# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define HAS_STRING_VIEW 1 -# else -# define HAS_STRING_VIEW 0 -# endif -#endif // HAS_STRING_VIEW - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAS_STRING_VIEW -# include -#endif -#include -#include - -#ifdef __GNUC__ -# pragma GCC diagnostic push -# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) -# pragma GCC diagnostic ignored "-Wpedantic" -# endif -# if __GNUC__ < 5 - // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -# endif -#endif - -#ifdef _MSC_VER -# pragma warning(push) -// warning C4127: conditional expression is constant -# pragma warning(disable : 4127) -#endif - -namespace date -{ - -//---------------+ -// Configuration | -//---------------+ - -#ifndef ONLY_C_LOCALE -# define ONLY_C_LOCALE 0 -#endif - -#if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910)) -// MSVC -# ifndef _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING -# define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING -# endif -# if _MSC_VER < 1910 -// before VS2017 -# define CONSTDATA const -# define CONSTCD11 -# define CONSTCD14 -# define NOEXCEPT _NOEXCEPT -# else -// VS2017 and later -# define CONSTDATA constexpr const -# define CONSTCD11 constexpr -# define CONSTCD14 constexpr -# define NOEXCEPT noexcept -# endif - -#elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150 -// Oracle Developer Studio 12.6 and earlier -# define CONSTDATA constexpr const -# define CONSTCD11 constexpr -# define CONSTCD14 -# define NOEXCEPT noexcept - -#elif __cplusplus >= 201402 -// C++14 -# define CONSTDATA constexpr const -# define CONSTCD11 constexpr -# define CONSTCD14 constexpr -# define NOEXCEPT noexcept -#else -// C++11 -# define CONSTDATA constexpr const -# define CONSTCD11 constexpr -# define CONSTCD14 -# define NOEXCEPT noexcept -#endif - -#ifndef HAS_UNCAUGHT_EXCEPTIONS -# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define HAS_UNCAUGHT_EXCEPTIONS 1 -# else -# define HAS_UNCAUGHT_EXCEPTIONS 0 -# endif -#endif // HAS_UNCAUGHT_EXCEPTIONS - -#ifndef HAS_VOID_T -# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define HAS_VOID_T 1 -# else -# define HAS_VOID_T 0 -# endif -#endif // HAS_VOID_T - -// Protect from Oracle sun macro -#ifdef sun -# undef sun -#endif - -// Work around for a NVCC compiler bug which causes it to fail -// to compile std::ratio_{multiply,divide} when used directly -// in the std::chrono::duration template instantiations below -namespace detail { -template -using ratio_multiply = decltype(std::ratio_multiply{}); - -template -using ratio_divide = decltype(std::ratio_divide{}); -} // namespace detail - -//-----------+ -// Interface | -//-----------+ - -// durations - -using days = std::chrono::duration - , std::chrono::hours::period>>; - -using weeks = std::chrono::duration - , days::period>>; - -using years = std::chrono::duration - , days::period>>; - -using months = std::chrono::duration - >>; - -// time_point - -template - using sys_time = std::chrono::time_point; - -using sys_days = sys_time; -using sys_seconds = sys_time; - -struct local_t {}; - -template - using local_time = std::chrono::time_point; - -using local_seconds = local_time; -using local_days = local_time; - -// types - -struct last_spec -{ - explicit last_spec() = default; -}; - -class day; -class month; -class year; - -class weekday; -class weekday_indexed; -class weekday_last; - -class month_day; -class month_day_last; -class month_weekday; -class month_weekday_last; - -class year_month; - -class year_month_day; -class year_month_day_last; -class year_month_weekday; -class year_month_weekday_last; - -// date composition operators - -CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; -CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; - -CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; -CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; -CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; -CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; -CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; - -CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; -CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; -CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; -CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; - -CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; -CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; -CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; -CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; - -CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; -CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; -CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; -CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; - -CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; -CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; -CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; -CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; -CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; -CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; - -CONSTCD11 - year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; -CONSTCD11 - year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; -CONSTCD11 - year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; -CONSTCD11 - year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; -CONSTCD11 - year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator/(const year& y, const month_weekday& mwd) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator/(int y, const month_weekday& mwd) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator/(const month_weekday& mwd, const year& y) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator/(const month_weekday& mwd, int y) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; - -// Detailed interface - -// day - -class day -{ - unsigned char d_; - -public: - day() = default; - explicit CONSTCD11 day(unsigned d) NOEXCEPT; - - CONSTCD14 day& operator++() NOEXCEPT; - CONSTCD14 day operator++(int) NOEXCEPT; - CONSTCD14 day& operator--() NOEXCEPT; - CONSTCD14 day operator--(int) NOEXCEPT; - - CONSTCD14 day& operator+=(const days& d) NOEXCEPT; - CONSTCD14 day& operator-=(const days& d) NOEXCEPT; - - CONSTCD11 explicit operator unsigned() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; -CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; -CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; -CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; -CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; -CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; - -CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; -CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; -CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; -CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const day& d); - -// month - -class month -{ - unsigned char m_; - -public: - month() = default; - explicit CONSTCD11 month(unsigned m) NOEXCEPT; - - CONSTCD14 month& operator++() NOEXCEPT; - CONSTCD14 month operator++(int) NOEXCEPT; - CONSTCD14 month& operator--() NOEXCEPT; - CONSTCD14 month operator--(int) NOEXCEPT; - - CONSTCD14 month& operator+=(const months& m) NOEXCEPT; - CONSTCD14 month& operator-=(const months& m) NOEXCEPT; - - CONSTCD11 explicit operator unsigned() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; -CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; -CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; -CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; -CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; -CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; - -CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; -CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; -CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; -CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const month& m); - -// year - -class year -{ - short y_; - -public: - year() = default; - explicit CONSTCD11 year(int y) NOEXCEPT; - - CONSTCD14 year& operator++() NOEXCEPT; - CONSTCD14 year operator++(int) NOEXCEPT; - CONSTCD14 year& operator--() NOEXCEPT; - CONSTCD14 year operator--(int) NOEXCEPT; - - CONSTCD14 year& operator+=(const years& y) NOEXCEPT; - CONSTCD14 year& operator-=(const years& y) NOEXCEPT; - - CONSTCD11 year operator-() const NOEXCEPT; - CONSTCD11 year operator+() const NOEXCEPT; - - CONSTCD11 bool is_leap() const NOEXCEPT; - - CONSTCD11 explicit operator int() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; - - static CONSTCD11 year min() NOEXCEPT { return year{-32767}; } - static CONSTCD11 year max() NOEXCEPT { return year{32767}; } -}; - -CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; -CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; -CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; -CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; -CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; -CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; - -CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; -CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; -CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; -CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year& y); - -// weekday - -class weekday -{ - unsigned char wd_; -public: - weekday() = default; - explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; - CONSTCD14 weekday(const sys_days& dp) NOEXCEPT; - CONSTCD14 explicit weekday(const local_days& dp) NOEXCEPT; - - CONSTCD14 weekday& operator++() NOEXCEPT; - CONSTCD14 weekday operator++(int) NOEXCEPT; - CONSTCD14 weekday& operator--() NOEXCEPT; - CONSTCD14 weekday operator--(int) NOEXCEPT; - - CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; - CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; - - CONSTCD11 bool ok() const NOEXCEPT; - - CONSTCD11 unsigned c_encoding() const NOEXCEPT; - CONSTCD11 unsigned iso_encoding() const NOEXCEPT; - - CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; - CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; - -private: - static CONSTCD14 unsigned char weekday_from_days(int z) NOEXCEPT; - - friend CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; - friend CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; - friend CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; - template - friend std::basic_ostream& - operator<<(std::basic_ostream& os, const weekday& wd); - friend class weekday_indexed; -}; - -CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; -CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; - -CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; -CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; -CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; -CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday& wd); - -// weekday_indexed - -class weekday_indexed -{ - unsigned char wd_ : 4; - unsigned char index_ : 4; - -public: - weekday_indexed() = default; - CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT; - - CONSTCD11 date::weekday weekday() const NOEXCEPT; - CONSTCD11 unsigned index() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; -CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday_indexed& wdi); - -// weekday_last - -class weekday_last -{ - date::weekday wd_; - -public: - explicit CONSTCD11 weekday_last(const date::weekday& wd) NOEXCEPT; - - CONSTCD11 date::weekday weekday() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; -CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday_last& wdl); - -namespace detail -{ - -struct unspecified_month_disambiguator {}; - -} // namespace detail - -// year_month - -class year_month -{ - date::year y_; - date::month m_; - -public: - year_month() = default; - CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT; - - CONSTCD11 date::year year() const NOEXCEPT; - CONSTCD11 date::month month() const NOEXCEPT; - - template - CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; - template - CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; - CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; - CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; - - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; - -template -CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; -template -CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; -template -CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; - -CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; -CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; -CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; -CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month& ym); - -// month_day - -class month_day -{ - date::month m_; - date::day d_; - -public: - month_day() = default; - CONSTCD11 month_day(const date::month& m, const date::day& d) NOEXCEPT; - - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::day day() const NOEXCEPT; - - CONSTCD14 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; -CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; -CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; -CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; -CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; -CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_day& md); - -// month_day_last - -class month_day_last -{ - date::month m_; - -public: - CONSTCD11 explicit month_day_last(const date::month& m) NOEXCEPT; - - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; -CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; -CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; -CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; -CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; -CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_day_last& mdl); - -// month_weekday - -class month_weekday -{ - date::month m_; - date::weekday_indexed wdi_; -public: - CONSTCD11 month_weekday(const date::month& m, - const date::weekday_indexed& wdi) NOEXCEPT; - - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; - - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; -CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_weekday& mwd); - -// month_weekday_last - -class month_weekday_last -{ - date::month m_; - date::weekday_last wdl_; - -public: - CONSTCD11 month_weekday_last(const date::month& m, - const date::weekday_last& wd) NOEXCEPT; - - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; - - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 - bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; -CONSTCD11 - bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); - -// class year_month_day - -class year_month_day -{ - date::year y_; - date::month m_; - date::day d_; - -public: - year_month_day() = default; - CONSTCD11 year_month_day(const date::year& y, const date::month& m, - const date::day& d) NOEXCEPT; - CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; - - CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; - CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; - - template - CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; - template - CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; - CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; - CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; - - CONSTCD11 date::year year() const NOEXCEPT; - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::day day() const NOEXCEPT; - - CONSTCD14 operator sys_days() const NOEXCEPT; - CONSTCD14 explicit operator local_days() const NOEXCEPT; - CONSTCD14 bool ok() const NOEXCEPT; - -private: - static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; - CONSTCD14 days to_days() const NOEXCEPT; -}; - -CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; -CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; -CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; -CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; -CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; -CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; - -template -CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; -template -CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; -template -CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; -CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; -CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; -CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_day& ymd); - -// year_month_day_last - -class year_month_day_last -{ - date::year y_; - date::month_day_last mdl_; - -public: - CONSTCD11 year_month_day_last(const date::year& y, - const date::month_day_last& mdl) NOEXCEPT; - - template - CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; - template - CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; - CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; - CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; - - CONSTCD11 date::year year() const NOEXCEPT; - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; - CONSTCD14 date::day day() const NOEXCEPT; - - CONSTCD14 operator sys_days() const NOEXCEPT; - CONSTCD14 explicit operator local_days() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; -}; - -CONSTCD11 - bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; -CONSTCD11 - bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; -CONSTCD11 - bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; -CONSTCD11 - bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; -CONSTCD11 - bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; -CONSTCD11 - bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; - -template -CONSTCD14 -year_month_day_last -operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; - -template -CONSTCD14 -year_month_day_last -operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; - -CONSTCD11 -year_month_day_last -operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; - -CONSTCD11 -year_month_day_last -operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; - -template -CONSTCD14 -year_month_day_last -operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; - -CONSTCD11 -year_month_day_last -operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); - -// year_month_weekday - -class year_month_weekday -{ - date::year y_; - date::month m_; - date::weekday_indexed wdi_; - -public: - year_month_weekday() = default; - CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, - const date::weekday_indexed& wdi) NOEXCEPT; - CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; - CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; - - template - CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; - template - CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; - CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; - CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; - - CONSTCD11 date::year year() const NOEXCEPT; - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::weekday weekday() const NOEXCEPT; - CONSTCD11 unsigned index() const NOEXCEPT; - CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; - - CONSTCD14 operator sys_days() const NOEXCEPT; - CONSTCD14 explicit operator local_days() const NOEXCEPT; - CONSTCD14 bool ok() const NOEXCEPT; - -private: - static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; - CONSTCD14 days to_days() const NOEXCEPT; -}; - -CONSTCD11 - bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; -CONSTCD11 - bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday -operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday -operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday -operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; - -CONSTCD11 -year_month_weekday -operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); - -// year_month_weekday_last - -class year_month_weekday_last -{ - date::year y_; - date::month m_; - date::weekday_last wdl_; - -public: - CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m, - const date::weekday_last& wdl) NOEXCEPT; - - template - CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; - template - CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; - CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; - CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; - - CONSTCD11 date::year year() const NOEXCEPT; - CONSTCD11 date::month month() const NOEXCEPT; - CONSTCD11 date::weekday weekday() const NOEXCEPT; - CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; - - CONSTCD14 operator sys_days() const NOEXCEPT; - CONSTCD14 explicit operator local_days() const NOEXCEPT; - CONSTCD11 bool ok() const NOEXCEPT; - -private: - CONSTCD14 days to_days() const NOEXCEPT; -}; - -CONSTCD11 -bool -operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; - -CONSTCD11 -bool -operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday_last -operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday_last -operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; - -template -CONSTCD14 -year_month_weekday_last -operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; - -CONSTCD11 -year_month_weekday_last -operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; - -template -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); - -#if !defined(_MSC_VER) || (_MSC_VER >= 1900) -inline namespace literals -{ - -CONSTCD11 date::day operator "" _d(unsigned long long d) NOEXCEPT; -CONSTCD11 date::year operator "" _y(unsigned long long y) NOEXCEPT; - -} // inline namespace literals -#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) - -// CONSTDATA date::month January{1}; -// CONSTDATA date::month February{2}; -// CONSTDATA date::month March{3}; -// CONSTDATA date::month April{4}; -// CONSTDATA date::month May{5}; -// CONSTDATA date::month June{6}; -// CONSTDATA date::month July{7}; -// CONSTDATA date::month August{8}; -// CONSTDATA date::month September{9}; -// CONSTDATA date::month October{10}; -// CONSTDATA date::month November{11}; -// CONSTDATA date::month December{12}; -// -// CONSTDATA date::weekday Sunday{0u}; -// CONSTDATA date::weekday Monday{1u}; -// CONSTDATA date::weekday Tuesday{2u}; -// CONSTDATA date::weekday Wednesday{3u}; -// CONSTDATA date::weekday Thursday{4u}; -// CONSTDATA date::weekday Friday{5u}; -// CONSTDATA date::weekday Saturday{6u}; - -#if HAS_VOID_T - -template > -struct is_clock - : std::false_type -{}; - -template -struct is_clock> - : std::true_type -{}; - -template inline constexpr bool is_clock_v = is_clock::value; - -#endif // HAS_VOID_T - -//----------------+ -// Implementation | -//----------------+ - -// utilities -namespace detail { - -template> -class save_istream -{ -protected: - std::basic_ios& is_; - CharT fill_; - std::ios::fmtflags flags_; - std::streamsize precision_; - std::streamsize width_; - std::basic_ostream* tie_; - std::locale loc_; - -public: - ~save_istream() - { - is_.fill(fill_); - is_.flags(flags_); - is_.precision(precision_); - is_.width(width_); - is_.imbue(loc_); - is_.tie(tie_); - } - - save_istream(const save_istream&) = delete; - save_istream& operator=(const save_istream&) = delete; - - explicit save_istream(std::basic_ios& is) - : is_(is) - , fill_(is.fill()) - , flags_(is.flags()) - , precision_(is.precision()) - , width_(is.width(0)) - , tie_(is.tie(nullptr)) - , loc_(is.getloc()) - { - if (tie_ != nullptr) - tie_->flush(); - } -}; - -template> -class save_ostream - : private save_istream -{ -public: - ~save_ostream() - { - if ((this->flags_ & std::ios::unitbuf) && -#if HAS_UNCAUGHT_EXCEPTIONS - std::uncaught_exceptions() == 0 && -#else - !std::uncaught_exception() && -#endif - this->is_.good()) - this->is_.rdbuf()->pubsync(); - } - - save_ostream(const save_ostream&) = delete; - save_ostream& operator=(const save_ostream&) = delete; - - explicit save_ostream(std::basic_ios& os) - : save_istream(os) - { - } -}; - -template -struct choose_trunc_type -{ - static const int digits = std::numeric_limits::digits; - using type = typename std::conditional - < - digits < 32, - std::int32_t, - typename std::conditional - < - digits < 64, - std::int64_t, -#ifdef __SIZEOF_INT128__ - __int128 -#else - std::int64_t -#endif - >::type - >::type; -}; - -template -CONSTCD11 -inline -typename std::enable_if -< - !std::chrono::treat_as_floating_point::value, - T ->::type -trunc(T t) NOEXCEPT -{ - return t; -} - -template -CONSTCD14 -inline -typename std::enable_if -< - std::chrono::treat_as_floating_point::value, - T ->::type -trunc(T t) NOEXCEPT -{ - using std::numeric_limits; - using I = typename choose_trunc_type::type; - CONSTDATA auto digits = numeric_limits::digits; - static_assert(digits < numeric_limits::digits, ""); - CONSTDATA auto max = I{1} << (digits-1); - CONSTDATA auto min = -max; - const auto negative = t < T{0}; - if (min <= t && t <= max && t != 0 && t == t) - { - t = static_cast(static_cast(t)); - if (t == 0 && negative) - t = -t; - } - return t; -} - -template -struct static_gcd -{ - static const std::intmax_t value = static_gcd::value; -}; - -template -struct static_gcd -{ - static const std::intmax_t value = Xp; -}; - -template <> -struct static_gcd<0, 0> -{ - static const std::intmax_t value = 1; -}; - -template -struct no_overflow -{ -private: - static const std::intmax_t gcd_n1_n2 = static_gcd::value; - static const std::intmax_t gcd_d1_d2 = static_gcd::value; - static const std::intmax_t n1 = R1::num / gcd_n1_n2; - static const std::intmax_t d1 = R1::den / gcd_d1_d2; - static const std::intmax_t n2 = R2::num / gcd_n1_n2; - static const std::intmax_t d2 = R2::den / gcd_d1_d2; -#ifdef __cpp_constexpr - static const std::intmax_t max = std::numeric_limits::max(); -#else - static const std::intmax_t max = LLONG_MAX; -#endif - - template - struct mul // overflow == false - { - static const std::intmax_t value = Xp * Yp; - }; - - template - struct mul - { - static const std::intmax_t value = 1; - }; - -public: - static const bool value = (n1 <= max / d2) && (n2 <= max / d1); - typedef std::ratio::value, - mul::value> type; -}; - -} // detail - -// trunc towards zero -template -CONSTCD11 -inline -typename std::enable_if -< - detail::no_overflow::value, - To ->::type -trunc(const std::chrono::duration& d) -{ - return To{detail::trunc(std::chrono::duration_cast(d).count())}; -} - -template -CONSTCD11 -inline -typename std::enable_if -< - !detail::no_overflow::value, - To ->::type -trunc(const std::chrono::duration& d) -{ - using std::chrono::duration_cast; - using std::chrono::duration; - using rep = typename std::common_type::type; - return To{detail::trunc(duration_cast(duration_cast>(d)).count())}; -} - -#ifndef HAS_CHRONO_ROUNDING -# if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023918 || (_MSC_FULL_VER >= 190000000 && defined (__clang__))) -# define HAS_CHRONO_ROUNDING 1 -# elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510 -# define HAS_CHRONO_ROUNDING 1 -# elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800 -# define HAS_CHRONO_ROUNDING 1 -# else -# define HAS_CHRONO_ROUNDING 0 -# endif -#endif // HAS_CHRONO_ROUNDING - -#if HAS_CHRONO_ROUNDING == 0 - -// round down -template -CONSTCD14 -inline -typename std::enable_if -< - detail::no_overflow::value, - To ->::type -floor(const std::chrono::duration& d) -{ - auto t = trunc(d); - if (t > d) - return t - To{1}; - return t; -} - -template -CONSTCD14 -inline -typename std::enable_if -< - !detail::no_overflow::value, - To ->::type -floor(const std::chrono::duration& d) -{ - using rep = typename std::common_type::type; - return floor(floor>(d)); -} - -// round to nearest, to even on tie -template -CONSTCD14 -inline -To -round(const std::chrono::duration& d) -{ - auto t0 = floor(d); - auto t1 = t0 + To{1}; - if (t1 == To{0} && t0 < To{0}) - t1 = -t1; - auto diff0 = d - t0; - auto diff1 = t1 - d; - if (diff0 == diff1) - { - if (t0 - trunc(t0/2)*2 == To{0}) - return t0; - return t1; - } - if (diff0 < diff1) - return t0; - return t1; -} - -// round up -template -CONSTCD14 -inline -To -ceil(const std::chrono::duration& d) -{ - auto t = trunc(d); - if (t < d) - return t + To{1}; - return t; -} - -template ::is_signed - >::type> -CONSTCD11 -std::chrono::duration -abs(std::chrono::duration d) -{ - return d >= d.zero() ? d : static_cast(-d); -} - -// round down -template -CONSTCD11 -inline -std::chrono::time_point -floor(const std::chrono::time_point& tp) -{ - using std::chrono::time_point; - return time_point{date::floor(tp.time_since_epoch())}; -} - -// round to nearest, to even on tie -template -CONSTCD11 -inline -std::chrono::time_point -round(const std::chrono::time_point& tp) -{ - using std::chrono::time_point; - return time_point{round(tp.time_since_epoch())}; -} - -// round up -template -CONSTCD11 -inline -std::chrono::time_point -ceil(const std::chrono::time_point& tp) -{ - using std::chrono::time_point; - return time_point{ceil(tp.time_since_epoch())}; -} - -#else // HAS_CHRONO_ROUNDING == 1 - -using std::chrono::floor; -using std::chrono::ceil; -using std::chrono::round; -using std::chrono::abs; - -#endif // HAS_CHRONO_ROUNDING - -namespace detail -{ - -template -CONSTCD14 -inline -typename std::enable_if -< - !std::chrono::treat_as_floating_point::value, - To ->::type -round_i(const std::chrono::duration& d) -{ - return round(d); -} - -template -CONSTCD14 -inline -typename std::enable_if -< - std::chrono::treat_as_floating_point::value, - To ->::type -round_i(const std::chrono::duration& d) -{ - return d; -} - -template -CONSTCD11 -inline -std::chrono::time_point -round_i(const std::chrono::time_point& tp) -{ - using std::chrono::time_point; - return time_point{round_i(tp.time_since_epoch())}; -} - -} // detail - -// trunc towards zero -template -CONSTCD11 -inline -std::chrono::time_point -trunc(const std::chrono::time_point& tp) -{ - using std::chrono::time_point; - return time_point{trunc(tp.time_since_epoch())}; -} - -// day - -CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} -CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} -CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} -CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} -CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} -CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} -CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} -CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} -CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;} - -CONSTCD11 -inline -bool -operator==(const day& x, const day& y) NOEXCEPT -{ - return static_cast(x) == static_cast(y); -} - -CONSTCD11 -inline -bool -operator!=(const day& x, const day& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const day& x, const day& y) NOEXCEPT -{ - return static_cast(x) < static_cast(y); -} - -CONSTCD11 -inline -bool -operator>(const day& x, const day& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const day& x, const day& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const day& x, const day& y) NOEXCEPT -{ - return !(x < y); -} - -CONSTCD11 -inline -days -operator-(const day& x, const day& y) NOEXCEPT -{ - return days{static_cast(static_cast(x) - - static_cast(y))}; -} - -CONSTCD11 -inline -day -operator+(const day& x, const days& y) NOEXCEPT -{ - return day{static_cast(x) + static_cast(y.count())}; -} - -CONSTCD11 -inline -day -operator+(const days& x, const day& y) NOEXCEPT -{ - return y + x; -} - -CONSTCD11 -inline -day -operator-(const day& x, const days& y) NOEXCEPT -{ - return x + -y; -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const day& d) -{ - detail::save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << static_cast(d); - return os; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const day& d) -{ - detail::low_level_fmt(os, d); - if (!d.ok()) - os << " is not a valid day"; - return os; -} - -// month - -CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} -CONSTCD14 inline month& month::operator++() NOEXCEPT {*this += months{1}; return *this;} -CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} -CONSTCD14 inline month& month::operator--() NOEXCEPT {*this -= months{1}; return *this;} -CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} - -CONSTCD14 -inline -month& -month::operator+=(const months& m) NOEXCEPT -{ - *this = *this + m; - return *this; -} - -CONSTCD14 -inline -month& -month::operator-=(const months& m) NOEXCEPT -{ - *this = *this - m; - return *this; -} - -CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} -CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} - -CONSTCD11 -inline -bool -operator==(const month& x, const month& y) NOEXCEPT -{ - return static_cast(x) == static_cast(y); -} - -CONSTCD11 -inline -bool -operator!=(const month& x, const month& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const month& x, const month& y) NOEXCEPT -{ - return static_cast(x) < static_cast(y); -} - -CONSTCD11 -inline -bool -operator>(const month& x, const month& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const month& x, const month& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const month& x, const month& y) NOEXCEPT -{ - return !(x < y); -} - -CONSTCD14 -inline -months -operator-(const month& x, const month& y) NOEXCEPT -{ - auto const d = static_cast(x) - static_cast(y); - return months(d <= 11 ? d : d + 12); -} - -CONSTCD14 -inline -month -operator+(const month& x, const months& y) NOEXCEPT -{ - auto const mu = static_cast(static_cast(x)) + y.count() - 1; - auto const yr = (mu >= 0 ? mu : mu-11) / 12; - return month{static_cast(mu - yr * 12 + 1)}; -} - -CONSTCD14 -inline -month -operator+(const months& x, const month& y) NOEXCEPT -{ - return y + x; -} - -CONSTCD14 -inline -month -operator-(const month& x, const months& y) NOEXCEPT -{ - return x + -y; -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const month& m) -{ - if (m.ok()) - { - CharT fmt[] = {'%', 'b', 0}; - os << format(os.getloc(), fmt, m); - } - else - os << static_cast(m); - return os; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const month& m) -{ - detail::low_level_fmt(os, m); - if (!m.ok()) - os << " is not a valid month"; - return os; -} - -// year - -CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} -CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} -CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} -CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} -CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} -CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} -CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} -CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};} -CONSTCD11 inline year year::operator+() const NOEXCEPT {return *this;} - -CONSTCD11 -inline -bool -year::is_leap() const NOEXCEPT -{ - return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); -} - -CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} - -CONSTCD11 -inline -bool -year::ok() const NOEXCEPT -{ - return y_ != std::numeric_limits::min(); -} - -CONSTCD11 -inline -bool -operator==(const year& x, const year& y) NOEXCEPT -{ - return static_cast(x) == static_cast(y); -} - -CONSTCD11 -inline -bool -operator!=(const year& x, const year& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const year& x, const year& y) NOEXCEPT -{ - return static_cast(x) < static_cast(y); -} - -CONSTCD11 -inline -bool -operator>(const year& x, const year& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const year& x, const year& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const year& x, const year& y) NOEXCEPT -{ - return !(x < y); -} - -CONSTCD11 -inline -years -operator-(const year& x, const year& y) NOEXCEPT -{ - return years{static_cast(x) - static_cast(y)}; -} - -CONSTCD11 -inline -year -operator+(const year& x, const years& y) NOEXCEPT -{ - return year{static_cast(x) + y.count()}; -} - -CONSTCD11 -inline -year -operator+(const years& x, const year& y) NOEXCEPT -{ - return y + x; -} - -CONSTCD11 -inline -year -operator-(const year& x, const years& y) NOEXCEPT -{ - return year{static_cast(x) - y.count()}; -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const year& y) -{ - detail::save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::internal); - os.width(4 + (y < year{0})); - os.imbue(std::locale::classic()); - os << static_cast(y); - return os; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year& y) -{ - detail::low_level_fmt(os, y); - if (!y.ok()) - os << " is not a valid year"; - return os; -} - -// weekday - -CONSTCD14 -inline -unsigned char -weekday::weekday_from_days(int z) NOEXCEPT -{ - auto u = static_cast(z); - return static_cast(z >= -4 ? (u+4) % 7 : u % 7); -} - -CONSTCD11 -inline -weekday::weekday(unsigned wd) NOEXCEPT - : wd_(static_cast(wd != 7 ? wd : 0)) - {} - -CONSTCD14 -inline -weekday::weekday(const sys_days& dp) NOEXCEPT - : wd_(weekday_from_days(dp.time_since_epoch().count())) - {} - -CONSTCD14 -inline -weekday::weekday(const local_days& dp) NOEXCEPT - : wd_(weekday_from_days(dp.time_since_epoch().count())) - {} - -CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {*this += days{1}; return *this;} -CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} -CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {*this -= days{1}; return *this;} -CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} - -CONSTCD14 -inline -weekday& -weekday::operator+=(const days& d) NOEXCEPT -{ - *this = *this + d; - return *this; -} - -CONSTCD14 -inline -weekday& -weekday::operator-=(const days& d) NOEXCEPT -{ - *this = *this - d; - return *this; -} - -CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} - -CONSTCD11 -inline -unsigned weekday::c_encoding() const NOEXCEPT -{ - return unsigned{wd_}; -} - -CONSTCD11 -inline -unsigned weekday::iso_encoding() const NOEXCEPT -{ - return unsigned{((wd_ == 0u) ? 7u : wd_)}; -} - -CONSTCD11 -inline -bool -operator==(const weekday& x, const weekday& y) NOEXCEPT -{ - return x.wd_ == y.wd_; -} - -CONSTCD11 -inline -bool -operator!=(const weekday& x, const weekday& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD14 -inline -days -operator-(const weekday& x, const weekday& y) NOEXCEPT -{ - auto const wdu = x.wd_ - y.wd_; - auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; - return days{wdu - wk * 7}; -} - -CONSTCD14 -inline -weekday -operator+(const weekday& x, const days& y) NOEXCEPT -{ - auto const wdu = static_cast(static_cast(x.wd_)) + y.count(); - auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; - return weekday{static_cast(wdu - wk * 7)}; -} - -CONSTCD14 -inline -weekday -operator+(const days& x, const weekday& y) NOEXCEPT -{ - return y + x; -} - -CONSTCD14 -inline -weekday -operator-(const weekday& x, const days& y) NOEXCEPT -{ - return x + -y; -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const weekday& wd) -{ - if (wd.ok()) - { - CharT fmt[] = {'%', 'a', 0}; - os << format(fmt, wd); - } - else - os << wd.c_encoding(); - return os; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday& wd) -{ - detail::low_level_fmt(os, wd); - if (!wd.ok()) - os << " is not a valid weekday"; - return os; -} - -#if !defined(_MSC_VER) || (_MSC_VER >= 1900) -inline namespace literals -{ - -CONSTCD11 -inline -date::day -operator "" _d(unsigned long long d) NOEXCEPT -{ - return date::day{static_cast(d)}; -} - -CONSTCD11 -inline -date::year -operator "" _y(unsigned long long y) NOEXCEPT -{ - return date::year(static_cast(y)); -} -#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) - -CONSTDATA date::last_spec last{}; - -CONSTDATA date::month jan{1}; -CONSTDATA date::month feb{2}; -CONSTDATA date::month mar{3}; -CONSTDATA date::month apr{4}; -CONSTDATA date::month may{5}; -CONSTDATA date::month jun{6}; -CONSTDATA date::month jul{7}; -CONSTDATA date::month aug{8}; -CONSTDATA date::month sep{9}; -CONSTDATA date::month oct{10}; -CONSTDATA date::month nov{11}; -CONSTDATA date::month dec{12}; - -CONSTDATA date::weekday sun{0u}; -CONSTDATA date::weekday mon{1u}; -CONSTDATA date::weekday tue{2u}; -CONSTDATA date::weekday wed{3u}; -CONSTDATA date::weekday thu{4u}; -CONSTDATA date::weekday fri{5u}; -CONSTDATA date::weekday sat{6u}; - -#if !defined(_MSC_VER) || (_MSC_VER >= 1900) -} // inline namespace literals -#endif - -CONSTDATA date::month January{1}; -CONSTDATA date::month February{2}; -CONSTDATA date::month March{3}; -CONSTDATA date::month April{4}; -CONSTDATA date::month May{5}; -CONSTDATA date::month June{6}; -CONSTDATA date::month July{7}; -CONSTDATA date::month August{8}; -CONSTDATA date::month September{9}; -CONSTDATA date::month October{10}; -CONSTDATA date::month November{11}; -CONSTDATA date::month December{12}; - -CONSTDATA date::weekday Monday{1}; -CONSTDATA date::weekday Tuesday{2}; -CONSTDATA date::weekday Wednesday{3}; -CONSTDATA date::weekday Thursday{4}; -CONSTDATA date::weekday Friday{5}; -CONSTDATA date::weekday Saturday{6}; -CONSTDATA date::weekday Sunday{7}; - -// weekday_indexed - -CONSTCD11 -inline -weekday -weekday_indexed::weekday() const NOEXCEPT -{ - return date::weekday{static_cast(wd_)}; -} - -CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} - -CONSTCD11 -inline -bool -weekday_indexed::ok() const NOEXCEPT -{ - return weekday().ok() && 1 <= index_ && index_ <= 5; -} - -#ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wconversion" -#endif // __GNUC__ - -CONSTCD11 -inline -weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT - : wd_(static_cast(static_cast(wd.wd_))) - , index_(static_cast(index)) - {} - -#ifdef __GNUC__ -# pragma GCC diagnostic pop -#endif // __GNUC__ - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const weekday_indexed& wdi) -{ - return low_level_fmt(os, wdi.weekday()) << '[' << wdi.index() << ']'; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday_indexed& wdi) -{ - detail::low_level_fmt(os, wdi); - if (!wdi.ok()) - os << " is not a valid weekday_indexed"; - return os; -} - -CONSTCD11 -inline -weekday_indexed -weekday::operator[](unsigned index) const NOEXCEPT -{ - return {*this, index}; -} - -CONSTCD11 -inline -bool -operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT -{ - return x.weekday() == y.weekday() && x.index() == y.index(); -} - -CONSTCD11 -inline -bool -operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT -{ - return !(x == y); -} - -// weekday_last - -CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} -CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} -CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) NOEXCEPT : wd_(wd) {} - -CONSTCD11 -inline -bool -operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT -{ - return x.weekday() == y.weekday(); -} - -CONSTCD11 -inline -bool -operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT -{ - return !(x == y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const weekday_last& wdl) -{ - return low_level_fmt(os, wdl.weekday()) << "[last]"; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const weekday_last& wdl) -{ - detail::low_level_fmt(os, wdl); - if (!wdl.ok()) - os << " is not a valid weekday_last"; - return os; -} - -CONSTCD11 -inline -weekday_last -weekday::operator[](last_spec) const NOEXCEPT -{ - return weekday_last{*this}; -} - -// year_month - -CONSTCD11 -inline -year_month::year_month(const date::year& y, const date::month& m) NOEXCEPT - : y_(y) - , m_(m) - {} - -CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} -CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} -CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} - -template -CONSTCD14 -inline -year_month& -year_month::operator+=(const months& dm) NOEXCEPT -{ - *this = *this + dm; - return *this; -} - -template -CONSTCD14 -inline -year_month& -year_month::operator-=(const months& dm) NOEXCEPT -{ - *this = *this - dm; - return *this; -} - -CONSTCD14 -inline -year_month& -year_month::operator+=(const years& dy) NOEXCEPT -{ - *this = *this + dy; - return *this; -} - -CONSTCD14 -inline -year_month& -year_month::operator-=(const years& dy) NOEXCEPT -{ - *this = *this - dy; - return *this; -} - -CONSTCD11 -inline -bool -operator==(const year_month& x, const year_month& y) NOEXCEPT -{ - return x.year() == y.year() && x.month() == y.month(); -} - -CONSTCD11 -inline -bool -operator!=(const year_month& x, const year_month& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const year_month& x, const year_month& y) NOEXCEPT -{ - return x.year() < y.year() ? true - : (x.year() > y.year() ? false - : (x.month() < y.month())); -} - -CONSTCD11 -inline -bool -operator>(const year_month& x, const year_month& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const year_month& x, const year_month& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const year_month& x, const year_month& y) NOEXCEPT -{ - return !(x < y); -} - -template -CONSTCD14 -inline -year_month -operator+(const year_month& ym, const months& dm) NOEXCEPT -{ - auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); - auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; - dmi = dmi - dy * 12 + 1; - return (ym.year() + years(dy)) / month(static_cast(dmi)); -} - -template -CONSTCD14 -inline -year_month -operator+(const months& dm, const year_month& ym) NOEXCEPT -{ - return ym + dm; -} - -template -CONSTCD14 -inline -year_month -operator-(const year_month& ym, const months& dm) NOEXCEPT -{ - return ym + -dm; -} - -CONSTCD11 -inline -months -operator-(const year_month& x, const year_month& y) NOEXCEPT -{ - return (x.year() - y.year()) + - months(static_cast(x.month()) - static_cast(y.month())); -} - -CONSTCD11 -inline -year_month -operator+(const year_month& ym, const years& dy) NOEXCEPT -{ - return (ym.year() + dy) / ym.month(); -} - -CONSTCD11 -inline -year_month -operator+(const years& dy, const year_month& ym) NOEXCEPT -{ - return ym + dy; -} - -CONSTCD11 -inline -year_month -operator-(const year_month& ym, const years& dy) NOEXCEPT -{ - return ym + -dy; -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const year_month& ym) -{ - low_level_fmt(os, ym.year()) << '/'; - return low_level_fmt(os, ym.month()); -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month& ym) -{ - detail::low_level_fmt(os, ym); - if (!ym.ok()) - os << " is not a valid year_month"; - return os; -} - -// month_day - -CONSTCD11 -inline -month_day::month_day(const date::month& m, const date::day& d) NOEXCEPT - : m_(m) - , d_(d) - {} - -CONSTCD11 inline date::month month_day::month() const NOEXCEPT {return m_;} -CONSTCD11 inline date::day month_day::day() const NOEXCEPT {return d_;} - -CONSTCD14 -inline -bool -month_day::ok() const NOEXCEPT -{ - CONSTDATA date::day d[] = - { - date::day(31), date::day(29), date::day(31), - date::day(30), date::day(31), date::day(30), - date::day(31), date::day(31), date::day(30), - date::day(31), date::day(30), date::day(31) - }; - return m_.ok() && date::day{1} <= d_ && d_ <= d[static_cast(m_)-1]; -} - -CONSTCD11 -inline -bool -operator==(const month_day& x, const month_day& y) NOEXCEPT -{ - return x.month() == y.month() && x.day() == y.day(); -} - -CONSTCD11 -inline -bool -operator!=(const month_day& x, const month_day& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const month_day& x, const month_day& y) NOEXCEPT -{ - return x.month() < y.month() ? true - : (x.month() > y.month() ? false - : (x.day() < y.day())); -} - -CONSTCD11 -inline -bool -operator>(const month_day& x, const month_day& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const month_day& x, const month_day& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const month_day& x, const month_day& y) NOEXCEPT -{ - return !(x < y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const month_day& md) -{ - low_level_fmt(os, md.month()) << '/'; - return low_level_fmt(os, md.day()); -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_day& md) -{ - detail::low_level_fmt(os, md); - if (!md.ok()) - os << " is not a valid month_day"; - return os; -} - -// month_day_last - -CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} -CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} -CONSTCD11 inline month_day_last::month_day_last(const date::month& m) NOEXCEPT : m_(m) {} - -CONSTCD11 -inline -bool -operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return x.month() == y.month(); -} - -CONSTCD11 -inline -bool -operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return x.month() < y.month(); -} - -CONSTCD11 -inline -bool -operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT -{ - return !(x < y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const month_day_last& mdl) -{ - return low_level_fmt(os, mdl.month()) << "/last"; -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_day_last& mdl) -{ - detail::low_level_fmt(os, mdl); - if (!mdl.ok()) - os << " is not a valid month_day_last"; - return os; -} - -// month_weekday - -CONSTCD11 -inline -month_weekday::month_weekday(const date::month& m, - const date::weekday_indexed& wdi) NOEXCEPT - : m_(m) - , wdi_(wdi) - {} - -CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} - -CONSTCD11 -inline -weekday_indexed -month_weekday::weekday_indexed() const NOEXCEPT -{ - return wdi_; -} - -CONSTCD11 -inline -bool -month_weekday::ok() const NOEXCEPT -{ - return m_.ok() && wdi_.ok(); -} - -CONSTCD11 -inline -bool -operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT -{ - return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); -} - -CONSTCD11 -inline -bool -operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT -{ - return !(x == y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const month_weekday& mwd) -{ - low_level_fmt(os, mwd.month()) << '/'; - return low_level_fmt(os, mwd.weekday_indexed()); -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_weekday& mwd) -{ - detail::low_level_fmt(os, mwd); - if (!mwd.ok()) - os << " is not a valid month_weekday"; - return os; -} - -// month_weekday_last - -CONSTCD11 -inline -month_weekday_last::month_weekday_last(const date::month& m, - const date::weekday_last& wdl) NOEXCEPT - : m_(m) - , wdl_(wdl) - {} - -CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} - -CONSTCD11 -inline -weekday_last -month_weekday_last::weekday_last() const NOEXCEPT -{ - return wdl_; -} - -CONSTCD11 -inline -bool -month_weekday_last::ok() const NOEXCEPT -{ - return m_.ok() && wdl_.ok(); -} - -CONSTCD11 -inline -bool -operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT -{ - return x.month() == y.month() && x.weekday_last() == y.weekday_last(); -} - -CONSTCD11 -inline -bool -operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT -{ - return !(x == y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const month_weekday_last& mwdl) -{ - low_level_fmt(os, mwdl.month()) << '/'; - return low_level_fmt(os, mwdl.weekday_last()); -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) -{ - detail::low_level_fmt(os, mwdl); - if (!mwdl.ok()) - os << " is not a valid month_weekday_last"; - return os; -} - -// year_month_day_last - -CONSTCD11 -inline -year_month_day_last::year_month_day_last(const date::year& y, - const date::month_day_last& mdl) NOEXCEPT - : y_(y) - , mdl_(mdl) - {} - -template -CONSTCD14 -inline -year_month_day_last& -year_month_day_last::operator+=(const months& m) NOEXCEPT -{ - *this = *this + m; - return *this; -} - -template -CONSTCD14 -inline -year_month_day_last& -year_month_day_last::operator-=(const months& m) NOEXCEPT -{ - *this = *this - m; - return *this; -} - -CONSTCD14 -inline -year_month_day_last& -year_month_day_last::operator+=(const years& y) NOEXCEPT -{ - *this = *this + y; - return *this; -} - -CONSTCD14 -inline -year_month_day_last& -year_month_day_last::operator-=(const years& y) NOEXCEPT -{ - *this = *this - y; - return *this; -} - -CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} -CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} - -CONSTCD11 -inline -month_day_last -year_month_day_last::month_day_last() const NOEXCEPT -{ - return mdl_; -} - -CONSTCD14 -inline -day -year_month_day_last::day() const NOEXCEPT -{ - CONSTDATA date::day d[] = - { - date::day(31), date::day(28), date::day(31), - date::day(30), date::day(31), date::day(30), - date::day(31), date::day(31), date::day(30), - date::day(31), date::day(30), date::day(31) - }; - return (month() != February || !y_.is_leap()) && mdl_.ok() ? - d[static_cast(month()) - 1] : date::day{29}; -} - -CONSTCD14 -inline -year_month_day_last::operator sys_days() const NOEXCEPT -{ - return sys_days(year()/month()/day()); -} - -CONSTCD14 -inline -year_month_day_last::operator local_days() const NOEXCEPT -{ - return local_days(year()/month()/day()); -} - -CONSTCD11 -inline -bool -year_month_day_last::ok() const NOEXCEPT -{ - return y_.ok() && mdl_.ok(); -} - -CONSTCD11 -inline -bool -operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return x.year() == y.year() && x.month_day_last() == y.month_day_last(); -} - -CONSTCD11 -inline -bool -operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return x.year() < y.year() ? true - : (x.year() > y.year() ? false - : (x.month_day_last() < y.month_day_last())); -} - -CONSTCD11 -inline -bool -operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT -{ - return !(x < y); -} - -namespace detail -{ - -template -std::basic_ostream& -low_level_fmt(std::basic_ostream& os, const year_month_day_last& ymdl) -{ - low_level_fmt(os, ymdl.year()) << '/'; - return low_level_fmt(os, ymdl.month_day_last()); -} - -} // namespace detail - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) -{ - detail::low_level_fmt(os, ymdl); - if (!ymdl.ok()) - os << " is not a valid year_month_day_last"; - return os; -} - -template -CONSTCD14 -inline -year_month_day_last -operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT -{ - return (ymdl.year() / ymdl.month() + dm) / last; -} - -template -CONSTCD14 -inline -year_month_day_last -operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT -{ - return ymdl + dm; -} - -template -CONSTCD14 -inline -year_month_day_last -operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT -{ - return ymdl + (-dm); -} - -CONSTCD11 -inline -year_month_day_last -operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT -{ - return {ymdl.year()+dy, ymdl.month_day_last()}; -} - -CONSTCD11 -inline -year_month_day_last -operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT -{ - return ymdl + dy; -} - -CONSTCD11 -inline -year_month_day_last -operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT -{ - return ymdl + (-dy); -} - -// year_month_day - -CONSTCD11 -inline -year_month_day::year_month_day(const date::year& y, const date::month& m, - const date::day& d) NOEXCEPT - : y_(y) - , m_(m) - , d_(d) - {} - -CONSTCD14 -inline -year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT - : y_(ymdl.year()) - , m_(ymdl.month()) - , d_(ymdl.day()) - {} - -CONSTCD14 -inline -year_month_day::year_month_day(sys_days dp) NOEXCEPT - : year_month_day(from_days(dp.time_since_epoch())) - {} - -CONSTCD14 -inline -year_month_day::year_month_day(local_days dp) NOEXCEPT - : year_month_day(from_days(dp.time_since_epoch())) - {} - -CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} -CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} -CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} - -template -CONSTCD14 -inline -year_month_day& -year_month_day::operator+=(const months& m) NOEXCEPT -{ - *this = *this + m; - return *this; -} - -template -CONSTCD14 -inline -year_month_day& -year_month_day::operator-=(const months& m) NOEXCEPT -{ - *this = *this - m; - return *this; -} - -CONSTCD14 -inline -year_month_day& -year_month_day::operator+=(const years& y) NOEXCEPT -{ - *this = *this + y; - return *this; -} - -CONSTCD14 -inline -year_month_day& -year_month_day::operator-=(const years& y) NOEXCEPT -{ - *this = *this - y; - return *this; -} - -CONSTCD14 -inline -days -year_month_day::to_days() const NOEXCEPT -{ - static_assert(std::numeric_limits::digits >= 18, - "This algorithm has not been ported to a 16 bit unsigned integer"); - static_assert(std::numeric_limits::digits >= 20, - "This algorithm has not been ported to a 16 bit signed integer"); - auto const y = static_cast(y_) - (m_ <= February); - auto const m = static_cast(m_); - auto const d = static_cast(d_); - auto const era = (y >= 0 ? y : y-399) / 400; - auto const yoe = static_cast(y - era * 400); // [0, 399] - auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] - auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] - return days{era * 146097 + static_cast(doe) - 719468}; -} - -CONSTCD14 -inline -year_month_day::operator sys_days() const NOEXCEPT -{ - return sys_days{to_days()}; -} - -CONSTCD14 -inline -year_month_day::operator local_days() const NOEXCEPT -{ - return local_days{to_days()}; -} - -CONSTCD14 -inline -bool -year_month_day::ok() const NOEXCEPT -{ - if (!(y_.ok() && m_.ok())) - return false; - return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day(); -} - -CONSTCD11 -inline -bool -operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); -} - -CONSTCD11 -inline -bool -operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return !(x == y); -} - -CONSTCD11 -inline -bool -operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return x.year() < y.year() ? true - : (x.year() > y.year() ? false - : (x.month() < y.month() ? true - : (x.month() > y.month() ? false - : (x.day() < y.day())))); -} - -CONSTCD11 -inline -bool -operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return y < x; -} - -CONSTCD11 -inline -bool -operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return !(y < x); -} - -CONSTCD11 -inline -bool -operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT -{ - return !(x < y); -} - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_day& ymd) -{ - detail::save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.imbue(std::locale::classic()); - os << static_cast(ymd.year()) << '-'; - os.width(2); - os << static_cast(ymd.month()) << '-'; - os.width(2); - os << static_cast(ymd.day()); - if (!ymd.ok()) - os << " is not a valid year_month_day"; - return os; -} - -CONSTCD14 -inline -year_month_day -year_month_day::from_days(days dp) NOEXCEPT -{ - static_assert(std::numeric_limits::digits >= 18, - "This algorithm has not been ported to a 16 bit unsigned integer"); - static_assert(std::numeric_limits::digits >= 20, - "This algorithm has not been ported to a 16 bit signed integer"); - auto const z = dp.count() + 719468; - auto const era = (z >= 0 ? z : z - 146096) / 146097; - auto const doe = static_cast(z - era * 146097); // [0, 146096] - auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] - auto const y = static_cast(yoe) + era * 400; - auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] - auto const mp = (5*doy + 2)/153; // [0, 11] - auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] - auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12] - return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; -} - -template -CONSTCD14 -inline -year_month_day -operator+(const year_month_day& ymd, const months& dm) NOEXCEPT -{ - return (ymd.year() / ymd.month() + dm) / ymd.day(); -} - -template -CONSTCD14 -inline -year_month_day -operator+(const months& dm, const year_month_day& ymd) NOEXCEPT -{ - return ymd + dm; -} - -template -CONSTCD14 -inline -year_month_day -operator-(const year_month_day& ymd, const months& dm) NOEXCEPT -{ - return ymd + (-dm); -} - -CONSTCD11 -inline -year_month_day -operator+(const year_month_day& ymd, const years& dy) NOEXCEPT -{ - return (ymd.year() + dy) / ymd.month() / ymd.day(); -} - -CONSTCD11 -inline -year_month_day -operator+(const years& dy, const year_month_day& ymd) NOEXCEPT -{ - return ymd + dy; -} - -CONSTCD11 -inline -year_month_day -operator-(const year_month_day& ymd, const years& dy) NOEXCEPT -{ - return ymd + (-dy); -} - -// year_month_weekday - -CONSTCD11 -inline -year_month_weekday::year_month_weekday(const date::year& y, const date::month& m, - const date::weekday_indexed& wdi) - NOEXCEPT - : y_(y) - , m_(m) - , wdi_(wdi) - {} - -CONSTCD14 -inline -year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT - : year_month_weekday(from_days(dp.time_since_epoch())) - {} - -CONSTCD14 -inline -year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT - : year_month_weekday(from_days(dp.time_since_epoch())) - {} - -template -CONSTCD14 -inline -year_month_weekday& -year_month_weekday::operator+=(const months& m) NOEXCEPT -{ - *this = *this + m; - return *this; -} - -template -CONSTCD14 -inline -year_month_weekday& -year_month_weekday::operator-=(const months& m) NOEXCEPT -{ - *this = *this - m; - return *this; -} - -CONSTCD14 -inline -year_month_weekday& -year_month_weekday::operator+=(const years& y) NOEXCEPT -{ - *this = *this + y; - return *this; -} - -CONSTCD14 -inline -year_month_weekday& -year_month_weekday::operator-=(const years& y) NOEXCEPT -{ - *this = *this - y; - return *this; -} - -CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} -CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} - -CONSTCD11 -inline -weekday -year_month_weekday::weekday() const NOEXCEPT -{ - return wdi_.weekday(); -} - -CONSTCD11 -inline -unsigned -year_month_weekday::index() const NOEXCEPT -{ - return wdi_.index(); -} - -CONSTCD11 -inline -weekday_indexed -year_month_weekday::weekday_indexed() const NOEXCEPT -{ - return wdi_; -} - -CONSTCD14 -inline -year_month_weekday::operator sys_days() const NOEXCEPT -{ - return sys_days{to_days()}; -} - -CONSTCD14 -inline -year_month_weekday::operator local_days() const NOEXCEPT -{ - return local_days{to_days()}; -} - -CONSTCD14 -inline -bool -year_month_weekday::ok() const NOEXCEPT -{ - if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) - return false; - if (wdi_.index() <= 4) - return true; - auto d2 = wdi_.weekday() - date::weekday(static_cast(y_/m_/1)) + - days((wdi_.index()-1)*7 + 1); - return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); -} - -CONSTCD14 -inline -year_month_weekday -year_month_weekday::from_days(days d) NOEXCEPT -{ - sys_days dp{d}; - auto const wd = date::weekday(dp); - auto const ymd = year_month_day(dp); - return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; -} - -CONSTCD14 -inline -days -year_month_weekday::to_days() const NOEXCEPT -{ - auto d = sys_days(y_/m_/1); - return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) - ).time_since_epoch(); -} - -CONSTCD11 -inline -bool -operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT -{ - return x.year() == y.year() && x.month() == y.month() && - x.weekday_indexed() == y.weekday_indexed(); -} - -CONSTCD11 -inline -bool -operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT -{ - return !(x == y); -} - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) -{ - detail::low_level_fmt(os, ymwdi.year()) << '/'; - detail::low_level_fmt(os, ymwdi.month()) << '/'; - detail::low_level_fmt(os, ymwdi.weekday_indexed()); - if (!ymwdi.ok()) - os << " is not a valid year_month_weekday"; - return os; -} - -template -CONSTCD14 -inline -year_month_weekday -operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT -{ - return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); -} - -template -CONSTCD14 -inline -year_month_weekday -operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT -{ - return ymwd + dm; -} - -template -CONSTCD14 -inline -year_month_weekday -operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT -{ - return ymwd + (-dm); -} - -CONSTCD11 -inline -year_month_weekday -operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT -{ - return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; -} - -CONSTCD11 -inline -year_month_weekday -operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT -{ - return ymwd + dy; -} - -CONSTCD11 -inline -year_month_weekday -operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT -{ - return ymwd + (-dy); -} - -// year_month_weekday_last - -CONSTCD11 -inline -year_month_weekday_last::year_month_weekday_last(const date::year& y, - const date::month& m, - const date::weekday_last& wdl) NOEXCEPT - : y_(y) - , m_(m) - , wdl_(wdl) - {} - -template -CONSTCD14 -inline -year_month_weekday_last& -year_month_weekday_last::operator+=(const months& m) NOEXCEPT -{ - *this = *this + m; - return *this; -} - -template -CONSTCD14 -inline -year_month_weekday_last& -year_month_weekday_last::operator-=(const months& m) NOEXCEPT -{ - *this = *this - m; - return *this; -} - -CONSTCD14 -inline -year_month_weekday_last& -year_month_weekday_last::operator+=(const years& y) NOEXCEPT -{ - *this = *this + y; - return *this; -} - -CONSTCD14 -inline -year_month_weekday_last& -year_month_weekday_last::operator-=(const years& y) NOEXCEPT -{ - *this = *this - y; - return *this; -} - -CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} -CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} - -CONSTCD11 -inline -weekday -year_month_weekday_last::weekday() const NOEXCEPT -{ - return wdl_.weekday(); -} - -CONSTCD11 -inline -weekday_last -year_month_weekday_last::weekday_last() const NOEXCEPT -{ - return wdl_; -} - -CONSTCD14 -inline -year_month_weekday_last::operator sys_days() const NOEXCEPT -{ - return sys_days{to_days()}; -} - -CONSTCD14 -inline -year_month_weekday_last::operator local_days() const NOEXCEPT -{ - return local_days{to_days()}; -} - -CONSTCD11 -inline -bool -year_month_weekday_last::ok() const NOEXCEPT -{ - return y_.ok() && m_.ok() && wdl_.ok(); -} - -CONSTCD14 -inline -days -year_month_weekday_last::to_days() const NOEXCEPT -{ - auto const d = sys_days(y_/m_/last); - return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); -} - -CONSTCD11 -inline -bool -operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT -{ - return x.year() == y.year() && x.month() == y.month() && - x.weekday_last() == y.weekday_last(); -} - -CONSTCD11 -inline -bool -operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT -{ - return !(x == y); -} - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) -{ - detail::low_level_fmt(os, ymwdl.year()) << '/'; - detail::low_level_fmt(os, ymwdl.month()) << '/'; - detail::low_level_fmt(os, ymwdl.weekday_last()); - if (!ymwdl.ok()) - os << " is not a valid year_month_weekday_last"; - return os; -} - -template -CONSTCD14 -inline -year_month_weekday_last -operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT -{ - return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); -} - -template -CONSTCD14 -inline -year_month_weekday_last -operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT -{ - return ymwdl + dm; -} - -template -CONSTCD14 -inline -year_month_weekday_last -operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT -{ - return ymwdl + (-dm); -} - -CONSTCD11 -inline -year_month_weekday_last -operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT -{ - return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; -} - -CONSTCD11 -inline -year_month_weekday_last -operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT -{ - return ymwdl + dy; -} - -CONSTCD11 -inline -year_month_weekday_last -operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT -{ - return ymwdl + (-dy); -} - -// year_month from operator/() - -CONSTCD11 -inline -year_month -operator/(const year& y, const month& m) NOEXCEPT -{ - return {y, m}; -} - -CONSTCD11 -inline -year_month -operator/(const year& y, int m) NOEXCEPT -{ - return y / month(static_cast(m)); -} - -// month_day from operator/() - -CONSTCD11 -inline -month_day -operator/(const month& m, const day& d) NOEXCEPT -{ - return {m, d}; -} - -CONSTCD11 -inline -month_day -operator/(const day& d, const month& m) NOEXCEPT -{ - return m / d; -} - -CONSTCD11 -inline -month_day -operator/(const month& m, int d) NOEXCEPT -{ - return m / day(static_cast(d)); -} - -CONSTCD11 -inline -month_day -operator/(int m, const day& d) NOEXCEPT -{ - return month(static_cast(m)) / d; -} - -CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} - -// month_day_last from operator/() - -CONSTCD11 -inline -month_day_last -operator/(const month& m, last_spec) NOEXCEPT -{ - return month_day_last{m}; -} - -CONSTCD11 -inline -month_day_last -operator/(last_spec, const month& m) NOEXCEPT -{ - return m/last; -} - -CONSTCD11 -inline -month_day_last -operator/(int m, last_spec) NOEXCEPT -{ - return month(static_cast(m))/last; -} - -CONSTCD11 -inline -month_day_last -operator/(last_spec, int m) NOEXCEPT -{ - return m/last; -} - -// month_weekday from operator/() - -CONSTCD11 -inline -month_weekday -operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT -{ - return {m, wdi}; -} - -CONSTCD11 -inline -month_weekday -operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT -{ - return m / wdi; -} - -CONSTCD11 -inline -month_weekday -operator/(int m, const weekday_indexed& wdi) NOEXCEPT -{ - return month(static_cast(m)) / wdi; -} - -CONSTCD11 -inline -month_weekday -operator/(const weekday_indexed& wdi, int m) NOEXCEPT -{ - return m / wdi; -} - -// month_weekday_last from operator/() - -CONSTCD11 -inline -month_weekday_last -operator/(const month& m, const weekday_last& wdl) NOEXCEPT -{ - return {m, wdl}; -} - -CONSTCD11 -inline -month_weekday_last -operator/(const weekday_last& wdl, const month& m) NOEXCEPT -{ - return m / wdl; -} - -CONSTCD11 -inline -month_weekday_last -operator/(int m, const weekday_last& wdl) NOEXCEPT -{ - return month(static_cast(m)) / wdl; -} - -CONSTCD11 -inline -month_weekday_last -operator/(const weekday_last& wdl, int m) NOEXCEPT -{ - return m / wdl; -} - -// year_month_day from operator/() - -CONSTCD11 -inline -year_month_day -operator/(const year_month& ym, const day& d) NOEXCEPT -{ - return {ym.year(), ym.month(), d}; -} - -CONSTCD11 -inline -year_month_day -operator/(const year_month& ym, int d) NOEXCEPT -{ - return ym / day(static_cast(d)); -} - -CONSTCD11 -inline -year_month_day -operator/(const year& y, const month_day& md) NOEXCEPT -{ - return y / md.month() / md.day(); -} - -CONSTCD11 -inline -year_month_day -operator/(int y, const month_day& md) NOEXCEPT -{ - return year(y) / md; -} - -CONSTCD11 -inline -year_month_day -operator/(const month_day& md, const year& y) NOEXCEPT -{ - return y / md; -} - -CONSTCD11 -inline -year_month_day -operator/(const month_day& md, int y) NOEXCEPT -{ - return year(y) / md; -} - -// year_month_day_last from operator/() - -CONSTCD11 -inline -year_month_day_last -operator/(const year_month& ym, last_spec) NOEXCEPT -{ - return {ym.year(), month_day_last{ym.month()}}; -} - -CONSTCD11 -inline -year_month_day_last -operator/(const year& y, const month_day_last& mdl) NOEXCEPT -{ - return {y, mdl}; -} - -CONSTCD11 -inline -year_month_day_last -operator/(int y, const month_day_last& mdl) NOEXCEPT -{ - return year(y) / mdl; -} - -CONSTCD11 -inline -year_month_day_last -operator/(const month_day_last& mdl, const year& y) NOEXCEPT -{ - return y / mdl; -} - -CONSTCD11 -inline -year_month_day_last -operator/(const month_day_last& mdl, int y) NOEXCEPT -{ - return year(y) / mdl; -} - -// year_month_weekday from operator/() - -CONSTCD11 -inline -year_month_weekday -operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT -{ - return {ym.year(), ym.month(), wdi}; -} - -CONSTCD11 -inline -year_month_weekday -operator/(const year& y, const month_weekday& mwd) NOEXCEPT -{ - return {y, mwd.month(), mwd.weekday_indexed()}; -} - -CONSTCD11 -inline -year_month_weekday -operator/(int y, const month_weekday& mwd) NOEXCEPT -{ - return year(y) / mwd; -} - -CONSTCD11 -inline -year_month_weekday -operator/(const month_weekday& mwd, const year& y) NOEXCEPT -{ - return y / mwd; -} - -CONSTCD11 -inline -year_month_weekday -operator/(const month_weekday& mwd, int y) NOEXCEPT -{ - return year(y) / mwd; -} - -// year_month_weekday_last from operator/() - -CONSTCD11 -inline -year_month_weekday_last -operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT -{ - return {ym.year(), ym.month(), wdl}; -} - -CONSTCD11 -inline -year_month_weekday_last -operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT -{ - return {y, mwdl.month(), mwdl.weekday_last()}; -} - -CONSTCD11 -inline -year_month_weekday_last -operator/(int y, const month_weekday_last& mwdl) NOEXCEPT -{ - return year(y) / mwdl; -} - -CONSTCD11 -inline -year_month_weekday_last -operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT -{ - return y / mwdl; -} - -CONSTCD11 -inline -year_month_weekday_last -operator/(const month_weekday_last& mwdl, int y) NOEXCEPT -{ - return year(y) / mwdl; -} - -template -struct fields; - -template -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const fields& fds, const std::string* abbrev = nullptr, - const std::chrono::seconds* offset_sec = nullptr); - -template -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - fields& fds, std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr); - -// hh_mm_ss - -namespace detail -{ - -struct undocumented {explicit undocumented() = default;}; - -// width::value is the number of fractional decimal digits in 1/n -// width<0>::value and width<1>::value are defined to be 0 -// If 1/n takes more than 18 fractional decimal digits, -// the result is truncated to 19. -// Example: width<2>::value == 1 -// Example: width<3>::value == 19 -// Example: width<4>::value == 2 -// Example: width<10>::value == 1 -// Example: width<1000>::value == 3 -template -struct width -{ - static_assert(d > 0, "width called with zero denominator"); - static CONSTDATA unsigned value = 1 + width::value; -}; - -template -struct width -{ - static CONSTDATA unsigned value = 0; -}; - -template -struct static_pow10 -{ -private: - static CONSTDATA std::uint64_t h = static_pow10::value; -public: - static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1); -}; - -template <> -struct static_pow10<0> -{ - static CONSTDATA std::uint64_t value = 1; -}; - -template -class decimal_format_seconds -{ - using CT = typename std::common_type::type; - using rep = typename CT::rep; - static unsigned CONSTDATA trial_width = - detail::width::value; -public: - static unsigned CONSTDATA width = trial_width < 19 ? trial_width : 6u; - using precision = std::chrono::duration::value>>; - -private: - std::chrono::seconds s_; - precision sub_s_; - -public: - CONSTCD11 decimal_format_seconds() - : s_() - , sub_s_() - {} - - CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT - : s_(std::chrono::duration_cast(d)) - , sub_s_(std::chrono::duration_cast(d - s_)) - {} - - CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} - CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} - CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;} - - CONSTCD14 precision to_duration() const NOEXCEPT - { - return s_ + sub_s_; - } - - CONSTCD11 bool in_conventional_range() const NOEXCEPT - { - return sub_s_ < std::chrono::seconds{1} && s_ < std::chrono::minutes{1}; - } - - template - friend - std::basic_ostream& - operator<<(std::basic_ostream& os, const decimal_format_seconds& x) - { - return x.print(os, std::chrono::treat_as_floating_point{}); - } - - template - std::basic_ostream& - print(std::basic_ostream& os, std::true_type) const - { - date::detail::save_ostream _(os); - std::chrono::duration d = s_ + sub_s_; - if (d < std::chrono::seconds{10}) - os << '0'; - os.precision(width+6); - os << std::fixed << d.count(); - return os; - } - - template - std::basic_ostream& - print(std::basic_ostream& os, std::false_type) const - { - date::detail::save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << s_.count(); - if (width > 0) - { -#if !ONLY_C_LOCALE - os << std::use_facet>(os.getloc()).decimal_point(); -#else - os << '.'; -#endif - date::detail::save_ostream _s(os); - os.imbue(std::locale::classic()); - os.width(width); - os << sub_s_.count(); - } - return os; - } -}; - -template -inline -CONSTCD11 -typename std::enable_if - < - std::numeric_limits::is_signed, - std::chrono::duration - >::type -abs(std::chrono::duration d) -{ - return d >= d.zero() ? +d : -d; -} - -template -inline -CONSTCD11 -typename std::enable_if - < - !std::numeric_limits::is_signed, - std::chrono::duration - >::type -abs(std::chrono::duration d) -{ - return d; -} - -} // namespace detail - -template -class hh_mm_ss -{ - using dfs = detail::decimal_format_seconds::type>; - - std::chrono::hours h_; - std::chrono::minutes m_; - dfs s_; - bool neg_; - -public: - static unsigned CONSTDATA fractional_width = dfs::width; - using precision = typename dfs::precision; - - CONSTCD11 hh_mm_ss() NOEXCEPT - : hh_mm_ss(Duration::zero()) - {} - - CONSTCD11 explicit hh_mm_ss(Duration d) NOEXCEPT - : h_(std::chrono::duration_cast(detail::abs(d))) - , m_(std::chrono::duration_cast(detail::abs(d)) - h_) - , s_(detail::abs(d) - h_ - m_) - , neg_(d < Duration::zero()) - {} - - CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} - CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} - CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();} - CONSTCD14 std::chrono::seconds& - seconds(detail::undocumented) NOEXCEPT {return s_.seconds();} - CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();} - CONSTCD11 bool is_negative() const NOEXCEPT {return neg_;} - - CONSTCD11 explicit operator precision() const NOEXCEPT {return to_duration();} - CONSTCD11 precision to_duration() const NOEXCEPT - {return (s_.to_duration() + m_ + h_) * (1-2*neg_);} - - CONSTCD11 bool in_conventional_range() const NOEXCEPT - { - return !neg_ && h_ < days{1} && m_ < std::chrono::hours{1} && - s_.in_conventional_range(); - } - -private: - - template - friend - std::basic_ostream& - operator<<(std::basic_ostream& os, hh_mm_ss const& tod) - { - if (tod.is_negative()) - os << '-'; - if (tod.h_ < std::chrono::hours{10}) - os << '0'; - os << tod.h_.count() << ':'; - if (tod.m_ < std::chrono::minutes{10}) - os << '0'; - os << tod.m_.count() << ':' << tod.s_; - return os; - } - - template - friend - std::basic_ostream& - date::to_stream(std::basic_ostream& os, const CharT* fmt, - const fields& fds, const std::string* abbrev, - const std::chrono::seconds* offset_sec); - - template - friend - std::basic_istream& - date::from_stream(std::basic_istream& is, const CharT* fmt, - fields& fds, - std::basic_string* abbrev, std::chrono::minutes* offset); -}; - -inline -CONSTCD14 -bool -is_am(std::chrono::hours const& h) NOEXCEPT -{ - using std::chrono::hours; - return hours{0} <= h && h < hours{12}; -} - -inline -CONSTCD14 -bool -is_pm(std::chrono::hours const& h) NOEXCEPT -{ - using std::chrono::hours; - return hours{12} <= h && h < hours{24}; -} - -inline -CONSTCD14 -std::chrono::hours -make12(std::chrono::hours h) NOEXCEPT -{ - using std::chrono::hours; - if (h < hours{12}) - { - if (h == hours{0}) - h = hours{12}; - } - else - { - if (h != hours{12}) - h = h - hours{12}; - } - return h; -} - -inline -CONSTCD14 -std::chrono::hours -make24(std::chrono::hours h, bool is_pm) NOEXCEPT -{ - using std::chrono::hours; - if (is_pm) - { - if (h != hours{12}) - h = h + hours{12}; - } - else if (h == hours{12}) - h = hours{0}; - return h; -} - -template -using time_of_day = hh_mm_ss; - -template -CONSTCD11 -inline -hh_mm_ss> -make_time(const std::chrono::duration& d) -{ - return hh_mm_ss>(d); -} - -template -inline -typename std::enable_if -< - !std::is_convertible::value, - std::basic_ostream& ->::type -operator<<(std::basic_ostream& os, const sys_time& tp) -{ - auto const dp = date::floor(tp); - return os << year_month_day(dp) << ' ' << make_time(tp-dp); -} - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const sys_days& dp) -{ - return os << year_month_day(dp); -} - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, const local_time& ut) -{ - return (date::operator<<(os, sys_time{ut.time_since_epoch()})); -} - -namespace detail -{ - -template -class string_literal; - -template -inline -CONSTCD14 -string_literal::type, - N1 + N2 - 1> -operator+(const string_literal& x, const string_literal& y) NOEXCEPT; - -template -class string_literal -{ - CharT p_[N]; - - CONSTCD11 string_literal() NOEXCEPT - : p_{} - {} - -public: - using const_iterator = const CharT*; - - string_literal(string_literal const&) = default; - string_literal& operator=(string_literal const&) = delete; - - template ::type> - CONSTCD11 string_literal(CharT c) NOEXCEPT - : p_{c} - { - } - - template ::type> - CONSTCD11 string_literal(CharT c1, CharT c2) NOEXCEPT - : p_{c1, c2} - { - } - - template ::type> - CONSTCD11 string_literal(CharT c1, CharT c2, CharT c3) NOEXCEPT - : p_{c1, c2, c3} - { - } - - CONSTCD14 string_literal(const CharT(&a)[N]) NOEXCEPT - : p_{} - { - for (std::size_t i = 0; i < N; ++i) - p_[i] = a[i]; - } - - template ::type> - CONSTCD14 string_literal(const char(&a)[N]) NOEXCEPT - : p_{} - { - for (std::size_t i = 0; i < N; ++i) - p_[i] = a[i]; - } - - template ::value>::type> - CONSTCD14 string_literal(string_literal const& a) NOEXCEPT - : p_{} - { - for (std::size_t i = 0; i < N; ++i) - p_[i] = a[i]; - } - - CONSTCD11 const CharT* data() const NOEXCEPT {return p_;} - CONSTCD11 std::size_t size() const NOEXCEPT {return N-1;} - - CONSTCD11 const_iterator begin() const NOEXCEPT {return p_;} - CONSTCD11 const_iterator end() const NOEXCEPT {return p_ + N-1;} - - CONSTCD11 CharT const& operator[](std::size_t n) const NOEXCEPT - { - return p_[n]; - } - - template - friend - std::basic_ostream& - operator<<(std::basic_ostream& os, const string_literal& s) - { - return os << s.p_; - } - - template - friend - CONSTCD14 - string_literal::type, - N1 + N2 - 1> - operator+(const string_literal& x, const string_literal& y) NOEXCEPT; -}; - -template -CONSTCD11 -inline -string_literal -operator+(const string_literal& x, const string_literal& y) NOEXCEPT -{ - return string_literal(x[0], y[0]); -} - -template -CONSTCD11 -inline -string_literal -operator+(const string_literal& x, const string_literal& y) NOEXCEPT -{ - return string_literal(x[0], x[1], y[0]); -} - -template -CONSTCD14 -inline -string_literal::type, - N1 + N2 - 1> -operator+(const string_literal& x, const string_literal& y) NOEXCEPT -{ - using CT = typename std::conditional::type; - - string_literal r; - std::size_t i = 0; - for (; i < N1-1; ++i) - r.p_[i] = CT(x.p_[i]); - for (std::size_t j = 0; j < N2; ++j, ++i) - r.p_[i] = CT(y.p_[j]); - - return r; -} - - -template -inline -std::basic_string -operator+(std::basic_string x, const string_literal& y) -{ - x.append(y.data(), y.size()); - return x; -} - -#if __cplusplus >= 201402 && (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) \ - && (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150) - -template ::value || - std::is_same::value || - std::is_same::value || - std::is_same::value>> -CONSTCD14 -inline -string_literal -msl(CharT c) NOEXCEPT -{ - return string_literal{c}; -} - -CONSTCD14 -inline -std::size_t -to_string_len(std::intmax_t i) -{ - std::size_t r = 0; - do - { - i /= 10; - ++r; - } while (i > 0); - return r; -} - -template -CONSTCD14 -inline -std::enable_if_t -< - N < 10, - string_literal -> -msl() NOEXCEPT -{ - return msl(char(N % 10 + '0')); -} - -template -CONSTCD14 -inline -std::enable_if_t -< - 10 <= N, - string_literal -> -msl() NOEXCEPT -{ - return msl() + msl(char(N % 10 + '0')); -} - -template -CONSTCD14 -inline -std::enable_if_t -< - std::ratio::type::den != 1, - string_literal::type::num) + - to_string_len(std::ratio::type::den) + 4> -> -msl(std::ratio) NOEXCEPT -{ - using R = typename std::ratio::type; - return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + - msl() + msl(CharT{']'}); -} - -template -CONSTCD14 -inline -std::enable_if_t -< - std::ratio::type::den == 1, - string_literal::type::num) + 3> -> -msl(std::ratio) NOEXCEPT -{ - using R = typename std::ratio::type; - return msl(CharT{'['}) + msl() + msl(CharT{']'}); -} - - -#else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) - -inline -std::string -to_string(std::uint64_t x) -{ - return std::to_string(x); -} - -template -inline -std::basic_string -to_string(std::uint64_t x) -{ - auto y = std::to_string(x); - return std::basic_string(y.begin(), y.end()); -} - -template -inline -typename std::enable_if -< - std::ratio::type::den != 1, - std::basic_string ->::type -msl(std::ratio) -{ - using R = typename std::ratio::type; - return std::basic_string(1, '[') + to_string(R::num) + CharT{'/'} + - to_string(R::den) + CharT{']'}; -} - -template -inline -typename std::enable_if -< - std::ratio::type::den == 1, - std::basic_string ->::type -msl(std::ratio) -{ - using R = typename std::ratio::type; - return std::basic_string(1, '[') + to_string(R::num) + CharT{']'}; -} - -#endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) - -template -CONSTCD11 -inline -string_literal -msl(std::atto) NOEXCEPT -{ - return string_literal{'a'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::femto) NOEXCEPT -{ - return string_literal{'f'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::pico) NOEXCEPT -{ - return string_literal{'p'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::nano) NOEXCEPT -{ - return string_literal{'n'}; -} - -template -CONSTCD11 -inline -typename std::enable_if -< - std::is_same::value, - string_literal ->::type -msl(std::micro) NOEXCEPT -{ - return string_literal{'\xC2', '\xB5'}; -} - -template -CONSTCD11 -inline -typename std::enable_if -< - !std::is_same::value, - string_literal ->::type -msl(std::micro) NOEXCEPT -{ - return string_literal{CharT{static_cast('\xB5')}}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::milli) NOEXCEPT -{ - return string_literal{'m'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::centi) NOEXCEPT -{ - return string_literal{'c'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::deca) NOEXCEPT -{ - return string_literal{'d', 'a'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::deci) NOEXCEPT -{ - return string_literal{'d'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::hecto) NOEXCEPT -{ - return string_literal{'h'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::kilo) NOEXCEPT -{ - return string_literal{'k'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::mega) NOEXCEPT -{ - return string_literal{'M'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::giga) NOEXCEPT -{ - return string_literal{'G'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::tera) NOEXCEPT -{ - return string_literal{'T'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::peta) NOEXCEPT -{ - return string_literal{'P'}; -} - -template -CONSTCD11 -inline -string_literal -msl(std::exa) NOEXCEPT -{ - return string_literal{'E'}; -} - -template -CONSTCD11 -inline -auto -get_units(Period p) - -> decltype(msl(p) + string_literal{'s'}) -{ - return msl(p) + string_literal{'s'}; -} - -template -CONSTCD11 -inline -string_literal -get_units(std::ratio<1>) -{ - return string_literal{'s'}; -} - -template -CONSTCD11 -inline -string_literal -get_units(std::ratio<3600>) -{ - return string_literal{'h'}; -} - -template -CONSTCD11 -inline -string_literal -get_units(std::ratio<60>) -{ - return string_literal{'m', 'i', 'n'}; -} - -template -CONSTCD11 -inline -string_literal -get_units(std::ratio<86400>) -{ - return string_literal{'d'}; -} - -template > -struct make_string; - -template <> -struct make_string -{ - template - static - std::string - from(Rep n) - { - return std::to_string(n); - } -}; - -template -struct make_string -{ - template - static - std::basic_string - from(Rep n) - { - auto s = std::to_string(n); - return std::basic_string(s.begin(), s.end()); - } -}; - -template <> -struct make_string -{ - template - static - std::wstring - from(Rep n) - { - return std::to_wstring(n); - } -}; - -template -struct make_string -{ - template - static - std::basic_string - from(Rep n) - { - auto s = std::to_wstring(n); - return std::basic_string(s.begin(), s.end()); - } -}; - -} // namespace detail - -// to_stream - -CONSTDATA year nanyear{-32768}; - -template -struct fields -{ - year_month_day ymd{nanyear/0/0}; - weekday wd{8u}; - hh_mm_ss tod{}; - bool has_tod = false; - -#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ <= 409) - fields() : ymd{nanyear/0/0}, wd{8u}, tod{}, has_tod{false} {} -#else - fields() = default; -#endif - - fields(year_month_day ymd_) : ymd(ymd_) {} - fields(weekday wd_) : wd(wd_) {} - fields(hh_mm_ss tod_) : tod(tod_), has_tod(true) {} - - fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} - fields(year_month_day ymd_, hh_mm_ss tod_) : ymd(ymd_), tod(tod_), - has_tod(true) {} - - fields(weekday wd_, hh_mm_ss tod_) : wd(wd_), tod(tod_), has_tod(true) {} - - fields(year_month_day ymd_, weekday wd_, hh_mm_ss tod_) - : ymd(ymd_) - , wd(wd_) - , tod(tod_) - , has_tod(true) - {} -}; - -namespace detail -{ - -template -unsigned -extract_weekday(std::basic_ostream& os, const fields& fds) -{ - if (!fds.ymd.ok() && !fds.wd.ok()) - { - // fds does not contain a valid weekday - os.setstate(std::ios::failbit); - return 8; - } - weekday wd; - if (fds.ymd.ok()) - { - wd = weekday{sys_days(fds.ymd)}; - if (fds.wd.ok() && wd != fds.wd) - { - // fds.ymd and fds.wd are inconsistent - os.setstate(std::ios::failbit); - return 8; - } - } - else - wd = fds.wd; - return static_cast((wd - Sunday).count()); -} - -template -unsigned -extract_month(std::basic_ostream& os, const fields& fds) -{ - if (!fds.ymd.month().ok()) - { - // fds does not contain a valid month - os.setstate(std::ios::failbit); - return 0; - } - return static_cast(fds.ymd.month()); -} - -} // namespace detail - -#if ONLY_C_LOCALE - -namespace detail -{ - -inline -std::pair -weekday_names() -{ - static const std::string nm[] = - { - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat" - }; - return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); -} - -inline -std::pair -month_names() -{ - static const std::string nm[] = - { - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec" - }; - return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); -} - -inline -std::pair -ampm_names() -{ - static const std::string nm[] = - { - "AM", - "PM" - }; - return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); -} - -template -FwdIter -scan_keyword(std::basic_istream& is, FwdIter kb, FwdIter ke) -{ - size_t nkw = static_cast(std::distance(kb, ke)); - const unsigned char doesnt_match = '\0'; - const unsigned char might_match = '\1'; - const unsigned char does_match = '\2'; - unsigned char statbuf[100]; - unsigned char* status = statbuf; - std::unique_ptr stat_hold(0, free); - if (nkw > sizeof(statbuf)) - { - status = (unsigned char*)std::malloc(nkw); - if (status == nullptr) - throw std::bad_alloc(); - stat_hold.reset(status); - } - size_t n_might_match = nkw; // At this point, any keyword might match - size_t n_does_match = 0; // but none of them definitely do - // Initialize all statuses to might_match, except for "" keywords are does_match - unsigned char* st = status; - for (auto ky = kb; ky != ke; ++ky, ++st) - { - if (!ky->empty()) - *st = might_match; - else - { - *st = does_match; - --n_might_match; - ++n_does_match; - } - } - // While there might be a match, test keywords against the next CharT - for (size_t indx = 0; is && n_might_match > 0; ++indx) - { - // Peek at the next CharT but don't consume it - auto ic = is.peek(); - if (ic == EOF) - { - is.setstate(std::ios::eofbit); - break; - } - auto c = static_cast(toupper(static_cast(ic))); - bool consume = false; - // For each keyword which might match, see if the indx character is c - // If a match if found, consume c - // If a match is found, and that is the last character in the keyword, - // then that keyword matches. - // If the keyword doesn't match this character, then change the keyword - // to doesn't match - st = status; - for (auto ky = kb; ky != ke; ++ky, ++st) - { - if (*st == might_match) - { - if (c == static_cast(toupper(static_cast((*ky)[indx])))) - { - consume = true; - if (ky->size() == indx+1) - { - *st = does_match; - --n_might_match; - ++n_does_match; - } - } - else - { - *st = doesnt_match; - --n_might_match; - } - } - } - // consume if we matched a character - if (consume) - { - (void)is.get(); - // If we consumed a character and there might be a matched keyword that - // was marked matched on a previous iteration, then such keywords - // are now marked as not matching. - if (n_might_match + n_does_match > 1) - { - st = status; - for (auto ky = kb; ky != ke; ++ky, ++st) - { - if (*st == does_match && ky->size() != indx+1) - { - *st = doesnt_match; - --n_does_match; - } - } - } - } - } - // We've exited the loop because we hit eof and/or we have no more "might matches". - // Return the first matching result - for (st = status; kb != ke; ++kb, ++st) - if (*st == does_match) - break; - if (kb == ke) - is.setstate(std::ios::failbit); - return kb; -} - -} // namespace detail - -#endif // ONLY_C_LOCALE - -template -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const fields& fds, const std::string* abbrev, - const std::chrono::seconds* offset_sec) -{ -#if ONLY_C_LOCALE - using detail::weekday_names; - using detail::month_names; - using detail::ampm_names; -#endif - using detail::save_ostream; - using detail::get_units; - using detail::extract_weekday; - using detail::extract_month; - using std::ios; - using std::chrono::duration_cast; - using std::chrono::seconds; - using std::chrono::minutes; - using std::chrono::hours; - date::detail::save_ostream ss(os); - os.fill(' '); - os.flags(std::ios::skipws | std::ios::dec); - os.width(0); - tm tm{}; - bool insert_negative = fds.has_tod && fds.tod.to_duration() < Duration::zero(); -#if !ONLY_C_LOCALE - auto& facet = std::use_facet>(os.getloc()); -#endif - const CharT* command = nullptr; - CharT modified = CharT{}; - for (; *fmt; ++fmt) - { - switch (*fmt) - { - case 'a': - case 'A': - if (command) - { - if (modified == CharT{}) - { - tm.tm_wday = static_cast(extract_weekday(os, fds)); - if (os.fail()) - return os; -#if !ONLY_C_LOCALE - const CharT f[] = {'%', *fmt}; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); -#else // ONLY_C_LOCALE - os << weekday_names().first[tm.tm_wday+7*(*fmt == 'a')]; -#endif // ONLY_C_LOCALE - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'b': - case 'B': - case 'h': - if (command) - { - if (modified == CharT{}) - { - tm.tm_mon = static_cast(extract_month(os, fds)) - 1; -#if !ONLY_C_LOCALE - const CharT f[] = {'%', *fmt}; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); -#else // ONLY_C_LOCALE - os << month_names().first[tm.tm_mon+12*(*fmt != 'B')]; -#endif // ONLY_C_LOCALE - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'c': - case 'x': - if (command) - { - if (modified == CharT{'O'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.ok()) - os.setstate(std::ios::failbit); - if (*fmt == 'c' && !fds.has_tod) - os.setstate(std::ios::failbit); -#if !ONLY_C_LOCALE - tm = std::tm{}; - auto const& ymd = fds.ymd; - auto ld = local_days(ymd); - if (*fmt == 'c') - { - tm.tm_sec = static_cast(fds.tod.seconds().count()); - tm.tm_min = static_cast(fds.tod.minutes().count()); - tm.tm_hour = static_cast(fds.tod.hours().count()); - } - tm.tm_mday = static_cast(static_cast(ymd.day())); - tm.tm_mon = static_cast(extract_month(os, fds) - 1); - tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(extract_weekday(os, fds)); - if (os.fail()) - return os; - tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); - CharT f[3] = {'%'}; - auto fe = std::begin(f) + 1; - if (modified == CharT{'E'}) - *fe++ = modified; - *fe++ = *fmt; - facet.put(os, os, os.fill(), &tm, std::begin(f), fe); -#else // ONLY_C_LOCALE - if (*fmt == 'c') - { - auto wd = static_cast(extract_weekday(os, fds)); - os << weekday_names().first[static_cast(wd)+7] - << ' '; - os << month_names().first[extract_month(os, fds)-1+12] << ' '; - auto d = static_cast(static_cast(fds.ymd.day())); - if (d < 10) - os << ' '; - os << d << ' ' - << make_time(duration_cast(fds.tod.to_duration())) - << ' ' << fds.ymd.year(); - - } - else // *fmt == 'x' - { - auto const& ymd = fds.ymd; - save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << static_cast(ymd.month()) << CharT{'/'}; - os.width(2); - os << static_cast(ymd.day()) << CharT{'/'}; - os.width(2); - os << static_cast(ymd.year()) % 100; - } -#endif // ONLY_C_LOCALE - } - command = nullptr; - modified = CharT{}; - } - else - os << *fmt; - break; - case 'C': - if (command) - { - if (modified == CharT{'O'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.year().ok()) - os.setstate(std::ios::failbit); - auto y = static_cast(fds.ymd.year()); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - if (y >= 0) - { - os.width(2); - os << y/100; - } - else - { - os << CharT{'-'}; - os.width(2); - os << -(y-99)/100; - } - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'E'}) - { - tm.tm_year = y - 1900; - CharT f[3] = {'%', 'E', 'C'}; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - command = nullptr; - modified = CharT{}; - } - else - os << *fmt; - break; - case 'd': - case 'e': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.day().ok()) - os.setstate(std::ios::failbit); - auto d = static_cast(static_cast(fds.ymd.day())); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - save_ostream _(os); - if (*fmt == CharT{'d'}) - os.fill('0'); - else - os.fill(' '); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << d; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - tm.tm_mday = d; - CharT f[3] = {'%', 'O', *fmt}; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - command = nullptr; - modified = CharT{}; - } - else - os << *fmt; - break; - case 'D': - if (command) - { - if (modified == CharT{}) - { - if (!fds.ymd.ok()) - os.setstate(std::ios::failbit); - auto const& ymd = fds.ymd; - save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << static_cast(ymd.month()) << CharT{'/'}; - os.width(2); - os << static_cast(ymd.day()) << CharT{'/'}; - os.width(2); - os << static_cast(ymd.year()) % 100; - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'F': - if (command) - { - if (modified == CharT{}) - { - if (!fds.ymd.ok()) - os.setstate(std::ios::failbit); - auto const& ymd = fds.ymd; - save_ostream _(os); - os.imbue(std::locale::classic()); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(4); - os << static_cast(ymd.year()) << CharT{'-'}; - os.width(2); - os << static_cast(ymd.month()) << CharT{'-'}; - os.width(2); - os << static_cast(ymd.day()); - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'g': - case 'G': - if (command) - { - if (modified == CharT{}) - { - if (!fds.ymd.ok()) - os.setstate(std::ios::failbit); - auto ld = local_days(fds.ymd); - auto y = year_month_day{ld + days{3}}.year(); - auto start = local_days((y-years{1})/December/Thursday[last]) + - (Monday-Thursday); - if (ld < start) - --y; - if (*fmt == CharT{'G'}) - os << y; - else - { - save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(2); - os << std::abs(static_cast(y)) % 100; - } - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'H': - case 'I': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - if (insert_negative) - { - os << '-'; - insert_negative = false; - } - auto hms = fds.tod; -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - auto h = *fmt == CharT{'I'} ? date::make12(hms.hours()) : hms.hours(); - if (h < hours{10}) - os << CharT{'0'}; - os << h.count(); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_hour = static_cast(hms.hours().count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'j': - if (command) - { - if (modified == CharT{}) - { - if (fds.ymd.ok() || fds.has_tod) - { - days doy; - if (fds.ymd.ok()) - { - auto ld = local_days(fds.ymd); - auto y = fds.ymd.year(); - doy = ld - local_days(y/January/1) + days{1}; - } - else - { - doy = duration_cast(fds.tod.to_duration()); - } - save_ostream _(os); - os.fill('0'); - os.flags(std::ios::dec | std::ios::right); - os.width(3); - os << doy.count(); - } - else - { - os.setstate(std::ios::failbit); - } - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'm': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.month().ok()) - os.setstate(std::ios::failbit); - auto m = static_cast(fds.ymd.month()); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - if (m < 10) - os << CharT{'0'}; - os << m; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_mon = static_cast(m-1); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'M': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - if (insert_negative) - { - os << '-'; - insert_negative = false; - } -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - if (fds.tod.minutes() < minutes{10}) - os << CharT{'0'}; - os << fds.tod.minutes().count(); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_min = static_cast(fds.tod.minutes().count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'n': - if (command) - { - if (modified == CharT{}) - os << CharT{'\n'}; - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'p': - if (command) - { - if (modified == CharT{}) - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); -#if !ONLY_C_LOCALE - const CharT f[] = {'%', *fmt}; - tm.tm_hour = static_cast(fds.tod.hours().count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); -#else - if (date::is_am(fds.tod.hours())) - os << ampm_names().first[0]; - else - os << ampm_names().first[1]; -#endif - } - else - { - os << CharT{'%'} << modified << *fmt; - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'Q': - case 'q': - if (command) - { - if (modified == CharT{}) - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - auto d = fds.tod.to_duration(); - if (*fmt == 'q') - os << get_units(typename decltype(d)::period::type{}); - else - os << d.count(); - } - else - { - os << CharT{'%'} << modified << *fmt; - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'r': - if (command) - { - if (modified == CharT{}) - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); -#if !ONLY_C_LOCALE - const CharT f[] = {'%', *fmt}; - tm.tm_hour = static_cast(fds.tod.hours().count()); - tm.tm_min = static_cast(fds.tod.minutes().count()); - tm.tm_sec = static_cast(fds.tod.seconds().count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); -#else - hh_mm_ss tod(duration_cast(fds.tod.to_duration())); - save_ostream _(os); - os.fill('0'); - os.width(2); - os << date::make12(tod.hours()).count() << CharT{':'}; - os.width(2); - os << tod.minutes().count() << CharT{':'}; - os.width(2); - os << tod.seconds().count() << CharT{' '}; - if (date::is_am(tod.hours())) - os << ampm_names().first[0]; - else - os << ampm_names().first[1]; -#endif - } - else - { - os << CharT{'%'} << modified << *fmt; - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'R': - if (command) - { - if (modified == CharT{}) - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - if (fds.tod.hours() < hours{10}) - os << CharT{'0'}; - os << fds.tod.hours().count() << CharT{':'}; - if (fds.tod.minutes() < minutes{10}) - os << CharT{'0'}; - os << fds.tod.minutes().count(); - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'S': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - if (insert_negative) - { - os << '-'; - insert_negative = false; - } -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - os << fds.tod.s_; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_sec = static_cast(fds.tod.s_.seconds().count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 't': - if (command) - { - if (modified == CharT{}) - os << CharT{'\t'}; - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'T': - if (command) - { - if (modified == CharT{}) - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); - os << fds.tod; - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'u': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - auto wd = extract_weekday(os, fds); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - os << (wd != 0 ? wd : 7u); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_wday = static_cast(wd); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'U': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - auto const& ymd = fds.ymd; - if (!ymd.ok()) - os.setstate(std::ios::failbit); - auto ld = local_days(ymd); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - auto st = local_days(Sunday[1]/January/ymd.year()); - if (ld < st) - os << CharT{'0'} << CharT{'0'}; - else - { - auto wn = duration_cast(ld - st).count() + 1; - if (wn < 10) - os << CharT{'0'}; - os << wn; - } - } - #if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(extract_weekday(os, fds)); - if (os.fail()) - return os; - tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'V': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.ok()) - os.setstate(std::ios::failbit); - auto ld = local_days(fds.ymd); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - auto y = year_month_day{ld + days{3}}.year(); - auto st = local_days((y-years{1})/12/Thursday[last]) + - (Monday-Thursday); - if (ld < st) - { - --y; - st = local_days((y - years{1})/12/Thursday[last]) + - (Monday-Thursday); - } - auto wn = duration_cast(ld - st).count() + 1; - if (wn < 10) - os << CharT{'0'}; - os << wn; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - auto const& ymd = fds.ymd; - tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(extract_weekday(os, fds)); - if (os.fail()) - return os; - tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'w': - if (command) - { - auto wd = extract_weekday(os, fds); - if (os.fail()) - return os; -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - os << wd; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_wday = static_cast(wd); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - else - { - os << CharT{'%'} << modified << *fmt; - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'W': - if (command) - { - if (modified == CharT{'E'}) - os << CharT{'%'} << modified << *fmt; - else - { - auto const& ymd = fds.ymd; - if (!ymd.ok()) - os.setstate(std::ios::failbit); - auto ld = local_days(ymd); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - auto st = local_days(Monday[1]/January/ymd.year()); - if (ld < st) - os << CharT{'0'} << CharT{'0'}; - else - { - auto wn = duration_cast(ld - st).count() + 1; - if (wn < 10) - os << CharT{'0'}; - os << wn; - } - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_year = static_cast(ymd.year()) - 1900; - tm.tm_wday = static_cast(extract_weekday(os, fds)); - if (os.fail()) - return os; - tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'X': - if (command) - { - if (modified == CharT{'O'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.has_tod) - os.setstate(std::ios::failbit); -#if !ONLY_C_LOCALE - tm = std::tm{}; - tm.tm_sec = static_cast(fds.tod.seconds().count()); - tm.tm_min = static_cast(fds.tod.minutes().count()); - tm.tm_hour = static_cast(fds.tod.hours().count()); - CharT f[3] = {'%'}; - auto fe = std::begin(f) + 1; - if (modified == CharT{'E'}) - *fe++ = modified; - *fe++ = *fmt; - facet.put(os, os, os.fill(), &tm, std::begin(f), fe); -#else - os << fds.tod; -#endif - } - command = nullptr; - modified = CharT{}; - } - else - os << *fmt; - break; - case 'y': - if (command) - { - if (!fds.ymd.year().ok()) - os.setstate(std::ios::failbit); - auto y = static_cast(fds.ymd.year()); -#if !ONLY_C_LOCALE - if (modified == CharT{}) - { -#endif - y = std::abs(y) % 100; - if (y < 10) - os << CharT{'0'}; - os << y; -#if !ONLY_C_LOCALE - } - else - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_year = y - 1900; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'Y': - if (command) - { - if (modified == CharT{'O'}) - os << CharT{'%'} << modified << *fmt; - else - { - if (!fds.ymd.year().ok()) - os.setstate(std::ios::failbit); - auto y = fds.ymd.year(); -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - save_ostream _(os); - os.imbue(std::locale::classic()); - os << y; - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'E'}) - { - const CharT f[] = {'%', modified, *fmt}; - tm.tm_year = static_cast(y) - 1900; - facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); - } -#endif - } - modified = CharT{}; - command = nullptr; - } - else - os << *fmt; - break; - case 'z': - if (command) - { - if (offset_sec == nullptr) - { - // Can not format %z with unknown offset - os.setstate(ios::failbit); - return os; - } - auto m = duration_cast(*offset_sec); - auto neg = m < minutes{0}; - m = date::abs(m); - auto h = duration_cast(m); - m -= h; - if (neg) - os << CharT{'-'}; - else - os << CharT{'+'}; - if (h < hours{10}) - os << CharT{'0'}; - os << h.count(); - if (modified != CharT{}) - os << CharT{':'}; - if (m < minutes{10}) - os << CharT{'0'}; - os << m.count(); - command = nullptr; - modified = CharT{}; - } - else - os << *fmt; - break; - case 'Z': - if (command) - { - if (modified == CharT{}) - { - if (abbrev == nullptr) - { - // Can not format %Z with unknown time_zone - os.setstate(ios::failbit); - return os; - } - for (auto c : *abbrev) - os << CharT(c); - } - else - { - os << CharT{'%'} << modified << *fmt; - modified = CharT{}; - } - command = nullptr; - } - else - os << *fmt; - break; - case 'E': - case 'O': - if (command) - { - if (modified == CharT{}) - { - modified = *fmt; - } - else - { - os << CharT{'%'} << modified << *fmt; - command = nullptr; - modified = CharT{}; - } - } - else - os << *fmt; - break; - case '%': - if (command) - { - if (modified == CharT{}) - { - os << CharT{'%'}; - command = nullptr; - } - else - { - os << CharT{'%'} << modified << CharT{'%'}; - command = nullptr; - modified = CharT{}; - } - } - else - command = fmt; - break; - default: - if (command) - { - os << CharT{'%'}; - command = nullptr; - } - if (modified != CharT{}) - { - os << modified; - modified = CharT{}; - } - os << *fmt; - break; - } - } - if (command) - os << CharT{'%'}; - if (modified != CharT{}) - os << modified; - return os; -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const year& y) -{ - using CT = std::chrono::seconds; - fields fds{y/0/0}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const month& m) -{ - using CT = std::chrono::seconds; - fields fds{m/0/nanyear}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const day& d) -{ - using CT = std::chrono::seconds; - fields fds{d/0/nanyear}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const weekday& wd) -{ - using CT = std::chrono::seconds; - fields fds{wd}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const year_month& ym) -{ - using CT = std::chrono::seconds; - fields fds{ym/0}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, const month_day& md) -{ - using CT = std::chrono::seconds; - fields fds{md/nanyear}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const year_month_day& ymd) -{ - using CT = std::chrono::seconds; - fields fds{ymd}; - return to_stream(os, fmt, fds); -} - -template -inline -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const std::chrono::duration& d) -{ - using Duration = std::chrono::duration; - using CT = typename std::common_type::type; - fields fds{hh_mm_ss{d}}; - return to_stream(os, fmt, fds); -} - -template -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const local_time& tp, const std::string* abbrev = nullptr, - const std::chrono::seconds* offset_sec = nullptr) -{ - using CT = typename std::common_type::type; - auto ld = std::chrono::time_point_cast(tp); - fields fds; - if (ld <= tp) - fds = fields{year_month_day{ld}, hh_mm_ss{tp-local_seconds{ld}}}; - else - fds = fields{year_month_day{ld - days{1}}, - hh_mm_ss{days{1} - (local_seconds{ld} - tp)}}; - return to_stream(os, fmt, fds, abbrev, offset_sec); -} - -template -std::basic_ostream& -to_stream(std::basic_ostream& os, const CharT* fmt, - const sys_time& tp) -{ - using std::chrono::seconds; - using CT = typename std::common_type::type; - const std::string abbrev("UTC"); - CONSTDATA seconds offset{0}; - auto sd = std::chrono::time_point_cast(tp); - fields fds; - if (sd <= tp) - fds = fields{year_month_day{sd}, hh_mm_ss{tp-sys_seconds{sd}}}; - else - fds = fields{year_month_day{sd - days{1}}, - hh_mm_ss{days{1} - (sys_seconds{sd} - tp)}}; - return to_stream(os, fmt, fds, &abbrev, &offset); -} - -// format - -template -auto -format(const std::locale& loc, const CharT* fmt, const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt, tp), - std::basic_string{}) -{ - std::basic_ostringstream os; - os.exceptions(std::ios::failbit | std::ios::badbit); - os.imbue(loc); - to_stream(os, fmt, tp); - return os.str(); -} - -template -auto -format(const CharT* fmt, const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt, tp), - std::basic_string{}) -{ - std::basic_ostringstream os; - os.exceptions(std::ios::failbit | std::ios::badbit); - to_stream(os, fmt, tp); - return os.str(); -} - -template -auto -format(const std::locale& loc, const std::basic_string& fmt, - const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), - std::basic_string{}) -{ - std::basic_ostringstream os; - os.exceptions(std::ios::failbit | std::ios::badbit); - os.imbue(loc); - to_stream(os, fmt.c_str(), tp); - return os.str(); -} - -template -auto -format(const std::basic_string& fmt, const Streamable& tp) - -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), - std::basic_string{}) -{ - std::basic_ostringstream os; - os.exceptions(std::ios::failbit | std::ios::badbit); - to_stream(os, fmt.c_str(), tp); - return os.str(); -} - -// parse - -namespace detail -{ - -template -bool -read_char(std::basic_istream& is, CharT fmt, std::ios::iostate& err) -{ - auto ic = is.get(); - if (Traits::eq_int_type(ic, Traits::eof()) || - !Traits::eq(Traits::to_char_type(ic), fmt)) - { - err |= std::ios::failbit; - is.setstate(std::ios::failbit); - return false; - } - return true; -} - -template -unsigned -read_unsigned(std::basic_istream& is, unsigned m = 1, unsigned M = 10) -{ - unsigned x = 0; - unsigned count = 0; - while (true) - { - auto ic = is.peek(); - if (Traits::eq_int_type(ic, Traits::eof())) - break; - auto c = static_cast(Traits::to_char_type(ic)); - if (!('0' <= c && c <= '9')) - break; - (void)is.get(); - ++count; - x = 10*x + static_cast(c - '0'); - if (count == M) - break; - } - if (count < m) - is.setstate(std::ios::failbit); - return x; -} - -template -int -read_signed(std::basic_istream& is, unsigned m = 1, unsigned M = 10) -{ - auto ic = is.peek(); - if (!Traits::eq_int_type(ic, Traits::eof())) - { - auto c = static_cast(Traits::to_char_type(ic)); - if (('0' <= c && c <= '9') || c == '-' || c == '+') - { - if (c == '-' || c == '+') - { - (void)is.get(); - --M; - } - auto x = static_cast(read_unsigned(is, std::max(m, 1u), M)); - if (!is.fail()) - { - if (c == '-') - x = -x; - return x; - } - } - } - if (m > 0) - is.setstate(std::ios::failbit); - return 0; -} - -template -long double -read_long_double(std::basic_istream& is, unsigned m = 1, unsigned M = 10) -{ - unsigned count = 0; - unsigned fcount = 0; - unsigned long long i = 0; - unsigned long long f = 0; - bool parsing_fraction = false; -#if ONLY_C_LOCALE - typename Traits::int_type decimal_point = '.'; -#else - auto decimal_point = Traits::to_int_type( - std::use_facet>(is.getloc()).decimal_point()); -#endif - while (true) - { - auto ic = is.peek(); - if (Traits::eq_int_type(ic, Traits::eof())) - break; - if (Traits::eq_int_type(ic, decimal_point)) - { - decimal_point = Traits::eof(); - parsing_fraction = true; - } - else - { - auto c = static_cast(Traits::to_char_type(ic)); - if (!('0' <= c && c <= '9')) - break; - if (!parsing_fraction) - { - i = 10*i + static_cast(c - '0'); - } - else - { - f = 10*f + static_cast(c - '0'); - ++fcount; - } - } - (void)is.get(); - if (++count == M) - break; - } - if (count < m) - { - is.setstate(std::ios::failbit); - return 0; - } - return static_cast(i) + static_cast(f)/std::pow(10.L, fcount); -} - -struct rs -{ - int& i; - unsigned m; - unsigned M; -}; - -struct ru -{ - int& i; - unsigned m; - unsigned M; -}; - -struct rld -{ - long double& i; - unsigned m; - unsigned M; -}; - -template -void -read(std::basic_istream&) -{ -} - -template -void -read(std::basic_istream& is, CharT a0, Args&& ...args); - -template -void -read(std::basic_istream& is, rs a0, Args&& ...args); - -template -void -read(std::basic_istream& is, ru a0, Args&& ...args); - -template -void -read(std::basic_istream& is, int a0, Args&& ...args); - -template -void -read(std::basic_istream& is, rld a0, Args&& ...args); - -template -void -read(std::basic_istream& is, CharT a0, Args&& ...args) -{ - // No-op if a0 == CharT{} - if (a0 != CharT{}) - { - auto ic = is.peek(); - if (Traits::eq_int_type(ic, Traits::eof())) - { - is.setstate(std::ios::failbit | std::ios::eofbit); - return; - } - if (!Traits::eq(Traits::to_char_type(ic), a0)) - { - is.setstate(std::ios::failbit); - return; - } - (void)is.get(); - } - read(is, std::forward(args)...); -} - -template -void -read(std::basic_istream& is, rs a0, Args&& ...args) -{ - auto x = read_signed(is, a0.m, a0.M); - if (is.fail()) - return; - a0.i = x; - read(is, std::forward(args)...); -} - -template -void -read(std::basic_istream& is, ru a0, Args&& ...args) -{ - auto x = read_unsigned(is, a0.m, a0.M); - if (is.fail()) - return; - a0.i = static_cast(x); - read(is, std::forward(args)...); -} - -template -void -read(std::basic_istream& is, int a0, Args&& ...args) -{ - if (a0 != -1) - { - auto u = static_cast(a0); - CharT buf[std::numeric_limits::digits10+2u] = {}; - auto e = buf; - do - { - *e++ = static_cast(CharT(u % 10) + CharT{'0'}); - u /= 10; - } while (u > 0); -#if defined(__GNUC__) && __GNUC__ >= 11 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif - std::reverse(buf, e); -#if defined(__GNUC__) && __GNUC__ >= 11 -#pragma GCC diagnostic pop -#endif - for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) - read(is, *p); - } - if (is.rdstate() == std::ios::goodbit) - read(is, std::forward(args)...); -} - -template -void -read(std::basic_istream& is, rld a0, Args&& ...args) -{ - auto x = read_long_double(is, a0.m, a0.M); - if (is.fail()) - return; - a0.i = x; - read(is, std::forward(args)...); -} - -template -inline -void -checked_set(T& value, T from, T not_a_value, std::basic_ios& is) -{ - if (!is.fail()) - { - if (value == not_a_value) - value = std::move(from); - else if (value != from) - is.setstate(std::ios::failbit); - } -} - -} // namespace detail; - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - fields& fds, std::basic_string* abbrev, - std::chrono::minutes* offset) -{ - using std::numeric_limits; - using std::ios; - using std::chrono::duration; - using std::chrono::duration_cast; - using std::chrono::seconds; - using std::chrono::minutes; - using std::chrono::hours; - using detail::round_i; - typename std::basic_istream::sentry ok{is, true}; - if (ok) - { - date::detail::save_istream ss(is); - is.fill(' '); - is.flags(std::ios::skipws | std::ios::dec); - is.width(0); -#if !ONLY_C_LOCALE - auto& f = std::use_facet>(is.getloc()); - std::tm tm{}; -#endif - const CharT* command = nullptr; - auto modified = CharT{}; - auto width = -1; - - CONSTDATA int not_a_year = numeric_limits::min(); - CONSTDATA int not_a_2digit_year = 100; - CONSTDATA int not_a_century = numeric_limits::min(); - CONSTDATA int not_a_month = 0; - CONSTDATA int not_a_day = 0; - CONSTDATA int not_a_hour = numeric_limits::min(); - CONSTDATA int not_a_hour_12_value = 0; - CONSTDATA int not_a_minute = not_a_hour; - CONSTDATA Duration not_a_second = Duration::min(); - CONSTDATA int not_a_doy = -1; - CONSTDATA int not_a_weekday = 8; - CONSTDATA int not_a_week_num = 100; - CONSTDATA int not_a_ampm = -1; - CONSTDATA minutes not_a_offset = minutes::min(); - - int Y = not_a_year; // c, F, Y * - int y = not_a_2digit_year; // D, x, y * - int g = not_a_2digit_year; // g * - int G = not_a_year; // G * - int C = not_a_century; // C * - int m = not_a_month; // b, B, h, m, c, D, F, x * - int d = not_a_day; // c, d, D, e, F, x * - int j = not_a_doy; // j * - int wd = not_a_weekday; // a, A, u, w * - int H = not_a_hour; // c, H, R, T, X * - int I = not_a_hour_12_value; // I, r * - int p = not_a_ampm; // p, r * - int M = not_a_minute; // c, M, r, R, T, X * - Duration s = not_a_second; // c, r, S, T, X * - int U = not_a_week_num; // U * - int V = not_a_week_num; // V * - int W = not_a_week_num; // W * - std::basic_string temp_abbrev; // Z * - minutes temp_offset = not_a_offset; // z * - - using detail::read; - using detail::rs; - using detail::ru; - using detail::rld; - using detail::checked_set; - for (; *fmt != CharT{} && !is.fail(); ++fmt) - { - switch (*fmt) - { - case 'a': - case 'A': - case 'u': - case 'w': // wd: a, A, u, w - if (command) - { - int trial_wd = not_a_weekday; - if (*fmt == 'a' || *fmt == 'A') - { - if (modified == CharT{}) - { -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - is.setstate(err); - if (!is.fail()) - trial_wd = tm.tm_wday; -#else - auto nm = detail::weekday_names(); - auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - if (!is.fail()) - trial_wd = i % 7; -#endif - } - else - read(is, CharT{'%'}, width, modified, *fmt); - } - else // *fmt == 'u' || *fmt == 'w' - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - read(is, ru{trial_wd, 1, width == -1 ? - 1u : static_cast(width)}); - if (!is.fail()) - { - if (*fmt == 'u') - { - if (!(1 <= trial_wd && trial_wd <= 7)) - { - trial_wd = not_a_weekday; - is.setstate(ios::failbit); - } - else if (trial_wd == 7) - trial_wd = 0; - } - else // *fmt == 'w' - { - if (!(0 <= trial_wd && trial_wd <= 6)) - { - trial_wd = not_a_weekday; - is.setstate(ios::failbit); - } - } - } - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - is.setstate(err); - if (!is.fail()) - trial_wd = tm.tm_wday; - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - } - if (trial_wd != not_a_weekday) - checked_set(wd, trial_wd, not_a_weekday, is); - } - else // !command - read(is, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - break; - case 'b': - case 'B': - case 'h': - if (command) - { - if (modified == CharT{}) - { - int ttm = not_a_month; -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - ttm = tm.tm_mon + 1; - is.setstate(err); -#else - auto nm = detail::month_names(); - auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - if (!is.fail()) - ttm = i % 12 + 1; -#endif - checked_set(m, ttm, not_a_month, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'c': - if (command) - { - if (modified != CharT{'O'}) - { -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - { - checked_set(Y, tm.tm_year + 1900, not_a_year, is); - checked_set(m, tm.tm_mon + 1, not_a_month, is); - checked_set(d, tm.tm_mday, not_a_day, is); - checked_set(H, tm.tm_hour, not_a_hour, is); - checked_set(M, tm.tm_min, not_a_minute, is); - checked_set(s, duration_cast(seconds{tm.tm_sec}), - not_a_second, is); - } - is.setstate(err); -#else - // "%a %b %e %T %Y" - auto nm = detail::weekday_names(); - auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - checked_set(wd, static_cast(i % 7), not_a_weekday, is); - ws(is); - nm = detail::month_names(); - i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - checked_set(m, static_cast(i % 12 + 1), not_a_month, is); - ws(is); - int td = not_a_day; - read(is, rs{td, 1, 2}); - checked_set(d, td, not_a_day, is); - ws(is); - using dfs = detail::decimal_format_seconds; - CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; - int tH; - int tM; - long double S{}; - read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, - CharT{':'}, rld{S, 1, w}); - checked_set(H, tH, not_a_hour, is); - checked_set(M, tM, not_a_minute, is); - checked_set(s, round_i(duration{S}), - not_a_second, is); - ws(is); - int tY = not_a_year; - read(is, rs{tY, 1, 4u}); - checked_set(Y, tY, not_a_year, is); -#endif - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'x': - if (command) - { - if (modified != CharT{'O'}) - { -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - { - checked_set(Y, tm.tm_year + 1900, not_a_year, is); - checked_set(m, tm.tm_mon + 1, not_a_month, is); - checked_set(d, tm.tm_mday, not_a_day, is); - } - is.setstate(err); -#else - // "%m/%d/%y" - int ty = not_a_2digit_year; - int tm = not_a_month; - int td = not_a_day; - read(is, ru{tm, 1, 2}, CharT{'/'}, ru{td, 1, 2}, CharT{'/'}, - rs{ty, 1, 2}); - checked_set(y, ty, not_a_2digit_year, is); - checked_set(m, tm, not_a_month, is); - checked_set(d, td, not_a_day, is); -#endif - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'X': - if (command) - { - if (modified != CharT{'O'}) - { -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - { - checked_set(H, tm.tm_hour, not_a_hour, is); - checked_set(M, tm.tm_min, not_a_minute, is); - checked_set(s, duration_cast(seconds{tm.tm_sec}), - not_a_second, is); - } - is.setstate(err); -#else - // "%T" - using dfs = detail::decimal_format_seconds; - CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; - int tH = not_a_hour; - int tM = not_a_minute; - long double S{}; - read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, - CharT{':'}, rld{S, 1, w}); - checked_set(H, tH, not_a_hour, is); - checked_set(M, tM, not_a_minute, is); - checked_set(s, round_i(duration{S}), - not_a_second, is); -#endif - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'C': - if (command) - { - int tC = not_a_century; -#if !ONLY_C_LOCALE - if (modified == CharT{}) - { -#endif - read(is, rs{tC, 1, width == -1 ? 2u : static_cast(width)}); -#if !ONLY_C_LOCALE - } - else - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - { - auto tY = tm.tm_year + 1900; - tC = (tY >= 0 ? tY : tY-99) / 100; - } - is.setstate(err); - } -#endif - checked_set(C, tC, not_a_century, is); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'D': - if (command) - { - if (modified == CharT{}) - { - int tn = not_a_month; - int td = not_a_day; - int ty = not_a_2digit_year; - read(is, ru{tn, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, - ru{td, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, - rs{ty, 1, 2}); - checked_set(y, ty, not_a_2digit_year, is); - checked_set(m, tn, not_a_month, is); - checked_set(d, td, not_a_day, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'F': - if (command) - { - if (modified == CharT{}) - { - int tY = not_a_year; - int tn = not_a_month; - int td = not_a_day; - read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}, - CharT{'-'}, ru{tn, 1, 2}, CharT{'-'}, ru{td, 1, 2}); - checked_set(Y, tY, not_a_year, is); - checked_set(m, tn, not_a_month, is); - checked_set(d, td, not_a_day, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'd': - case 'e': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - int td = not_a_day; - read(is, rs{td, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(d, td, not_a_day, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - command = nullptr; - width = -1; - modified = CharT{}; - if ((err & ios::failbit) == 0) - checked_set(d, tm.tm_mday, not_a_day, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'H': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - int tH = not_a_hour; - read(is, ru{tH, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(H, tH, not_a_hour, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(H, tm.tm_hour, not_a_hour, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'I': - if (command) - { - if (modified == CharT{}) - { - int tI = not_a_hour_12_value; - // reads in an hour into I, but most be in [1, 12] - read(is, rs{tI, 1, width == -1 ? 2u : static_cast(width)}); - if (!(1 <= tI && tI <= 12)) - is.setstate(ios::failbit); - checked_set(I, tI, not_a_hour_12_value, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'j': - if (command) - { - if (modified == CharT{}) - { - int tj = not_a_doy; - read(is, ru{tj, 1, width == -1 ? 3u : static_cast(width)}); - checked_set(j, tj, not_a_doy, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'M': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - int tM = not_a_minute; - read(is, ru{tM, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(M, tM, not_a_minute, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(M, tm.tm_min, not_a_minute, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'm': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - int tn = not_a_month; - read(is, rs{tn, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(m, tn, not_a_month, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(m, tm.tm_mon + 1, not_a_month, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'n': - case 't': - if (command) - { - if (modified == CharT{}) - { - // %n matches a single white space character - // %t matches 0 or 1 white space characters - auto ic = is.peek(); - if (Traits::eq_int_type(ic, Traits::eof())) - { - ios::iostate err = ios::eofbit; - if (*fmt == 'n') - err |= ios::failbit; - is.setstate(err); - break; - } - if (isspace(ic)) - { - (void)is.get(); - } - else if (*fmt == 'n') - is.setstate(ios::failbit); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'p': - if (command) - { - if (modified == CharT{}) - { - int tp = not_a_ampm; -#if !ONLY_C_LOCALE - tm = std::tm{}; - tm.tm_hour = 1; - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - is.setstate(err); - if (tm.tm_hour == 1) - tp = 0; - else if (tm.tm_hour == 13) - tp = 1; - else - is.setstate(err); -#else - auto nm = detail::ampm_names(); - auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - tp = static_cast(i); -#endif - checked_set(p, tp, not_a_ampm, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - - break; - case 'r': - if (command) - { - if (modified == CharT{}) - { -#if !ONLY_C_LOCALE - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - { - checked_set(H, tm.tm_hour, not_a_hour, is); - checked_set(M, tm.tm_min, not_a_hour, is); - checked_set(s, duration_cast(seconds{tm.tm_sec}), - not_a_second, is); - } - is.setstate(err); -#else - // "%I:%M:%S %p" - using dfs = detail::decimal_format_seconds; - CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; - long double S{}; - int tI = not_a_hour_12_value; - int tM = not_a_minute; - read(is, ru{tI, 1, 2}, CharT{':'}, ru{tM, 1, 2}, - CharT{':'}, rld{S, 1, w}); - checked_set(I, tI, not_a_hour_12_value, is); - checked_set(M, tM, not_a_minute, is); - checked_set(s, round_i(duration{S}), - not_a_second, is); - ws(is); - auto nm = detail::ampm_names(); - auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; - checked_set(p, static_cast(i), not_a_ampm, is); -#endif - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'R': - if (command) - { - if (modified == CharT{}) - { - int tH = not_a_hour; - int tM = not_a_minute; - read(is, ru{tH, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, - ru{tM, 1, 2}, CharT{'\0'}); - checked_set(H, tH, not_a_hour, is); - checked_set(M, tM, not_a_minute, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'S': - if (command) - { - #if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'E'}) -#endif - { - using dfs = detail::decimal_format_seconds; - CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; - long double S{}; - read(is, rld{S, 1, width == -1 ? w : static_cast(width)}); - checked_set(s, round_i(duration{S}), - not_a_second, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'O'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(s, duration_cast(seconds{tm.tm_sec}), - not_a_second, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'T': - if (command) - { - if (modified == CharT{}) - { - using dfs = detail::decimal_format_seconds; - CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; - int tH = not_a_hour; - int tM = not_a_minute; - long double S{}; - read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, - CharT{':'}, rld{S, 1, w}); - checked_set(H, tH, not_a_hour, is); - checked_set(M, tM, not_a_minute, is); - checked_set(s, round_i(duration{S}), - not_a_second, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'Y': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#else - if (modified != CharT{'O'}) -#endif - { - int tY = not_a_year; - read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}); - checked_set(Y, tY, not_a_year, is); - } -#if !ONLY_C_LOCALE - else if (modified == CharT{'E'}) - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(Y, tm.tm_year + 1900, not_a_year, is); - is.setstate(err); - } -#endif - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'y': - if (command) - { -#if !ONLY_C_LOCALE - if (modified == CharT{}) -#endif - { - int ty = not_a_2digit_year; - read(is, ru{ty, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(y, ty, not_a_2digit_year, is); - } -#if !ONLY_C_LOCALE - else - { - ios::iostate err = ios::goodbit; - f.get(is, nullptr, is, err, &tm, command, fmt+1); - if ((err & ios::failbit) == 0) - checked_set(Y, tm.tm_year + 1900, not_a_year, is); - is.setstate(err); - } -#endif - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'g': - if (command) - { - if (modified == CharT{}) - { - int tg = not_a_2digit_year; - read(is, ru{tg, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(g, tg, not_a_2digit_year, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'G': - if (command) - { - if (modified == CharT{}) - { - int tG = not_a_year; - read(is, rs{tG, 1, width == -1 ? 4u : static_cast(width)}); - checked_set(G, tG, not_a_year, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'U': - if (command) - { - if (modified == CharT{}) - { - int tU = not_a_week_num; - read(is, ru{tU, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(U, tU, not_a_week_num, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'V': - if (command) - { - if (modified == CharT{}) - { - int tV = not_a_week_num; - read(is, ru{tV, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(V, tV, not_a_week_num, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'W': - if (command) - { - if (modified == CharT{}) - { - int tW = not_a_week_num; - read(is, ru{tW, 1, width == -1 ? 2u : static_cast(width)}); - checked_set(W, tW, not_a_week_num, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'E': - case 'O': - if (command) - { - if (modified == CharT{}) - { - modified = *fmt; - } - else - { - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - } - else - read(is, *fmt); - break; - case '%': - if (command) - { - if (modified == CharT{}) - read(is, *fmt); - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - command = fmt; - break; - case 'z': - if (command) - { - int tH, tM; - minutes toff = not_a_offset; - bool neg = false; - auto ic = is.peek(); - if (!Traits::eq_int_type(ic, Traits::eof())) - { - auto c = static_cast(Traits::to_char_type(ic)); - if (c == '-') - { - neg = true; - (void)is.get(); - } - else if (c == '+') - (void)is.get(); - } - if (modified == CharT{}) - { - read(is, rs{tH, 2, 2}); - if (!is.fail()) - toff = hours{std::abs(tH)}; - if (is.good()) - { - ic = is.peek(); - if (!Traits::eq_int_type(ic, Traits::eof())) - { - auto c = static_cast(Traits::to_char_type(ic)); - if ('0' <= c && c <= '9') - { - read(is, ru{tM, 2, 2}); - if (!is.fail()) - toff += minutes{tM}; - } - } - } - } - else - { - read(is, rs{tH, 1, 2}); - if (!is.fail()) - toff = hours{std::abs(tH)}; - if (is.good()) - { - ic = is.peek(); - if (!Traits::eq_int_type(ic, Traits::eof())) - { - auto c = static_cast(Traits::to_char_type(ic)); - if (c == ':') - { - (void)is.get(); - read(is, ru{tM, 2, 2}); - if (!is.fail()) - toff += minutes{tM}; - } - } - } - } - if (neg) - toff = -toff; - checked_set(temp_offset, toff, not_a_offset, is); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - case 'Z': - if (command) - { - if (modified == CharT{}) - { - std::basic_string buf; - while (is.rdstate() == std::ios::goodbit) - { - auto i = is.rdbuf()->sgetc(); - if (Traits::eq_int_type(i, Traits::eof())) - { - is.setstate(ios::eofbit); - break; - } - auto wc = Traits::to_char_type(i); - auto c = static_cast(wc); - // is c a valid time zone name or abbreviation character? - if (!(CharT{1} < wc && wc < CharT{127}) || !(isalnum(c) || - c == '_' || c == '/' || c == '-' || c == '+')) - break; - buf.push_back(c); - is.rdbuf()->sbumpc(); - } - if (buf.empty()) - is.setstate(ios::failbit); - checked_set(temp_abbrev, buf, {}, is); - } - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - else - read(is, *fmt); - break; - default: - if (command) - { - if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9') - { - width = static_cast(*fmt) - '0'; - while ('0' <= fmt[1] && fmt[1] <= '9') - width = 10*width + static_cast(*++fmt) - '0'; - } - else - { - if (modified == CharT{}) - read(is, CharT{'%'}, width, *fmt); - else - read(is, CharT{'%'}, width, modified, *fmt); - command = nullptr; - width = -1; - modified = CharT{}; - } - } - else // !command - { - if (isspace(static_cast(*fmt))) - { - // space matches 0 or more white space characters - if (is.good()) - ws(is); - } - else - read(is, *fmt); - } - break; - } - } - // is.fail() || *fmt == CharT{} - if (is.rdstate() == ios::goodbit && command) - { - if (modified == CharT{}) - read(is, CharT{'%'}, width); - else - read(is, CharT{'%'}, width, modified); - } - if (!is.fail()) - { - if (y != not_a_2digit_year) - { - // Convert y and an optional C to Y - if (!(0 <= y && y <= 99)) - goto broken; - if (C == not_a_century) - { - if (Y == not_a_year) - { - if (y >= 69) - C = 19; - else - C = 20; - } - else - { - C = (Y >= 0 ? Y : Y-100) / 100; - } - } - int tY; - if (C >= 0) - tY = 100*C + y; - else - tY = 100*(C+1) - (y == 0 ? 100 : y); - if (Y != not_a_year && Y != tY) - goto broken; - Y = tY; - } - if (g != not_a_2digit_year) - { - // Convert g and an optional C to G - if (!(0 <= g && g <= 99)) - goto broken; - if (C == not_a_century) - { - if (G == not_a_year) - { - if (g >= 69) - C = 19; - else - C = 20; - } - else - { - C = (G >= 0 ? G : G-100) / 100; - } - } - int tG; - if (C >= 0) - tG = 100*C + g; - else - tG = 100*(C+1) - (g == 0 ? 100 : g); - if (G != not_a_year && G != tG) - goto broken; - G = tG; - } - if (Y < static_cast(year::min()) || Y > static_cast(year::max())) - Y = not_a_year; - bool computed = false; - if (G != not_a_year && V != not_a_week_num && wd != not_a_weekday) - { - year_month_day ymd_trial = sys_days(year{G-1}/December/Thursday[last]) + - (Monday-Thursday) + weeks{V-1} + - (weekday{static_cast(wd)}-Monday); - if (Y == not_a_year) - Y = static_cast(ymd_trial.year()); - else if (year{Y} != ymd_trial.year()) - goto broken; - if (m == not_a_month) - m = static_cast(static_cast(ymd_trial.month())); - else if (month(static_cast(m)) != ymd_trial.month()) - goto broken; - if (d == not_a_day) - d = static_cast(static_cast(ymd_trial.day())); - else if (day(static_cast(d)) != ymd_trial.day()) - goto broken; - computed = true; - } - if (Y != not_a_year && U != not_a_week_num && wd != not_a_weekday) - { - year_month_day ymd_trial = sys_days(year{Y}/January/Sunday[1]) + - weeks{U-1} + - (weekday{static_cast(wd)} - Sunday); - if (year{Y} != ymd_trial.year()) - goto broken; - if (m == not_a_month) - m = static_cast(static_cast(ymd_trial.month())); - else if (month(static_cast(m)) != ymd_trial.month()) - goto broken; - if (d == not_a_day) - d = static_cast(static_cast(ymd_trial.day())); - else if (day(static_cast(d)) != ymd_trial.day()) - goto broken; - computed = true; - } - if (Y != not_a_year && W != not_a_week_num && wd != not_a_weekday) - { - year_month_day ymd_trial = sys_days(year{Y}/January/Monday[1]) + - weeks{W-1} + - (weekday{static_cast(wd)} - Monday); - if (year{Y} != ymd_trial.year()) - goto broken; - if (m == not_a_month) - m = static_cast(static_cast(ymd_trial.month())); - else if (month(static_cast(m)) != ymd_trial.month()) - goto broken; - if (d == not_a_day) - d = static_cast(static_cast(ymd_trial.day())); - else if (day(static_cast(d)) != ymd_trial.day()) - goto broken; - computed = true; - } - if (j != not_a_doy && Y != not_a_year) - { - auto ymd_trial = year_month_day{local_days(year{Y}/1/1) + days{j-1}}; - if (m == not_a_month) - m = static_cast(static_cast(ymd_trial.month())); - else if (month(static_cast(m)) != ymd_trial.month()) - goto broken; - if (d == not_a_day) - d = static_cast(static_cast(ymd_trial.day())); - else if (day(static_cast(d)) != ymd_trial.day()) - goto broken; - j = not_a_doy; - } - auto ymd = year{Y}/m/d; - if (ymd.ok()) - { - if (wd == not_a_weekday) - wd = static_cast((weekday(sys_days(ymd)) - Sunday).count()); - else if (wd != static_cast((weekday(sys_days(ymd)) - Sunday).count())) - goto broken; - if (!computed) - { - if (G != not_a_year || V != not_a_week_num) - { - sys_days sd = ymd; - auto G_trial = year_month_day{sd + days{3}}.year(); - auto start = sys_days((G_trial - years{1})/December/Thursday[last]) + - (Monday - Thursday); - if (sd < start) - { - --G_trial; - if (V != not_a_week_num) - start = sys_days((G_trial - years{1})/December/Thursday[last]) - + (Monday - Thursday); - } - if (G != not_a_year && G != static_cast(G_trial)) - goto broken; - if (V != not_a_week_num) - { - auto V_trial = duration_cast(sd - start).count() + 1; - if (V != V_trial) - goto broken; - } - } - if (U != not_a_week_num) - { - auto start = sys_days(Sunday[1]/January/ymd.year()); - auto U_trial = floor(sys_days(ymd) - start).count() + 1; - if (U != U_trial) - goto broken; - } - if (W != not_a_week_num) - { - auto start = sys_days(Monday[1]/January/ymd.year()); - auto W_trial = floor(sys_days(ymd) - start).count() + 1; - if (W != W_trial) - goto broken; - } - } - } - fds.ymd = ymd; - if (I != not_a_hour_12_value) - { - if (!(1 <= I && I <= 12)) - goto broken; - if (p != not_a_ampm) - { - // p is in [0, 1] == [AM, PM] - // Store trial H in I - if (I == 12) - --p; - I += p*12; - // Either set H from I or make sure H and I are consistent - if (H == not_a_hour) - H = I; - else if (I != H) - goto broken; - } - else // p == not_a_ampm - { - // if H, make sure H and I could be consistent - if (H != not_a_hour) - { - if (I == 12) - { - if (H != 0 && H != 12) - goto broken; - } - else if (!(I == H || I == H+12)) - { - goto broken; - } - } - else // I is ambiguous, AM or PM? - goto broken; - } - } - if (H != not_a_hour) - { - fds.has_tod = true; - fds.tod = hh_mm_ss{hours{H}}; - } - if (M != not_a_minute) - { - fds.has_tod = true; - fds.tod.m_ = minutes{M}; - } - if (s != not_a_second) - { - fds.has_tod = true; - fds.tod.s_ = detail::decimal_format_seconds{s}; - } - if (j != not_a_doy) - { - fds.has_tod = true; - fds.tod.h_ += hours{days{j}}; - } - if (wd != not_a_weekday) - fds.wd = weekday{static_cast(wd)}; - if (abbrev != nullptr) - *abbrev = std::move(temp_abbrev); - if (offset != nullptr && temp_offset != not_a_offset) - *offset = temp_offset; - } - return is; - } -broken: - is.setstate(ios::failbit); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, year& y, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.year().ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - y = fds.ymd.year(); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, month& m, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.month().ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - m = fds.ymd.month(); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, day& d, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.day().ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - d = fds.ymd.day(); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, weekday& wd, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.wd.ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - wd = fds.wd; - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, year_month& ym, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.month().ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - ym = fds.ymd.year()/fds.ymd.month(); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, month_day& md, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - md = fds.ymd.month()/fds.ymd.day(); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - year_month_day& ymd, std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = std::chrono::seconds; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.ok()) - is.setstate(std::ios::failbit); - if (!is.fail()) - ymd = fds.ymd; - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - sys_time& tp, std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = typename std::common_type::type; - using detail::round_i; - std::chrono::minutes offset_local{}; - auto offptr = offset ? offset : &offset_local; - fields fds{}; - fds.has_tod = true; - date::from_stream(is, fmt, fds, abbrev, offptr); - if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) - is.setstate(std::ios::failbit); - if (!is.fail()) - tp = round_i(sys_days(fds.ymd) - *offptr + fds.tod.to_duration()); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - local_time& tp, std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using CT = typename std::common_type::type; - using detail::round_i; - fields fds{}; - fds.has_tod = true; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) - is.setstate(std::ios::failbit); - if (!is.fail()) - tp = round_i(local_seconds{local_days(fds.ymd)} + fds.tod.to_duration()); - return is; -} - -template > -std::basic_istream& -from_stream(std::basic_istream& is, const CharT* fmt, - std::chrono::duration& d, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) -{ - using Duration = std::chrono::duration; - using CT = typename std::common_type::type; - using detail::round_i; - fields fds{}; - date::from_stream(is, fmt, fds, abbrev, offset); - if (!fds.has_tod) - is.setstate(std::ios::failbit); - if (!is.fail()) - d = round_i(fds.tod.to_duration()); - return is; -} - -template , - class Alloc = std::allocator> -struct parse_manip -{ - const std::basic_string format_; - Parsable& tp_; - std::basic_string* abbrev_; - std::chrono::minutes* offset_; - -public: - parse_manip(std::basic_string format, Parsable& tp, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) - : format_(std::move(format)) - , tp_(tp) - , abbrev_(abbrev) - , offset_(offset) - {} - -#if HAS_STRING_VIEW - parse_manip(const CharT* format, Parsable& tp, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) - : format_(format) - , tp_(tp) - , abbrev_(abbrev) - , offset_(offset) - {} - - parse_manip(std::basic_string_view format, Parsable& tp, - std::basic_string* abbrev = nullptr, - std::chrono::minutes* offset = nullptr) - : format_(format) - , tp_(tp) - , abbrev_(abbrev) - , offset_(offset) - {} -#endif // HAS_STRING_VIEW -}; - -template -std::basic_istream& -operator>>(std::basic_istream& is, - const parse_manip& x) -{ - return date::from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); -} - -template -inline -auto -parse(const std::basic_string& format, Parsable& tp) - -> decltype(date::from_stream(std::declval&>(), - format.c_str(), tp), - parse_manip{format, tp}) -{ - return {format, tp}; -} - -template -inline -auto -parse(const std::basic_string& format, Parsable& tp, - std::basic_string& abbrev) - -> decltype(date::from_stream(std::declval&>(), - format.c_str(), tp, &abbrev), - parse_manip{format, tp, &abbrev}) -{ - return {format, tp, &abbrev}; -} - -template -inline -auto -parse(const std::basic_string& format, Parsable& tp, - std::chrono::minutes& offset) - -> decltype(date::from_stream(std::declval&>(), - format.c_str(), tp, - std::declval*>(), - &offset), - parse_manip{format, tp, nullptr, &offset}) -{ - return {format, tp, nullptr, &offset}; -} - -template -inline -auto -parse(const std::basic_string& format, Parsable& tp, - std::basic_string& abbrev, std::chrono::minutes& offset) - -> decltype(date::from_stream(std::declval&>(), - format.c_str(), tp, &abbrev, &offset), - parse_manip{format, tp, &abbrev, &offset}) -{ - return {format, tp, &abbrev, &offset}; -} - -// const CharT* formats - -template -inline -auto -parse(const CharT* format, Parsable& tp) - -> decltype(date::from_stream(std::declval&>(), format, tp), - parse_manip{format, tp}) -{ - return {format, tp}; -} - -template -inline -auto -parse(const CharT* format, Parsable& tp, std::basic_string& abbrev) - -> decltype(date::from_stream(std::declval&>(), format, - tp, &abbrev), - parse_manip{format, tp, &abbrev}) -{ - return {format, tp, &abbrev}; -} - -template -inline -auto -parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset) - -> decltype(date::from_stream(std::declval&>(), format, - tp, std::declval*>(), &offset), - parse_manip{format, tp, nullptr, &offset}) -{ - return {format, tp, nullptr, &offset}; -} - -template -inline -auto -parse(const CharT* format, Parsable& tp, - std::basic_string& abbrev, std::chrono::minutes& offset) - -> decltype(date::from_stream(std::declval&>(), format, - tp, &abbrev, &offset), - parse_manip{format, tp, &abbrev, &offset}) -{ - return {format, tp, &abbrev, &offset}; -} - -// duration streaming - -template -inline -std::basic_ostream& -operator<<(std::basic_ostream& os, - const std::chrono::duration& d) -{ - return os << detail::make_string::from(d.count()) + - detail::get_units(typename Period::type{}); -} - -} // namespace date - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -#ifdef __GNUC__ -# pragma GCC diagnostic pop -#endif - -#endif // DATE_H From a601e9eb0d86e92f247a4937e7362df36e208802 Mon Sep 17 00:00:00 2001 From: Philippe Verney Date: Wed, 26 Nov 2025 18:35:08 +0100 Subject: [PATCH 32/32] Fix RESQML22 uri validation --- example/withFesapi/etpClient.cpp | 6 +++++- src/etp/EtpHelpers.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp index 3735e98..dc92b35 100644 --- a/example/withFesapi/etpClient.cpp +++ b/example/withFesapi/etpClient.cpp @@ -724,6 +724,10 @@ int main(int argc, char **argv) std::string authorization; std::getline(std::cin, authorization); + std::cout << "Give the data partition id you want to direct your requests (or hit enter if no data partition)" << std::endl; + std::string dataPartition; + std::getline(std::cin, dataPartition); + COMMON_NS::DataObjectRepository repo; repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::RESQML2_0_1); repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_0); @@ -732,7 +736,7 @@ int main(int argc, char **argv) ETP_NS::InitializationParameters initializationParams = argc == 2 ? ETP_NS::InitializationParameters(gen(), argv[1]) // URL based : ETP_NS::InitializationParameters(gen(), argv[1], std::stoi(argv[2]), argc < 4 ? "/" : argv[3]); // IP Port and target based - std::map< std::string, std::string > additionalHeaderField = { {"data-partition-id", "osdu"} }; // Example for OSDU RDDMS + std::map< std::string, std::string > additionalHeaderField = { {"data-partition-id", dataPartition} }; // Example for OSDU RDDMS initializationParams.setAdditionalHandshakeHeaderFields(additionalHeaderField); std::cout << "Creating a client session..." << std::endl; diff --git a/src/etp/EtpHelpers.cpp b/src/etp/EtpHelpers.cpp index b6cc811..366eea7 100644 --- a/src/etp/EtpHelpers.cpp +++ b/src/etp/EtpHelpers.cpp @@ -60,7 +60,7 @@ bool ETP_NS::EtpHelpers::validateUri(const std::string & uri) return std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)])?", std::regex::ECMAScript)) || std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(resqml20|eml20)\.obj_[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)) || - std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(witsml|resqml|prodml|eml)([0-9]{2})\[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)); + std::regex_match(uri, std::regex("^eml:///(dataspace[(]'.*'[)]/)?(witsml|resqml|prodml|eml)([0-9]{2})\.[a-zA-Z0-9]+[(][a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}(,.*)?[)]", std::regex::ECMAScript)); } bool ETP_NS::EtpHelpers::validateDataObjectUri(const std::string & uri)