diff --git a/src/ystdlib/CMakeLists.txt b/src/ystdlib/CMakeLists.txt index 9a52a2e0..b2651234 100644 --- a/src/ystdlib/CMakeLists.txt +++ b/src/ystdlib/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(containers) add_subdirectory(error_handling) +add_subdirectory(io_interface) diff --git a/src/ystdlib/io_interface/CMakeLists.txt b/src/ystdlib/io_interface/CMakeLists.txt new file mode 100644 index 00000000..d979f169 --- /dev/null +++ b/src/ystdlib/io_interface/CMakeLists.txt @@ -0,0 +1,14 @@ +cpp_library( + NAME io_interface + NAMESPACE ystdlib + PUBLIC_HEADERS + ErrorCode.hpp + ReaderInterface.hpp + WriterInterface.hpp + PRIVATE_SOURCES + ReaderInterface.cpp + WriterInterface.cpp + TESTS_SOURCES + test/test_ReaderInterface.cpp + test/test_WriterInterface.cpp +) diff --git a/src/ystdlib/io_interface/ErrorCode.hpp b/src/ystdlib/io_interface/ErrorCode.hpp new file mode 100644 index 00000000..7dfb2ea0 --- /dev/null +++ b/src/ystdlib/io_interface/ErrorCode.hpp @@ -0,0 +1,33 @@ +#ifndef YSTDLIB_IO_INTERFACE_ERRORCODE_HPP +#define YSTDLIB_IO_INTERFACE_ERRORCODE_HPP + +// NOLINTBEGIN + +namespace ystdlib::io_interface { +typedef enum { + ErrorCode_Success = 0, + ErrorCode_BadParam, + ErrorCode_BadParam_DB_URI, + ErrorCode_Corrupt, + ErrorCode_errno, + ErrorCode_EndOfFile, + ErrorCode_FileExists, + ErrorCode_FileNotFound, + ErrorCode_NoMem, + ErrorCode_NotInit, + ErrorCode_NotReady, + ErrorCode_OutOfBounds, + ErrorCode_TooLong, + ErrorCode_Truncated, + ErrorCode_Unsupported, + ErrorCode_NoAccess, + ErrorCode_Failure, + ErrorCode_Failure_Metadata_Corrupted, + ErrorCode_MetadataCorrupted, + ErrorCode_Failure_DB_Bulk_Write, + ErrorCode_Failure_Network, +} ErrorCode; +} // namespace ystdlib::io_interface + +// NOLINTEND +#endif // YSTDLIB_IO_INTERFACE_ERRORCODE_HPP diff --git a/src/ystdlib/io_interface/ReaderInterface.cpp b/src/ystdlib/io_interface/ReaderInterface.cpp new file mode 100644 index 00000000..381dc98c --- /dev/null +++ b/src/ystdlib/io_interface/ReaderInterface.cpp @@ -0,0 +1,129 @@ +// NOLINTBEGIN +#include "ReaderInterface.hpp" + +using std::string; + +namespace ystdlib::io_interface { +ErrorCode ReaderInterface::try_read_to_delimiter( + char delim, + bool keep_delimiter, + bool append, + std::string& str +) { + if (false == append) { + str.clear(); + } + + size_t original_str_length = str.length(); + + // Read character by character into str, until we find a delimiter + char c; + size_t num_bytes_read; + while (true) { + auto error_code = try_read(&c, 1, num_bytes_read); + if (ErrorCode_Success != error_code) { + if (ErrorCode_EndOfFile == error_code && str.length() > original_str_length) { + return ErrorCode_Success; + } + return error_code; + } + + if (delim == c) { + break; + } + + str += c; + } + + // Add delimiter if necessary + if (keep_delimiter) { + str += delim; + } + + return ErrorCode_Success; +} + +bool ReaderInterface::read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) { + ErrorCode error_code = try_read(buf, num_bytes_to_read, num_bytes_read); + if (ErrorCode_EndOfFile == error_code) { + return false; + } + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + return true; +} + +bool ReaderInterface::read_to_delimiter(char delim, bool keep_delimiter, bool append, string& str) { + ErrorCode error_code = try_read_to_delimiter(delim, keep_delimiter, append, str); + if (ErrorCode_EndOfFile == error_code) { + return false; + } + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + + return true; +} + +ErrorCode ReaderInterface::try_read_exact_length(char* buf, size_t num_bytes) { + size_t num_bytes_read; + auto error_code = try_read(buf, num_bytes, num_bytes_read); + if (ErrorCode_Success != error_code) { + return error_code; + } + if (num_bytes_read < num_bytes) { + return ErrorCode_Truncated; + } + + return ErrorCode_Success; +} + +bool ReaderInterface::read_exact_length(char* buf, size_t num_bytes, bool eof_possible) { + ErrorCode error_code = try_read_exact_length(buf, num_bytes); + if (eof_possible && ErrorCode_EndOfFile == error_code) { + return false; + } + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + return true; +} + +ErrorCode ReaderInterface::try_read_string(size_t const str_length, string& str) { + // Resize string to fit str_length + str.resize(str_length); + + return try_read_exact_length(&str[0], str_length); +} + +bool ReaderInterface::read_string(size_t const str_length, string& str, bool eof_possible) { + ErrorCode error_code = try_read_string(str_length, str); + if (eof_possible && ErrorCode_EndOfFile == error_code) { + return false; + } + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + return true; +} + +void ReaderInterface::seek_from_begin(size_t pos) { + ErrorCode error_code = try_seek_from_begin(pos); + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } +} + +size_t ReaderInterface::get_pos() { + size_t pos; + ErrorCode error_code = try_get_pos(pos); + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + + return pos; +} +} // namespace ystdlib::io_interface + +// NOLINTEND diff --git a/src/ystdlib/io_interface/ReaderInterface.hpp b/src/ystdlib/io_interface/ReaderInterface.hpp new file mode 100644 index 00000000..dde1a503 --- /dev/null +++ b/src/ystdlib/io_interface/ReaderInterface.hpp @@ -0,0 +1,149 @@ +#ifndef YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP +#define YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP +// NOLINTBEGIN + +#include +#include + +#include "ErrorCode.hpp" + +namespace ystdlib::io_interface { +class ReaderInterface { +public: + // Types + class OperationFailed : public std::exception { + public: + OperationFailed(ErrorCode error_code) {} + }; + + // Destructor + virtual ~ReaderInterface() = default; + + // Methods + virtual ErrorCode try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) = 0; + virtual ErrorCode try_seek_from_begin(size_t pos) = 0; + virtual ErrorCode try_get_pos(size_t& pos) = 0; + + /** + * Tries to read up to the next delimiter and stores it in the given string. + * NOTE: Implementations should override this if they can achieve better performance. + * @param delim The delimiter to stop at + * @param keep_delimiter Whether to include the delimiter in the output string or not + * @param append Whether to append to the given string or replace its contents + * @param str The string read + * @return ErrorCode_Success on success + * @return Same as ReaderInterface::try_read otherwise + */ + virtual ErrorCode + try_read_to_delimiter(char delim, bool keep_delimiter, bool append, std::string& str); + + /** + * Reads up to a given number of bytes + * @param buf + * @param num_bytes_to_read The number of bytes to try and read + * @param num_bytes_read The actual number of bytes read + * @return false on EOF + * @return true otherwise + */ + bool read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read); + + /** + * Reads up to the next delimiter and stores it in the given string + * @param delim The delimiter to stop at + * @param keep_delimiter Whether to include the delimiter in the output string or not + * @param append Whether to append to the given string or replace its contents + * @param str The string read + * @return false on EOF + * @return true on success + */ + bool read_to_delimiter(char delim, bool keep_delimiter, bool append, std::string& str); + + /** + * Tries to read a number of bytes + * @param buf + * @param num_bytes Number of bytes to read + * @return Same as the underlying medium's try_read method + * @return ErrorCode_Truncated if 0 < # bytes read < num_bytes + */ + ErrorCode try_read_exact_length(char* buf, size_t num_bytes); + /** + * Reads a number of bytes + * @param buf + * @param num_bytes Number of bytes to read + * @param eof_possible If EOF should be possible (without reading any bytes) + * @return false if EOF is possible and EOF was hit + * @return true on success + */ + bool read_exact_length(char* buf, size_t num_bytes, bool eof_possible); + + /** + * Tries to read a numeric value from a file + * @param value The read value + * @return Same as FileReader::try_read_exact_length's return values + */ + template + ErrorCode try_read_numeric_value(ValueType& value); + /** + * Reads a numeric value + * @param value The read value + * @param eof_possible If EOF should be possible (without reading any bytes) + * @return false if EOF is possible and EOF was hit + * @return true on success + */ + template + bool read_numeric_value(ValueType& value, bool eof_possible); + + /** + * Tries to read a string + * @param str_length + * @param str The string read + * @return Same as ReaderInterface::try_read_exact_length + */ + ErrorCode try_read_string(size_t str_length, std::string& str); + /** + * Reads a string + * @param str_length + * @param str The string read + * @param eof_possible If EOF should be possible (without reading any bytes) + * @return false if EOF is possible and EOF was hit + * @return true on success + */ + bool read_string(size_t str_length, std::string& str, bool eof_possible); + + /** + * Seeks from the beginning to the given position + * @param pos + */ + void seek_from_begin(size_t pos); + + /** + * Gets the current position of the read head + * @return Position of the read head + */ + size_t get_pos(); +}; + +template +ErrorCode ReaderInterface::try_read_numeric_value(ValueType& value) { + ErrorCode error_code = try_read_exact_length(reinterpret_cast(&value), sizeof(value)); + if (ErrorCode_Success != error_code) { + return error_code; + } + return ErrorCode_Success; +} + +template +bool ReaderInterface::read_numeric_value(ValueType& value, bool eof_possible) { + ErrorCode error_code = try_read_numeric_value(value); + if (ErrorCode_EndOfFile == error_code && eof_possible) { + return false; + } + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + return true; +} +} // namespace ystdlib::io_interface + +// NOLINTEND +#endif // YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP diff --git a/src/ystdlib/io_interface/WriterInterface.cpp b/src/ystdlib/io_interface/WriterInterface.cpp new file mode 100644 index 00000000..a11061b8 --- /dev/null +++ b/src/ystdlib/io_interface/WriterInterface.cpp @@ -0,0 +1,38 @@ +// NOLINTBEGIN +#include "WriterInterface.hpp" + +namespace ystdlib::io_interface { +void WriterInterface::write_char(char c) { + write(&c, 1); +} + +void WriterInterface::write_string(std::string const& str) { + write(str.c_str(), str.length()); +} + +void WriterInterface::seek_from_begin(size_t pos) { + auto error_code = try_seek_from_begin(pos); + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } +} + +void WriterInterface::seek_from_current(off_t offset) { + auto error_code = try_seek_from_current(offset); + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } +} + +size_t WriterInterface::get_pos() const { + size_t pos; + ErrorCode error_code = try_get_pos(pos); + if (ErrorCode_Success != error_code) { + throw OperationFailed(error_code); + } + + return pos; +} +} // namespace ystdlib::io_interface + +// NOLINTEND diff --git a/src/ystdlib/io_interface/WriterInterface.hpp b/src/ystdlib/io_interface/WriterInterface.hpp new file mode 100644 index 00000000..be55ff73 --- /dev/null +++ b/src/ystdlib/io_interface/WriterInterface.hpp @@ -0,0 +1,78 @@ +#ifndef YSTDLIB_IO_INTERFACE_WRITERINTERFACE_HPP +#define YSTDLIB_IO_INTERFACE_WRITERINTERFACE_HPP +// NOLINTBEGIN + +#include +#include + +#include "ErrorCode.hpp" + +namespace ystdlib::io_interface { +class WriterInterface { +public: + // Types + class OperationFailed : public std::exception { + public: + OperationFailed(ErrorCode error_code) {} + }; + + // Destructor + virtual ~WriterInterface() = default; + + // Methods + /** + * Writes the given data to the underlying medium + * @param data + * @param data_length + */ + virtual void write(char const* data, size_t data_length) = 0; + virtual void flush() = 0; + virtual ErrorCode try_seek_from_begin(size_t pos) = 0; + virtual ErrorCode try_seek_from_current(off_t offset) = 0; + virtual ErrorCode try_get_pos(size_t& pos) const = 0; + + /** + * Writes a numeric value + * @param val Value to write + */ + template + void write_numeric_value(ValueType value); + + /** + * Writes a character to the underlying medium + * @param c + */ + void write_char(char c); + /** + * Writes a string to the underlying medium + * @param str + */ + void write_string(std::string const& str); + + /** + * Seeks from the beginning to the given position + * @param pos + */ + void seek_from_begin(size_t pos); + + /** + * Offsets from the current position by the given amount + * @param offset + */ + void seek_from_current(off_t offset); + + /** + * Gets the current position of the write head + * @return Position of the write head + */ + size_t get_pos() const; +}; + +template +void WriterInterface::write_numeric_value(ValueType val) { + write(reinterpret_cast(&val), sizeof(val)); +} +} // namespace ystdlib::io_interface + +// NOLINTEND +#endif // YSTDLIB_IO_INTERFACE_WRITERINTERFACE_HPP diff --git a/src/ystdlib/io_interface/test/test_ReaderInterface.cpp b/src/ystdlib/io_interface/test/test_ReaderInterface.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/ystdlib/io_interface/test/test_WriterInterface.cpp b/src/ystdlib/io_interface/test/test_WriterInterface.cpp new file mode 100644 index 00000000..e69de29b