#include "./stream.hpp" #include #include #include namespace mijin { // // internal defines // // // internal constants // // // internal types // // // internal variables // // // internal functions // // // public functions // void Stream::flush() {} StreamError Stream::readString(std::string& outString) { std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables) StreamError error = read(length); if (error != StreamError::SUCCESS) { return error; } std::string result; result.resize(length); error = readSpan(result.begin(), result.end()); if (error != StreamError::SUCCESS) { return error; } outString = std::move(result); return StreamError::SUCCESS; } StreamError Stream::writeString(std::string_view str) { assert(str.length() <= std::numeric_limits::max()); const std::uint32_t length = static_cast(str.length()); StreamError error = write(length); if (error != StreamError::SUCCESS) { return error; } return writeSpan(str.begin(), str.end()); } StreamError Stream::getTotalLength(std::size_t& outLength) { const StreamFeatures features = getFeatures(); if (!features.tell || !features.seek) { return StreamError::NOT_SUPPORTED; } const std::size_t origPos = tell(); if (StreamError error = seek(0, SeekMode::RELATIVE_TO_END); error != StreamError::SUCCESS) { return error; } outLength = tell(); if (StreamError error = seek(static_cast(origPos)); error != StreamError::SUCCESS) { return error; } return StreamError::SUCCESS; } StreamError Stream::readRest(TypelessBuffer& outBuffer) { // first try to allocate everything at once std::size_t length = 0; if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) { MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); length -= tell(); outBuffer.resize(length); if (StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS) { return error; } return StreamError::SUCCESS; } // could not determine the size, read chunk-wise static constexpr std::size_t CHUNK_SIZE = 4096; std::array chunk = {}; while (!isAtEnd()) { std::size_t bytesRead = 0; if (StreamError error = readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS) { return error; } outBuffer.resize(outBuffer.byteSize() + bytesRead); std::span bufferBytes = outBuffer.makeSpan(); std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); } return StreamError::SUCCESS; } FileStream::~FileStream() { if (handle) { close(); } } StreamError FileStream::open(const char* path, FileOpenMode mode_) { mode = mode_; const char* modeStr; // NOLINT(cppcoreguidelines-init-variables) switch (mode_) { case FileOpenMode::READ: modeStr = "rb"; break; case FileOpenMode::WRITE: modeStr = "wb"; break; case FileOpenMode::APPEND: modeStr = "ab"; break; case FileOpenMode::READ_WRITE: modeStr = "r+b"; break; } handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory) if (!handle && mode_ == FileOpenMode::READ_WRITE) { handle = std::fopen(path, "w+b"); // NOLINT(cppcoreguidelines-owning-memory) } if (!handle) { return StreamError::IO_ERROR; } int result = std::fseek(handle, 0, SEEK_END); assert(result == 0); length = std::ftell(handle); result = std::fseek(handle, 0, SEEK_SET); assert(result == 0); return StreamError::SUCCESS; } void FileStream::close() { assert(handle); const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory) assert(result == 0); } StreamError FileStream::readRaw(std::span buffer, bool partial, std::size_t* outBytesRead) { assert(handle); assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE); const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle); if (std::ferror(handle)) { return StreamError::IO_ERROR; } if (!partial && readBytes < buffer.size()) { return StreamError::IO_ERROR; } if (outBytesRead != nullptr) { *outBytesRead = readBytes; } return StreamError::SUCCESS; } StreamError FileStream::writeRaw(std::span buffer) { assert(handle); assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE); const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle); if (written != buffer.size() || std::ferror(handle)) { return StreamError::IO_ERROR; } length = std::max(length, std::ftell(handle)); return StreamError::SUCCESS; } std::size_t FileStream::tell() { assert(handle); return std::ftell(handle); } StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode) { assert(handle); int origin; // NOLINT(cppcoreguidelines-init-variables) switch (seekMode) { case SeekMode::ABSOLUTE: origin = SEEK_SET; break; case SeekMode::RELATIVE: origin = SEEK_CUR; break; case SeekMode::RELATIVE_TO_END: origin = SEEK_END; break; default: assert(!"Invalid value passed as seekMode!"); return StreamError::UNKNOWN_ERROR; } const int result = std::fseek(handle, static_cast(pos), origin); if (result != 0 || std::ferror(handle)) { return StreamError::IO_ERROR; } return StreamError::SUCCESS; } void FileStream::flush() { const int result = std::fflush(handle); assert(result == 0); } bool FileStream::isAtEnd() { assert(handle); (void) std::fgetc(handle); if (std::feof(handle)) { return true; } const int result = std::fseek(handle, -1, SEEK_CUR); assert(result == 0); return false; } StreamFeatures FileStream::getFeatures() { if (handle) { return { .read = (mode == FileOpenMode::READ), .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE), .tell = true, .seek = true }; } return {}; } void MemoryStream::openRW(std::span data) { assert(!isOpen()); data_ = data; pos_ = 0; canWrite_ = true; } void MemoryStream::openRO(std::span data) { assert(!isOpen()); data_ = std::span(const_cast(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-const-cast) we'll be fine pos_ = 0; canWrite_ = false; } void MemoryStream::close() { assert(isOpen()); data_ = {}; } StreamError MemoryStream::readRaw(std::span buffer, bool partial, std::size_t* outBytesRead) { assert(isOpen()); if (!partial && availableBytes() < buffer.size()) { return StreamError::IO_ERROR; // TODO: need more errors? } const std::size_t numBytes = std::min(buffer.size(), availableBytes()); std::copy_n(data_.begin() + static_cast(pos_), numBytes, buffer.begin()); if (outBytesRead) { *outBytesRead = numBytes; } pos_ += numBytes; return StreamError::SUCCESS; } StreamError MemoryStream::writeRaw(std::span buffer) { assert(isOpen()); assert(canWrite_); if (availableBytes() < buffer.size()) { return StreamError::IO_ERROR; } std::copy(buffer.begin(), buffer.end(), data_.begin() + static_cast(pos_)); pos_ += buffer.size(); return StreamError::SUCCESS; } std::size_t MemoryStream::tell() { assert(isOpen()); return pos_; } StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode) { assert(isOpen()); std::intptr_t newPos = -1; switch (seekMode) { case SeekMode::ABSOLUTE: newPos = pos; break; case SeekMode::RELATIVE: newPos = static_cast(pos_) + pos; break; case SeekMode::RELATIVE_TO_END: newPos = static_cast(data_.size()) + pos; break; } if (newPos < 0 || newPos > static_cast(data_.size())) { return StreamError::IO_ERROR; } pos_ = newPos; return StreamError::SUCCESS; } bool MemoryStream::isAtEnd() { assert(isOpen()); return pos_ == data_.size(); } StreamFeatures MemoryStream::getFeatures() { return { .read = true, .write = canWrite_, .tell = true, .seek = true }; } } // namespace mijin