diff --git a/CMakeLists.txt b/CMakeLists.txt index 64fad3282a1796dda04745378feed7bb5c891754..3c3603ca52409df19a008b0e1a73a4766d9be596 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ cmake_minimum_required(VERSION 3.13) -set(libcaosdb_VERSION 0.0.10) +set(libcaosdb_VERSION 0.0.11) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0) set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5) set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0) @@ -38,6 +38,7 @@ set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) diff --git a/README_SETUP.md b/README_SETUP.md index ea0284e2dd0458eee5aa7c9e165a3e6767e57d55..147745c32ad3d1460609e91942e4b7400b243560 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -116,6 +116,7 @@ Please adhere to [Google's C++ naming conventions](https://google.github.io/styl You can use a json file for the configuration of the client. See `test/test_data/test_caosdb_client.json` for an example. You may use `caosdb-client-configuration-schema.json` to validate your schema. +Typically, you will need to provide the path to your SSL certificate. The client will load the configuration file from the first existing file in the following locations (precedence from highest to lowest): diff --git a/conanfile.py b/conanfile.py index 0e31f8dfeaae2cd681690324611031bd3017ec67..300f652d76620d2cd6d4c698a0d651941daed5a4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -3,7 +3,7 @@ from conans import ConanFile, CMake, tools class CaosdbConan(ConanFile): name = "caosdb" - version = "0.0.10" + version = "0.0.11" license = "AGPL-3.0-or-later" author = "Timm C. Fitschen <t.fitschen@indiscale.com>" url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git" diff --git a/doc/capi/index.rst.in b/doc/capi/index.rst.in index 887f7ee90c7c6a419a90e6b67f00602fc7816f41..a15f3b876035058113a61f2325cb739f01a39fbb 100644 --- a/doc/capi/index.rst.in +++ b/doc/capi/index.rst.in @@ -24,6 +24,21 @@ C API ===== +.. note:: + + When working with libcaosdb's C API keep the following in + mind. Delete all objects (transactions, entities, properties, + parents, ...) that you created using a `caosdb_..._create_...` + function and only those. + + The underlying reason is that all C++ objects are realized in the + Extern C interface as mutable structs containing a void pointer to + the actuall C++ object which is not filled when initializing the + struct but after calling a create function instead. If the C++ + object wasn't created using a create function, e.g., the parent + object when getting a parent of an entity, it is owned by another + object and deleted together with that object. + .. toctree:: :glob: diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 542ad975f5241ce7755994b57afcf53e1cdeefc7..c0db7a2c007d13394a06bede00d1c601ad83bef2 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -27,6 +27,7 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_BINARY_DIR}/caosdb/constants.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/exceptions.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/handler_interface.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/log_level.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.h @@ -34,8 +35,18 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/status_code.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_status.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/unary_rpc_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utility.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/register_file_upload_handler.h + # TODO this file is still missing + # ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/register_file_download_handler.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/upload_request_handler.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/download_request_handler.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_writer.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_reader.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_error.h ) # pass variable to parent scope diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index 5d7930a0d63e484f01be52dab7749d02b3916c33..ef9597bb06cb5454160eb02938219a8ced564d17 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -45,6 +45,7 @@ using boost::filesystem::path; using caosdb::authentication::Authenticator; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1alpha1::EntityTransactionService; +using caosdb::entity::v1alpha1::FileTransmissionService; using caosdb::info::VersionInfo; using caosdb::info::v1alpha1::GeneralInfoService; using caosdb::transaction::Transaction; @@ -104,6 +105,9 @@ private: /// Service for entity transactions. We use a shared pointer because /// Transaction instances also own this service stub. std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service; + /// Service for file transmission (download and upload). We use a shared + /// pointer because Transaction instances also own this service stub. + std::shared_ptr<FileTransmissionService::Stub> file_transmission_service; }; /** diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index 37a1797c586fb4234e8c385a5d0a1b89c2589ce7..3917e934122453e71d782604903143ecf5625bd1 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -29,29 +29,241 @@ #ifndef CAOSDB_ENTITY_H #define CAOSDB_ENTITY_H -#include <string> // for string -#include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Message -#include "caosdb/message_code.h" // for get_message_code, Messag... -#include "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso... +#include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField +#include "caosdb/logging.h" // for CAOSDB_LOG_WARN +#include "caosdb/message_code.h" // for get_message_code +#include "caosdb/status_code.h" // for StatusCode +#include <boost/filesystem/operations.hpp> // for exists, is_di... +#include <boost/filesystem/path.hpp> // for path +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <google/protobuf/message.h> // for RepeatedPtrField +#include <google/protobuf/util/json_util.h> // for MessageToJson... +#include <iosfwd> // for streamsize +#include <iterator> // for iterator, output_iterato... +#include <map> // for map +#include <random> // for mt19937, rand... +#include <stdexcept> // for out_of_range +#include <string> // for string, basic... namespace caosdb::entity { +using boost::filesystem::exists; +using boost::filesystem::is_directory; using caosdb::entity::v1alpha1::IdResponse; using ProtoParent = caosdb::entity::v1alpha1::Parent; using ProtoProperty = caosdb::entity::v1alpha1::Property; using ProtoEntity = caosdb::entity::v1alpha1::Entity; +using ProtoFileDescriptor = caosdb::entity::v1alpha1::FileDescriptor; using ProtoMessage = caosdb::entity::v1alpha1::Message; +using caosdb::StatusCode; +using caosdb::entity::v1alpha1::EntityResponse; +using caosdb::entity::v1alpha1::FileTransmissionId; +using ::google::protobuf::RepeatedPtrField; +using google::protobuf::RepeatedPtrField; + +static const std::string logger_name = "caosdb::entity"; + +struct FileDescriptor { + FileTransmissionId *file_transmission_id; + ProtoFileDescriptor *wrapped; + boost::filesystem::path local_path; +}; + +/** + * Abstract base class for Messages, Properties and Parents container classes. + * + * This is a list-like class. + */ +template <typename T, typename P> class RepeatedPtrFieldWrapper { + class iterator; + +public: + /** + * Return the current size of the container. + */ + [[nodiscard]] inline auto size() const -> int { return wrapped->size(); } + /** + * Return a const reference to the element at the given index. + */ + [[nodiscard]] inline auto at(int index) const -> const T & { + return *mutable_at(index); + } + + /** + * Return a mutable pointer to the element at the given index. + */ + [[nodiscard]] inline auto mutable_at(int index) const -> T * { + if (index >= size() || index < 0) { + throw std::out_of_range("Container has size " + std::to_string(size())); + } + if (cache.count(index) == 0) { + cache.emplace(index, T(&(wrapped->at(index)))); + } + return &(cache.at(index)); + } + /** + * Return iterator positioned at the beginning of the list. + */ + auto begin() -> iterator; + /** + * Return iterator positioned at the end of the list. + */ + auto end() -> iterator; + /** + * Return constant iterator positioned at the beginning of the list. + */ + auto begin() const -> const iterator; + /** + * Return constant iterator positioned at the end of the list. + */ + auto end() const -> const iterator; + + friend class Entity; + + virtual ~RepeatedPtrFieldWrapper(){}; + +protected: + RepeatedPtrFieldWrapper(){}; + explicit inline RepeatedPtrFieldWrapper( + ::google::protobuf::RepeatedPtrField<P> *wrapped) + : wrapped(wrapped){}; + + /** + * Append an element. This adds the element to the end of the wrapped list + * and increases the size by one. + */ + inline auto Append(const T &element) -> void { + auto *destination = this->wrapped->Add(); + destination->Swap(element.wrapped); + + // Clear the originally wrapped object and return it to the Arena + element.wrapped->Clear(); + + // set the pointer to the new object which is owned by the RepeatedPtrField + element.wrapped = destination; + } + + /** + * Remove the element at the given index. + */ + inline auto remove(int index) -> void { + this->wrapped->DeleteSubrange(index, 1); + if (cache.count(index) > 0) { + cache.erase(index); + } + + // shift all indices in the cache above index (such that the values do not + // get deleted/copied because this could destroy pointers (c-interface). + for (int i = index + 1; i < size(); i++) { + if (cache.count(i) > 0) { + auto handle = cache.extract(i); + handle.key()--; + cache.insert(std::move(handle)); + } + } + } + + ::google::protobuf::RepeatedPtrField<P> *wrapped; + mutable std::map<int, T> cache; + +private: + class iterator : public std::iterator<std::output_iterator_tag, T> { + public: + explicit iterator(const RepeatedPtrFieldWrapper<T, P> *instance, + int index = 0); + auto operator*() const -> T &; + auto operator++() -> iterator &; + auto operator++(int) -> iterator; + auto operator!=(const iterator &rhs) const -> bool; + + private: + int current_index = 0; + const RepeatedPtrFieldWrapper<T, P> *instance; + }; +}; + +template <class T, class P> +RepeatedPtrFieldWrapper<T, P>::iterator::iterator( + const RepeatedPtrFieldWrapper<T, P> *instance, int index) + : current_index(index), instance(instance) {} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::iterator::operator*() const -> T & { + return *(this->instance->mutable_at(current_index)); +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::iterator::operator++() + -> RepeatedPtrFieldWrapper<T, P>::iterator & { + current_index++; + return *this; +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::iterator::operator++(int) + -> RepeatedPtrFieldWrapper<T, P>::iterator { + iterator tmp(*this); + operator++(); + return tmp; +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::iterator::operator!=( + const iterator &rhs) const -> bool { + return this->current_index != rhs.current_index; +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::begin() + -> RepeatedPtrFieldWrapper<T, P>::iterator { + return RepeatedPtrFieldWrapper<T, P>::iterator(this, 0); +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::end() + -> RepeatedPtrFieldWrapper<T, P>::iterator { + return RepeatedPtrFieldWrapper<T, P>::iterator(this, size()); +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::begin() const + -> const RepeatedPtrFieldWrapper<T, P>::iterator { + return RepeatedPtrFieldWrapper<T, P>::iterator(this, 0); +} + +template <typename T, typename P> +auto RepeatedPtrFieldWrapper<T, P>::end() const + -> const RepeatedPtrFieldWrapper<T, P>::iterator { + return RepeatedPtrFieldWrapper<T, P>::iterator(this, size()); +} /** - * Messages convey information about the state and result of transactions. + * Messages convey information about the state of entities and result of + * transactions. * * A Message object can be thought of as kinf of a generalized error object in * other frameworks. Please have a look at MessageCodes for more details. */ class Message { public: + /** + * Get the code of this message. + * + * The message code is a unique identifier of the type of message and is + * intended to make it easiert to identify messages in a machine-readable + * way. + */ [[nodiscard]] inline auto GetCode() const -> MessageCode { return get_message_code(wrapped->code()); } + /** + * Get the description of this message. + * + * The description is intended for a human reader. + */ [[nodiscard]] inline auto GetDescription() const -> std::string { return wrapped->description(); } @@ -62,6 +274,7 @@ public: // friend class Parent; // friend class Property; friend class Messages; + friend class RepeatedPtrFieldWrapper<Message, ProtoMessage>; private: explicit inline Message(ProtoMessage *wrapped) : wrapped(wrapped){}; @@ -72,12 +285,9 @@ private: /** * Container for Messages. */ -class Messages { +class Messages : public RepeatedPtrFieldWrapper<Message, ProtoMessage> { public: - [[nodiscard]] inline auto Size() const -> int { return wrapped->size(); } - [[nodiscard]] inline auto At(int index) const -> const Message { - return Message(&(wrapped->at(index))); - } + ~Messages(); friend class Entity; // TODO(fspreck) Same here. @@ -85,9 +295,7 @@ public: // friend class Property; private: - inline Messages() : wrapped(nullptr){}; - - ::google::protobuf::RepeatedPtrField<ProtoMessage> *wrapped; + inline Messages() : RepeatedPtrFieldWrapper(){}; }; /** @@ -166,6 +374,7 @@ public: friend class Entity; friend class Parents; + friend class RepeatedPtrFieldWrapper<Parent, ProtoParent>; private: /** @@ -194,40 +403,17 @@ private: * * Should only be instantiated and write-accessed by the owning entity. */ -class Parents { +class Parents : public RepeatedPtrFieldWrapper<Parent, ProtoParent> { public: - /** - * Return the current size of the parent container. - * - * That is also the number of parents the owning entity currently has. - */ - [[nodiscard]] inline auto Size() const -> int { return wrapped->size(); } - /** - * Return the parent at the given index. - */ - [[nodiscard]] inline auto At(int index) const -> const Parent { - return Parent(&(wrapped->at(index))); - } - + ~Parents() = default; friend class Entity; private: - inline Parents(){}; + inline Parents() : RepeatedPtrFieldWrapper(){}; explicit inline Parents( - ::google::protobuf::RepeatedPtrField<ProtoParent> *wrapped) - : wrapped(wrapped){}; - - /** - * Append a parent. - * - * This increases the Size() by one. - */ - auto Append(const Parent &parent) -> void; - /** - * The collection of parent messages which serves as a backend for this - * class. - */ - ::google::protobuf::RepeatedPtrField<ProtoParent> *wrapped; + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent> + *wrapped) + : RepeatedPtrFieldWrapper(wrapped){}; }; /** @@ -320,6 +506,7 @@ public: friend class Entity; friend class Properties; + friend class RepeatedPtrFieldWrapper<Property, ProtoProperty>; private: static auto CreateProtoProperty() -> ProtoProperty *; @@ -332,37 +519,20 @@ private: * * Should only be instantiated and write-accessed by the owning entity. */ -class Properties { +class Properties + : public RepeatedPtrFieldWrapper<Property, + caosdb::entity::v1alpha1::Property> { public: - /** - * Return the current size of the properties container. - * - * This is also the number of properties the owningn entity currently has. - */ - [[nodiscard]] inline auto Size() const -> int { return wrapped->size(); } - /** - * Return the property at the given index. - */ - [[nodiscard]] auto At(int index) const -> const Property { - return Property(&(wrapped->at(index))); - } - + ~Properties() = default; friend class Entity; private: inline Properties(){}; explicit inline Properties( - ::google::protobuf::RepeatedPtrField<ProtoProperty> *wrapped) - : wrapped(wrapped){}; - - /** - * Append a property - * - * This increases the Size() by one. - */ - auto Append(const Property &property) -> void; - - ::google::protobuf::RepeatedPtrField<ProtoProperty> *wrapped; + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Property> + *wrapped) + : RepeatedPtrFieldWrapper<Property, caosdb::entity::v1alpha1::Property>( + wrapped){}; }; /** @@ -375,12 +545,18 @@ public: this->wrapped->CopyFrom(*original.wrapped); }; explicit Entity(IdResponse *idResponse); - explicit inline Entity(ProtoEntity *wrapped) : wrapped(wrapped) { - errors.wrapped = this->wrapped->mutable_errors(); - warnings.wrapped = this->wrapped->mutable_warnings(); - infos.wrapped = this->wrapped->mutable_infos(); + explicit Entity(ProtoEntity *wrapped) : wrapped(wrapped) { properties.wrapped = this->wrapped->mutable_properties(); parents.wrapped = this->wrapped->mutable_parents(); + errors.wrapped = CreateMessagesField(); + warnings.wrapped = CreateMessagesField(); + infos.wrapped = CreateMessagesField(); + }; + explicit inline Entity(EntityResponse *response) + : Entity(response->release_entity()) { + errors.wrapped->Swap(response->mutable_errors()); + warnings.wrapped->Swap(response->mutable_warnings()); + infos.wrapped->Swap(response->mutable_infos()); }; [[nodiscard]] inline auto GetId() const noexcept -> const std::string & { @@ -448,18 +624,80 @@ public: auto SetUnit(const std::string &unit) -> void; // Currently no references or lists. auto SetDatatype(const std::string &datatype) -> void; + auto AppendProperty(const Property &property) -> void; + auto RemoveProperty(int index) -> void; auto AppendParent(const Parent &parent) -> void; + auto RemoveParent(int index) -> void; /** * Copy all of this entity's features to the target ProtoEntity. */ auto CopyTo(ProtoEntity *target) -> void; -protected: + auto SetFilePath(const std::string &path) -> void; + inline auto HasFile() const -> bool { + return !this->file_descriptor.local_path.empty(); + } + auto SetFileTransmissionRegistrationId(const std::string ®istration_id) + -> void; + inline auto SetFileTransmissionId(FileTransmissionId *file_transmission_id) + -> void { + file_transmission_id->set_file_id(GetNextFileId()); + file_descriptor.file_transmission_id = file_transmission_id; + } + + inline auto GetFileDescriptor() -> FileDescriptor & { + return this->file_descriptor; + } + + inline auto GetLocalPath() const noexcept -> const boost::filesystem::path & { + return this->file_descriptor.local_path; + } + + inline auto SetLocalPath(const boost::filesystem::path &local_path) noexcept + -> StatusCode { + if (GetRole() != "File") { + CAOSDB_LOG_WARN(logger_name) + << "Entity::SetLocalPath failed. This is not a file entity."; + return StatusCode::NOT_A_FILE_ENTITY; + } + if (!exists(local_path)) { + CAOSDB_LOG_WARN(logger_name) + << "Entity::SetLocalPath failed. This file does not exists: " + << local_path.string(); + return StatusCode::FILE_DOES_NOT_EXIST_LOCALLY; + } + if (is_directory(local_path)) { + CAOSDB_LOG_WARN(logger_name) + << "Entity::SetLocalPath failed. This file is a directory: " + << local_path.string(); + return StatusCode::PATH_IS_A_DIRECTORY; + } + + CAOSDB_LOG_TRACE(logger_name) + << "Entity::SetLocalPath(" << local_path.string() << ");"; + this->file_descriptor.local_path = local_path; + return StatusCode::SUCCESS; + } + +private: + inline auto GetNextFileId() -> std::string { + std::string str = "0123456789abcdef"; + std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution<int> distribution(0, str.size() - 1); + std::string result(10, '\0'); + + for (auto &dis : result) { + dis = str[distribution(generator)]; + } + return result; + } static auto CreateProtoEntity() -> ProtoEntity *; + static auto CreateMessagesField() -> RepeatedPtrField<ProtoMessage> *; auto SetId(const std::string &id) -> void; auto SetVersionId(const std::string &id) -> void; + FileDescriptor file_descriptor; ProtoEntity *wrapped; Properties properties; Parents parents; diff --git a/include/caosdb/file_transmission/download_request_handler.h b/include/caosdb/file_transmission/download_request_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..cb2108aa3c9c091316e557c1d732297d84171db9 --- /dev/null +++ b/include/caosdb/file_transmission/download_request_handler.h @@ -0,0 +1,124 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_DOWNLOAD_REQUEST_HANDLER_H +#define CAOSDB_FILE_TRANSMISSION_DOWNLOAD_REQUEST_HANDLER_H + +#include "caosdb/entity.h" // for FileDescriptor +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1alpha1/main.pb.h" // for FileDownloadResponse +#include "caosdb/file_transmission/file_writer.h" // for FileWriter +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncReader +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <grpcpp/impl/codegen/status.h> // for Status +#include <memory> // for unique_ptr + +namespace caosdb::transaction { +using caosdb::entity::FileDescriptor; +using caosdb::entity::v1alpha1::FileDownloadRequest; +using caosdb::entity::v1alpha1::FileDownloadResponse; +using caosdb::entity::v1alpha1::FileTransmissionService; +using caosdb::transaction::HandlerInterface; +using caosdb::transaction::HandlerTag; + +/* + * Handler for the download request of a single file + */ +class DownloadRequestHandler final : public HandlerInterface { +public: + DownloadRequestHandler(HandlerTag tag, FileTransmissionService::Stub *stub, + grpc::CompletionQueue *cq, + FileDescriptor file_descriptor); + + ~DownloadRequestHandler() override = default; + + DownloadRequestHandler(const DownloadRequestHandler &) = delete; + DownloadRequestHandler &operator=(const DownloadRequestHandler &) = delete; + DownloadRequestHandler(DownloadRequestHandler &&) = delete; + DownloadRequestHandler &operator=(DownloadRequestHandler &&) = delete; + + void Start() override { OnNext(true); } + + bool OnNext(bool ok) override; + + void Cancel() override; + +protected: + enum class CallState { NewCall, SendingRequest, ReceivingFile, CallComplete }; + + void handleNewCallState(); + void handleSendingRequestState(); + void handleReceivingFileState(); + void handleCallCompleteState(); + + HandlerTag tag_; + + FileTransmissionService::Stub *stub_; + grpc::CompletionQueue *cq_; + grpc::ClientContext ctx_; + + std::unique_ptr<grpc::ClientAsyncReader<FileDownloadResponse>> rpc_; + + FileDownloadRequest *request_; + FileDownloadResponse *response_; + grpc::Status status_; + + CallState state_; + + std::unique_ptr<FileWriter> fileWriter_; + + FileDescriptor file_descriptor_; + + unsigned long long bytesReceived_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/file_transmission/file_error.h b/include/caosdb/file_transmission/file_error.h new file mode 100644 index 0000000000000000000000000000000000000000..ae057be3ba47f894f8fdbc22adb6e6d56accdafd --- /dev/null +++ b/include/caosdb/file_transmission/file_error.h @@ -0,0 +1,64 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_FILE_ERROR_H +#define CAOSDB_FILE_TRANSMISSION_FILE_ERROR_H + +#include <stdexcept> +#include <string> + +namespace caosdb::transaction { + +class FileIOError : public std::runtime_error { +public: + FileIOError(const std::string &message) : std::runtime_error(message) {} +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/file_transmission/file_reader.h b/include/caosdb/file_transmission/file_reader.h new file mode 100644 index 0000000000000000000000000000000000000000..67c1247fa97cc43e28064b4e0da81812f41b231a --- /dev/null +++ b/include/caosdb/file_transmission/file_reader.h @@ -0,0 +1,89 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_FILE_READER_H +#define CAOSDB_FILE_TRANSMISSION_FILE_READER_H + +#include <boost/filesystem/fstream.hpp> // for ifstream +#include <boost/filesystem/operations.hpp> // for exists +#include <boost/filesystem/path.hpp> // for path +#include <fstream> // for ifstream, size_t +#include <string> // for string + +namespace caosdb::transaction { +using boost::filesystem::exists; +using boost::filesystem::ifstream; +using boost::filesystem::path; + +class FileReader final { +public: + FileReader(boost::filesystem::path filename); + + ~FileReader() = default; + + FileReader(const FileReader &) = delete; + FileReader &operator=(const FileReader &) = delete; + + FileReader(FileReader &&) = default; + FileReader &operator=(FileReader &&) = default; + + unsigned long long fileSize() const { return size_; } + + std::size_t read(std::string &buffer); + +private: + void openFile(); + + std::ifstream stream_; + boost::filesystem::path filename_; + unsigned long long size_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/file_transmission/file_writer.h b/include/caosdb/file_transmission/file_writer.h new file mode 100644 index 0000000000000000000000000000000000000000..801d74b9547951d2a3b86ed4b333bfb4b7035aa9 --- /dev/null +++ b/include/caosdb/file_transmission/file_writer.h @@ -0,0 +1,81 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_FILE_WRITER_H +#define CAOSDB_FILE_TRANSMISSION_FILE_WRITER_H + +#include <boost/filesystem/path.hpp> // for path +#include <fstream> // for ofstream +#include <string> // for string + +namespace caosdb::transaction { + +class FileWriter final { +public: + FileWriter(boost::filesystem::path filename); + + ~FileWriter() = default; + + FileWriter(const FileWriter &) = delete; + FileWriter &operator=(const FileWriter &) = delete; + + FileWriter(FileWriter &&) = default; + FileWriter &operator=(FileWriter &&) = default; + + void write(const std::string &buffer); + +private: + void openFile(); + + std::ofstream stream_; + boost::filesystem::path filename_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/file_transmission/register_file_upload_handler.h b/include/caosdb/file_transmission/register_file_upload_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..17a42c0a2a2cf1d591048abb6e4c8b329bfd008c --- /dev/null +++ b/include/caosdb/file_transmission/register_file_upload_handler.h @@ -0,0 +1,97 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_REGISTER_FILE_UPLOAD_H +#define CAOSDB_FILE_TRANSMISSION_REGISTER_FILE_UPLOAD_H + +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1alpha1/main.pb.h" // for FileDownloadResponse +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include "caosdb/unary_rpc_handler.h" +#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRespons... +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <memory> // for unique_ptr + +namespace caosdb::transaction { + +using caosdb::entity::v1alpha1::FileTransmissionService; +using caosdb::entity::v1alpha1::RegisterFileUploadRequest; +using caosdb::entity::v1alpha1::RegisterFileUploadResponse; + +class RegisterFileUploadHandler final : public UnaryRpcHandler { +public: + RegisterFileUploadHandler(HandlerTag tag, FileTransmissionService::Stub *stub, + grpc::CompletionQueue *completion_queue, + RegisterFileUploadRequest *request, + RegisterFileUploadResponse *response); + + ~RegisterFileUploadHandler(); + + RegisterFileUploadHandler(const RegisterFileUploadHandler &) = delete; + RegisterFileUploadHandler & + operator=(const RegisterFileUploadHandler &) = delete; + RegisterFileUploadHandler(RegisterFileUploadHandler &&) = delete; + RegisterFileUploadHandler &operator=(RegisterFileUploadHandler &&) = delete; + +protected: + void handleNewCallState() override; + + HandlerTag tag_; + + FileTransmissionService::Stub *stub_; + + std::unique_ptr<grpc::ClientAsyncResponseReader<RegisterFileUploadResponse>> + rpc_; + + RegisterFileUploadRequest *request_; + RegisterFileUploadResponse *response_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/file_transmission/upload_request_handler.h b/include/caosdb/file_transmission/upload_request_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..54621d12ba7d67d374037ecdb1f1259662071319 --- /dev/null +++ b/include/caosdb/file_transmission/upload_request_handler.h @@ -0,0 +1,129 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_FILE_TRANSMISSION_UPLOAD_REQUEST_HANDLER_H +#define CAOSDB_FILE_TRANSMISSION_UPLOAD_REQUEST_HANDLER_H + +#include "caosdb/entity.h" // for FileDescriptor +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1alpha1/main.pb.h" // for FileUploadRequest +#include "caosdb/file_transmission/file_reader.h" // for FileReader +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include <cstdint> // for uint64_t +#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncWriter +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <grpcpp/impl/codegen/status.h> // for Status +#include <memory> // for unique_ptr + +namespace caosdb::transaction { +using caosdb::entity::FileDescriptor; +using caosdb::entity::v1alpha1::FileTransmissionService; +using caosdb::entity::v1alpha1::FileUploadRequest; +using caosdb::entity::v1alpha1::FileUploadResponse; +using caosdb::transaction::HandlerInterface; +using caosdb::transaction::HandlerTag; + +class UploadRequestHandler final : public HandlerInterface { +public: + UploadRequestHandler(HandlerTag tag, FileTransmissionService::Stub *stub, + grpc::CompletionQueue *cq, + FileDescriptor file_descriptor); + + ~UploadRequestHandler() override = default; + + UploadRequestHandler(const UploadRequestHandler &) = delete; + UploadRequestHandler &operator=(const UploadRequestHandler &) = delete; + UploadRequestHandler(UploadRequestHandler &&) = delete; + UploadRequestHandler &operator=(UploadRequestHandler &&) = delete; + + void Start() override { OnNext(true); } + + bool OnNext(bool ok) override; + + void Cancel() override; + +protected: + enum class CallState { + NewCall, + SendingHeader, + SendingFile, + ExpectingResponse, + CallComplete + }; + + void handleNewCallState(); + void handleSendingHeaderState(); + void handleSendingFileState(); + void handleExpectingResponseState(); + void handleCallCompleteState(); + + HandlerTag tag_; + + FileTransmissionService::Stub *stub_; + grpc::CompletionQueue *cq_; + grpc::ClientContext ctx_; + + std::unique_ptr<grpc::ClientAsyncWriter<FileUploadRequest>> rpc_; + + FileUploadRequest *request_; + FileUploadResponse *response_; + grpc::Status status_; + + CallState state_; + + std::unique_ptr<FileReader> fileReader_; + + FileDescriptor file_descriptor_; + + uint64_t bytesToSend_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/handler_interface.h b/include/caosdb/handler_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..f5d77f86c35fe60edbd088afd235ed7ba633c69c --- /dev/null +++ b/include/caosdb/handler_interface.h @@ -0,0 +1,96 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_HANDLER_INTERFACE_H +#define CAOSDB_HANDLER_INTERFACE_H + +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <memory> +#include <string> + +namespace caosdb::transaction { + +const static std::string logger_name = "caosdb::transaction"; +/* + * Baseclass for UnaryRpcHandler, DownloadRequestHandler and + * UploadRequestHandler + * + * It handles a request: Its status is contained in the transaction_status + * member variable and the functions Start, OnNext and Cancel need to be + * overwritten by child classes. + */ +class HandlerInterface { +public: + HandlerInterface() : transaction_status(TransactionStatus::READY()) {} + + virtual ~HandlerInterface() = default; + + virtual void Start() = 0; + + /* + * ok indicates whether the current request is in a good state or not. If + * ok is false, the request will be ended. + * + * returns false if the handler is done + */ + virtual bool OnNext(bool ok) = 0; + + virtual void Cancel() = 0; + + inline TransactionStatus GetStatus() { return this->transaction_status; } + +protected: + TransactionStatus transaction_status; +}; + +using HandlerPtr = std::unique_ptr<HandlerInterface>; +using HandlerTag = HandlerPtr *; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/protobuf_helper.h b/include/caosdb/protobuf_helper.h index ce7dd5ef7f58ce0fb1e72df08367426deb70cfca..bd3395f4de7e8cd586a613869e84b1d5ffeb729b 100644 --- a/include/caosdb/protobuf_helper.h +++ b/include/caosdb/protobuf_helper.h @@ -22,7 +22,15 @@ #ifndef CAOSDB_PROTOBUF_HELPER_H #define CAOSDB_PROTOBUF_HELPER_H -#include <google/protobuf/arena.h> +#include <google/protobuf/arena.h> // for Arena +#include <google/protobuf/extension_set.h> // for Arena + +#define CAOSDB_DEBUG_MESSAGE_STRING(message, out) \ + std::string out; \ + { \ + google::protobuf::util::JsonOptions options; \ + google::protobuf::util::MessageToJsonString(message, &out, options); \ + } namespace caosdb::utility { diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h index a6fcda905284ae9dfa9b7e46c6be8da36bb32272..f996901581f10d3fdadc0137b3b0a3db74348036 100644 --- a/include/caosdb/status_code.h +++ b/include/caosdb/status_code.h @@ -41,6 +41,7 @@ enum StatusCode { INITIAL = -2, EXECUTING = -1, SUCCESS = 0, + // TODO(tf) Map other GRPC errors AUTHENTICATION_ERROR = 16, CONNECTION_ERROR = 14, GENERIC_RPC_ERROR = 20, @@ -52,6 +53,13 @@ enum StatusCode { TRANSACTION_TYPE_ERROR = 26, UNSUPPORTED_FEATURE = 27, ORIGINAL_ENTITY_MISSING_ID = 28, + EXTERN_C_ASSIGNMENT_ERROR = 29, + PATH_IS_A_DIRECTORY = 30, + FILE_DOES_NOT_EXIST_LOCALLY = 31, + FILE_UPLOAD_ERROR = 32, + FILE_DOWNLOAD_ERROR = 33, + NOT_A_FILE_ENTITY = 34, + OTHER_CLIENT_ERROR = 9999, }; auto get_status_description(int code) -> const std::string &; diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index e2a2ebc2456de8294ce360434331ed4f710b1e75..bda5bb355b5dc317e6727c8bcea5ac10450e582f 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -20,23 +20,30 @@ */ #ifndef CAOSDB_TRANSACTION_H #define CAOSDB_TRANSACTION_H -#include "boost/log/core/record.hpp" // for record -#include "boost/log/sources/record_ostream.hpp" // for basic_record_o... -#include "boost/preprocessor/seq/limits/enum_256.hpp" // for BOOST_PP_SEQ_E... -#include "boost/preprocessor/seq/limits/size_256.hpp" // for BOOST_PP_SEQ_S... -#include "caosdb/entity.h" // for Entity -#include "caosdb/logging.h" -#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe... -#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RetrieveReq... -#include "caosdb/transaction_status.h" // for TransactionStatus + +#include "caosdb/entity.h" // for Entity, FileDe... +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransact... +#include "caosdb/entity/v1alpha1/main.pb.h" // for MultiTransacti... +#include "caosdb/handler_interface.h" // for HandlerInterface +#include "caosdb/transaction_handler.h" // for EntityTransactionHandler +#include "caosdb/logging.h" // for CAOSDB_LOG_ERR... +#include "caosdb/protobuf_helper.h" // for get_arena #include "caosdb/status_code.h" // for StatusCode -#include "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso... -#include <iterator> +#include "caosdb/transaction_status.h" // for StatusCode +#include <boost/log/core/record.hpp> // for record +#include <boost/log/sources/record_ostream.hpp> // for basic_record_o... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_E... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_S... +#include <google/protobuf/arena.h> // for Arena +#include <google/protobuf/util/json_util.h> // for MessageToJsonS... +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <iterator> // for iterator, next +#include <map> // for map // IWYU pragma: no_include <ext/alloc_traits.h> -#include <memory> // for shared_ptr, unique_ptr -#include <stdexcept> -#include <string> // for string -#include <vector> // for vector +#include <memory> // for unique_ptr +#include <string> // for string +#include <utility> // for move +#include <vector> // for vector /** * Do all necessary checks and assure that another retrieval (by id or by @@ -158,19 +165,28 @@ */ namespace caosdb::transaction { using caosdb::entity::Entity; -using ProtoEntity = caosdb::entity::v1alpha1::Entity; +using caosdb::entity::FileDescriptor; +using caosdb::entity::v1alpha1::EntityResponse; using caosdb::entity::v1alpha1::EntityTransactionService; +using caosdb::entity::v1alpha1::FileDownloadRequest; +using caosdb::entity::v1alpha1::FileDownloadResponse; +using caosdb::entity::v1alpha1::FileTransmissionId; +using caosdb::entity::v1alpha1::FileTransmissionService; +using caosdb::entity::v1alpha1::FileUploadRequest; +using caosdb::entity::v1alpha1::FileUploadResponse; using caosdb::entity::v1alpha1::IdResponse; using caosdb::entity::v1alpha1::MultiTransactionRequest; using caosdb::entity::v1alpha1::MultiTransactionResponse; +using caosdb::entity::v1alpha1::RegisterFileUploadRequest; +using caosdb::entity::v1alpha1::RegisterFileUploadResponse; using caosdb::transaction::TransactionStatus; -using WrappedResponseCase = - caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase; +using TransactionResponseCase = + caosdb::entity::v1alpha1::TransactionResponse::TransactionResponseCase; +using caosdb::utility::get_arena; +using google::protobuf::Arena; class Transaction; -static const std::string logger_name = "caosdb::transaction"; - /** * Abstract base class for the results of a Transaction. */ @@ -179,8 +195,9 @@ class ResultSet { public: virtual ~ResultSet() = default; - [[nodiscard]] virtual auto Size() const noexcept -> int = 0; - [[nodiscard]] virtual auto At(const int index) const -> const Entity & = 0; + [[nodiscard]] virtual auto size() const noexcept -> int = 0; + [[nodiscard]] virtual auto at(const int index) const -> const Entity & = 0; + [[nodiscard]] virtual auto mutable_at(int index) const -> Entity * = 0; auto begin() const -> iterator; auto end() const -> iterator; @@ -199,51 +216,34 @@ private: }; }; -/** - * Container with results of a transaction. - * - * In contrast to UniqueResult, this one can also hold multiple entities or zero - * entities. - */ -class MultiResultSet : public ResultSet { +class AbstractMultiResultSet : public ResultSet { public: - ~MultiResultSet() = default; - explicit MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set); - [[nodiscard]] inline auto Size() const noexcept -> int override { - return this->entities.size(); + virtual ~AbstractMultiResultSet() = default; + inline explicit AbstractMultiResultSet( + std::vector<std::unique_ptr<Entity>> result_set) + : items(std::move(result_set)) {} + [[nodiscard]] inline auto size() const noexcept -> int override { + return this->items.size(); } - [[nodiscard]] inline auto At(const int index) const + [[nodiscard]] inline auto at(const int index) const -> const Entity & override { - return *(this->entities.at(index)); + return *(this->items.at(index)); + } + [[nodiscard]] inline auto mutable_at(int index) const -> Entity * override { + return this->items.at(index).get(); } - std::vector<std::unique_ptr<Entity>> entities; + +protected: + std::vector<std::unique_ptr<Entity>> items; }; /** - * Container with the single result of a transaction. - * - * In contrast to MultiResultSet, this one guarantees to hold exactly one - * entity. + * Container with results of a transaction. */ -class UniqueResult : public ResultSet { +class MultiResultSet : public AbstractMultiResultSet { public: - ~UniqueResult() = default; - explicit inline UniqueResult(ProtoEntity *protoEntity) - : entity(new Entity(protoEntity)){}; - explicit inline UniqueResult(IdResponse *idResponse) - : entity(new Entity(idResponse)){}; - [[nodiscard]] auto GetEntity() const -> const Entity &; - [[nodiscard]] inline auto Size() const noexcept -> int override { return 1; } - [[nodiscard]] inline auto At(const int index) const - -> const Entity & override { - if (index != 0) { - throw std::out_of_range("Index out of range. Length is 1."); - } - return *(this->entity); - } - -private: - std::unique_ptr<Entity> entity; + ~MultiResultSet() = default; + explicit MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set); }; /** @@ -259,15 +259,36 @@ public: * yet. */ enum TransactionType { - NONE, /// Unspecified or not specified yet. - READ_ONLY, /// Only retrievals (by id, by query) - INSERT, /// Only insertions - UPDATE, /// Only updates - DELETE, /// Only deletions - MIXED_WRITE, /// Only insertions, deletions, updates - MIXED_READ_AND_WRITE /// all kind of transaction. + NONE, //!< Unspecified or not specified yet. + READ_ONLY, //!< Only retrievals (by id, by query) + INSERT, //!< Only insertions + UPDATE, //!< Only updates + DELETE, //!< Only deletions + MIXED_WRITE, //!< Only insertions, deletions, updates + MIXED_READ_AND_WRITE //!< all kind of transaction. }; - Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub); + Transaction(std::shared_ptr<EntityTransactionService::Stub> entity_service, + std::shared_ptr<FileTransmissionService::Stub> file_service); + + ~Transaction(); + + Transaction(const Transaction &) = delete; + Transaction &operator=(const Transaction &) = delete; + Transaction(Transaction &&) = delete; + Transaction &operator=(Transaction &&) = delete; + + /** + * Add an entity id to this transaction for retrieval and also download the + * file. + * + * If the entity doesn't have a file a warning is appended. + * + * If the file cannot be downloaded due to unsufficient permissions an error + * is appended. + */ + auto RetrieveAndDownloadFilesById(const std::string &id, + const std::string &local_path) noexcept + -> StatusCode; /** * Add an entity id to this transaction for retrieval. @@ -348,13 +369,12 @@ public: /** * Return the current status of the transaction. */ - [[nodiscard]] inline auto GetStatus() const -> TransactionStatus { + [[nodiscard]] inline auto GetStatus() const noexcept -> TransactionStatus { return this->status; } - [[nodiscard]] inline auto GetResultSet() const -> const ResultSet & { - const ResultSet *result_set = this->result_set.get(); - return *result_set; + [[nodiscard]] inline auto GetResultSet() const noexcept -> const ResultSet & { + return *(this->result_set.get()); } /** @@ -364,7 +384,7 @@ public: * this transaction. In all other cases, the return value will be * -1. */ - [[nodiscard]] inline auto GetCountResult() const -> long { + [[nodiscard]] inline auto GetCountResult() const noexcept -> long { return query_count; } @@ -402,12 +422,58 @@ public: return out; } + /** + * Return the vector which holds all the files which are to be uploaded. + */ + inline auto GetUploadFiles() const -> const std::vector<FileDescriptor> & { + return upload_files; + } + +protected: + /** + * Await and process the current handler's results. + * + * This implies consecutive calls to the handler's OnNext function. + */ + auto ProcessCalls() -> TransactionStatus; + + /** + * Cancels any active handler and drains the completion_queue. + * + * Can stay protected until ExecuteAsynchronously() is actually asynchronous. + * Then it is also intended for aborting an execution after it has already + * started. + */ + auto Cancel() -> void; + + /** + * Return the Arena where this transaction may create Message instances. + * + * Currently, this implementation is only a call to + * caosdb::utility::get_arena(), but in the future we might want to have a + * smarter memory management. + */ + inline auto GetArena() const -> Arena * { return get_arena(); } + private: + grpc::CompletionQueue completion_queue; + std::unique_ptr<HandlerInterface> handler_; + + std::vector<FileDescriptor> upload_files; + std::map<std::string, FileDescriptor> download_files; + + // auto RegisterUploadFile(RegisterFileUploadResponse *response) -> void; + auto UploadFile(FileUploadResponse *response, + const FileDescriptor &file_descriptor, + const std::string ®istration_id) -> void; + auto DownloadFile(FileDownloadResponse *response, + const FileTransmissionId &file_transmission_id) -> void; bool has_query = false; TransactionType transaction_type = TransactionType::NONE; mutable std::unique_ptr<ResultSet> result_set; mutable TransactionStatus status = TransactionStatus::INITIAL(); - std::shared_ptr<EntityTransactionService::Stub> service_stub; + std::shared_ptr<EntityTransactionService::Stub> entity_service; + std::shared_ptr<FileTransmissionService::Stub> file_service; MultiTransactionRequest *request; mutable MultiTransactionResponse *response; std::string error_message; diff --git a/include/caosdb/transaction_handler.h b/include/caosdb/transaction_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..0a7154d03dfbde19284807d1e1f40998c89e1de0 --- /dev/null +++ b/include/caosdb/transaction_handler.h @@ -0,0 +1,45 @@ +#pragma once +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for FileTransmissionS... +#include "caosdb/entity/v1alpha1/main.pb.h" // for FileDownloadResponse +#include "caosdb/handler_interface.h" // for HandlerTag +#include "caosdb/unary_rpc_handler.h" // for HandlerTag, Handl... +#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRespons... +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <memory> // for unique_ptr + +namespace caosdb::transaction { + +using caosdb::entity::v1alpha1::EntityTransactionService; +using caosdb::entity::v1alpha1::MultiTransactionRequest; +using caosdb::entity::v1alpha1::MultiTransactionResponse; + +class EntityTransactionHandler final : public UnaryRpcHandler { +public: + EntityTransactionHandler(HandlerTag tag, EntityTransactionService::Stub *stub, + grpc::CompletionQueue *completion_queue, + MultiTransactionRequest *request, + MultiTransactionResponse *response); + + ~EntityTransactionHandler() override = default; + + EntityTransactionHandler(const EntityTransactionHandler &) = delete; + EntityTransactionHandler & + operator=(const EntityTransactionHandler &) = delete; + EntityTransactionHandler(EntityTransactionHandler &&) = delete; + EntityTransactionHandler &operator=(EntityTransactionHandler &&) = delete; + +protected: + virtual void handleNewCallState() override; + + HandlerTag tag_; + + EntityTransactionService::Stub *stub_; + + std::unique_ptr<grpc::ClientAsyncResponseReader<MultiTransactionResponse>> + rpc_; + + MultiTransactionRequest *request_; + MultiTransactionResponse *response_; +}; + +} // namespace caosdb::transaction diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h index 6f2f5cefd7b36f8c986726b4955f53381339f0a2..ca40c578bc1c3dd2555ccda97c06b4da726e9c7b 100644 --- a/include/caosdb/transaction_status.h +++ b/include/caosdb/transaction_status.h @@ -119,6 +119,22 @@ public: caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR) + " Original error: " + details); } + /** + * Factory for a FILE_UPLOAD_ERROR status. + * + * This status means that the transaction failed during the upload of the + * file blobs of file entities. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(FILE_UPLOAD_ERROR, + StatusCode::FILE_UPLOAD_ERROR); + /** + * Factory for a FILE_DOWN_ERROR status. + * + * This status means that the transaction failed during the download of the + * file blobs of file entities. + */ + CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(FILE_DOWNLOAD_ERROR, + StatusCode::FILE_DOWNLOAD_ERROR); /** * Factory for a TRANSACTION_ERROR status. * @@ -154,6 +170,19 @@ public: " Original error: " + details); } + /** + * Factory for a GENERIC_ERROR status. + * + * This status means that the transaction failed due to errors which + * supposedly do not have a special handling. + */ + inline static auto GENERIC_ERROR(const std::string &details) { + return TransactionStatus( + StatusCode::GENERIC_ERROR, + caosdb::get_status_description(StatusCode::GENERIC_ERROR) + + "Original error: " + details); + } + inline auto ThrowExceptionIfError() const -> void { TransactionStatus::ThrowExceptionIfError(this->code, this->description); } @@ -176,7 +205,7 @@ public: case StatusCode::TRANSACTION_TYPE_ERROR: throw TransactionTypeError(description); default: - throw Exception(StatusCode::GENERIC_ERROR, description); + throw Exception(code, description); } } @@ -211,6 +240,9 @@ public: */ inline auto GetCode() const -> StatusCode { return this->code; } + TransactionStatus(StatusCode code, const std::string &description) + : code(code), description(description){}; + private: /** * The code is an identifier of errors. @@ -221,9 +253,6 @@ private: * Description of the error */ std::string description; - - TransactionStatus(StatusCode code, const std::string &description) - : code(code), description(description){}; }; } // namespace caosdb::transaction diff --git a/include/caosdb/unary_rpc_handler.h b/include/caosdb/unary_rpc_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..6955504a2baea38cb0bd8075bdca2d20c9f8a52a --- /dev/null +++ b/include/caosdb/unary_rpc_handler.h @@ -0,0 +1,89 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > 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. + */ +#ifndef CAOSDB_UNARY_RPC_HANDLER_H +#define CAOSDB_UNARY_RPC_HANDLER_H + +#include "caosdb/handler_interface.h" // for HandlerTag, Handl... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <grpcpp/impl/codegen/status.h> // for Status + +namespace caosdb::transaction { + +class UnaryRpcHandler : public HandlerInterface { +public: + inline UnaryRpcHandler(grpc::CompletionQueue *completion_queue) + : HandlerInterface(), state_(CallState::NewCall), + completion_queue(completion_queue) {} + + void Start() override { + transaction_status = TransactionStatus::EXECUTING(); + OnNext(true); + } + + bool OnNext(bool ok) override; + + void Cancel() override; + +protected: + virtual void handleNewCallState() = 0; + void handleCallCompleteState(); + + enum class CallState { NewCall, CallComplete }; + CallState state_; + grpc::CompletionQueue *completion_queue; + + grpc::ClientContext call_context; + grpc::Status status_; +}; + +} // namespace caosdb::transaction + +#endif diff --git a/include/caosdb/utility.h b/include/caosdb/utility.h index 30e21747ec4ce39493e24e10e09d7c53c7ab1308..dd53ff952b9737fe76222d998b6813e744b0ad3d 100644 --- a/include/caosdb/utility.h +++ b/include/caosdb/utility.h @@ -33,6 +33,8 @@ #include <memory> #include <string> #include <string_view> +#include <mutex> +#include <shared_mutex> namespace caosdb::utility { using boost::filesystem::exists; diff --git a/include/ccaosdb.h b/include/ccaosdb.h index bb62a5105db9559444f5b5d178ced583c41c9fcb..4597690ee2f77e18b086df702b97642b18d19744 100644 --- a/include/ccaosdb.h +++ b/include/ccaosdb.h @@ -1,3 +1,24 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + * + */ #ifdef __cplusplus extern "C" { #endif @@ -31,6 +52,12 @@ int caosdb_constants_COMPATIBLE_SERVER_VERSION_PATCH(); */ const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE(); +/** + * Return the status code reserved for errors in clients wrapping this + * interface. + */ +int caosdb_status_code_OTHER_CLIENT_ERROR(); + /** * A wrapper of the C++ Connection class. * @@ -40,6 +67,7 @@ const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE(); */ typedef struct { void *wrapped_connection; + bool _deletable = false; } caosdb_connection_connection; /** @@ -51,6 +79,7 @@ typedef struct { */ typedef struct { void *wrapped_connection_configuration; + bool _deletable = false; } caosdb_connection_connection_configuration; /** @@ -70,10 +99,12 @@ typedef struct { typedef struct { void *wrapped_certificate_provider; + bool _deletable = false; } caosdb_connection_certificate_provider; typedef struct { void *wrapped_authenticator; + bool _deletable = false; } caosdb_authentication_authenticator; /** @@ -242,62 +273,77 @@ int caosdb_connection_connection_manager_get_connection( // not sufficient yet. typedef struct { void *wrapped_transaction; + bool _deletable = false; } caosdb_transaction_transaction; +/** + * Create a transaction on an existing connection. + * + * This transaction has to be deleted manually by + * caosdb_transaction_delete_transaction() later on. + */ int caosdb_connection_connection_create_transaction( caosdb_connection_connection *connection, caosdb_transaction_transaction *out); +int caosdb_transaction_delete_transaction( + caosdb_transaction_transaction *transaction); int caosdb_transaction_transaction_retrieve_by_id( caosdb_transaction_transaction *transaction, const char *id); +int caosdb_transaction_transaction_retrieve_by_ids( + caosdb_transaction_transaction *transaction, const char *ids[], int length); +int caosdb_transaction_transaction_query( + caosdb_transaction_transaction *transaction, const char *query); int caosdb_transaction_transaction_execute( caosdb_transaction_transaction *transaction); +// TODO(fspreck) execute_asynchronously may be added as a separate +// function once we actually support asynchronous execution. typedef struct { void *wrapped_result_set; + bool _deletable = false; } caosdb_transaction_result_set; int caosdb_transaction_transaction_get_result_set( caosdb_transaction_transaction *transaction, caosdb_transaction_result_set *out); +int caosdb_transaction_transaction_get_count_result( + caosdb_transaction_transaction *transaction, long *out); + typedef struct { void *wrapped_entity; - char **id; - char **role; - char **name; - char **description; - char **datatype; - char **unit; - char **value; - char **version_id; + bool _deletable = false; } caosdb_entity_entity; -int caosdb_transaction_result_set_get_entity( - caosdb_transaction_result_set *result_set, caosdb_entity_entity *entity, - int index); +int caosdb_transaction_result_set_at(caosdb_transaction_result_set *result_set, + caosdb_entity_entity *entity, int index); +int caosdb_transaction_result_set_size( + caosdb_transaction_result_set *result_set, int *out); typedef struct { void *wrapped_property; - char **id; - char **name; - char **description; - char **datatype; - char **unit; - char **value; + bool _deletable = false; } caosdb_entity_property; typedef struct { void *wrapped_parent; - char **id; - char **name; - char **description; + bool _deletable = false; } caosdb_entity_parent; typedef struct { void *wrapped_message; - int *code; - char **description; + bool _deletable = false; } caosdb_entity_message; -// GETTERS FOR COMPLEX OBJECTS +// GETTERS FOR EVERYTHING +int caosdb_entity_entity_get_id(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_role(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_name(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_description(caosdb_entity_entity *entity, + char *out); +int caosdb_entity_entity_get_datatype(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_unit(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_value(caosdb_entity_entity *entity, char *out); +int caosdb_entity_entity_get_version_id(caosdb_entity_entity *entity, + char *out); int caosdb_entity_entity_get_errors_size(caosdb_entity_entity *entity, int *out); int caosdb_entity_entity_get_error(caosdb_entity_entity *entity, @@ -318,6 +364,29 @@ int caosdb_entity_entity_get_parents_size(caosdb_entity_entity *entity, int caosdb_entity_entity_get_parent(caosdb_entity_entity *entity, caosdb_entity_parent *out, int index); +int caosdb_entity_property_get_id(caosdb_entity_property *property, char *out); +int caosdb_entity_property_get_name(caosdb_entity_property *property, + char *out); +int caosdb_entity_property_get_description(caosdb_entity_property *property, + char *out); +int caosdb_entity_property_get_importance(caosdb_entity_property *property, + char *out); +int caosdb_entity_property_get_datatype(caosdb_entity_property *property, + char *out); +int caosdb_entity_property_get_unit(caosdb_entity_property *property, + char *out); +int caosdb_entity_property_get_value(caosdb_entity_property *property, + char *out); + +int caosdb_entity_parent_get_id(caosdb_entity_parent *parent, char *out); +int caosdb_entity_parent_get_name(caosdb_entity_parent *parent, char *out); +int caosdb_entity_parent_get_description(caosdb_entity_parent *parent, + char *out); + +int caosdb_entity_message_get_code(caosdb_entity_message *message, int *out); +int caosdb_entity_message_get_description(caosdb_entity_message *message, + char *out); + // CONSTRUCTORS AND DESTRUCTORS int caosdb_entity_create_entity(caosdb_entity_entity *out); int caosdb_entity_delete_entity(caosdb_entity_entity *out); @@ -326,11 +395,43 @@ int caosdb_entity_delete_property(caosdb_entity_property *out); int caosdb_entity_create_parent(caosdb_entity_parent *out); int caosdb_entity_delete_parent(caosdb_entity_parent *out); -// SETTERS FOR COMPLEX OBJECTS +// SETTERS FOR EVERYTHING THAT MAY BE SET +int caosdb_entity_entity_set_role(caosdb_entity_entity *entity, + const char *role); +int caosdb_entity_entity_set_name(caosdb_entity_entity *entity, + const char *name); +int caosdb_entity_entity_set_description(caosdb_entity_entity *entity, + const char *description); +int caosdb_entity_entity_set_datatype(caosdb_entity_entity *entity, + const char *datatype); +int caosdb_entity_entity_set_unit(caosdb_entity_entity *entity, + const char *unit); +int caosdb_entity_entity_set_value(caosdb_entity_entity *entity, + const char *value); int caosdb_entity_entity_append_parent(caosdb_entity_entity *entity, caosdb_entity_parent *parent); +int caosdb_entity_entity_remove_parent(caosdb_entity_entity *entity, int index); int caosdb_entity_entity_append_property(caosdb_entity_entity *entity, caosdb_entity_property *property); +int caosdb_entity_entity_remove_property(caosdb_entity_entity *entity, + int index); + +int caosdb_entity_property_set_id(caosdb_entity_property *property, + const char *id); +int caosdb_entity_property_set_name(caosdb_entity_property *property, + const char *name); +int caosdb_entity_property_set_datatype(caosdb_entity_property *property, + const char *datatype); +int caosdb_entity_property_set_importance(caosdb_entity_property *property, + const char *importance); +int caosdb_entity_property_set_unit(caosdb_entity_property *property, + const char *unit); +int caosdb_entity_property_set_value(caosdb_entity_property *property, + const char *value); + +int caosdb_entity_parent_set_id(caosdb_entity_parent *parent, const char *id); +int caosdb_entity_parent_set_name(caosdb_entity_parent *parent, + const char *name); #ifdef __cplusplus } diff --git a/proto b/proto index 45d120f78f17986ba67c10b6a4a130e7fa60b34c..485173a714d9ff7c2388945b0a4cad35980cda69 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 45d120f78f17986ba67c10b6a4a130e7fa60b34c +Subproject commit 485173a714d9ff7c2388945b0a4cad35980cda69 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95b2dabac276216bd744ea347a71f986070fce8f..89180ee81fc6b07d4f081c90fd1200530a2d60b6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,13 @@ set(libcaosdb_SRC ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/configuration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/protobuf_helper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/unary_rpc_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/register_file_upload_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/upload_request_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/download_request_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_writer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/file_transmission/file_reader.cpp ) # pass variable to parent scope diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index 4ff50d24d329fa6ece3a8e87d304378f3d157782..b43444ce085d262a0ed8e960001e31c8da1a0124 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -37,6 +37,7 @@ namespace caosdb::connection { using caosdb::configuration::ConfigurationManager; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1alpha1::EntityTransactionService; +using caosdb::entity::v1alpha1::FileTransmissionService; using caosdb::info::VersionInfo; using caosdb::info::v1alpha1::GeneralInfoService; using caosdb::info::v1alpha1::GetVersionInfoRequest; @@ -52,6 +53,8 @@ Connection::Connection(const ConnectionConfiguration &configuration) { this->general_info_service = GeneralInfoService::NewStub(this->channel); this->entity_transaction_service = std::make_shared<EntityTransactionService::Stub>(this->channel); + this->file_transmission_service = + std::make_shared<FileTransmissionService::Stub>(this->channel); } auto Connection::RetrieveVersionInfoNoExceptions() const noexcept @@ -95,8 +98,9 @@ auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { [[nodiscard]] auto Connection::CreateTransaction() const -> std::unique_ptr<Transaction> { - auto service_stub = this->entity_transaction_service; - return std::make_unique<Transaction>(service_stub); + auto entity_service = this->entity_transaction_service; + auto file_service = this->file_transmission_service; + return std::make_unique<Transaction>(entity_service, file_service); } auto ConnectionManager::mHasConnection(const std::string &name) const -> bool { diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp index 03a234be80e9a965170ce35240e6c838c62cba6f..8f410a3c6c454375fe827ba8058e20423ef9ccce 100644 --- a/src/caosdb/entity.cpp +++ b/src/caosdb/entity.cpp @@ -20,16 +20,22 @@ * */ #include "caosdb/entity.h" -#include "caosdb/entity/v1alpha1/main.pb.h" // for Parent, Arena::CreateMay... +#include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Property #include "caosdb/protobuf_helper.h" // for get_arena -#include "google/protobuf/arena.h" // for Arena +#include <google/protobuf/arena.h> // for Arena +#include <new> // for operator new namespace caosdb::entity { using caosdb::entity::v1alpha1::IdResponse; using ProtoParent = caosdb::entity::v1alpha1::Parent; using ProtoProperty = caosdb::entity::v1alpha1::Property; using ProtoEntity = caosdb::entity::v1alpha1::Entity; +using ProtoMessage = caosdb::entity::v1alpha1::Message; +using ProtoFileDescriptor = caosdb::entity::v1alpha1::FileDescriptor; using caosdb::utility::get_arena; +using google::protobuf::Arena; + +Messages::~Messages() = default; Parent::Parent() : wrapped(Parent::CreateProtoParent()) { // TODO(fspreck) Re-enable once we have decided how to attach @@ -40,17 +46,13 @@ Parent::Parent() : wrapped(Parent::CreateProtoParent()) { } auto Parent::CreateProtoParent() -> ProtoParent * { - return google::protobuf::Arena::CreateMessage<ProtoParent>(get_arena()); + return Arena::CreateMessage<ProtoParent>(get_arena()); } auto Parent::SetName(const std::string &name) -> void { this->wrapped->set_name(name); } -auto Parent::GetDescription() const -> const std::string & { - return this->wrapped->description(); -} - auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); } [[nodiscard]] auto Parent::GetId() const -> const std::string & { @@ -61,21 +63,14 @@ auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); } return this->wrapped->name(); } -auto Parents::Append(const Parent &parent) -> void { - auto *destination = this->wrapped->Add(); - destination->Swap(parent.wrapped); - - // Clear the originally wrapped object and return it to the Arena - parent.wrapped->Clear(); - - // set the pointer to the new object which is owned by the RepeatedPtrField - parent.wrapped = destination; +[[nodiscard]] auto Parent::GetDescription() const -> const std::string & { + return this->wrapped->description(); } Property::Property() : wrapped(Property::CreateProtoProperty()) {} auto Property::CreateProtoProperty() -> ProtoProperty * { - return google::protobuf::Arena::CreateMessage<ProtoProperty>(get_arena()); + return Arena::CreateMessage<ProtoProperty>(get_arena()); } [[nodiscard]] auto Property::GetId() const -> const std::string & { @@ -134,15 +129,6 @@ auto Property::SetDatatype(const std::string &datatype) -> void { this->wrapped->set_datatype(datatype); } -auto Properties::Append(const Property &property) -> void { - auto *destination = this->wrapped->Add(); - destination->Swap(property.wrapped); - - property.wrapped->Clear(); - - property.wrapped = destination; -} - [[nodiscard]] auto Entity::GetParents() const -> const Parents & { return parents; } @@ -151,6 +137,8 @@ auto Entity::AppendParent(const Parent &parent) -> void { this->parents.Append(parent); } +auto Entity::RemoveParent(int index) -> void { this->parents.remove(index); } + [[nodiscard]] auto Entity::GetProperties() const -> const Properties & { return properties; } @@ -159,24 +147,25 @@ auto Entity::AppendProperty(const Property &property) -> void { this->properties.Append(property); } +auto Entity::RemoveProperty(int index) -> void { + this->properties.remove(index); +} + auto Entity::CreateProtoEntity() -> ProtoEntity * { - return google::protobuf::Arena::CreateMessage<ProtoEntity>(get_arena()); + return Arena::CreateMessage<ProtoEntity>(get_arena()); } -Entity::Entity() : wrapped(Entity::CreateProtoEntity()) { - properties.wrapped = this->wrapped->mutable_properties(); - parents.wrapped = this->wrapped->mutable_parents(); - errors.wrapped = this->wrapped->mutable_errors(); - warnings.wrapped = this->wrapped->mutable_warnings(); - infos.wrapped = this->wrapped->mutable_infos(); +auto Entity::CreateMessagesField() -> RepeatedPtrField<ProtoMessage> * { + return Arena::CreateMessage<RepeatedPtrField<ProtoMessage>>(get_arena()); } +Entity::Entity() : Entity(Entity::CreateProtoEntity()) {} + Entity::Entity(IdResponse *idResponse) : Entity() { this->wrapped->set_id(idResponse->id()); - this->wrapped->mutable_errors()->Swap(idResponse->mutable_entity_errors()); - this->wrapped->mutable_warnings()->Swap( - idResponse->mutable_entity_warnings()); - this->wrapped->mutable_infos()->Swap(idResponse->mutable_entity_infos()); + this->errors.wrapped->Swap(idResponse->mutable_errors()); + this->warnings.wrapped->Swap(idResponse->mutable_warnings()); + this->infos.wrapped->Swap(idResponse->mutable_infos()); } auto Entity::SetId(const std::string &id) -> void { this->wrapped->set_id(id); } @@ -213,4 +202,8 @@ auto Entity::SetDatatype(const std::string &datatype) -> void { this->wrapped->set_datatype(datatype); } +auto Entity::SetFilePath(const std::string &path) -> void { + this->wrapped->mutable_file_descriptor()->set_path(path); +} + } // namespace caosdb::entity diff --git a/src/caosdb/file_transmission/download_request_handler.cpp b/src/caosdb/file_transmission/download_request_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fb36ca5b46cbf2c4f08c2c06bc9faf6b00e038be --- /dev/null +++ b/src/caosdb/file_transmission/download_request_handler.cpp @@ -0,0 +1,207 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/file_transmission/download_request_handler.h" +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <boost/filesystem/path.hpp> // for operator<<, path +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <exception> // IWYU pragma: keep +// IWYU pragma: no_include <bits/exception.h> +#include <google/protobuf/arena.h> // for Arena +#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncRe... +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <grpcpp/impl/codegen/status.h> // for Status +#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... +#include <iostream> // for char_traits +#include <stdexcept> // for runtime_error +#include <string> // for string, opera... +#include <utility> // for move + +namespace caosdb::transaction { +using caosdb::StatusCode; +using caosdb::utility::get_arena; +using google::protobuf::Arena; + +DownloadRequestHandler::DownloadRequestHandler( + HandlerTag tag, FileTransmissionService::Stub *stub, + grpc::CompletionQueue *cq, FileDescriptor file_descriptor) + : tag_(tag), stub_(stub), cq_(cq), + request_(Arena::CreateMessage<FileDownloadRequest>(get_arena())), + response_(Arena::CreateMessage<FileDownloadResponse>(get_arena())), + state_(CallState::NewCall), file_descriptor_(std::move(file_descriptor)), + bytesReceived_(0) {} + +bool DownloadRequestHandler::OnNext(bool ok) { + try { + if (ok) { + if (state_ == CallState::NewCall) { + this->handleNewCallState(); + } else if (state_ == CallState::SendingRequest) { + this->handleSendingRequestState(); + } else if (state_ == CallState::ReceivingFile) { + this->handleReceivingFileState(); + } else if (state_ == CallState::CallComplete) { + this->handleCallCompleteState(); + return false; + } + } else { + state_ = CallState::CallComplete; + rpc_->Finish(&status_, tag_); + } + + return true; + } catch (std::exception &e) { + CAOSDB_LOG_ERROR(logger_name) + << "DownloadRequestHandler caught an exception: " << e.what(); + transaction_status = TransactionStatus::GENERIC_ERROR(e.what()); + state_ = CallState::CallComplete; + } catch (...) { + CAOSDB_LOG_ERROR(logger_name) + << "Transaction error: unknown exception caught"; + transaction_status = TransactionStatus::GENERIC_ERROR( + "DownloadRequestHandler caught an unknown exception"); + state_ = CallState::CallComplete; + } + + if (state_ == CallState::NewCall) { + return false; + } + + ctx_.TryCancel(); + + return true; +} + +void DownloadRequestHandler::Cancel() { ctx_.TryCancel(); } + +void DownloadRequestHandler::handleNewCallState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter DownloadRequestHandler::handleNewCallState. local_path = " + << file_descriptor_.local_path + << ", download_id = " << file_descriptor_.file_transmission_id; + fileWriter_ = std::make_unique<FileWriter>(file_descriptor_.local_path); + + request_->mutable_file_transmission_id()->CopyFrom( + *(file_descriptor_.file_transmission_id)); + + rpc_ = stub_->PrepareAsyncFileDownload(&ctx_, *request_, cq_); + + transaction_status = TransactionStatus::EXECUTING(); + state_ = CallState::SendingRequest; + rpc_->StartCall(tag_); + CAOSDB_LOG_TRACE(logger_name) + << "Leave DownloadRequestHandler::handleNewCallState"; +} + +void DownloadRequestHandler::handleSendingRequestState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter DownloadRequestHandler::handleSendingRequestState"; + state_ = CallState::ReceivingFile; + rpc_->Read(response_, tag_); + CAOSDB_LOG_TRACE(logger_name) + << "Leave DownloadRequestHandler::handleSendingRequestState"; +} + +void DownloadRequestHandler::handleReceivingFileState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter DownloadRequestHandler::handleReceivingFileState"; + if (response_->has_chunk()) { + const auto &chunkData = response_->chunk().data(); + if (chunkData.empty()) { + CAOSDB_LOG_DEBUG(logger_name) << "Received an empty FileChunk, ignoring"; + } else { + fileWriter_->write(chunkData); + bytesReceived_ += chunkData.size(); + } + + state_ = CallState::ReceivingFile; + response_->Clear(); + rpc_->Read(response_, tag_); + } else { + throw std::runtime_error("File chunk expected"); + } + CAOSDB_LOG_TRACE(logger_name) + << "Leave DownloadRequestHandler::handleReceivingFileState"; +} + +void DownloadRequestHandler::handleCallCompleteState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter DownloadRequestHandler::handleCallCompleteState"; + + switch (status_.error_code()) { + case grpc::OK: { + CAOSDB_LOG_INFO(logger_name) + << "DownloadRequestHandler finished successfully (" + << file_descriptor_.local_path << "): Download complete, " + << bytesReceived_ << " bytes received."; + } break; + default: { + auto code(static_cast<StatusCode>(status_.error_code())); + std::string description(get_status_description(code) + + " Original message: " + status_.error_message()); + transaction_status = TransactionStatus(code, description); + CAOSDB_LOG_ERROR(logger_name) + << "DownloadRequestHandler finished with an error (" + << file_descriptor_.local_path << "): Download aborted with code " << code + << " - " << description; + } break; + } + + CAOSDB_LOG_TRACE(logger_name) + << "Leave DownloadRequestHandler::handleCallCompleteState"; +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/file_transmission/file_reader.cpp b/src/caosdb/file_transmission/file_reader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f118eca01c6d17975684a15de56eb7c693fdc117 --- /dev/null +++ b/src/caosdb/file_transmission/file_reader.cpp @@ -0,0 +1,92 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/file_transmission/file_reader.h" +#include "caosdb/file_transmission/file_error.h" // for FileIOError +#include <boost/filesystem/path.hpp> // for path +#include <utility> // for move + +namespace caosdb::transaction { + +FileReader::FileReader(boost::filesystem::path filename) + : filename_(std::move(filename)), size_(0) { + this->openFile(); +} + +void FileReader::openFile() { + stream_.open(filename_, std::ios::binary | std::ios::ate); + if (!stream_) { + throw FileIOError("Can't open file for reading: " + filename_.string()); + } + + auto size = stream_.tellg(); + stream_.seekg(0); + if (size > 0) { + size_ = static_cast<decltype(size_)>(size); + } +} + +std::size_t FileReader::read(std::string &buffer) { + std::size_t bytesRead = 0; + + if (!stream_.eof()) { + auto bufferSize = buffer.size(); + if (bufferSize > 0) { + // TODO(henrik): fix nolint + if (!stream_.read(&buffer[0], bufferSize)) { // NOLINT + throw FileIOError("Can't read file: " + filename_.string()); + } + + bytesRead = static_cast<std::size_t>(stream_.gcount()); + } + } + + return bytesRead; +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/file_transmission/file_writer.cpp b/src/caosdb/file_transmission/file_writer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90c816fa8c6d3d43ecad3e4e0dbf575d7016d15e --- /dev/null +++ b/src/caosdb/file_transmission/file_writer.cpp @@ -0,0 +1,78 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/file_transmission/file_writer.h" +#include "caosdb/file_transmission/file_error.h" // for FileIOError +#include <boost/filesystem/path.hpp> // for path +#include <utility> // for move + +namespace caosdb::transaction { + +FileWriter::FileWriter(boost::filesystem::path filename) + : filename_(std::move(filename)) { + this->openFile(); +} + +void FileWriter::openFile() { + stream_.open(filename_, std::ios::binary | std::ios::trunc); + if (!stream_) { + throw FileIOError("Can't open file for writing: " + filename_.string()); + } +} + +void FileWriter::write(const std::string &buffer) { + auto bufferSize = buffer.size(); + if (bufferSize > 0) { + // TODO(henrik): fix nolint + if (!stream_.write(buffer.data(), bufferSize)) { // NOLINT + throw FileIOError("Can't write file: " + filename_.string()); + } + } +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/file_transmission/register_file_upload_handler.cpp b/src/caosdb/file_transmission/register_file_upload_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b2020223f1c79f69e77b1e0950bac1c8f1220986 --- /dev/null +++ b/src/caosdb/file_transmission/register_file_upload_handler.cpp @@ -0,0 +1,84 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/file_transmission/register_file_upload_handler.h" +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include <boost/log/core/record.hpp> // for record +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRes... +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue + +namespace caosdb::transaction { + +RegisterFileUploadHandler::~RegisterFileUploadHandler() = default; + +RegisterFileUploadHandler::RegisterFileUploadHandler( + HandlerTag tag, FileTransmissionService::Stub *stub, + grpc::CompletionQueue *completion_queue, RegisterFileUploadRequest *request, + RegisterFileUploadResponse *response) + : UnaryRpcHandler(completion_queue), tag_(tag), stub_(stub), + request_(request), response_(response) {} + +void RegisterFileUploadHandler::handleNewCallState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter RegisterFileUploadHandler::handleNewCallState."; + + rpc_ = stub_->PrepareAsyncRegisterFileUpload(&call_context, *request_, + completion_queue); + + state_ = CallState::CallComplete; + rpc_->StartCall(); + rpc_->Finish(response_, &status_, tag_); + + CAOSDB_LOG_TRACE(logger_name) + << "Leave RegisterFileUploadHandler::handleNewCallState"; +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/file_transmission/upload_request_handler.cpp b/src/caosdb/file_transmission/upload_request_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27926bfaabe4dd18feca80f191ff496573196e52 --- /dev/null +++ b/src/caosdb/file_transmission/upload_request_handler.cpp @@ -0,0 +1,217 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/file_transmission/upload_request_handler.h" +#include "caosdb/logging.h" // for CAOSDB_LOG_ERROR +#include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include "caosdb/transaction_status.h" // for TransactionStatus +#include <algorithm> // for min +#include <boost/filesystem/path.hpp> // for operator<<, path +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <cstdint> // for uint64_t +#include <exception> // IWYU pragma: keep +// IWYU pragma: no_include <bits/exception.h> +#include <google/protobuf/arena.h> // for Arena +#include <grpcpp/impl/codegen/async_stream.h> // for ClientAsyncWr... +#include <grpcpp/impl/codegen/call_op_set.h> // for WriteOptions +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <grpcpp/impl/codegen/status.h> // for Status +#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... +#include <iostream> // for endl, streamsize +#include <string> // for basic_string +#include <utility> // for move + +namespace caosdb::transaction { +using caosdb::StatusCode; +using caosdb::utility::get_arena; +using google::protobuf::Arena; + +UploadRequestHandler::UploadRequestHandler(HandlerTag tag, + FileTransmissionService::Stub *stub, + grpc::CompletionQueue *cq, + FileDescriptor file_descriptor) + : tag_(tag), stub_(stub), cq_(cq), + request_(Arena::CreateMessage<FileUploadRequest>(get_arena())), + response_(Arena::CreateMessage<FileUploadResponse>(get_arena())), + state_(CallState::NewCall), file_descriptor_(std::move(file_descriptor)), + bytesToSend_(0) {} + +bool UploadRequestHandler::OnNext(bool ok) { + try { + if (state_ == CallState::CallComplete) { + this->handleCallCompleteState(); + return false; + } else if (ok) { + if (state_ == CallState::NewCall) { + this->handleNewCallState(); + } else if (state_ == CallState::SendingHeader) { + this->handleSendingHeaderState(); + } else if (state_ == CallState::SendingFile) { + this->handleSendingFileState(); + } else if (state_ == CallState::ExpectingResponse) { + this->handleExpectingResponseState(); + } + } else { + state_ = CallState::CallComplete; + rpc_->Finish(&status_, tag_); + } + + return true; + } catch (std::exception &e) { + CAOSDB_LOG_ERROR(logger_name) + << "UploadRequestHandler caught an exception: " << e.what(); + transaction_status = TransactionStatus::GENERIC_ERROR(e.what()); + state_ = CallState::CallComplete; + } catch (...) { + CAOSDB_LOG_ERROR(logger_name) + << "Transaction error: unknown exception caught"; + transaction_status = TransactionStatus::GENERIC_ERROR( + "UploadRequestHandler caught an unknown exception"); + state_ = CallState::CallComplete; + } + + if (state_ == CallState::NewCall) { + return false; + } + + ctx_.TryCancel(); + + return true; +} + +void UploadRequestHandler::Cancel() { ctx_.TryCancel(); } + +void UploadRequestHandler::handleNewCallState() { + auto filename = file_descriptor_.local_path; + fileReader_ = std::make_unique<FileReader>(filename); + + rpc_ = stub_->PrepareAsyncFileUpload(&ctx_, response_, cq_); + + transaction_status = TransactionStatus::EXECUTING(); + state_ = CallState::SendingHeader; + rpc_->StartCall(tag_); +} + +void UploadRequestHandler::handleSendingHeaderState() { + auto *tid = request_->mutable_chunk()->mutable_file_transmission_id(); + tid->CopyFrom(*(file_descriptor_.file_transmission_id)); + + bytesToSend_ = fileReader_->fileSize(); + + if (bytesToSend_ > 0) { + state_ = CallState::SendingFile; + } else { + state_ = CallState::ExpectingResponse; + } + + rpc_->Write(*request_, tag_); +} + +void UploadRequestHandler::handleSendingFileState() { + const uint64_t DefaultChunkSize = 4 * 1024; // 4K + + auto chunkSize = std::min(DefaultChunkSize, bytesToSend_); + + request_->Clear(); + auto *buffer = request_->mutable_chunk()->mutable_data(); + buffer->resize(chunkSize); + + fileReader_->read(*buffer); + bytesToSend_ -= chunkSize; + + grpc::WriteOptions writeOptions; + if (bytesToSend_ > 0) { + state_ = CallState::SendingFile; + } else { + state_ = CallState::ExpectingResponse; + writeOptions.set_last_message(); + } + + rpc_->Write(*request_, writeOptions, tag_); +} + +void UploadRequestHandler::handleExpectingResponseState() { + state_ = CallState::CallComplete; + rpc_->Finish(&status_, tag_); +} + +void UploadRequestHandler::handleCallCompleteState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter UploadRequestHandler::handleCallCompleteState"; + + switch (status_.error_code()) { + case grpc::OK: { + auto bytesSent = fileReader_ != nullptr ? fileReader_->fileSize() : 0; + CAOSDB_LOG_INFO(logger_name) + << "UploadRequestHandler finished successfully (" + << file_descriptor_.local_path << "): upload complete, " << bytesSent + << " bytes sent"; + } break; + default: { + auto code(static_cast<StatusCode>(status_.error_code())); + std::string description(get_status_description(code) + + " Original message: " + status_.error_message()); + transaction_status = TransactionStatus(code, description); + CAOSDB_LOG_ERROR(logger_name) + << "UploadRequestHandler finished with an error (" + << file_descriptor_.local_path << "): Upload aborted with code " << code + << " - " << description; + } break; + } + + CAOSDB_LOG_TRACE(logger_name) + << "Leave UploadRequestHandler::handleCallCompleteState"; +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/logging.cpp b/src/caosdb/logging.cpp index d8f2de95233dffcbaf4aef2640cbea569f4ba006..5e6cd4e3577b57ec7491a26ae71982667a096609 100644 --- a/src/caosdb/logging.cpp +++ b/src/caosdb/logging.cpp @@ -165,6 +165,7 @@ auto initialize_logging(const LoggingConfiguration &configuration) -> void { off_settings["Core.DisableLogging"] = true; boost::log::init_from_settings(off_settings); + // now set everything up boost::log::settings new_settings; if (configuration.GetLevel() == CAOSDB_LOG_LEVEL_OFF) { diff --git a/src/caosdb/protobuf_helper.cpp b/src/caosdb/protobuf_helper.cpp index a9ad000595285f1c0fb6402182e0d48294daa37d..e8bbd07834ead9b561c7e8769ed834527337f7a6 100644 --- a/src/caosdb/protobuf_helper.cpp +++ b/src/caosdb/protobuf_helper.cpp @@ -19,7 +19,8 @@ * */ #include "caosdb/protobuf_helper.h" -#include <google/protobuf/arena.h> +#include <google/protobuf/arena.h> // for Arena +#include <google/protobuf/extension_set.h> // for Arena namespace caosdb::utility { diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index 61f52786f48594e3182c6101a1be305672e17def..a46019f3a5b83cc0bb058aa6446100bceac87242 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -18,23 +18,32 @@ * */ #include "caosdb/transaction.h" -#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS... -#include "caosdb/entity/v1alpha1/main.pb.h" // for SingleRetrieveRequest -#include "caosdb/logging.h" -#include "caosdb/protobuf_helper.h" // for get_arena -#include "caosdb/status_code.h" // for StatusCode, AUTHEN... -#include "google/protobuf/arena.h" // for Arena -#include "grpcpp/grpcpp.h" // for CompletionQueue -#include "grpcpp/impl/codegen/async_unary_call.h" // for ClientAsyncRespons... -#include "grpcpp/impl/codegen/client_context.h" // for ClientContext -#include "grpcpp/impl/codegen/completion_queue.h" // for CompletionQueue -#include "grpcpp/impl/codegen/status.h" // for Status -#include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH... -#include <cassert> // for assert -#include <map> // for map -#include <memory> // for allocator, unique_ptr +#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransac... +#include "caosdb/entity/v1alpha1/main.pb.h" // for TransactionRe... +#include "caosdb/file_transmission/download_request_handler.h" // Download... +#include "caosdb/file_transmission/file_reader.h" // for path +#include "caosdb/file_transmission/register_file_upload_handler.h" +#include "caosdb/file_transmission/upload_request_handler.h" // Upload... +#include "caosdb/logging.h" // for CAOSDB_LOG_FATAL +#include "caosdb/status_code.h" // for StatusCode +#include "caosdb/transaction_handler.h" +#include <algorithm> // for max +#include <boost/filesystem/path.hpp> // for operator<<, path +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +// IWYU pragma: no_include <bits/exception.h> +#include <exception> // IWYU pragma: keep +#include <google/protobuf/arena.h> // for Arena +#include <grpc/impl/codegen/gpr_types.h> +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <iosfwd> // for streamsize +#include <map> // for map, operator!= +#include <memory> // for unique_ptr #include <stdexcept> // for out_of_range -#include <utility> // for move +#include <utility> // for move, pair namespace caosdb { @@ -79,8 +88,23 @@ auto get_status_description(int code) -> const std::string & { "have " "an id. This is the case when you did not retrieve it before applying any " "changes and instantiated the Entity class explicitely."}, + {StatusCode::NOT_A_FILE_ENTITY, "You can only add files to file entities."}, + {StatusCode::PATH_IS_A_DIRECTORY, "The given path is a directory."}, + {StatusCode::FILE_DOES_NOT_EXIST_LOCALLY, + "The file does not not exist in the local file system."}, + {StatusCode::FILE_DOWNLOAD_ERROR, + "The transaction failed during the download of the files"}, + {StatusCode::FILE_UPLOAD_ERROR, + "The transaction failed during the upload of the files"}, {StatusCode::UNSUPPORTED_FEATURE, - "This feature is not available in the this client implementation."}}; + "This feature is not available in the this client implementation."}, + {StatusCode::EXTERN_C_ASSIGNMENT_ERROR, + "You tried to assign a new object to the wrapped void pointer. You have " + "to delete the old pointee first."}, + {StatusCode::OTHER_CLIENT_ERROR, + "This is code is reserved to errors raised by other clients wrapping the " + "C++ client (or its Extern C interface). This should never occur when " + "working with the C++ code itself."}}; try { return descriptions.at(code); } catch (const std::out_of_range &exc) { @@ -92,22 +116,23 @@ auto get_status_description(int code) -> const std::string & { namespace caosdb::transaction { using caosdb::entity::v1alpha1::EntityTransactionService; +using caosdb::entity::v1alpha1::FileTransmissionService; using caosdb::entity::v1alpha1::MultiTransactionRequest; using caosdb::entity::v1alpha1::MultiTransactionResponse; -using WrappedResponseCase = - caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase; -using QueryResponseCase = - caosdb::entity::v1alpha1::RetrieveResponse::QueryResponseCase; -using caosdb::utility::get_arena; -using grpc::ClientAsyncResponseReader; +using TransactionResponseCase = + caosdb::entity::v1alpha1::TransactionResponse::TransactionResponseCase; +using RetrieveResponseCase = + caosdb::entity::v1alpha1::RetrieveResponse::RetrieveResponseCase; using ProtoEntity = caosdb::entity::v1alpha1::Entity; -using grpc::CompletionQueue; +using google::protobuf::Arena; +using NextStatus = grpc::CompletionQueue::NextStatus; +using RegistrationStatus = caosdb::entity::v1alpha1::RegistrationStatus; ResultSet::iterator::iterator(const ResultSet *result_set_param, int index) : current_index(index), result_set(result_set_param) {} auto ResultSet::iterator::operator*() const -> const Entity & { - return this->result_set->At(current_index); + return this->result_set->at(current_index); } auto ResultSet::iterator::operator++() -> ResultSet::iterator & { @@ -130,24 +155,19 @@ auto ResultSet::begin() const -> ResultSet::iterator { } auto ResultSet::end() const -> ResultSet::iterator { - return ResultSet::iterator(this, Size()); + return ResultSet::iterator(this, size()); } MultiResultSet::MultiResultSet(std::vector<std::unique_ptr<Entity>> result_set) - : entities(std::move(result_set)) {} - -[[nodiscard]] auto UniqueResult::GetEntity() const -> const Entity & { - const Entity *result = this->entity.get(); - return *result; -} + : AbstractMultiResultSet(std::move(result_set)) {} Transaction::Transaction( - std::shared_ptr<EntityTransactionService::Stub> service_stub) - : request(google::protobuf::Arena::CreateMessage<MultiTransactionRequest>( - get_arena())), - response(google::protobuf::Arena::CreateMessage<MultiTransactionResponse>( - get_arena())) { - this->service_stub = std::move(service_stub); + std::shared_ptr<EntityTransactionService::Stub> entity_service, + std::shared_ptr<FileTransmissionService::Stub> file_service) + : request(Arena::CreateMessage<MultiTransactionRequest>(GetArena())), + response(Arena::CreateMessage<MultiTransactionResponse>(GetArena())) { + this->entity_service = std::move(entity_service); + this->file_service = std::move(file_service); this->query_count = -1; } @@ -161,6 +181,21 @@ auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode { return this->status.GetCode(); } +auto Transaction::RetrieveAndDownloadFilesById( + const std::string &id, const std::string &local_path) noexcept -> StatusCode { + ASSERT_CAN_ADD_RETRIEVAL + + auto *retrieve_request = + this->request->add_requests()->mutable_retrieve_request(); + retrieve_request->set_id(id); + retrieve_request->set_register_file_download(true); + + download_files[id].local_path = local_path; + + this->status = TransactionStatus::GO_ON(); + return this->status.GetCode(); +} + auto Transaction::Query(const std::string &query) noexcept -> StatusCode { ASSERT_CAN_ADD_QUERY @@ -187,11 +222,18 @@ auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode { auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { ASSERT_CAN_ADD_INSERTION - auto *sub_request = this->request->add_requests(); - auto *proto_entity = sub_request->mutable_insert_request(); + auto *entity_request = this->request->add_requests() + ->mutable_insert_request() + ->mutable_entity_request(); + auto *proto_entity = entity_request->mutable_entity(); // copy the original entity for the transaction entity->CopyTo(proto_entity); + if (entity->HasFile()) { + auto *file_transmission_id = entity_request->mutable_upload_id(); + entity->SetFileTransmissionId(file_transmission_id); + upload_files.push_back(entity->GetFileDescriptor()); + } this->status = TransactionStatus::READY(); return this->status.GetCode(); } @@ -199,10 +241,17 @@ auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode { auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode { ASSERT_CAN_ADD_UPDATE - auto *sub_request = this->request->add_requests(); - auto *proto_entity = sub_request->mutable_update_request(); + auto *entity_request = this->request->add_requests() + ->mutable_update_request() + ->mutable_entity_request(); + auto *proto_entity = entity_request->mutable_entity(); entity->CopyTo(proto_entity); + if (entity->HasFile()) { + auto *file_transmission_id = entity_request->mutable_upload_id(); + entity->SetFileTransmissionId(file_transmission_id); + upload_files.push_back(entity->GetFileDescriptor()); + } this->status = TransactionStatus::READY(); return this->status.GetCode(); } @@ -216,7 +265,8 @@ auto Transaction::Execute() -> TransactionStatus { return status; } -auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { +// TODO(tf) This has apparently a cognitive complexity of 39>25 (threshold). +auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { // NOLINT if (!IsStatus(TransactionStatus::READY()) && !IsStatus(TransactionStatus::GO_ON())) { return StatusCode::TRANSACTION_STATUS_ERROR; @@ -239,128 +289,250 @@ auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode { } this->status = TransactionStatus::EXECUTING(); - grpc::Status grpc_status; - CompletionQueue cq; + // upload files first + if (!upload_files.empty()) { + CAOSDB_LOG_INFO(logger_name) + << "Number of files to be uploaded: " << upload_files.size(); + + auto *registration_request = + Arena::CreateMessage<RegisterFileUploadRequest>(GetArena()); + auto *registration_response = + Arena::CreateMessage<RegisterFileUploadResponse>(GetArena()); + + handler_ = std::make_unique<RegisterFileUploadHandler>( + &handler_, file_service.get(), &completion_queue, registration_request, + registration_response); + this->status = ProcessCalls(); + + if (registration_response->status() != + RegistrationStatus::REGISTRATION_STATUS_ACCEPTED) { + this->status = TransactionStatus::FILE_UPLOAD_ERROR(); + return StatusCode::EXECUTING; + } - grpc::ClientContext context; - std::unique_ptr<ClientAsyncResponseReader<MultiTransactionResponse>> rpc( - this->service_stub->PrepareAsyncMultiTransaction(&context, *(this->request), - &cq)); - rpc->StartCall(); + for (auto &file_descriptor : upload_files) { + file_descriptor.file_transmission_id->set_registration_id( + registration_response->registration_id()); + CAOSDB_LOG_INFO(logger_name) + << "Uploading " << file_descriptor.local_path; + handler_ = std::make_unique<UploadRequestHandler>( + &handler_, file_service.get(), &completion_queue, file_descriptor); + this->status = ProcessCalls(); + if (this->status.GetCode() != StatusCode::EXECUTING) { + return StatusCode::EXECUTING; + } + } + } - int tag = 1; - void *send_tag = static_cast<void *>(&tag); - rpc->Finish(this->response, &grpc_status, send_tag); - void *recv_tag = nullptr; - bool ok = false; + handler_ = std::make_unique<EntityTransactionHandler>( + &handler_, entity_service.get(), &completion_queue, request, response); + this->status = ProcessCalls(); + if (this->status.GetCode() != StatusCode::EXECUTING) { + return StatusCode::EXECUTING; + } - // TODO(tf) make this actually asynchronous by moving this to WaitForIt() - cq.Next(&recv_tag, &ok); - assert(recv_tag == send_tag); - assert(ok); + // file download afterwards + if (status.GetCode() == StatusCode::EXECUTING && !download_files.empty()) { + // run over all retrieved entities and get the download_id + for (auto sub_response : *(response->mutable_responses())) { + if (sub_response.transaction_response_case() == + TransactionResponseCase::kRetrieveResponse) { + if (sub_response.retrieve_response() + .entity_response() + .has_download_id()) { + auto *entity_response = + sub_response.mutable_retrieve_response()->mutable_entity_response(); + auto entity_id = entity_response->entity().id(); + download_files[entity_id].file_transmission_id = + entity_response->release_download_id(); + // TODO(tf) handle error + } + } + } - if (!grpc_status.ok()) { - switch (grpc_status.error_code()) { - case grpc::StatusCode::UNAUTHENTICATED: - this->status = TransactionStatus::AUTHENTICATION_ERROR(); - break; - case grpc::StatusCode::UNAVAILABLE: - this->status = TransactionStatus::CONNECTION_ERROR(); - break; - default: - auto error_details = std::to_string(grpc_status.error_code()) + " - " + - grpc_status.error_message(); - this->status = TransactionStatus::RPC_ERROR(error_details); + for (const auto &item : download_files) { + auto file_descriptor(item.second); + CAOSDB_LOG_INFO(logger_name) + << "Downloading " << file_descriptor.local_path; + + handler_ = std::make_unique<DownloadRequestHandler>( + &handler_, file_service.get(), &completion_queue, file_descriptor); + this->status = ProcessCalls(); + if (this->status.GetCode() != StatusCode::EXECUTING) { + return StatusCode::EXECUTING; + } } - } else { - this->status = TransactionStatus::SUCCESS(); } return StatusCode::EXECUTING; } -auto Transaction::WaitForIt() const noexcept -> TransactionStatus { - if (this->response->responses_size() == 1) { - auto *responses = this->response->mutable_responses(0); - switch (responses->wrapped_response_case()) { - case WrappedResponseCase::kRetrieveResponse: { - auto *retrieve_response = responses->mutable_retrieve_response(); - switch (retrieve_response->query_response_case()) { - case QueryResponseCase::kEntity: { - auto *entity = retrieve_response->release_entity(); - if (!entity->errors().empty()) { - this->status = TransactionStatus::TRANSACTION_ERROR( - "The request returned with errors."); - } - this->result_set = std::make_unique<UniqueResult>(entity); +// TODO(tf) This has apparently a cognitive complexity of 36>25 (threshold). +auto Transaction::WaitForIt() const noexcept -> TransactionStatus { // NOLINT + if (this->status.GetCode() != StatusCode::EXECUTING) { + return this->status; + } + this->status = TransactionStatus::SUCCESS(); + bool set_error = false; + auto *responses = this->response->mutable_responses(); + std::vector<std::unique_ptr<Entity>> entities; + for (auto &sub_response : *responses) { + std::unique_ptr<Entity> result; + switch (sub_response.transaction_response_case()) { + + case TransactionResponseCase::kRetrieveResponse: { + auto *retrieve_response = sub_response.mutable_retrieve_response(); + + switch (retrieve_response->retrieve_response_case()) { + case RetrieveResponseCase::kEntityResponse: { + auto *retrieve_entity_response = + retrieve_response->release_entity_response(); + result = std::make_unique<Entity>(retrieve_entity_response); } break; - case QueryResponseCase::kSelectResult: { + case RetrieveResponseCase::kSelectResult: { + CAOSDB_LOG_ERROR(logger_name) << "Results of a SELECT query cannot be " + "processed by this client yet."; // TODO(tf) Select queries } break; - case QueryResponseCase::kCountResult: { + case RetrieveResponseCase::kCountResult: { this->query_count = retrieve_response->count_result(); - std::vector<std::unique_ptr<Entity>> entities; - this->result_set = - std::make_unique<MultiResultSet>(std::move(entities)); + } break; + case RetrieveResponseCase::kFindResult: { + std::unique_ptr<Entity> find_result; + for (auto &entity_response : + *retrieve_response->mutable_find_result()->mutable_result_set()) { + find_result = std::make_unique<Entity>(&entity_response); + if (find_result->HasErrors()) { + set_error = true; + } + entities.push_back(std::move(find_result)); + } } break; default: - // TODO(tf) Error + CAOSDB_LOG_FATAL(logger_name) << "Received invalid QueryResponseCase."; break; } - } break; - case WrappedResponseCase::kUpdateResponse: { - auto *updatedIdResponse = responses->mutable_update_response(); - if (!updatedIdResponse->entity_errors().empty()) { - this->status = TransactionStatus::TRANSACTION_ERROR( - "The request returned with errors."); - } - this->result_set = std::make_unique<UniqueResult>(updatedIdResponse); - } break; - case WrappedResponseCase::kInsertResponse: { - auto *insertedIdResponse = responses->mutable_insert_response(); - if (!insertedIdResponse->entity_errors().empty()) { - this->status = TransactionStatus::TRANSACTION_ERROR( - "The request returned with errors."); - } - this->result_set = std::make_unique<UniqueResult>(insertedIdResponse); - } break; - case WrappedResponseCase::kDeleteResponse: { - auto *deletedIdResponse = responses->mutable_delete_response(); - if (!deletedIdResponse->entity_errors().empty()) { - this->status = TransactionStatus::TRANSACTION_ERROR( - "The request returned with errors."); - } - this->result_set = std::make_unique<UniqueResult>(deletedIdResponse); - } break; + + break; // break TransactionResponseCase::kRetrieveResponse + } + + case TransactionResponseCase::kInsertResponse: { + auto *inserted_id_response = + sub_response.mutable_insert_response()->release_id_response(); + result = std::make_unique<Entity>(inserted_id_response); + break; + } + case TransactionResponseCase::kDeleteResponse: { + auto *deleted_id_response = + sub_response.mutable_delete_response()->release_id_response(); + result = std::make_unique<Entity>(deleted_id_response); + break; + } + case TransactionResponseCase::kUpdateResponse: { + auto *updated_id_response = + sub_response.mutable_update_response()->release_id_response(); + result = std::make_unique<Entity>(updated_id_response); + break; + } default: - // TODO(tf) Error and Update + CAOSDB_LOG_FATAL(logger_name) + << "Received invalid TransactionResponseCase."; break; } - } else { - auto *responses = this->response->mutable_responses(); - std::vector<std::unique_ptr<Entity>> entities; - for (auto sub_response : *responses) { - switch (sub_response.wrapped_response_case()) { - case WrappedResponseCase::kRetrieveResponse: - entities.push_back(std::make_unique<Entity>( - sub_response.mutable_retrieve_response()->release_entity())); - break; - case WrappedResponseCase::kInsertResponse: - entities.push_back( - std::make_unique<Entity>(sub_response.release_insert_response())); - break; - case WrappedResponseCase::kDeleteResponse: - entities.push_back( - std::make_unique<Entity>(sub_response.release_insert_response())); - break; - default: - // TODO(tf) Updates - break; + if (result != nullptr) { + if (result->HasErrors()) { + set_error = true; } + entities.push_back(std::move(result)); + } + } + + // copy local path of downloaded files into the entities file descriptor + for (auto &entity : entities) { + auto id = entity->GetId(); + if (!id.empty() && download_files.count(id) == 1) { + const auto &local_path = download_files.at(id).local_path; + entity->SetLocalPath(local_path); } - this->result_set = std::make_unique<MultiResultSet>(std::move(entities)); + } + this->result_set = std::make_unique<MultiResultSet>(std::move(entities)); + + if (set_error) { + this->status = TransactionStatus::TRANSACTION_ERROR( + "The request terminated with errors."); } return this->status; } +auto Transaction::ProcessCalls() -> TransactionStatus { + gpr_timespec deadline; + deadline.tv_sec = 1; + deadline.tv_nsec = 0; + deadline.clock_type = gpr_clock_type::GPR_TIMESPAN; + + TransactionStatus result = TransactionStatus::EXECUTING(); + handler_->Start(); + void *tag = nullptr; + bool ok = false; + while (true) { + switch (completion_queue.AsyncNext(&tag, &ok, deadline)) { + case NextStatus::GOT_EVENT: { + if (tag != nullptr) { + auto res = handler_->OnNext(ok); + if (!res) { + // The handler has finished it's work + result = handler_->GetStatus(); + handler_.reset(); + return result; + } + } else { + std::string description("Invalid tag delivered by notification queue."); + CAOSDB_LOG_ERROR(logger_name) << description; + handler_.reset(); + return TransactionStatus::RPC_ERROR(description); + } + } break; + case NextStatus::SHUTDOWN: { + CAOSDB_LOG_ERROR(logger_name) + << "Notification queue has been shut down unexpectedly."; + result = handler_->GetStatus(); + handler_.reset(); + return result; + } break; + case NextStatus::TIMEOUT: { + CAOSDB_LOG_DEBUG(logger_name) << "Timeout, waiting..."; + } break; + default: + CAOSDB_LOG_FATAL(logger_name) + << "Got an invalid NextStatus from CompletionQueue."; + result = handler_->GetStatus(); + handler_.reset(); + return result; + } + } + result = handler_->GetStatus(); + handler_.reset(); + return result; +} + +Transaction::~Transaction() { + this->Cancel(); + + completion_queue.Shutdown(); + + // drain the queue + void *ignoredTag = nullptr; + bool ok = false; + while (completion_queue.Next(&ignoredTag, &ok)) { + ; + } +} + +void Transaction::Cancel() { + // TODO(tf) State Canceled + if (handler_ != nullptr) { + handler_->Cancel(); + } +} + } // namespace caosdb::transaction diff --git a/src/caosdb/transaction_handler.cpp b/src/caosdb/transaction_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf020af35bf56b2e68da11a92b0961995397f2fd --- /dev/null +++ b/src/caosdb/transaction_handler.cpp @@ -0,0 +1,40 @@ +#include "caosdb/transaction_handler.h" +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +#include <exception> // IWYU pragma: keep +// IWYU pragma: no_include <bits/exception.h> +#include <grpcpp/impl/codegen/async_unary_call.h> // for ClientAsyncRes... +#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue +#include <iosfwd> // for streamsize + +namespace caosdb::transaction { + +EntityTransactionHandler::EntityTransactionHandler( + HandlerTag tag, EntityTransactionService::Stub *stub, + grpc::CompletionQueue *completion_queue, MultiTransactionRequest *request, + MultiTransactionResponse *response) + : UnaryRpcHandler(completion_queue), tag_(tag), stub_(stub), + request_(request), response_(response) {} + +void EntityTransactionHandler::handleNewCallState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter EntityTransactionHandler::handleNewCallState with " + "CompletionQueue " + << completion_queue; + + rpc_ = stub_->PrepareAsyncMultiTransaction(&call_context, *request_, + completion_queue); + + state_ = CallState::CallComplete; + rpc_->StartCall(); + rpc_->Finish(response_, &status_, tag_); + + CAOSDB_LOG_TRACE(logger_name) + << "Leave EntityTransactionHandler::handleNewCallState"; +} + +} // namespace caosdb::transaction diff --git a/src/caosdb/unary_rpc_handler.cpp b/src/caosdb/unary_rpc_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d1e1cf618ef119278b02fdc3657d17482a87601 --- /dev/null +++ b/src/caosdb/unary_rpc_handler.cpp @@ -0,0 +1,135 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + ********************************************************************************* + * + * This is derived work which is heavily based on + * https://github.com/NeiRo21/grpcpp-bidi-streaming, Commit + * cd9cb78e5d6d72806c2ec4c703e5e856b223dc96, Aug 10, 2020 + * + * The orginal work is licensed as + * + * > MIT License + * > + * > Copyright (c) 2019 NeiRo21 + * > + * > Permission is hereby granted, free of charge, to any person obtaining a + * > copy of this software and associated documentation files (the "Software"), + * > to deal in the Software without restriction, including without limitation + * > the rights to use, copy, modify, merge, publish, distribute, sublicense, + * > and/or sell copies of the Software, and to permit persons to whom the + * > Software is furnished to do so, subject to the following conditions: + * > + * > The above copyright notice and this permission notice shall be included in + * > all copies or substantial portions of the Software. + * > + * > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * > DEALINGS IN THE SOFTWARE. + */ +#include "caosdb/unary_rpc_handler.h" +#include "caosdb/logging.h" // for CAOSDB_LOG_TRACE +#include "caosdb/status_code.h" // for GENERIC_RPC_E... +#include <boost/log/core/record.hpp> // for record +#include <boost/log/detail/attachable_sstream_buf.hpp> // for basic_ostring... +#include <boost/log/sources/record_ostream.hpp> // for basic_record_... +#include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_... +#include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_... +// IWYU pragma: no_include <bits/exception.h> +#include <exception> // IWYU pragma: keep +#include <grpcpp/impl/codegen/client_context.h> // for ClientContext +#include <grpcpp/impl/codegen/status.h> // for Status +#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT... +#include <iosfwd> // for streamsize +#include <string> // for string, opera... + +namespace caosdb::transaction { + +bool UnaryRpcHandler::OnNext(bool ok) { + try { + if (ok) { + if (state_ == CallState::NewCall) { + this->handleNewCallState(); + } else if (state_ == CallState::CallComplete) { + this->handleCallCompleteState(); + return false; + } + } else { + CAOSDB_LOG_ERROR(logger_name) + << "UnaryRpcHandler::OnNext(false)!. This should not happen."; + // TODO(tf) Handle this error: + // in CallComplete state: "Client-side Finish: ok should always be true" + // in ReceivingFile state: "ok indicates that the RPC is going to go to + // the wire. If it is false, it not going to the wire. This would happen + // if the channel is either permanently broken or transiently broken but + // with the fail-fast option. (Note that async unary RPCs don't post a CQ + // tag at this point, nor do client-streaming or bidi-streaming RPCs that + // have the initial metadata corked option set.)" + } + + return true; + } catch (std::exception &e) { + CAOSDB_LOG_ERROR(logger_name) + << "UnaryRpcHandler caught an exception: " << e.what(); + transaction_status = TransactionStatus::GENERIC_ERROR(e.what()); + state_ = CallState::CallComplete; + } catch (...) { + CAOSDB_LOG_ERROR(logger_name) + << "Transaction error: unknown exception caught"; + transaction_status = TransactionStatus::GENERIC_ERROR( + "UnaryRpcHandler caught an unknown exception"); + state_ = CallState::CallComplete; + } + + if (state_ != CallState::NewCall) { + call_context.TryCancel(); + } + + return false; +} + +void UnaryRpcHandler::Cancel() { call_context.TryCancel(); } + +void UnaryRpcHandler::handleCallCompleteState() { + CAOSDB_LOG_TRACE(logger_name) + << "Enter UnaryRpcHandler::handleCallCompleteState"; + + switch (status_.error_code()) { + case grpc::OK: + CAOSDB_LOG_INFO(logger_name) << "UnaryRpcHandler finished successfully."; + break; + default: + auto code(static_cast<StatusCode>(status_.error_code())); + std::string description(get_status_description(code) + + " Original message: " + status_.error_message()); + transaction_status = TransactionStatus(code, description); + CAOSDB_LOG_ERROR(logger_name) + << "UnaryRpcHandler finished with an error (Code " << code + << "): " << description; + break; + } + + CAOSDB_LOG_TRACE(logger_name) + << "Leave UnaryRpcHandler::handleCallCompleteState"; +} + +} // namespace caosdb::transaction diff --git a/src/ccaosdb.cpp b/src/ccaosdb.cpp index 5f89af16ed3da4de89df1d83e1de0bdecb79592f..d962c5bae38d655b90ce4e208bac9f042aabc7fa 100644 --- a/src/ccaosdb.cpp +++ b/src/ccaosdb.cpp @@ -1,3 +1,24 @@ +/* + * This file is a part of the CaosDB Project. + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see + * <https://www.gnu.org/licenses/>. + * + */ #include "ccaosdb.h" #include "caosdb/connection.h" #include "caosdb/constants.h" @@ -5,10 +26,12 @@ #include "caosdb/status_code.h" #include "caosdb/logging.h" #include <cassert> +#include <cstring> #include <exception> #include <iostream> #include <stdio.h> #include <string.h> +#include <vector> extern "C" { @@ -20,6 +43,7 @@ extern "C" { */ #define ERROR_RETURN_CODE(code, fun, body) \ fun { \ + CAOSDB_LOG_TRACE(CCAOSDB_LOGGER_NAME) << "Enter " << #fun; \ try { \ body \ } catch (const std::exception &exc) { \ @@ -28,6 +52,90 @@ extern "C" { } \ } +/** + * Macro for entity getters + */ +#define CAOSDB_ENTITY_GET(element, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_entity_get_##element(caosdb_entity_entity *entity, \ + char *out), \ + { \ + auto *wrapped_entity = \ + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); \ + body_part return 0; \ + }) + +/** + * Macro for entity setters + */ +#define CAOSDB_ENTITY_SET(element, value, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_entity_set_##element(caosdb_entity_entity *entity, \ + const char *value), \ + { \ + auto *wrapped_entity = \ + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); \ + body_part return 0; \ + }) + +/** + * Macro for property getters + */ +#define CAOSDB_PROPERTY_GET(element, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_property_get_##element(caosdb_entity_property *property, \ + char *out), \ + { \ + auto *wrapped_property = \ + static_cast<caosdb::entity::Property *>(property->wrapped_property); \ + body_part return 0; \ + }) + +/** + * Macro for property setters + */ +#define CAOSDB_PROPERTY_SET(element, value, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_property_set_##element(caosdb_entity_property *property, \ + const char *value), \ + { \ + auto *wrapped_property = \ + static_cast<caosdb::entity::Property *>(property->wrapped_property); \ + body_part return 0; \ + }) + +/** + * Macro for parent getters + */ +#define CAOSDB_PARENT_GET(element, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_parent_get_##element(caosdb_entity_parent *parent, \ + char *out), \ + { \ + auto *wrapped_parent = \ + static_cast<caosdb::entity::Parent *>(parent->wrapped_parent); \ + body_part return 0; \ + }) + +/** + * Macro for parent setters + */ +#define CAOSDB_PARENT_SET(element, value, body_part) \ + ERROR_RETURN_CODE( \ + GENERIC_ERROR, \ + int caosdb_entity_parent_set_##element(caosdb_entity_parent *parent, \ + const char *value), \ + { \ + auto *wrapped_parent = \ + static_cast<caosdb::entity::Parent *>(parent->wrapped_parent); \ + body_part return 0; \ + }) + int caosdb_constants_LIBCAOSDB_VERSION_MAJOR() { return caosdb::LIBCAOSDB_VERSION_MAJOR; } @@ -56,6 +164,10 @@ const char *caosdb_constants_COMPATIBLE_SERVER_VERSION_PRE_RELEASE() { return caosdb::COMPATIBLE_SERVER_VERSION_PRE_RELEASE; } +int caosdb_status_code_OTHER_CLIENT_ERROR() { + return caosdb::StatusCode::OTHER_CLIENT_ERROR; +} + const char *caosdb_utility_get_env_var(const char *name, const char *fallback) { return caosdb::utility::get_env_var(name, fallback); } @@ -69,9 +181,13 @@ ERROR_RETURN_CODE(GENERIC_ERROR, caosdb_connection_certificate_provider *out, const char *path), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } out->wrapped_certificate_provider = new caosdb::configuration::PemFileCertificateProvider( std::string(path)); + out->_deletable = true; return 0; }) @@ -80,8 +196,11 @@ ERROR_RETURN_CODE( int caosdb_connection_delete_certificate_provider( caosdb_connection_certificate_provider *provider), { - delete static_cast<caosdb::configuration::CertificateProvider *>( - provider->wrapped_certificate_provider); + if (provider->_deletable) { + delete static_cast<caosdb::configuration::CertificateProvider *>( + provider->wrapped_certificate_provider); + } + provider->_deletable = false; return 0; }) @@ -90,20 +209,28 @@ ERROR_RETURN_CODE(GENERIC_ERROR, caosdb_authentication_authenticator *out, const char *username, const char *password), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } out->wrapped_authenticator = new caosdb::authentication::PlainPasswordAuthenticator( std::string(username), std::string(password)); + out->_deletable = true; return 0; }) -ERROR_RETURN_CODE(GENERIC_ERROR, - int caosdb_authentication_delete_authenticator( - caosdb_authentication_authenticator *authenticator), - { - delete static_cast<caosdb::authentication::Authenticator *>( - authenticator->wrapped_authenticator); - return 0; - }) +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_authentication_delete_authenticator( + caosdb_authentication_authenticator *authenticator), + { + if (authenticator->_deletable) { + delete static_cast<caosdb::authentication::Authenticator *>( + authenticator->wrapped_authenticator); + } + authenticator->_deletable = false; + return 0; + }) ERROR_RETURN_CODE( GENERIC_ERROR, @@ -112,6 +239,9 @@ ERROR_RETURN_CODE( const int port, caosdb_authentication_authenticator *authenticator, caosdb_connection_certificate_provider *provider), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } auto host_str = std::string(host); if (authenticator != nullptr && provider != nullptr) { auto wrapped_provider = @@ -141,6 +271,7 @@ ERROR_RETURN_CODE( out->wrapped_connection_configuration = new caosdb::configuration::TlsConnectionConfiguration(host_str, port); } + out->_deletable = true; return 0; }) @@ -150,8 +281,12 @@ ERROR_RETURN_CODE( caosdb_connection_connection_configuration *out, const char *host, const int port), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } out->wrapped_connection_configuration = new caosdb::configuration::InsecureConnectionConfiguration(host, port); + out->_deletable = true; return 0; }) @@ -160,8 +295,11 @@ ERROR_RETURN_CODE( int caosdb_connection_delete_connection_configuration( caosdb_connection_connection_configuration *configuration), { - delete static_cast<caosdb::configuration::ConnectionConfiguration *>( - configuration->wrapped_connection_configuration); + if (configuration->_deletable) { + delete static_cast<caosdb::configuration::ConnectionConfiguration *>( + configuration->wrapped_connection_configuration); + } + configuration->_deletable = false; return 0; }) @@ -171,10 +309,14 @@ ERROR_RETURN_CODE( caosdb_connection_connection *out, const caosdb_connection_connection_configuration *configuration), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } caosdb::configuration::ConnectionConfiguration *config = static_cast<caosdb::configuration::ConnectionConfiguration *>( configuration->wrapped_connection_configuration); out->wrapped_connection = new caosdb::connection::Connection(*config); + out->_deletable = true; return 0; }) @@ -182,8 +324,11 @@ ERROR_RETURN_CODE(GENERIC_ERROR, int caosdb_connection_delete_connection( caosdb_connection_connection *connection), { - delete static_cast<caosdb::connection::Connection *>( - connection->wrapped_connection); + if (connection->_deletable) { + delete static_cast<caosdb::connection::Connection *>( + connection->wrapped_connection); + } + connection->_deletable = false; return 0; }) @@ -226,8 +371,12 @@ ERROR_RETURN_CODE( int caosdb_connection_connection_manager_get_default_connection( caosdb_connection_connection *out), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } out->wrapped_connection = caosdb::connection::ConnectionManager::GetDefaultConnection().get(); + out->_deletable = false; return 0; }) @@ -235,10 +384,454 @@ ERROR_RETURN_CODE(GENERIC_ERROR, int caosdb_connection_connection_manager_get_connection( caosdb_connection_connection *out, const char *name), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } out->wrapped_connection = caosdb::connection::ConnectionManager::GetConnection( std::string(name)) .get(); + // managed by the connection manager now, so not + // to be deleted manually + out->_deletable = false; + return 0; + }) + +/**************************************************************************** + * ENTITY STUFF AND TRANSACTIONS + ****************************************************************************/ +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_connection_connection_create_transaction( + caosdb_connection_connection *connection, + caosdb_transaction_transaction *out), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_connection = + static_cast<caosdb::connection::Connection *>( + connection->wrapped_connection); + out->wrapped_transaction = + wrapped_connection->CreateTransaction().release(); + out->_deletable = true; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_delete_transaction( + caosdb_transaction_transaction *transaction), + { + if (transaction->_deletable) { + delete static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + } + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_retrieve_by_id( + caosdb_transaction_transaction *transaction, + const char *id), + { + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + return wrapped_transaction->RetrieveById(std::string(id)); + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_retrieve_by_ids( + caosdb_transaction_transaction *transaction, + const char *ids[], int length), + { + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + return wrapped_transaction->RetrieveById(ids, ids + length); + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_query( + caosdb_transaction_transaction *transaction, + const char *query), + { + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + return wrapped_transaction->Query(std::string(query)); + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_execute( + caosdb_transaction_transaction *transaction), + { + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + wrapped_transaction->ExecuteAsynchronously(); + auto status = wrapped_transaction->WaitForIt(); + return status.GetCode(); + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_get_result_set( + caosdb_transaction_transaction *transaction, + caosdb_transaction_result_set *out), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + out->wrapped_result_set = + (void *)(&(wrapped_transaction->GetResultSet())); + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_transaction_get_count_result( + caosdb_transaction_transaction *transaction, long *out), + { + auto *wrapped_transaction = + static_cast<caosdb::transaction::Transaction *>( + transaction->wrapped_transaction); + long cr(wrapped_transaction->GetCountResult()); + *out = cr; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_result_set_at( + caosdb_transaction_result_set *result_set, + caosdb_entity_entity *entity, int index), + { + auto *wrapped_result_set = + static_cast<caosdb::transaction::MultiResultSet *>( + result_set->wrapped_result_set); + entity->wrapped_entity = + wrapped_result_set->mutable_at(index); + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_transaction_result_set_size( + caosdb_transaction_result_set *result_set, int *out), + { + auto *wrapped_result_set = + static_cast<caosdb::transaction::MultiResultSet *>( + result_set->wrapped_result_set); + int size(wrapped_result_set->size()); + *out = size; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_entity_create_entity(caosdb_entity_entity *out), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + out->wrapped_entity = new caosdb::entity::Entity(); + out->_deletable = true; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_entity_delete_entity(caosdb_entity_entity *out), { + if (out->_deletable) { + delete static_cast<caosdb::entity::Entity *>( + out->wrapped_entity); + } + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, int caosdb_entity_create_property(caosdb_entity_property *out), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + out->wrapped_property = new caosdb::entity::Property(); + out->_deletable = true; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, int caosdb_entity_delete_property(caosdb_entity_property *out), + { + if (out->_deletable) { + delete static_cast<caosdb::entity::Property *>(out->wrapped_property); + } + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_entity_create_parent(caosdb_entity_parent *out), { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + out->wrapped_parent = new caosdb::entity::Parent(); + out->_deletable = true; + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_entity_delete_parent(caosdb_entity_parent *out), { + if (out->_deletable) { + delete static_cast<caosdb::entity::Parent *>( + out->wrapped_parent); + } + out->_deletable = false; return 0; }) + +CAOSDB_ENTITY_GET(id, strcpy(out, wrapped_entity->GetId().c_str());) +CAOSDB_ENTITY_GET(role, strcpy(out, wrapped_entity->GetRole().c_str());) +CAOSDB_ENTITY_GET(name, strcpy(out, wrapped_entity->GetName().c_str());) +CAOSDB_ENTITY_GET(description, + strcpy(out, wrapped_entity->GetDescription().c_str());) +CAOSDB_ENTITY_GET(datatype, strcpy(out, wrapped_entity->GetDatatype().c_str());) +CAOSDB_ENTITY_GET(value, strcpy(out, wrapped_entity->GetValue().c_str());) +CAOSDB_ENTITY_GET(unit, strcpy(out, wrapped_entity->GetUnit().c_str());) +CAOSDB_ENTITY_GET(version_id, + strcpy(out, wrapped_entity->GetVersionId().c_str());) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_errors_size(caosdb_entity_entity *entity, + int *out), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + *out = wrapped_entity->GetErrors().size(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_error(caosdb_entity_entity *entity, + caosdb_entity_message *out, int index), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + out->wrapped_message = wrapped_entity->GetErrors().mutable_at(index); + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_warnings_size(caosdb_entity_entity *entity, + int *out), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + *out = wrapped_entity->GetWarnings().size(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_warning(caosdb_entity_entity *entity, + caosdb_entity_message *out, int index), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + out->wrapped_message = wrapped_entity->GetWarnings().mutable_at(index); + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_infos_size(caosdb_entity_entity *entity, + int *out), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + *out = wrapped_entity->GetInfos().size(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_info(caosdb_entity_entity *entity, + caosdb_entity_message *out, int index), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + out->wrapped_message = wrapped_entity->GetInfos().mutable_at(index); + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_properties_size(caosdb_entity_entity *entity, + int *out), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + *out = wrapped_entity->GetProperties().size(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_property(caosdb_entity_entity *entity, + caosdb_entity_property *out, int index), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + out->wrapped_property = wrapped_entity->GetProperties().mutable_at(index); + out->_deletable = false; + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_parents_size(caosdb_entity_entity *entity, + int *out), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + *out = wrapped_entity->GetParents().size(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_get_parent(caosdb_entity_entity *entity, + caosdb_entity_parent *out, int index), + { + if (out->_deletable) { + return caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR; + } + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + out->wrapped_parent = wrapped_entity->GetParents().mutable_at(index); + out->_deletable = false; + return 0; + }) + +CAOSDB_PARENT_GET(id, strcpy(out, wrapped_parent->GetId().c_str());) +CAOSDB_PARENT_GET(name, strcpy(out, wrapped_parent->GetName().c_str());) +CAOSDB_PARENT_GET(description, + strcpy(out, wrapped_parent->GetDescription().c_str());) + +CAOSDB_PROPERTY_GET(id, strcpy(out, wrapped_property->GetId().c_str());) +CAOSDB_PROPERTY_GET(name, strcpy(out, wrapped_property->GetName().c_str());) +CAOSDB_PROPERTY_GET(description, + strcpy(out, wrapped_property->GetDescription().c_str());) +CAOSDB_PROPERTY_GET(importance, + strcpy(out, wrapped_property->GetImportance().c_str());) +CAOSDB_PROPERTY_GET(datatype, + strcpy(out, wrapped_property->GetDatatype().c_str());) +CAOSDB_PROPERTY_GET(unit, strcpy(out, wrapped_property->GetUnit().c_str());) +CAOSDB_PROPERTY_GET(value, strcpy(out, wrapped_property->GetValue().c_str());) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_message_get_code(caosdb_entity_message *message, int *out), + { + auto *wrapped_message = + static_cast<caosdb::entity::Message *>(message->wrapped_message); + *out = wrapped_message->GetCode(); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_message_get_description(caosdb_entity_message *message, + char *out), + { + auto *wrapped_message = + static_cast<caosdb::entity::Message *>(message->wrapped_message); + strcpy(out, wrapped_message->GetDescription().c_str()); + return 0; + }) + +CAOSDB_ENTITY_SET(role, role, wrapped_entity->SetRole(std::string(role));) +CAOSDB_ENTITY_SET(name, name, wrapped_entity->SetName(std::string(name));) +CAOSDB_ENTITY_SET(description, description, + wrapped_entity->SetDescription(std::string(description));) +CAOSDB_ENTITY_SET(datatype, datatype, + wrapped_entity->SetDatatype(std::string(datatype));) +CAOSDB_ENTITY_SET(unit, unit, wrapped_entity->SetUnit(std::string(unit));) +CAOSDB_ENTITY_SET(value, value, wrapped_entity->SetValue(std::string(value));) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_append_parent(caosdb_entity_entity *entity, + caosdb_entity_parent *parent), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + auto *wrapped_parent = + static_cast<caosdb::entity::Parent *>(parent->wrapped_parent); + wrapped_entity->AppendParent(*wrapped_parent); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_remove_parent(caosdb_entity_entity *entity, + int index), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + wrapped_entity->RemoveParent(index); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_append_property(caosdb_entity_entity *entity, + caosdb_entity_property *property), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + auto *wrapped_property = + static_cast<caosdb::entity::Property *>(property->wrapped_property); + wrapped_entity->AppendProperty(*wrapped_property); + return 0; + }) +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_entity_entity_remove_property(caosdb_entity_entity *entity, + int index), + { + auto *wrapped_entity = + static_cast<caosdb::entity::Entity *>(entity->wrapped_entity); + wrapped_entity->RemoveProperty(index); + return 0; + }) + +CAOSDB_PARENT_SET(id, id, wrapped_parent->SetId(std::string(id));) +CAOSDB_PARENT_SET(name, name, wrapped_parent->SetName(std::string(name));) + +CAOSDB_PROPERTY_SET(name, name, wrapped_property->SetName(std::string(name));) +CAOSDB_PROPERTY_SET(id, id, wrapped_property->SetId(std::string(id));) +CAOSDB_PROPERTY_SET(datatype, datatype, + wrapped_property->SetDatatype(std::string(datatype));) +CAOSDB_PROPERTY_SET(importance, importance, + wrapped_property->SetImportance(std::string(importance));) +CAOSDB_PROPERTY_SET(unit, unit, wrapped_property->SetUnit(std::string(unit));) +CAOSDB_PROPERTY_SET(value, value, + wrapped_property->SetValue(std::string(value));) } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 389d5d919f8200a77ea1ffb93267c11ac70baf75..0ecb50169e94b5f758a429054c80c80e9d420c4d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,6 +23,7 @@ set(test_cases test_configuration test_connection test_entity + test_file_transmission test_info test_protobuf test_transaction @@ -36,7 +37,7 @@ set(test_cases # special linting for tests set(_CMAKE_CXX_CLANG_TIDY_TEST_CHECKS - "${_CMAKE_CXX_CLANG_TIDY_CHECKS},-cert-err58-cpp,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-owning-memory,-modernize-use-trailing-return-type,-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-avoid-goto,-hicpp-avoid-goto,-readability-function-cognitive-complexity" + "${_CMAKE_CXX_CLANG_TIDY_CHECKS},-cert-err58-cpp,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-owning-memory,-modernize-use-trailing-return-type,-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-avoid-goto,-hicpp-avoid-goto,-readability-function-cognitive-complexity,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes" ) # add special cmake functions for gtest diff --git a/test/caosdb_test_utility.h.in b/test/caosdb_test_utility.h.in index 24d088bb9ff8a0c82d117da35241f1d8acc18012..baac638f5668a05c6464fef99410bb345ba8b3c3 100644 --- a/test/caosdb_test_utility.h.in +++ b/test/caosdb_test_utility.h.in @@ -22,6 +22,7 @@ #ifndef CAOSDB_TEST_UTILITY_H #define CAOSDB_TEST_UTILITY_H +#include <string> /** * @file caosdb_test_utility.h * @brief Utility for the unit tests diff --git a/test/test_ccaosdb.cpp b/test/test_ccaosdb.cpp index abb06dd7426a642ef1e54593d67e28015168cc71..1009cc5962c6a309052509e36d39c3f6ee97ea98 100644 --- a/test/test_ccaosdb.cpp +++ b/test/test_ccaosdb.cpp @@ -21,12 +21,15 @@ */ #include "caosdb/configuration.h" +#include "caosdb/status_code.h" // for StatusCode #include "caosdb_test_utility.h" // for EXPECT_THROW_MESSAGE, TEST_DATA_DIR #include "ccaosdb.h" // for caosdb_utility_get_env_var +#include <cstring> // for strcmp #include <gtest/gtest-message.h> // for Message #include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestFactoryImpl #include <gtest/gtest_pred_impl.h> // for Test, TestInfo, EXPECT_EQ, TEST -#include <string> // for allocator +#include <iostream> +#include <string> // for allocator class test_ccaosdb : public ::testing::Test { protected: @@ -47,6 +50,11 @@ TEST_F(test_ccaosdb, test_get_env_var) { EXPECT_EQ("fall-back", some_var); } +TEST_F(test_ccaosdb, test_other_client_error) { + EXPECT_EQ(caosdb_status_code_OTHER_CLIENT_ERROR(), + caosdb::StatusCode::OTHER_CLIENT_ERROR); +} + TEST_F(test_ccaosdb, test_get_default_connection) { caosdb_connection_connection out; @@ -61,3 +69,349 @@ TEST_F(test_ccaosdb, test_get_connection) { "local-caosdb-admin"); EXPECT_TRUE(out.wrapped_connection); } + +TEST_F(test_ccaosdb, test_execute_transaction) { + caosdb_connection_connection connection; + caosdb_connection_connection_manager_get_connection(&connection, + "local-caosdb-admin"); + + caosdb_transaction_transaction transaction; + caosdb_connection_connection_create_transaction(&connection, &transaction); + + EXPECT_TRUE(transaction.wrapped_transaction); + + int return_code( + caosdb_transaction_transaction_retrieve_by_id(&transaction, "some_id")); + EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON); + + return_code = caosdb_transaction_transaction_execute(&transaction); + EXPECT_EQ(return_code, caosdb::StatusCode::CONNECTION_ERROR); + + return_code = caosdb_transaction_delete_transaction(&transaction); + EXPECT_EQ(return_code, 0); + + caosdb_transaction_transaction multi_transaction; + caosdb_connection_connection_create_transaction(&connection, + &multi_transaction); + + // We explicitely want to define a C-style array here, so we disable + // linting + const char *ids[] = {"id1", "id2", "id3"}; // NOLINT + return_code = + caosdb_transaction_transaction_retrieve_by_ids(&multi_transaction, ids, 3); + EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON); + + return_code = caosdb_transaction_delete_transaction(&multi_transaction); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_multi_retrieve) { + std::cout << "Entering test_multi_retrieve ..." << std::endl; + caosdb_connection_connection connection; + caosdb_connection_connection_manager_get_connection(&connection, + "local-caosdb-admin"); + + std::cout << "Creating transaction" << std::endl; + caosdb_transaction_transaction multi_transaction; + caosdb_connection_connection_create_transaction(&connection, + &multi_transaction); + + // We explicitely want to define a C-style array here, so we disable + // linting + const char *ids[] = {"id1", "id2", "id3"}; // NOLINT + std::cout << "Adding mutli retrieval ..." << std::endl; + int return_code( + caosdb_transaction_transaction_retrieve_by_ids(&multi_transaction, ids, 3)); + EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON); + + std::cout << "Deleting transaction ..." << std::endl; + return_code = caosdb_transaction_delete_transaction(&multi_transaction); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_query) { + caosdb_connection_connection connection; + caosdb_connection_connection_manager_get_connection(&connection, + "local-caosdb-admin"); + + caosdb_transaction_transaction transaction; + caosdb_connection_connection_create_transaction(&connection, &transaction); + + int return_code(caosdb_transaction_transaction_query( + &transaction, "FIND ENTITY WITH id=123")); + EXPECT_EQ(return_code, caosdb::StatusCode::GO_ON); + + return_code = caosdb_transaction_delete_transaction(&transaction); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_entity) { + caosdb_entity_entity entity; + + int return_code(caosdb_entity_create_entity(&entity)); + EXPECT_EQ(return_code, 0); + + // cannot be created again without deletion + return_code = caosdb_entity_create_entity(&entity); + EXPECT_EQ(return_code, caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR); + + // deletion and re-creation is ok + return_code = caosdb_entity_delete_entity(&entity); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_create_entity(&entity); + EXPECT_EQ(return_code, 0); + + // In-depth check for one pair of setter and getter, just compare + // the strings for the rest + return_code = caosdb_entity_entity_set_name(&entity, "length"); + EXPECT_EQ(return_code, 0); + char out[255] = {"a"}; // NOLINT + return_code = caosdb_entity_entity_get_name(&entity, out); + EXPECT_EQ(return_code, 0); + EXPECT_EQ(strcmp(out, "length"), 0); + + caosdb_entity_entity_set_role(&entity, "Property"); + caosdb_entity_entity_get_role(&entity, out); + EXPECT_EQ(strcmp(out, "Property"), 0); + + caosdb_entity_entity_set_description(&entity, "The length of an object"); + caosdb_entity_entity_get_description(&entity, out); + EXPECT_EQ(strcmp(out, "The length of an object"), 0); + + caosdb_entity_entity_set_datatype(&entity, "DOUBLE"); + caosdb_entity_entity_get_datatype(&entity, out); + EXPECT_EQ(strcmp(out, "DOUBLE"), 0); + + caosdb_entity_entity_set_unit(&entity, "m"); + caosdb_entity_entity_get_unit(&entity, out); + EXPECT_EQ(strcmp(out, "m"), 0); + + caosdb_entity_entity_set_value(&entity, "5.0"); + caosdb_entity_entity_get_value(&entity, out); + EXPECT_EQ(strcmp(out, "5.0"), 0); + + return_code = caosdb_entity_delete_entity(&entity); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_parent) { + caosdb_entity_parent parent; + + int return_code(caosdb_entity_create_parent(&parent)); + EXPECT_EQ(return_code, 0); + + caosdb_entity_parent_set_id(&parent, "some_id"); + caosdb_entity_parent_set_name(&parent, "some_name"); + + char out[255] = {"a"}; // NOLINT + caosdb_entity_parent_get_id(&parent, out); + EXPECT_EQ(strcmp(out, "some_id"), 0); + + caosdb_entity_parent_get_name(&parent, out); + EXPECT_EQ(strcmp(out, "some_name"), 0); + + return_code = caosdb_entity_delete_parent(&parent); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_property) { + caosdb_entity_property property; + + int return_code(caosdb_entity_create_property(&property)); + EXPECT_EQ(return_code, 0); + + caosdb_entity_property_set_id(&property, "some_id"); + caosdb_entity_property_set_name(&property, "some_name"); + caosdb_entity_property_set_datatype(&property, "some_datatype"); + caosdb_entity_property_set_importance(&property, "some_importance"); + caosdb_entity_property_set_unit(&property, "some_unit"); + caosdb_entity_property_set_value(&property, "some_value"); + + char out[255] = {"a"}; // NOLINT + caosdb_entity_property_get_id(&property, out); + EXPECT_EQ(strcmp(out, "some_id"), 0); + + caosdb_entity_property_get_name(&property, out); + EXPECT_EQ(strcmp(out, "some_name"), 0); + + caosdb_entity_property_get_datatype(&property, out); + EXPECT_EQ(strcmp(out, "some_datatype"), 0); + + caosdb_entity_property_get_importance(&property, out); + EXPECT_EQ(strcmp(out, "some_importance"), 0); + + caosdb_entity_property_get_unit(&property, out); + EXPECT_EQ(strcmp(out, "some_unit"), 0); + + caosdb_entity_property_get_value(&property, out); + EXPECT_EQ(strcmp(out, "some_value"), 0); + + return_code = caosdb_entity_delete_property(&property); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_entity_with_parent_and_property) { + std::cout << "Creating objects ... " << std::endl; + caosdb_entity_parent input_parent; + int return_code(caosdb_entity_create_parent(&input_parent)); + EXPECT_EQ(return_code, 0); + + caosdb_entity_parent_set_id(&input_parent, "parent_id"); + caosdb_entity_parent_set_name(&input_parent, "parent_name"); + + caosdb_entity_property input_property; + return_code = caosdb_entity_create_property(&input_property); + EXPECT_EQ(return_code, 0); + + caosdb_entity_property_set_id(&input_property, "property_id"); + caosdb_entity_property_set_name(&input_property, "property_name"); + caosdb_entity_property_set_datatype(&input_property, "property_datatype"); + caosdb_entity_property_set_value(&input_property, "property_value"); + + caosdb_entity_entity entity; + return_code = caosdb_entity_create_entity(&entity); + EXPECT_EQ(return_code, 0); + + std::cout << "Appending parent and property ..." << std::endl; + return_code = caosdb_entity_entity_append_parent(&entity, &input_parent); + EXPECT_EQ(return_code, 0); + + return_code = caosdb_entity_entity_append_property(&entity, &input_property); + EXPECT_EQ(return_code, 0); + + std::cout << "Counting parents and properties ..." << std::endl; + int count[] = {0}; // NOLINT + return_code = caosdb_entity_entity_get_parents_size(&entity, count); + EXPECT_EQ(return_code, 0); + EXPECT_EQ(*count, 1); + + return_code = caosdb_entity_entity_get_properties_size(&entity, count); + EXPECT_EQ(return_code, 0); + EXPECT_EQ(*count, 1); + + char in[255] = {"a"}; // NOLINT + char out[255] = {"b"}; // NOLINT + + std::cout << "Comparing ..." << std::endl; + // cannot assign an already assigned property + return_code = caosdb_entity_entity_get_property(&entity, &input_property, 0); + EXPECT_EQ(return_code, caosdb::StatusCode::EXTERN_C_ASSIGNMENT_ERROR); + caosdb_entity_property output_property; + return_code = caosdb_entity_entity_get_property(&entity, &output_property, 0); + std::cout << "Got output property." << std::endl; + EXPECT_EQ(return_code, 0); + + caosdb_entity_property_get_id(&input_property, in); + std::cout << "Got input id." << std::endl; + caosdb_entity_property_get_id(&output_property, out); + std::cout << "Got output id." << std::endl; + EXPECT_EQ(strcmp(in, out), 0); + + caosdb_entity_property_get_name(&input_property, in); + caosdb_entity_property_get_name(&output_property, out); + EXPECT_EQ(strcmp(in, out), 0); + + caosdb_entity_property_get_datatype(&input_property, in); + caosdb_entity_property_get_datatype(&output_property, out); + EXPECT_EQ(strcmp(in, out), 0); + + caosdb_entity_property_get_value(&input_property, in); + caosdb_entity_property_get_value(&output_property, out); + EXPECT_EQ(strcmp(in, out), 0); + + std::cout << "Comparing parent..." << std::endl; + caosdb_entity_parent output_parent; + return_code = caosdb_entity_entity_get_parent(&entity, &output_parent, 0); + std::cout << "Got output parent." << std::endl; + EXPECT_EQ(return_code, 0); + + caosdb_entity_parent_get_id(&input_parent, in); + caosdb_entity_parent_get_id(&output_parent, out); + EXPECT_EQ(strcmp(in, out), 0); + + caosdb_entity_parent_get_name(&input_parent, in); + caosdb_entity_parent_get_name(&output_parent, out); + EXPECT_EQ(strcmp(in, out), 0); + + // Delete everything + std::cout << "Deleting ..." << std::endl; + return_code = caosdb_entity_delete_parent(&input_parent); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_delete_property(&input_property); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_delete_entity(&entity); + EXPECT_EQ(return_code, 0); + + // This tests the `_deletable` flag. The wrapped cpp objects of + // `output_parent` and `output_property` are owned by the entity, so + // they have been deleted together with the entity. With a wrong + // `_deletable` flag, the following would cause segfaults. + // + return_code = caosdb_entity_delete_parent(&output_parent); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_delete_property(&output_property); + EXPECT_EQ(return_code, 0); +} + +TEST_F(test_ccaosdb, test_remove_property) { + caosdb_entity_entity entity; + int return_code(caosdb_entity_create_entity(&entity)); + EXPECT_EQ(return_code, 0); + + // Create two properties with names + caosdb_entity_property in_prop_1; + return_code = caosdb_entity_create_property(&in_prop_1); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_property_set_name(&in_prop_1, "Property 1"); + EXPECT_EQ(return_code, 0); + + caosdb_entity_property in_prop_2; + return_code = caosdb_entity_create_property(&in_prop_2); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_property_set_name(&in_prop_2, "Property 2"); + EXPECT_EQ(return_code, 0); + + // Append them + return_code = caosdb_entity_entity_append_property(&entity, &in_prop_1); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_entity_append_property(&entity, &in_prop_2); + EXPECT_EQ(return_code, 0); + + // Delete one and see that the number of properties decreases by one + int count[] = {0}; // NOLINT + return_code = caosdb_entity_entity_get_properties_size(&entity, count); + EXPECT_EQ(return_code, 0); + EXPECT_EQ(*count, 2); + + return_code = caosdb_entity_entity_remove_property(&entity, 0); + EXPECT_EQ(return_code, 0); + + return_code = caosdb_entity_entity_get_properties_size(&entity, count); + EXPECT_EQ(return_code, 0); + EXPECT_EQ(*count, 1); + + caosdb_entity_property out_prop; + return_code = caosdb_entity_entity_get_property(&entity, &out_prop, 0); + EXPECT_EQ(return_code, 0); + + char in[255] = {"a"}; // NOLINT + char out[255] = {"b"}; // NOLINT + + // Deleted the first property, so the second one should remain. + return_code = caosdb_entity_property_get_name(&in_prop_2, in); + EXPECT_EQ(return_code, 0); + return_code = caosdb_entity_property_get_name(&out_prop, out); + EXPECT_EQ(return_code, 0); + + EXPECT_EQ(strcmp(in, out), 0); + + // Delete everything we have created + return_code = caosdb_entity_delete_property(&in_prop_2); + EXPECT_EQ(return_code, 0); + + return_code = caosdb_entity_delete_property(&in_prop_1); + EXPECT_EQ(return_code, 0); + + return_code = caosdb_entity_delete_entity(&entity); + EXPECT_EQ(return_code, 0); +} diff --git a/test/test_entity.cpp b/test/test_entity.cpp index bfa0504e39a95a923d0ca54b2ce73c0faa286763..3d2343bbb17e3fb484ebebb8c5db471ab336e3c2 100644 --- a/test/test_entity.cpp +++ b/test/test_entity.cpp @@ -20,17 +20,21 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ +#include "caosdb_test_utility.h" #include "caosdb/entity.h" // for Entity, Parent, Par... #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe... #include "caosdb/entity/v1alpha1/main.pb.h" // for IdResponse, Message #include "caosdb/message_code.h" // for MessageCode #include "caosdb/protobuf_helper.h" // for get_arena +#include "caosdb/status_code.h" // for StatusCode, FILE_DO... #include "caosdb/transaction.h" // for Transaction -#include "google/protobuf/arena.h" // for Arena -#include "gtest/gtest-message.h" // for Message -#include "gtest/gtest-test-part.h" // for TestPartResult, Sui... -#include "gtest/gtest_pred_impl.h" // for Test, EXPECT_EQ -#include <memory> // for allocator, shared_ptr +#include <google/protobuf/arena.h> // for Arena +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for TestPartResult, Sui... +#include <gtest/gtest_pred_impl.h> // for Test, EXPECT_EQ +#include <iostream> +#include <memory> // for allocator, shared_ptr +#include <string> // for operator+, string namespace caosdb::entity { using caosdb::entity::v1alpha1::IdResponse; @@ -52,11 +56,11 @@ TEST(test_entity, test_append_parent) { parent.SetId("some-id"); auto entity = Entity(); - EXPECT_EQ(entity.GetParents().Size(), 0); + EXPECT_EQ(entity.GetParents().size(), 0); entity.AppendParent(parent); - EXPECT_EQ(entity.GetParents().Size(), 1); + EXPECT_EQ(entity.GetParents().size(), 1); - auto same_parent = entity.GetParents().At(0); + auto same_parent = entity.GetParents().at(0); EXPECT_EQ(same_parent.GetId(), "some-id"); } @@ -88,11 +92,11 @@ TEST(test_entity, test_append_property) { prop.SetUnit("prop_unit"); prop.SetDatatype("prop_dtype"); - EXPECT_EQ(entity.GetProperties().Size(), 0); + EXPECT_EQ(entity.GetProperties().size(), 0); entity.AppendProperty(prop); - EXPECT_EQ(entity.GetProperties().Size(), 1); + EXPECT_EQ(entity.GetProperties().size(), 1); - auto same_prop = entity.GetProperties().At(0); + auto same_prop = entity.GetProperties().at(0); EXPECT_EQ(prop.GetName(), same_prop.GetName()); EXPECT_EQ(prop.GetId(), same_prop.GetId()); @@ -126,15 +130,16 @@ TEST(test_entity, test_copy_to) { EXPECT_EQ(entity.GetRole(), copied.GetRole()); EXPECT_EQ(entity.GetName(), copied.GetName()); - EXPECT_EQ(copied.GetParents().At(0).GetId(), parent.GetId()); - EXPECT_EQ(copied.GetParents().At(0).GetName(), parent.GetName()); - EXPECT_EQ(copied.GetProperties().At(0).GetId(), prop.GetId()); - EXPECT_EQ(copied.GetProperties().At(0).GetName(), prop.GetName()); + EXPECT_EQ(copied.GetParents().at(0).GetId(), parent.GetId()); + EXPECT_EQ(copied.GetParents().at(0).GetName(), parent.GetName()); + EXPECT_EQ(copied.GetProperties().at(0).GetId(), prop.GetId()); + EXPECT_EQ(copied.GetProperties().at(0).GetName(), prop.GetName()); } TEST(test_entity, test_insert_entity) { auto transaction = caosdb::transaction::Transaction( - std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); + std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr), + std::shared_ptr<transaction::FileTransmissionService::Stub>(nullptr)); auto entity = Entity(); entity.SetRole("entity_role"); @@ -151,7 +156,8 @@ TEST(test_entity, test_insert_entity) { TEST(test_entity, test_insert_with_role) { auto transaction = caosdb::transaction::Transaction( - std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); + std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr), + std::shared_ptr<transaction::FileTransmissionService::Stub>(nullptr)); auto entity = Entity(); entity.SetRole("Property"); @@ -171,7 +177,8 @@ TEST(test_entity, test_insert_with_role) { TEST(test_entity, test_insert_with_parent) { auto transaction = caosdb::transaction::Transaction( - std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); + std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr), + std::shared_ptr<transaction::FileTransmissionService::Stub>(nullptr)); auto entity = Entity(); entity.SetName("entity_name"); @@ -187,15 +194,16 @@ TEST(test_entity, test_insert_with_parent) { transaction.InsertEntity(&entity); EXPECT_EQ(entity.GetName(), "entity_name"); - EXPECT_EQ(entity.GetParents().Size(), 1); - auto inserted_parent = entity.GetParents().At(0); + EXPECT_EQ(entity.GetParents().size(), 1); + auto inserted_parent = entity.GetParents().at(0); EXPECT_EQ(inserted_parent.GetId(), parent.GetId()); EXPECT_EQ(inserted_parent.GetName(), parent.GetName()); } TEST(test_entity, test_insert_with_property) { auto transaction = caosdb::transaction::Transaction( - std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr)); + std::shared_ptr<transaction::EntityTransactionService::Stub>(nullptr), + std::shared_ptr<transaction::FileTransmissionService::Stub>(nullptr)); auto entity = Entity(); entity.SetName("entity_name"); @@ -212,9 +220,9 @@ TEST(test_entity, test_insert_with_property) { transaction.InsertEntity(&entity); - EXPECT_EQ(entity.GetProperties().Size(), 1); + EXPECT_EQ(entity.GetProperties().size(), 1); - auto inserted_prop = entity.GetProperties().At(0); + auto inserted_prop = entity.GetProperties().at(0); EXPECT_EQ(prop.GetName(), inserted_prop.GetName()); EXPECT_EQ(prop.GetId(), inserted_prop.GetId()); @@ -227,7 +235,7 @@ TEST(test_entity, test_insert_with_property) { TEST(test_entity, test_from_id_response) { IdResponse idResponse; idResponse.set_id("entity_id"); - auto *error = idResponse.add_entity_errors(); + auto *error = idResponse.add_errors(); error->set_code(MessageCode::ENTITY_DOES_NOT_EXIST); error->set_description("error_desc"); @@ -235,31 +243,154 @@ TEST(test_entity, test_from_id_response) { EXPECT_EQ(entity.GetId(), "entity_id"); EXPECT_TRUE(entity.HasErrors()); - EXPECT_EQ(entity.GetErrors().Size(), 1); - EXPECT_EQ(entity.GetErrors().At(0).GetDescription(), "error_desc"); - EXPECT_EQ(entity.GetErrors().At(0).GetCode(), + EXPECT_EQ(entity.GetErrors().size(), 1); + EXPECT_EQ(entity.GetErrors().at(0).GetDescription(), "error_desc"); + EXPECT_EQ(entity.GetErrors().at(0).GetCode(), MessageCode::ENTITY_DOES_NOT_EXIST); IdResponse idr_warnings_and_infos; idr_warnings_and_infos.set_id("other_entity_id"); - auto *warning = idr_warnings_and_infos.add_entity_warnings(); + auto *warning = idr_warnings_and_infos.add_warnings(); warning->set_description("warning_desc"); warning->set_code(MessageCode::ENTITY_HAS_NO_PROPERTIES); - auto *info = idr_warnings_and_infos.add_entity_infos(); + auto *info = idr_warnings_and_infos.add_infos(); info->set_description("info_desc"); info->set_code(MessageCode::UNSPECIFIED); Entity other_ent(&idr_warnings_and_infos); EXPECT_EQ(other_ent.GetId(), "other_entity_id"); - EXPECT_EQ(other_ent.GetWarnings().Size(), 1); + EXPECT_EQ(other_ent.GetWarnings().size(), 1); EXPECT_TRUE(other_ent.HasWarnings()); - EXPECT_EQ(other_ent.GetWarnings().At(0).GetDescription(), "warning_desc"); - EXPECT_EQ(other_ent.GetWarnings().At(0).GetCode(), + EXPECT_EQ(other_ent.GetWarnings().at(0).GetDescription(), "warning_desc"); + EXPECT_EQ(other_ent.GetWarnings().at(0).GetCode(), MessageCode::ENTITY_HAS_NO_PROPERTIES); - EXPECT_EQ(other_ent.GetInfos().Size(), 1); - EXPECT_EQ(other_ent.GetInfos().At(0).GetDescription(), "info_desc"); - EXPECT_EQ(other_ent.GetInfos().At(0).GetCode(), MessageCode::UNSPECIFIED); + EXPECT_EQ(other_ent.GetInfos().size(), 1); + EXPECT_EQ(other_ent.GetInfos().at(0).GetDescription(), "info_desc"); + EXPECT_EQ(other_ent.GetInfos().at(0).GetCode(), MessageCode::UNSPECIFIED); +} + +TEST(test_entity, test_add_file_to_non_file_entity) { + Entity entity; + EXPECT_EQ(entity.SetLocalPath("local/path"), StatusCode::NOT_A_FILE_ENTITY); +} + +TEST(test_entity, test_add_non_existing_file) { + Entity entity; + entity.SetRole("File"); + EXPECT_EQ(entity.SetLocalPath("non-existing/path"), + StatusCode::FILE_DOES_NOT_EXIST_LOCALLY); +} + +TEST(test_entity, test_add_directory_path) { + Entity entity; + entity.SetRole("File"); + EXPECT_EQ(entity.SetLocalPath("./"), StatusCode::PATH_IS_A_DIRECTORY); +} + +TEST(test_entity, test_remove_property) { + Entity entity; + for (int i = 0; i < 5; i++) { + auto name = "PROPERTY-" + std::to_string(i); + + Property property; + property.SetName(name); + EXPECT_EQ(property.GetName(), name); + + entity.AppendProperty(property); + EXPECT_EQ(property.GetName(), name); + + // not initializing the cache + } + ASSERT_EQ(entity.GetProperties().size(), 5); + for (int i = 5; i < 10; i++) { + auto name = "PROPERTY-" + std::to_string(i); + + Property property; + property.SetName(name); + EXPECT_EQ(property.GetName(), name); + + entity.AppendProperty(property); + EXPECT_EQ(property.GetName(), name); + + // initializing the cache + const auto &property_2 = entity.GetProperties().at(i); + EXPECT_EQ(property_2.GetName(), name); + } + + ASSERT_EQ(entity.GetProperties().size(), 10); + + for (int i = 5; i < 10; i++) { + // double checking the cache + auto name = "PROPERTY-" + std::to_string(i); + const auto &property = entity.GetProperties().at(i); + EXPECT_EQ(property.GetName(), name); + } + + // Remove at index 3 + // P0,P1,P2,P3,P4,P5,P6,P7,P8,P9 + // ^ + // P0,P1,P2, P4,P5,P6,P7,P8,P9 + entity.RemoveProperty(3); + + // Remove at index 6 + // P0,P1,P2, P4,P5,P6,P7,P8,P9 + // ^ + // P0,P1,P2, P4,P5,P6, P8,P9 + entity.RemoveProperty(6); + ASSERT_EQ(entity.GetProperties().size(), 8); + + // AppendProperty another property + // P0,P1,P2, P4,P5,P6, P8,P9 + // ^ + // P0,P1,P2, P4,P5,P6, P8,P9,P10 + Property property10; + property10.SetName("PROPERTY-10"); + entity.AppendProperty(property10); + ASSERT_EQ(entity.GetProperties().size(), 9); + + std::cout << "[" << std::endl; + for (int i = 0; i < 9; i++) { + std::cout << " " << entity.GetProperties().at(i).GetName() << ",\n"; + } + std::cout << "]" << std::endl; + + for (int i = 0; i < 3; i++) { + auto name = "PROPERTY-" + std::to_string(i); + const auto &property = entity.GetProperties().at(i); + EXPECT_EQ(property.GetName(), name); + } + for (int i = 3; i < 6; i++) { + auto name = "PROPERTY-" + std::to_string(i + 1); + const auto &property = entity.GetProperties().at(i); + EXPECT_EQ(property.GetName(), name); + } + for (int i = 6; i < 9; i++) { + auto name = "PROPERTY-" + std::to_string(i + 2); + const auto &property = entity.GetProperties().at(i); + EXPECT_EQ(property.GetName(), name); + } +} + +TEST(test_entity, test_property_iterator) { + Entity entity; + for (int i = 0; i < 5; i++) { + auto name = "PROPERTY-" + std::to_string(i); + + Property property; + property.SetName(name); + EXPECT_EQ(property.GetName(), name); + + entity.AppendProperty(property); + } + ASSERT_EQ(entity.GetProperties().size(), 5); + int counter = 0; + for (auto &property : entity.GetProperties()) { + auto name = "PROPERTY-" + std::to_string(counter); + EXPECT_EQ(property.GetName(), name); + counter++; + } + EXPECT_EQ(counter, 5); } TEST(test_entity, test_description) { @@ -282,4 +413,11 @@ TEST(test_entity, test_description) { EXPECT_EQ(property.GetDescription(), "desc property"); EXPECT_EQ(parent.GetDescription(), "desc parent"); } + +TEST(test_entity, test_add_file) { + Entity entity; + entity.SetRole("File"); + EXPECT_EQ(entity.SetLocalPath(TEST_DATA_DIR + "/test.json"), + StatusCode::SUCCESS); +} } // namespace caosdb::entity diff --git a/test/test_file_transmission.cpp b/test/test_file_transmission.cpp new file mode 100644 index 0000000000000000000000000000000000000000..801b89db3a07f5b96e530cfbdfc8335c892a1331 --- /dev/null +++ b/test/test_file_transmission.cpp @@ -0,0 +1,63 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#include "caosdb/file_transmission/file_writer.h" +#include "caosdb/file_transmission/file_reader.h" +#include <boost/filesystem/operations.hpp> // for exists, file_size, remove +#include <boost/filesystem/path.hpp> // for path +#include <boost/filesystem/path_traits.hpp> // for filesystem +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver +#include <gtest/gtest_pred_impl.h> // for Test, EXPECT_EQ, AssertionResult +#include <string> // for string + +namespace fs = boost::filesystem; + +namespace caosdb::transaction { + +class test_file_transmission : public ::testing::Test { +protected: + fs::path test_file_name; + + void SetUp() override { + test_file_name = fs::path("this_is_a_test_file_remove_me.dat"); + } + + void TearDown() override { fs::remove(test_file_name); } +}; + +TEST_F(test_file_transmission, test_file_writer_reader) { + ASSERT_FALSE(fs::exists(test_file_name)); + + FileWriter writer(test_file_name); + std::string buffer_out(1024, 'c'); + for (int i = 0; i < 8; i++) { + writer.write(buffer_out); + EXPECT_EQ(fs::file_size(test_file_name), 1024 * (i + 1)); + } + + FileReader reader(test_file_name); + std::string buffer_in(1024, '\0'); + for (int i = 0; i < 8; i++) { + reader.read(buffer_in); + EXPECT_EQ(buffer_in, std::string(1024, 'c')); + } +} + +} // namespace caosdb::transaction diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index b062cb8a6372ac5db749cc9b6acd998f8829a14a..70ba13a78b10db3af421411ae7f9ba631a49ff03 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -23,22 +23,24 @@ #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity #include "caosdb/exceptions.h" // for ConnectionError #include "caosdb/status_code.h" -#include "caosdb/transaction.h" // for Transaction, UniqueResult -#include "caosdb/transaction_status.h" // for ConnectionError -#include "caosdb_test_utility.h" // for EXPECT_THROW_MESSAGE -#include "gtest/gtest-message.h" // for Message -#include "gtest/gtest-test-part.h" // for SuiteApiResolver, TestPa... -#include "gtest/gtest_pred_impl.h" // for Test, TestInfo, TEST -#include <memory> // for allocator, unique_ptr -#include <string> // for string, basic_string -#include <utility> // for move -#include <vector> // for vector +#include "caosdb/transaction.h" // for Transaction +#include "caosdb/transaction_handler.h" // for MultiTransactionResponse +#include "caosdb/transaction_status.h" // for ConnectionError +#include "caosdb_test_utility.h" // for EXPECT_THROW_MESSAGE +#include <gtest/gtest-message.h> // for Message +#include <gtest/gtest-test-part.h> // for SuiteApiResolver, TestPa... +#include <gtest/gtest_pred_impl.h> // for Test, TestInfo, TEST +#include <memory> // for allocator, unique_ptr +#include <stdexcept> // for out_of_range +#include <string> // for string, basic_string +#include <utility> // for move +#include <vector> // for vector namespace caosdb::transaction { using caosdb::configuration::InsecureConnectionConfiguration; using caosdb::connection::Connection; +using caosdb::entity::Entity; using caosdb::exceptions::ConnectionError; -using caosdb::transaction::UniqueResult; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using caosdb::entity::v1alpha1::RetrieveResponse; @@ -52,19 +54,29 @@ TEST(test_transaction, create_transaction) { EXPECT_THROW_MESSAGE( transaction->Execute(), ConnectionError, "The attempt to execute this transaction was not successful because the " - "connection to the server could not be established."); + "connection to the server could not be established. " + "Original message: failed to connect to all addresses"); } -TEST(test_transaction, unique_result) { - auto *entity = new ProtoEntity(); - entity->set_id("test"); - UniqueResult result(entity); +TEST(test_transaction, test_multi_result_set) { + std::vector<std::unique_ptr<Entity>> entities; + for (int i = 0; i < 5; i++) { + entities.push_back(std::make_unique<Entity>()); + entities[i]->SetName("E" + std::to_string(i)); + } + MultiResultSet result_set(std::move(entities)); - EXPECT_EQ("test", result.GetEntity().GetId()); + EXPECT_EQ(result_set.size(), 5); + EXPECT_EQ(result_set.mutable_at(3)->GetName(), "E3"); + EXPECT_EQ(result_set.at(4).GetName(), "E4"); + EXPECT_EQ(result_set.at(4).GetName(), "E4"); + EXPECT_THROW(auto &e = result_set.at(15), std::out_of_range); - // DON'T DELETE! The caosdb::entity::Entity takes care of that - // Try it yourself: - // delete entity; + int counter = 0; + for (const auto &entity : result_set) { + EXPECT_EQ(entity.GetName(), "E" + std::to_string(counter++)); + } + EXPECT_EQ(counter, 5); } TEST(test_transaction, test_unavailable) { @@ -96,29 +108,19 @@ TEST(test_transaction, test_retrieve_by_ids) { TEST(test_transaction, test_multi_result_set_empty) { std::vector<std::unique_ptr<Entity>> empty; MultiResultSet rs(std::move(empty)); - EXPECT_EQ(rs.Size(), 0); + EXPECT_EQ(rs.size(), 0); } TEST(test_transaction, test_multi_result_iterator) { std::vector<std::unique_ptr<Entity>> one_elem; RetrieveResponse response; - response.mutable_entity()->set_id("100"); - one_elem.push_back(std::make_unique<Entity>(response.release_entity())); + response.mutable_entity_response()->mutable_entity()->set_id("100"); + one_elem.push_back( + std::make_unique<Entity>(response.release_entity_response())); MultiResultSet rs(std::move(one_elem)); - EXPECT_EQ(rs.Size(), 1); - - for (const Entity &entity : rs) { - EXPECT_EQ(entity.GetId(), "100"); - } -} - -TEST(test_transaction, test_unique_result_iterator) { - caosdb::entity::v1alpha1::Entity response; - response.set_id("100"); + EXPECT_EQ(rs.size(), 1); - UniqueResult rs(&response); - EXPECT_EQ(rs.Size(), 1); for (const Entity &entity : rs) { EXPECT_EQ(entity.GetId(), "100"); } @@ -127,12 +129,13 @@ TEST(test_transaction, test_unique_result_iterator) { TEST(test_transaction, test_multi_result_set_one) { std::vector<std::unique_ptr<Entity>> one_elem; RetrieveResponse response; - response.mutable_entity()->set_id("100"); - one_elem.push_back(std::make_unique<Entity>(response.release_entity())); + response.mutable_entity_response()->mutable_entity()->set_id("100"); + one_elem.push_back( + std::make_unique<Entity>(response.release_entity_response())); MultiResultSet rs(std::move(one_elem)); - EXPECT_EQ(rs.Size(), 1); - EXPECT_EQ(rs.At(0).GetId(), "100"); + EXPECT_EQ(rs.size(), 1); + EXPECT_EQ(rs.at(0).GetId(), "100"); } TEST(test_transaction, test_multi_result_set_three) { @@ -141,14 +144,17 @@ TEST(test_transaction, test_multi_result_set_three) { MultiTransactionResponse response; response.add_responses() ->mutable_retrieve_response() + ->mutable_entity_response() ->mutable_entity() ->set_id("100"); - auto *entity_with_error = - response.add_responses()->mutable_retrieve_response()->mutable_entity(); - entity_with_error->set_id("101"); + auto *entity_with_error = response.add_responses() + ->mutable_retrieve_response() + ->mutable_entity_response(); + entity_with_error->mutable_entity()->set_id("101"); entity_with_error->add_errors()->set_code(1); response.add_responses() ->mutable_retrieve_response() + ->mutable_entity_response() ->mutable_entity() ->set_id("102"); @@ -156,12 +162,12 @@ TEST(test_transaction, test_multi_result_set_three) { std::vector<std::unique_ptr<Entity>> entities; for (auto sub_response : *responses) { three_elem.push_back(std::make_unique<Entity>( - sub_response.mutable_retrieve_response()->release_entity())); + sub_response.mutable_retrieve_response()->release_entity_response())); } MultiResultSet rs(std::move(three_elem)); - EXPECT_EQ(rs.Size(), 3); - EXPECT_TRUE(rs.At(1).HasErrors()); + EXPECT_EQ(rs.size(), 3); + EXPECT_TRUE(rs.at(1).HasErrors()); } TEST(test_transaction, test_update_entity) { @@ -189,4 +195,36 @@ TEST(test_transaction, test_multi_deletion) { } } +TEST(test_transaction, test_retrieve_and_download) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::INITIAL); + transaction->RetrieveAndDownloadFilesById("asdf", "local_path"); + + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::GO_ON); + + EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::EXECUTING); + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::CONNECTION_ERROR); +} + +TEST(test_transaction, test_insert_with_file) { + const auto *host = "localhost"; + auto configuration = InsecureConnectionConfiguration(host, 8000); + Connection connection(configuration); + auto transaction = connection.CreateTransaction(); + Entity entity; + entity.SetRole("File"); + entity.SetLocalPath(TEST_DATA_DIR + "/test.json"); + + EXPECT_TRUE(transaction->GetUploadFiles().empty()); + transaction->InsertEntity(&entity); + EXPECT_EQ(transaction->GetUploadFiles().size(), 1); + + transaction->ExecuteAsynchronously(); + EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::FILE_UPLOAD_ERROR); +} + } // namespace caosdb::transaction