aboutsummaryrefslogtreecommitdiff
path: root/src/base/io
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/io')
-rw-r--r--src/base/io/AutoReadStream.cpp56
-rw-r--r--src/base/io/BufferStream.cpp109
-rw-r--r--src/base/io/CFileStream.cpp96
-rw-r--r--src/base/io/MemoryStream.cpp74
-rw-r--r--src/base/io/OpenFileFlag.cpp19
-rw-r--r--src/base/io/ProxyStream.cpp37
-rw-r--r--src/base/io/Resource.cpp48
-rw-r--r--src/base/io/Stream.cpp199
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