aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base/io
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
committercrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
commitdfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch)
tree1c751a14ba0da07ca2ff805633f97568060aa4c9 /include/cru/base/io
parentf51eb955e188858272230a990565931e7403f23b (diff)
downloadcru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip
Rename common to base.
Diffstat (limited to 'include/cru/base/io')
-rw-r--r--include/cru/base/io/AutoReadStream.h72
-rw-r--r--include/cru/base/io/BufferStream.h85
-rw-r--r--include/cru/base/io/CFileStream.h39
-rw-r--r--include/cru/base/io/MemoryStream.h36
-rw-r--r--include/cru/base/io/OpenFileFlag.h56
-rw-r--r--include/cru/base/io/ProxyStream.h42
-rw-r--r--include/cru/base/io/Resource.h8
-rw-r--r--include/cru/base/io/Stream.h126
8 files changed, 464 insertions, 0 deletions
diff --git a/include/cru/base/io/AutoReadStream.h b/include/cru/base/io/AutoReadStream.h
new file mode 100644
index 00000000..759d5026
--- /dev/null
+++ b/include/cru/base/io/AutoReadStream.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "BufferStream.h"
+#include "Stream.h"
+
+#include <mutex>
+#include <thread>
+
+namespace cru::io {
+struct AutoReadStreamOptions {
+ /**
+ * @brief Will be passed to BufferStreamOptions::block_size.
+ */
+ Index block_size = 0;
+
+ /**
+ * @brief Will be passed to BufferStreamOptions::total_size_limit.
+ */
+ Index total_size_limit = 0;
+
+ BufferStreamOptions GetBufferStreamOptions() const {
+ BufferStreamOptions options;
+ options.block_size = block_size;
+ options.total_size_limit = total_size_limit;
+ return options;
+ }
+};
+
+/**
+ * @brief A stream that wraps another stream and auto read it into a buffer in a
+ * background thread.
+ */
+class CRU_BASE_API AutoReadStream : public Stream {
+ public:
+ /**
+ * @brief Wrap a stream and auto read it in background.
+ * @param stream The stream to auto read.
+ * @param auto_delete Whether to delete the stream object in destructor.
+ * @param options Options to modify the behavior.
+ */
+ AutoReadStream(
+ Stream* stream, bool auto_delete,
+ const AutoReadStreamOptions& options = AutoReadStreamOptions());
+
+ ~AutoReadStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ void BeginToDrop(bool auto_delete = true);
+
+ protected:
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ void BackgroundThreadRun();
+
+ private:
+ Stream* stream_;
+ bool auto_delete_;
+
+ Index size_per_read_;
+ std::unique_ptr<BufferStream> buffer_stream_;
+ std::mutex buffer_stream_mutex_;
+
+ std::thread background_thread_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/BufferStream.h b/include/cru/base/io/BufferStream.h
new file mode 100644
index 00000000..5ebff546
--- /dev/null
+++ b/include/cru/base/io/BufferStream.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "../Buffer.h"
+#include "../Exception.h"
+#include "Stream.h"
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+
+namespace cru::io {
+class WriteAfterEofException : public Exception {
+ public:
+ using Exception::Exception;
+ ~WriteAfterEofException() override = default;
+};
+
+struct BufferStreamOptions {
+ /**
+ * Actually I have no ideas about the best value for this. May change it later
+ * when I get some ideas.
+ */
+ constexpr static Index kDefaultBlockSize = 1024;
+
+ /**
+ * @brief The size of a single buffer allocated each time new space is needed.
+ * Use default value if <= 0.
+ *
+ * When current buffer is full and there is no space for following data, a new
+ * buffer will be allocated and appended to the buffer list. Note if sum size
+ * of all buffers reaches the total_buffer_limit, no more buffer will be
+ * allocated but wait.
+ */
+ Index block_size = 0;
+
+ /**
+ * @brief Total size limit of saved data in buffer. No limit if <= 0.
+ *
+ * The size will be floor(total_size_limit / block_size). When the buffer is
+ * filled, it will block and wait for user to read to get free space of buffer
+ * to continue read.
+ */
+ Index total_size_limit = 0;
+
+ Index GetBlockSizeOrDefault() const {
+ return block_size <= 0 ? kDefaultBlockSize : block_size;
+ }
+
+ Index GetMaxBlockCount() const {
+ return total_size_limit / GetBlockSizeOrDefault();
+ }
+};
+
+/**
+ * @brief SPSC (Single Producer Single Consumer) buffer stream.
+ *
+ * If used by multiple producer or multiple consumer, the behavior is undefined.
+ */
+class BufferStream : public Stream {
+ public:
+ BufferStream(const BufferStreamOptions& options);
+ ~BufferStream() override;
+
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ void SetEof();
+
+ protected:
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+
+ private:
+ void DoClose();
+
+ private:
+ Index block_size_;
+ Index max_block_count_;
+
+ std::list<Buffer> buffer_list_;
+ bool eof_;
+
+ std::mutex mutex_;
+ std::condition_variable condition_variable_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/CFileStream.h b/include/cru/base/io/CFileStream.h
new file mode 100644
index 00000000..0b58bdc9
--- /dev/null
+++ b/include/cru/base/io/CFileStream.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <cstdio>
+
+namespace cru::io {
+class CRU_BASE_API CFileStream : public Stream {
+ public:
+ CFileStream(const char* path, const char* mode);
+ explicit CFileStream(std::FILE* file, bool readable = true,
+ bool writable = true, bool auto_close = true);
+
+ CRU_DELETE_COPY(CFileStream)
+ CRU_DELETE_MOVE(CFileStream)
+
+ ~CFileStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ std::FILE* GetHandle() const;
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoTell() override;
+ void DoRewind() override;
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ private:
+ std::FILE* file_;
+ bool auto_close_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/MemoryStream.h b/include/cru/base/io/MemoryStream.h
new file mode 100644
index 00000000..a1f90c3b
--- /dev/null
+++ b/include/cru/base/io/MemoryStream.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <functional>
+
+namespace cru::io {
+class CRU_BASE_API MemoryStream : public Stream {
+ public:
+ MemoryStream(
+ std::byte* buffer, Index size, bool read_only = false,
+ std::function<void(std::byte* buffer, Index size)> release_func = {});
+
+ ~MemoryStream() override;
+
+ public:
+ void Close() override;
+
+ std::byte* GetBuffer() const { return buffer_; }
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoGetSize() override { return size_; }
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+
+ private:
+ void DoClose();
+
+ private:
+ std::byte* buffer_;
+ Index size_;
+ Index position_;
+ std::function<void(std::byte* buffer, Index size)> release_func_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/OpenFileFlag.h b/include/cru/base/io/OpenFileFlag.h
new file mode 100644
index 00000000..4a5789fb
--- /dev/null
+++ b/include/cru/base/io/OpenFileFlag.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "../Bitmask.h"
+
+namespace cru::io {
+namespace details {
+struct OpenFileFlagTag {};
+} // namespace details
+using OpenFileFlag = Bitmask<details::OpenFileFlagTag>;
+
+struct OpenFileFlags {
+ /**
+ * \brief for reading
+ * If the file does not exist, FileNotExistException should be thrown.
+ */
+ static constexpr OpenFileFlag Read{0x1};
+
+ /**
+ * \brief for writing
+ * If the file does not exist and Create is not specified,
+ * FileNotExistException should be thrown.
+ */
+ static constexpr OpenFileFlag Write{0x2};
+
+ /**
+ * \brief when writing, seek to end first
+ * Only effective for writing.
+ */
+ static constexpr OpenFileFlag Append{0x4};
+
+ /**
+ * \brief when writing, truncate the file to empty
+ * Only effective for writing.
+ * So the content is erased! Be careful!
+ */
+ static constexpr OpenFileFlag Truncate{0x8};
+
+ /**
+ * \brief when writing, if the file does not exist, create one
+ * Only effective for writing. When file does not exist, FileNotExistException
+ * will NOT be thrown and a new file will be created.
+ */
+ static constexpr OpenFileFlag Create{0x10};
+
+ /**
+ * TODO: ???
+ */
+ static constexpr OpenFileFlag Exclusive{0x20};
+};
+
+/**
+ * Append, Truncate, Create must be used with Write.
+ * Append and Truncate must not be used together.
+ */
+bool CheckOpenFileFlag(OpenFileFlag flags);
+} // namespace cru::io
diff --git a/include/cru/base/io/ProxyStream.h b/include/cru/base/io/ProxyStream.h
new file mode 100644
index 00000000..42ec9dfd
--- /dev/null
+++ b/include/cru/base/io/ProxyStream.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <functional>
+
+namespace cru::io {
+struct ProxyStreamHandlers {
+ std::function<Index(Index offset, Stream::SeekOrigin origin)> seek;
+ std::function<Index(std::byte* buffer, Index offset, Index size)> read;
+ std::function<Index(const std::byte* buffer, Index offset, Index size)> write;
+ std::function<void()> flush;
+
+ /**
+ * @brief This method will be only called once when `Close` is called or the
+ * stream is destructed.
+ */
+ std::function<void()> close;
+};
+
+class ProxyStream : public Stream {
+ public:
+ explicit ProxyStream(ProxyStreamHandlers handlers);
+
+ ~ProxyStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ private:
+ ProxyStreamHandlers handlers_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/Resource.h b/include/cru/base/io/Resource.h
new file mode 100644
index 00000000..1d5313a6
--- /dev/null
+++ b/include/cru/base/io/Resource.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../Base.h"
+
+#include <filesystem>
+
+namespace cru::io {
+std::filesystem::path CRU_BASE_API GetResourceDir();
+}
diff --git a/include/cru/base/io/Stream.h b/include/cru/base/io/Stream.h
new file mode 100644
index 00000000..e0b61627
--- /dev/null
+++ b/include/cru/base/io/Stream.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include "../Base.h"
+
+#include "../Exception.h"
+#include "../String.h"
+
+#include <cstddef>
+
+namespace cru::io {
+class CRU_BASE_API StreamOperationNotSupportedException : public Exception {
+ public:
+ explicit StreamOperationNotSupportedException(String operation);
+
+ CRU_DEFAULT_DESTRUCTOR(StreamOperationNotSupportedException)
+
+ public:
+ String GetOperation() const { return operation_; }
+
+ public:
+ static void CheckSeek(bool seekable);
+ static void CheckRead(bool readable);
+ static void CheckWrite(bool writable);
+
+ private:
+ String operation_;
+};
+
+class CRU_BASE_API StreamAlreadyClosedException : public Exception {
+ public:
+ StreamAlreadyClosedException();
+
+ CRU_DEFAULT_DESTRUCTOR(StreamAlreadyClosedException)
+
+ static void Check(bool closed);
+};
+
+#define CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE \
+ void Close() override { DoClose(); }
+
+#define CRU_STREAM_BEGIN_CLOSE \
+ if (GetClosed()) return; \
+ CloseGuard close_guard(this);
+
+/**
+ * All stream is thread-unsafe by default unless being documented.
+ */
+class CRU_BASE_API Stream : public Object {
+ protected:
+ struct SupportedOperations {
+ std::optional<bool> can_seek;
+ std::optional<bool> can_read;
+ std::optional<bool> can_write;
+ };
+
+ struct CloseGuard {
+ explicit CloseGuard(Stream* stream) : stream(stream) {}
+ ~CloseGuard() { stream->SetClosed(true); }
+ Stream* stream;
+ };
+
+ protected:
+ explicit Stream(SupportedOperations supported_operations = {});
+ Stream(std::optional<bool> can_seek, std::optional<bool> can_read,
+ std::optional<bool> can_write);
+
+ public:
+ enum class SeekOrigin { Current, Begin, End };
+
+ CRU_DELETE_COPY(Stream)
+ CRU_DELETE_MOVE(Stream)
+
+ ~Stream() override = default;
+
+ public:
+ bool CanSeek();
+ Index Seek(Index offset, SeekOrigin origin = SeekOrigin::Current);
+ Index Tell();
+ void Rewind();
+ Index GetSize();
+
+ bool CanRead();
+ Index Read(std::byte* buffer, Index offset, Index size);
+ Index Read(std::byte* buffer, Index size);
+ Index Read(char* buffer, Index offset, Index size);
+ Index Read(char* buffer, Index size);
+
+ bool CanWrite();
+ Index Write(const std::byte* buffer, Index offset, Index size);
+ Index Write(const std::byte* buffer, Index size);
+ Index Write(const char* buffer, Index offset, Index size);
+ Index Write(const char* buffer, Index size);
+
+ void Flush();
+ virtual void Close() = 0;
+
+ virtual Buffer ReadToEnd(Index grow_size = 256);
+
+ // Utf8 encoding.
+ String ReadToEndAsUtf8String();
+
+ protected:
+ virtual bool DoCanSeek();
+ virtual bool DoCanRead();
+ virtual bool DoCanWrite();
+ virtual Index DoSeek(Index offset, SeekOrigin origin);
+ virtual Index DoTell();
+ virtual void DoRewind();
+ virtual Index DoGetSize();
+ virtual Index DoRead(std::byte* buffer, Index offset, Index size);
+ virtual Index DoWrite(const std::byte* buffer, Index offset, Index size);
+ virtual void DoFlush();
+
+ void SetSupportedOperations(SupportedOperations supported_operations) {
+ supported_operations_ = std::move(supported_operations);
+ }
+
+ bool GetClosed() { return closed_; }
+ void SetClosed(bool closed) { closed_ = closed; }
+ void CheckClosed() { StreamAlreadyClosedException::Check(closed_); }
+
+ private:
+ std::optional<SupportedOperations> supported_operations_;
+ bool closed_;
+};
+} // namespace cru::io