diff options
author | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
commit | dfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch) | |
tree | 1c751a14ba0da07ca2ff805633f97568060aa4c9 /src/base/io | |
parent | f51eb955e188858272230a990565931e7403f23b (diff) | |
download | cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2 cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip |
Rename common to base.
Diffstat (limited to 'src/base/io')
-rw-r--r-- | src/base/io/AutoReadStream.cpp | 56 | ||||
-rw-r--r-- | src/base/io/BufferStream.cpp | 109 | ||||
-rw-r--r-- | src/base/io/CFileStream.cpp | 96 | ||||
-rw-r--r-- | src/base/io/MemoryStream.cpp | 74 | ||||
-rw-r--r-- | src/base/io/OpenFileFlag.cpp | 19 | ||||
-rw-r--r-- | src/base/io/ProxyStream.cpp | 37 | ||||
-rw-r--r-- | src/base/io/Resource.cpp | 48 | ||||
-rw-r--r-- | src/base/io/Stream.cpp | 199 |
8 files changed, 638 insertions, 0 deletions
diff --git a/src/base/io/AutoReadStream.cpp b/src/base/io/AutoReadStream.cpp new file mode 100644 index 00000000..c24f61d1 --- /dev/null +++ b/src/base/io/AutoReadStream.cpp @@ -0,0 +1,56 @@ +#include "cru/base/io/AutoReadStream.h" +#include "cru/base/io/Stream.h" + +#include <thread> + +namespace cru::io { + +AutoReadStream::AutoReadStream(Stream* stream, bool auto_delete, + const AutoReadStreamOptions& options) + : Stream(false, true, stream->CanSeek()) { + auto buffer_stream_options = options.GetBufferStreamOptions(); + stream_ = stream; + size_per_read_ = buffer_stream_options.GetBlockSizeOrDefault(); + buffer_stream_ = std::make_unique<BufferStream>(buffer_stream_options); + background_thread_ = std::thread(&AutoReadStream::BackgroundThreadRun, this); +} + +AutoReadStream::~AutoReadStream() { + if (auto_delete_) { + delete stream_; + } +} + +Index AutoReadStream::DoRead(std::byte* buffer, Index offset, Index size) { + std::unique_lock lock(buffer_stream_mutex_); + return buffer_stream_->Read(buffer, offset, size); +} + +Index AutoReadStream::DoWrite(const std::byte* buffer, Index offset, + Index size) { + return stream_->Write(buffer, offset, size); +} + +void AutoReadStream::DoFlush() { stream_->Flush(); } + +void AutoReadStream::DoClose() {} + +void AutoReadStream::BackgroundThreadRun() { + std::vector<std::byte> buffer(size_per_read_); + while (true) { + try { + auto read = stream_->Read(buffer.data(), buffer.size()); + if (read == 0) { + buffer_stream_->SetEof(); + break; + } else { + buffer_stream_->Write(buffer.data(), read); + } + } catch (const StreamAlreadyClosedException& exception) { + buffer_stream_->SetEof(); + break; + } + } +} + +} // namespace cru::io diff --git a/src/base/io/BufferStream.cpp b/src/base/io/BufferStream.cpp new file mode 100644 index 00000000..e81731e8 --- /dev/null +++ b/src/base/io/BufferStream.cpp @@ -0,0 +1,109 @@ +#include "cru/base/io/BufferStream.h" +#include "cru/base/io/Stream.h" + +namespace cru::io { +BufferStream::BufferStream(const BufferStreamOptions& options) + : Stream(false, true, true) { + block_size_ = options.GetBlockSizeOrDefault(); + max_block_count_ = options.GetMaxBlockCount(); + + eof_ = false; +} + +BufferStream::~BufferStream() { DoClose(); } + +Index BufferStream::DoRead(std::byte* buffer, Index offset, Index size) { + std::unique_lock lock(mutex_); + + condition_variable_.wait(lock, + [this] { return !buffer_list_.empty() || eof_; }); + + if (buffer_list_.empty() && eof_) { + return 0; + } + + auto full = max_block_count_ > 0 && buffer_list_.size() == max_block_count_; + + Index read = 0; + + while (!buffer_list_.empty()) { + auto& stream_buffer = buffer_list_.front(); + auto this_read = + stream_buffer.PopFront(buffer + offset + read, size - read); + if (stream_buffer.GetUsedSize() == 0) { + buffer_list_.pop_front(); + } + read += this_read; + if (read == size) { + break; + } + } + + if (full && buffer_list_.size() < max_block_count_) { + // By convention, there should be at most one producer waiting. So + // notify_one and notify_all should be the same. + condition_variable_.notify_one(); + } + + return read; +} + +Index BufferStream::DoWrite(const std::byte* buffer, Index offset, Index size) { + std::unique_lock lock(mutex_); + + if (eof_) { + throw WriteAfterEofException( + u"Stream has been set eof. Can't write to it any more."); + } + + condition_variable_.wait(lock, [this] { + return max_block_count_ <= 0 || buffer_list_.size() < max_block_count_ || + buffer_list_.back().GetBackFree() > 0; + }); + + auto empty = buffer_list_.empty(); + + Index written = 0; + + if (empty) { + buffer_list_.push_back(Buffer(block_size_)); + } + + while (true) { + if (buffer_list_.back().GetBackFree() == 0) { + if (max_block_count_ > 0 && buffer_list_.size() == max_block_count_) { + break; + } + buffer_list_.push_back(Buffer(block_size_)); + } + auto& stream_buffer = buffer_list_.back(); + auto this_written = + stream_buffer.PushBack(buffer + offset + written, size - written); + written += this_written; + if (written == size) { + break; + } + } + + if (empty) { + // By convention, there should be at most one consumer waiting. So + // notify_one and notify_all should be the same. + condition_variable_.notify_one(); + } + + return written; +} + +void BufferStream::SetEof() { + std::unique_lock lock(mutex_); + + eof_ = true; + if (buffer_list_.empty()) { + // By convention, there should be at most one consumer waiting. So + // notify_one and notify_all should be the same. + condition_variable_.notify_one(); + } +} + +void BufferStream::DoClose() { CRU_STREAM_BEGIN_CLOSE } +} // namespace cru::io diff --git a/src/base/io/CFileStream.cpp b/src/base/io/CFileStream.cpp new file mode 100644 index 00000000..d5acc707 --- /dev/null +++ b/src/base/io/CFileStream.cpp @@ -0,0 +1,96 @@ +#include "cru/base/io/CFileStream.h" +#include "cru/base/Exception.h" +#include "cru/base/io/Stream.h" + +#include <cstdio> + +namespace cru::io { +static bool ModeCanRead(const char* mode) { + for (const char* p = mode; *p != '\0'; p++) { + if (*p == 'r' || *p == '+') { + return true; + } + } + return false; +} + +static bool ModeCanWrite(const char* mode) { + for (const char* p = mode; *p != '\0'; p++) { + if (*p == 'w' || *p == 'a' || *p == '+') { + return true; + } + } + return false; +} + +CFileStream::CFileStream(const char* path, const char* mode) + : Stream(true, ModeCanRead(mode), ModeCanWrite(mode)), + file_(std::fopen(path, mode)), + auto_close_(true) { + if (file_ == nullptr) { + throw ErrnoException(u"Cannot open file."); + } +} + +CFileStream::CFileStream(std::FILE* file, bool readable, bool writable, + bool auto_close) + : Stream(true, readable, writable), file_(file), auto_close_(auto_close) { + if (file_ == nullptr) { + throw Exception(u"File is NULL."); + } +} + +CFileStream::~CFileStream() { + if (auto_close_ && file_ != nullptr) { + std::fclose(file_); + } +} + +static int ConvertOriginFlag(Stream::SeekOrigin origin) { + switch (origin) { + case Stream::SeekOrigin::Begin: + return SEEK_SET; + case Stream::SeekOrigin::Current: + return SEEK_CUR; + case Stream::SeekOrigin::End: + return SEEK_END; + default: + throw Exception(u"Unknown seek origin."); + } +} + +Index CFileStream::DoSeek(Index offset, SeekOrigin origin) { + if (std::fseek(file_, offset, ConvertOriginFlag(origin))) { + throw ErrnoException(u"Seek failed."); + } + return DoTell(); +} + +Index CFileStream::DoTell() { + long position = std::ftell(file_); + if (position == -1) { + throw ErrnoException(u"Tell failed."); + } + return position; +} + +void CFileStream::DoRewind() { std::rewind(file_); } + +Index CFileStream::DoRead(std::byte* buffer, Index offset, Index size) { + auto count = std::fread(buffer + offset, 1, size, file_); + return count; +} + +Index CFileStream::DoWrite(const std::byte* buffer, Index offset, Index size) { + auto count = std::fwrite(buffer + offset, 1, size, file_); + return count; +} + +void CFileStream::DoFlush() { std::fflush(file_); } + +void CFileStream::DoClose() { + CRU_STREAM_BEGIN_CLOSE + std::fclose(file_); + file_ = nullptr; +} +} // namespace cru::io diff --git a/src/base/io/MemoryStream.cpp b/src/base/io/MemoryStream.cpp new file mode 100644 index 00000000..4c650f3e --- /dev/null +++ b/src/base/io/MemoryStream.cpp @@ -0,0 +1,74 @@ +#include "cru/base/io/MemoryStream.h" + +#include <cstring> +#include "cru/base/Exception.h" +#include "cru/base/io/Stream.h" + +namespace cru::io { +MemoryStream::MemoryStream( + std::byte* buffer, Index size, bool read_only, + std::function<void(std::byte* buffer, Index size)> release_func) + : Stream(true, true, !read_only), + buffer_(buffer), + size_(size), + position_(0), + release_func_(std::move(release_func)) { + if (!buffer) { + throw Exception(u"Buffer is nullptr"); + } + if (size <= 0) { + throw Exception(u"Size is 0 or negative."); + } +} + +MemoryStream::~MemoryStream() {} + +void MemoryStream::Close() { DoClose(); } + +Index MemoryStream::DoSeek(Index offset, SeekOrigin origin) { + switch (origin) { + case SeekOrigin::Current: + position_ += offset; + break; + case SeekOrigin::Begin: + position_ = offset; + break; + case SeekOrigin::End: + position_ = size_ + offset; + break; + } + return position_; +} + +Index MemoryStream::DoRead(std::byte* buffer, Index offset, Index size) { + if (position_ + size > size_) { + size = size_ - position_; + } + if (size <= 0) { + return 0; + } + std::memmove(buffer + offset, buffer_ + position_, size); + position_ += size; + return size; +} + +Index MemoryStream::DoWrite(const std::byte* buffer, Index offset, Index size) { + if (position_ + size > size_) { + size = size_ - position_; + } + if (size <= 0) { + return 0; + } + std::memmove(buffer_ + position_, buffer + offset, size); + position_ += size; + return size; +} + +void MemoryStream::DoClose() { + CRU_STREAM_BEGIN_CLOSE + release_func_(buffer_, size_); + buffer_ = nullptr; + release_func_ = {}; +} + +} // namespace cru::io diff --git a/src/base/io/OpenFileFlag.cpp b/src/base/io/OpenFileFlag.cpp new file mode 100644 index 00000000..47069b29 --- /dev/null +++ b/src/base/io/OpenFileFlag.cpp @@ -0,0 +1,19 @@ +#include "cru/base/io/OpenFileFlag.h" + +namespace cru::io { +bool CheckOpenFileFlag(OpenFileFlag flags) { + auto has = [flags](OpenFileFlag flag) { return flags & flag; }; + + if ((has(OpenFileFlags::Append) || has(OpenFileFlags::Truncate) || + has(OpenFileFlags::Create)) && + !has(OpenFileFlags::Write)) { + return false; + } + + if (has(OpenFileFlags::Truncate) && has(OpenFileFlags::Append)) { + return false; + } + + return true; +} +} // namespace cru::io diff --git a/src/base/io/ProxyStream.cpp b/src/base/io/ProxyStream.cpp new file mode 100644 index 00000000..de66169e --- /dev/null +++ b/src/base/io/ProxyStream.cpp @@ -0,0 +1,37 @@ +#include "cru/base/io/ProxyStream.h" +#include "cru/base/io/Stream.h" + +namespace cru::io { +ProxyStream::ProxyStream(ProxyStreamHandlers handlers) + : Stream(static_cast<bool>(handlers.seek), static_cast<bool>(handlers.read), + static_cast<bool>(handlers.write)), + handlers_(std::move(handlers)) {} + +ProxyStream::~ProxyStream() { DoClose(); } + +Index ProxyStream::DoSeek(Index offset, SeekOrigin origin) { + return handlers_.seek(offset, origin); +} + +Index ProxyStream::DoRead(std::byte* buffer, Index offset, Index size) { + return handlers_.read(buffer, offset, size); +} + +Index ProxyStream::DoWrite(const std::byte* buffer, Index offset, Index size) { + return handlers_.write(buffer, offset, size); +} + +void ProxyStream::DoFlush() { + if (handlers_.flush) { + handlers_.flush(); + } +} + +void ProxyStream::DoClose() { + CRU_STREAM_BEGIN_CLOSE + if (handlers_.close) { + handlers_.close(); + } + handlers_ = {}; +} +} // namespace cru::io diff --git a/src/base/io/Resource.cpp b/src/base/io/Resource.cpp new file mode 100644 index 00000000..d369b5f5 --- /dev/null +++ b/src/base/io/Resource.cpp @@ -0,0 +1,48 @@ +#include "cru/base/io/Resource.h" +#include "cru/base/Exception.h" +#include "cru/base/log/Logger.h" + +#if defined(CRU_PLATFORM_OSX) +#include <CoreFoundation/CoreFoundation.h> +#elif defined(CRU_PLATFORM_WINDOWS) +#include <Windows.h> +#endif + +#include <filesystem> + +namespace cru::io { +std::filesystem::path GetResourceDir() { + constexpr auto kLogTag = u"GetResourceDir"; + +#if defined(CRU_PLATFORM_OSX) + CFBundleRef main_bundle = CFBundleGetMainBundle(); + CFURLRef bundle_url = CFBundleCopyBundleURL(main_bundle); + CFStringRef cf_string_ref = + CFURLCopyFileSystemPath(bundle_url, kCFURLPOSIXPathStyle); + std::filesystem::path bundle_path( + CFStringGetCStringPtr(cf_string_ref, kCFStringEncodingUTF8)); + + CFRelease(bundle_url); + CFRelease(cf_string_ref); + + return bundle_path / "Contents/Resources"; +#elif defined(CRU_PLATFORM_WINDOWS) + wchar_t buffer[MAX_PATH]; + DWORD size = ::GetModuleFileNameW(nullptr, buffer, MAX_PATH); + std::filesystem::path module_path(buffer, buffer + size); + auto p = module_path; + while (p.has_parent_path()) { + p = p.parent_path(); + auto resource_dir_path = p / "assets"; + if (std::filesystem::exists(resource_dir_path) && + std::filesystem::is_directory(resource_dir_path)) { + return resource_dir_path; + } + } + + throw Exception(u"Failed to find resource directory."); +#else + throw Exception(u"Not implemented."); +#endif +} +} // namespace cru::io diff --git a/src/base/io/Stream.cpp b/src/base/io/Stream.cpp new file mode 100644 index 00000000..d65bac46 --- /dev/null +++ b/src/base/io/Stream.cpp @@ -0,0 +1,199 @@ +#include "cru/base/io/Stream.h" +#include "cru/base/Exception.h" +#include "cru/base/Format.h" + +#include <utility> + +namespace cru::io { +StreamOperationNotSupportedException::StreamOperationNotSupportedException( + String operation) + : operation_(std::move(operation)) { + SetMessage(Format(u"Stream operation {} not supported.", operation_)); +} + +void StreamOperationNotSupportedException::CheckSeek(bool seekable) { + if (!seekable) throw StreamOperationNotSupportedException(u"seek"); +} + +void StreamOperationNotSupportedException::CheckRead(bool readable) { + if (!readable) throw StreamOperationNotSupportedException(u"read"); +} + +void StreamOperationNotSupportedException::CheckWrite(bool writable) { + if (!writable) throw StreamOperationNotSupportedException(u"write"); +} + +StreamAlreadyClosedException::StreamAlreadyClosedException() + : Exception(u"Stream is already closed.") {} + +void StreamAlreadyClosedException::Check(bool closed) { + if (closed) throw StreamAlreadyClosedException(); +} + +Stream::Stream(SupportedOperations supported_operations) + : supported_operations_(std::move(supported_operations)), closed_(false) {} + +Stream::Stream(std::optional<bool> can_seek, std::optional<bool> can_read, + std::optional<bool> can_write) + : Stream(SupportedOperations{can_seek, can_read, can_write}) {} + +bool Stream::CanSeek() { + CheckClosed(); + return DoCanSeek(); +} + +Index Stream::Seek(Index offset, SeekOrigin origin) { + CheckClosed(); + StreamOperationNotSupportedException::CheckSeek(DoCanSeek()); + return DoSeek(offset, origin); +} + +Index Stream::Tell() { + CheckClosed(); + return DoTell(); +} + +void Stream::Rewind() { + CheckClosed(); + return DoRewind(); +} + +Index Stream::GetSize() { + CheckClosed(); + return DoGetSize(); +} + +bool Stream::CanRead() { + CheckClosed(); + return DoCanRead(); +} + +Index Stream::Read(std::byte* buffer, Index offset, Index size) { + CheckClosed(); + StreamOperationNotSupportedException::CheckRead(DoCanRead()); + return DoRead(buffer, offset, size); +} + +Index Stream::Read(std::byte* buffer, Index size) { + return Read(buffer, 0, size); +} + +Index Stream::Read(char* buffer, Index offset, Index size) { + return Read(reinterpret_cast<std::byte*>(buffer), offset, size); +} + +Index Stream::Read(char* buffer, Index size) { + return Read(reinterpret_cast<std::byte*>(buffer), 0, size); +} + +bool Stream::CanWrite() { + CheckClosed(); + return DoCanWrite(); +} + +Index Stream::Write(const std::byte* buffer, Index offset, Index size) { + CheckClosed(); + StreamOperationNotSupportedException::CheckWrite(DoCanWrite()); + return DoWrite(buffer, offset, size); +} + +Index Stream::Write(const std::byte* buffer, Index size) { + return Write(buffer, 0, size); +} + +Index Stream::Write(const char* buffer, Index offset, Index size) { + return Write(reinterpret_cast<const std::byte*>(buffer), offset, size); +} + +Index Stream::Write(const char* buffer, Index size) { + return Write(reinterpret_cast<const std::byte*>(buffer), size); +} + +void Stream::Flush() { + CheckClosed(); + DoFlush(); +} + +bool Stream::DoCanSeek() { + if (supported_operations_->can_seek) { + return *supported_operations_->can_seek; + } else { + throw Exception( + u"Can seek is neither set in supported_operations nor implemeted in " + u"virtual function."); + } +} + +bool Stream::DoCanRead() { + if (supported_operations_->can_read) { + return *supported_operations_->can_read; + } else { + throw Exception( + u"Can read is neither set in supported_operations nor implemeted in " + u"virtual function."); + } +} + +bool Stream::DoCanWrite() { + if (supported_operations_->can_write) { + return *supported_operations_->can_write; + } else { + throw Exception( + u"Can write is neither set in supported_operations nor implemeted in " + u"virtual function."); + } +} + +Index Stream::DoSeek(Index offset, SeekOrigin origin) { + throw Exception(u"Stream is seekable but DoSeek is not implemented."); +} + +Index Stream::DoTell() { + StreamOperationNotSupportedException::CheckSeek(DoCanSeek()); + return DoSeek(0, SeekOrigin::Current); +} + +void Stream::DoRewind() { + StreamOperationNotSupportedException::CheckSeek(DoCanSeek()); + DoSeek(0, SeekOrigin::Begin); +} + +Index Stream::DoGetSize() { + StreamOperationNotSupportedException::CheckSeek(DoCanSeek()); + Index current_position = DoTell(); + Seek(0, SeekOrigin::End); + Index size = DoTell(); + Seek(current_position, SeekOrigin::Begin); + return size; +} + +Index Stream::DoRead(std::byte* buffer, Index offset, Index size) { + throw Exception(u"Stream is readable but DoSeek is not implemented."); +} + +Index Stream::DoWrite(const std::byte* buffer, Index offset, Index size) { + throw Exception(u"Stream is writable but DoSeek is not implemented."); +} + +void Stream::DoFlush() {} + +Buffer Stream::ReadToEnd(Index grow_size) { + Buffer buffer(grow_size); + while (true) { + auto read = Read(buffer.GetUsedEndPtr(), buffer.GetBackFree()); + buffer.PushBackCount(read); + if (read == 0) { + break; + } + if (buffer.IsUsedReachEnd()) { + buffer.ResizeBuffer(buffer.GetBufferSize() + grow_size, true); + } + } + return buffer; +} + +String Stream::ReadToEndAsUtf8String() { + auto buffer = ReadToEnd(); + return String::FromUtf8(buffer); +} +} // namespace cru::io |