diff --git a/UnitTests/UnitTests.sln b/UnitTests/UnitTests.sln
index 36bb406ab..e60c33af6 100644
--- a/UnitTests/UnitTests.sln
+++ b/UnitTests/UnitTests.sln
@@ -23,7 +23,7 @@ Global
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
+ HideSolutionNode = false
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BBDB843D-98C3-46EF-BDE8-0E80FD851852}
diff --git a/UnitTests/UnitTests.vcxproj b/UnitTests/UnitTests.vcxproj
index dc1f70e7a..6542eeac3 100644
--- a/UnitTests/UnitTests.vcxproj
+++ b/UnitTests/UnitTests.vcxproj
@@ -111,11 +111,14 @@
-
+
+
+ stdcpp17
+
+
-
@@ -123,6 +126,7 @@
+
diff --git a/UnitTests/UnitTests.vcxproj.filters b/UnitTests/UnitTests.vcxproj.filters
index a42e11a19..09af76837 100644
--- a/UnitTests/UnitTests.vcxproj.filters
+++ b/UnitTests/UnitTests.vcxproj.filters
@@ -24,15 +24,15 @@
Source Files
-
- Source Files
-
Source Files
Source Files
+
+ Source Files
+
Source Files
diff --git a/UnitTests/ios.cpp b/UnitTests/ios.cpp
deleted file mode 100644
index 11019843a..000000000
--- a/UnitTests/ios.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- SPDX-License-Identifier: MIT
- Copyright © 2023 Amebis
-*/
-
-#include "pch.h"
-
-using namespace std;
-using namespace stdex;
-using namespace Microsoft::VisualStudio::CppUnitTestFramework;
-
-namespace UnitTests
-{
- TEST_CLASS(ios)
- {
- public:
- TEST_METHOD(fstream)
- {
- WCHAR path[MAX_PATH];
- ExpandEnvironmentStringsW(L"%windir%\\notepad.exe", path, _countof(path));
- stdex::fstream f(path, ios_base::in | ios_base::binary);
- Assert::IsTrue(f.good());
- Assert::IsTrue(f.mtime() < chrono::system_clock::now());
- }
-
- TEST_METHOD(isharedstrstream)
- {
- static const char data[] = "\xde\xad\xca\xfe";
- stdex::isharedstrstream f(data, _countof(data));
- Assert::IsTrue(f.good());
-
- char val;
- f.read(&val, 1);
- Assert::IsTrue(f.gcount() == 1);
- Assert::IsTrue(f.good());
- Assert::IsTrue(val == data[0]);
- f.read(&val, 1);
- Assert::IsTrue(f.gcount() == 1);
- Assert::IsTrue(f.good());
- Assert::IsTrue(val == data[1]);
-
- f.seekg(_countof(data) - 1);
- f.read(&val, 1);
- Assert::IsTrue(f.gcount() == 1);
- Assert::IsTrue(f.good());
- Assert::IsTrue(val == data[_countof(data) - 1]);
-
- f.read(&val, 1);
- Assert::IsTrue(f.eof());
- f.clear();
-
- f.seekg(-2, ios_base::end);
- f.read(&val, 1);
- Assert::IsTrue(f.gcount() == 1);
- Assert::IsTrue(f.good());
- Assert::IsTrue(val == data[_countof(data) - 2]);
- }
-
- TEST_METHOD(diagstream)
- {
- constexpr size_t n = 3;
- unique_ptr f[n];
- vector fv(n);
-
- {
- for (size_t i = 0; i < n; ++i) {
- WCHAR path[MAX_PATH];
- ExpandEnvironmentStringsW(stdex::sprintf(L"%%temp%%\\file%zu.dat", NULL, i).c_str(), path, _countof(path));
- f[i].reset(new std::fstream(path, ios_base::out | ios_base::binary));
- fv[i] = f[i].get();
- }
- stdex::diagstream d(fv.begin(), fv.end());
- srand(0);
- auto write_some_random = [](_Inout_ ostream& f, _In_ size_t amount)
- {
- for (size_t i = 0; i < amount; ++i) {
- auto r = static_cast(rand());
- f.write(reinterpret_cast(&r), sizeof(r) / sizeof(char));
- }
- };
- write_some_random(d, 15);
- d.seekp(3);
- write_some_random(d, 2);
- d.seekp(28);
- write_some_random(d, 7);
- }
-
- {
- for (size_t i = 0; i < n; ++i) {
- WCHAR path[MAX_PATH];
- ExpandEnvironmentStringsW(stdex::sprintf(L"%%temp%%\\file%zu.dat", NULL, i).c_str(), path, _countof(path));
- f[i].reset(new std::fstream(path, ios_base::in | ios_base::binary));
- fv[i] = f[i].get();
- }
- stdex::diagstream d(fv.begin(), fv.end());
- do {
- uint32_t r;
- d.read(reinterpret_cast(&r), sizeof(r) / sizeof(char));
- } while (!d.eof());
- }
- }
- };
-}
diff --git a/UnitTests/pch.h b/UnitTests/pch.h
index 7e2d87e1c..472527b48 100644
--- a/UnitTests/pch.h
+++ b/UnitTests/pch.h
@@ -14,7 +14,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -22,11 +21,14 @@
#include
#include
#include
+#include
#include
+#include
#include
#include
#include
#include
+#include
#include
diff --git a/UnitTests/stream.cpp b/UnitTests/stream.cpp
new file mode 100644
index 000000000..fda4b4b83
--- /dev/null
+++ b/UnitTests/stream.cpp
@@ -0,0 +1,111 @@
+/*
+ SPDX-License-Identifier: MIT
+ Copyright © 2023 Amebis
+*/
+
+#include "pch.h"
+
+using namespace std;
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace UnitTests
+{
+ TEST_CLASS(stream)
+ {
+ public:
+ TEST_METHOD(async)
+ {
+ constexpr size_t total = 1000;
+ stdex::stream::memory_file source(stdex::mul(total, sizeof(size_t)));
+ {
+ stdex::stream::async_writer<70> writer(source);
+ for (size_t i = 0; i < total; ++i) {
+ Assert::IsTrue(writer.ok());
+ writer << i;
+ }
+ }
+ Assert::AreEqual(0, source.seekbeg(0));
+ {
+ stdex::stream::async_reader<50> reader(source);
+ size_t x;
+ for (size_t i = 0; i < total; ++i) {
+ reader >> x;
+ Assert::IsTrue(reader.ok());
+ Assert::AreEqual(i, x);
+ }
+ reader >> x;
+ Assert::IsFalse(reader.ok());
+ }
+ }
+
+ TEST_METHOD(replicator)
+ {
+ constexpr size_t total = 1000;
+
+ stdex::stream::memory_file f1(stdex::mul(total, sizeof(size_t)));
+
+ std::basic_string filename2, filename3;
+#ifdef _WIN32
+ {
+ TCHAR temp_path[MAX_PATH];
+ Assert::IsTrue(ExpandEnvironmentStrings(_T("%TEMP%\\"), temp_path, _countof(temp_path)) < MAX_PATH);
+ filename2 = filename3 = temp_path;
+ }
+#else
+ filename2 = filename3 = "/tmp/";
+#endif
+ filename2 += _T("stdex-stream-replicator-2.tmp");
+ stdex::stream::file f2(
+ filename2.c_str(),
+ stdex::stream::mode_for_reading | stdex::stream::mode_for_writing | stdex::stream::mode_create | stdex::stream::mode_binary);
+
+ filename3 += _T("stdex-stream-replicator-3.tmp");
+ stdex::stream::cached_file f3(
+ filename3.c_str(),
+ stdex::stream::mode_for_reading | stdex::stream::mode_for_writing | stdex::stream::mode_create | stdex::stream::mode_binary,
+ 128);
+
+ {
+ stdex::stream::replicator writer;
+ stdex::stream::buffer f2_buf(f2, 0, 32);
+ writer.push_back(&f1);
+ writer.push_back(&f2_buf);
+ writer.push_back(&f3);
+ for (size_t i = 0; i < total; ++i) {
+ Assert::IsTrue(writer.ok());
+ writer << i;
+ }
+ }
+
+ f1.seekbeg(0);
+ f2.seekbeg(0);
+ f3.seekbeg(0);
+ {
+ stdex::stream::buffer f2_buf(f2, 64, 0);
+ size_t x;
+ for (size_t i = 0; i < total; ++i) {
+ f1 >> x;
+ Assert::IsTrue(f1.ok());
+ Assert::AreEqual(i, x);
+ f2_buf >> x;
+ Assert::IsTrue(f2_buf.ok());
+ Assert::AreEqual(i, x);
+ f3 >> x;
+ Assert::IsTrue(f3.ok());
+ Assert::AreEqual(i, x);
+ }
+ f1 >> x;
+ Assert::IsFalse(f1.ok());
+ f2_buf >> x;
+ Assert::IsFalse(f2_buf.ok());
+ f3 >> x;
+ Assert::IsFalse(f3.ok());
+ }
+
+ f2.close();
+ std::filesystem::remove(filename2);
+ f3.close();
+ std::filesystem::remove(filename3);
+ }
+ };
+}
diff --git a/include/stdex/internal.hpp b/include/stdex/internal.hpp
deleted file mode 100644
index 4e8706b96..000000000
--- a/include/stdex/internal.hpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- SPDX-License-Identifier: MIT
- Copyright © 2023 Amebis
-*/
-
-#pragma once
-
-#include
-
-namespace stdex
-{
- ///
- /// Helper template to allow access to internal std C++ private members
- ///
- /// \sa http://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html
- ///
- template
- struct robber {
- friend typename _Tag::type get(_Tag) {
- return _Member;
- }
- };
-
- ///
- /// Helper template to allow access to internal std C++ private members
- ///
- /// \sa http://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html
- ///
- template
- struct getter {
- typedef _Type _Class::* type;
- friend type get(getter<_Type, _Class>);
- };
-}
-
-#ifdef _WIN32
-/// \cond internal
-extern "C" {
- _ACRTIMP intptr_t __cdecl _get_osfhandle(_In_ int _FileHandle);
-}
-/// \endcond
-#endif
diff --git a/include/stdex/ios.hpp b/include/stdex/ios.hpp
deleted file mode 100644
index 48f1d3584..000000000
--- a/include/stdex/ios.hpp
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- SPDX-License-Identifier: MIT
- Copyright © 2023 Amebis
-*/
-
-#pragma once
-
-#ifdef _WIN32
-#include
-#endif
-#include "endian.hpp"
-#include "internal.hpp"
-#include "string.hpp"
-#include "sal.hpp"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-namespace stdex
-{
- ///
- /// Binary stream writer
- ///
- template
- class basic_ostreamfmt
- {
- public:
- std::basic_ostream<_Elem, _Traits> &sp; // Write stream
-
- inline basic_ostreamfmt(_Inout_ std::basic_ostream<_Elem, _Traits> &stream) : sp(stream) {}
-
- using pos_type = typename _Traits::pos_type;
- using off_type = typename _Traits::off_type;
- inline pos_type tellp() { return sp.tellp(); }
- inline basic_ostreamfmt<_Elem, _Traits>& seekp(pos_type pos) { sp.seekp(pos); return *this; }
- inline basic_ostreamfmt<_Elem, _Traits>& seekp(off_type off, std::ios_base::seekdir dir) { sp.seekp(off, dir); return *this; }
- inline bool good() const noexcept { return sp.good(); }
- inline bool eof() const noexcept { return sp.eof(); }
- inline bool fail() const noexcept { return sp.fail(); }
- inline bool bad() const noexcept { return sp.bad(); }
-
- inline basic_ostreamfmt<_Elem, _Traits>& write(_In_reads_bytes_(size) const void* data, _In_ std::streamsize size)
- {
- sp.write(reinterpret_cast(data), size/sizeof(_Elem));
- return *this;
- }
-
- template
- inline basic_ostreamfmt<_Elem, _Traits>& write(_In_ T value)
- {
- HE2LE(&value);
- sp.write(reinterpret_cast(&value), sizeof(T)/sizeof(_Elem));
- return *this;
- }
-
- inline basic_ostreamfmt<_Elem, _Traits>& write(_In_z_ const char* value)
- {
- size_t count = strlen(value);
- if (count > UINT32_MAX)
- throw std::invalid_argument("string too big");
- sp.write(static_cast(count));
- sp.write(reinterpret_cast(value), (std::streamsize)count * sizeof(char)/sizeof(_Elem));
- return *this;
- }
-
- inline basic_ostreamfmt<_Elem, _Traits>& write(_In_z_ const wchar_t* value)
- {
- size_t count = strlen(value);
- if (count > UINT32_MAX)
- throw std::invalid_argument("string too big");
- sp.write(static_cast(count));
-#ifdef BIG_ENDIAN
- for (size_t i = 0; i < count; ++i)
- sp.write(value[i]);
-#else
- sp.write(reinterpret_cast(value), (std::streamsize)count * sizeof(wchar_t)/sizeof(_Elem));
-#endif
- return *this;
- }
-
- inline basic_ostreamfmt<_Elem, _Traits>& write_byte(_In_ uint8_t value)
- {
- return write(value);
- }
-
- ///
- /// Formats string using `printf()` and write it to stream.
- ///
- /// \param[in] format String template using `printf()` style
- /// \param[in] locale Stdlib locale used to perform formatting. Use `NULL` to use locale globally set by `setlocale()`.
- /// \param[in] arg Arguments to `format`
- ///
- template
- void vprintf(_In_z_ _Printf_format_string_ const _Elem2 *format, _In_opt_ locale_t locale, _In_ va_list arg)
- {
- std::basic_string<_Elem2> str;
- vappendf(str, format, locale, arg);
- sp.write(reinterpret_cast(str.c_str()), str.size() * sizeof(_Elem2)/sizeof(_Elem));
- }
-
- ///
- /// Formats string using `printf()` and write it to stream.
- ///
- /// \param[in] format String template using `printf()` style
- /// \param[in] locale Stdlib locale used to perform formatting. Use `NULL` to use locale globally set by `setlocale()`.
- ///
- template
- void printf(_In_z_ _Printf_format_string_ const _Elem2 *format, _In_opt_ locale_t locale, ...)
- {
- va_list arg;
- va_start(arg, locale);
- vprintf(format, locale, arg);
- va_end(arg);
- }
-
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ int8_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ int16_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ int32_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ int64_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ uint8_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ uint16_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ uint32_t value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ uint64_t value) { return write(value); }
-#if defined(_NATIVE_SIZE_T_DEFINED) && defined(_WIN64)
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ size_t value) { return write(value); }
-#endif
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ float value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ double value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ char value) { return write(value); }
-#ifdef _NATIVE_WCHAR_T_DEFINED
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_ wchar_t value) { return write(value); }
-#endif
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_z_ const char* value) { return write(value); }
- inline basic_ostreamfmt<_Elem, _Traits>& operator <<(_In_z_ const wchar_t* value) { return write(value); }
- };
-
- using ostreamfmt = basic_ostreamfmt>;
- using wostreamfmt = basic_ostreamfmt>;
-
- ///
- /// Binary stream reader
- ///
- template
- class basic_istreamfmt
- {
- public:
- std::basic_istream<_Elem, _Traits> &sg; // Read stream
-
- inline basic_istreamfmt(_Inout_ std::basic_istream<_Elem, _Traits> &stream) : sg(stream) {}
-
- using pos_type = typename _Traits::pos_type;
- using off_type = typename _Traits::off_type;
- inline pos_type tellg() { return sg.tellg(); }
- inline basic_istreamfmt<_Elem, _Traits>& seekg(pos_type pos) { sg.seekg(pos); return *this; }
- inline basic_istreamfmt<_Elem, _Traits>& seekg(off_type off, std::ios_base::seekdir dir) { sg.seekg(off, dir); return *this; }
- inline bool good() const noexcept { return sg.good(); }
- inline bool eof() const noexcept { return sg.eof(); }
- inline bool fail() const noexcept { return sg.fail(); }
- inline bool bad() const noexcept { return sg.bad(); }
- inline std::streamsize gcount() const noexcept { return sg.gcount(); }
-
- inline basic_istreamfmt<_Elem, _Traits>& read(_Out_writes_bytes_(size) void* data, std::streamsize size)
- {
- sg.read(reinterpret_cast<_Elem*>(data), size/sizeof(_Elem));
- return *this;
- }
-
- template
- inline basic_istreamfmt<_Elem, _Traits>& read(_Out_ T& value)
- {
- sg.read(reinterpret_cast<_Elem*>(&value), sizeof(T)/sizeof(_Elem));
- if (sg.good())
- LE2HE(&value);
- return *this;
- }
-
- template , class _Alloc = std::allocator>
- inline basic_istreamfmt<_Elem, _Traits>& read(_Inout_ std::basic_string& value)
- {
- uint32_t count;
- sg.read(count);
- if (sg.good()) {
- value.resize(count);
- sg.read(reinterpret_cast<_Elem*>(&value[0]), (std::streamsize)count * sizeof(char)/sizeof(_Elem));
- }
- return *this;
- }
-
- template , class _Alloc = std::allocator>
- inline basic_istreamfmt<_Elem, _Traits>& read(_Inout_ std::basic_string& value)
- {
- uint32_t count;
- sg.read(count);
- if (sg.good()) {
- value.resize(count);
-#ifdef BIG_ENDIAN
- for (size_t i = 0; i < count; ++i)
- sg.read(value[i]);
-#else
- sg.read(reinterpret_cast<_Elem*>(&value[0]), (std::streamsize)count * sizeof(wchar_t)/sizeof(_Elem));
-#endif
- }
- return *this;
- }
-
- inline uint8_t read_byte()
- {
- uint8_t value;
- read(value);
- return value;
- }
-
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ int8_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ int16_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ int32_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ int64_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ uint8_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ uint16_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ uint32_t& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ uint64_t& value) { return read(value); }
-#if defined(_NATIVE_SIZE_T_DEFINED) && defined(_WIN64)
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ size_t& value) { return read(value); }
-#endif
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ float& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ double& value) { return read(value); }
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ char& value) { return read(value); }
-#ifdef _NATIVE_WCHAR_T_DEFINED
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Out_ wchar_t& value) { return read(value); }
-#endif
- template , class _Alloc = std::allocator>
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Inout_ std::basic_string& value) { return read(value); }
- template , class _Alloc = std::allocator>
- inline basic_istreamfmt<_Elem, _Traits>& operator >>(_Inout_ std::basic_string& value) { return read(value); }
- };
-
- using istreamfmt = basic_istreamfmt>;
- using wistreamfmt = basic_istreamfmt>;
-
- ///
- /// Binary stream reader/writer
- ///
- template
- class basic_iostreamfmt : public basic_ostreamfmt<_Elem, _Traits>, public basic_istreamfmt<_Elem, _Traits>
- {
- public:
- inline basic_iostreamfmt(_Inout_ std::basic_iostream<_Elem, _Traits> &stream) :
- basic_ostreamfmt<_Elem, _Traits>(stream),
- basic_istreamfmt<_Elem, _Traits>(stream)
- {}
- };
-
- using iostreamfmt = basic_iostreamfmt>;
- using wiostreamfmt = basic_iostreamfmt>;
-
- ///
- /// Shared-memory string stream buffer
- ///
- template
- class basic_sharedstrbuf : public std::basic_streambuf<_Elem, _Traits>
- {
- public:
- basic_sharedstrbuf(_In_reads_(size) const _Elem* data, _In_ size_t size)
- {
- setg(const_cast<_Elem*>(data), const_cast<_Elem*>(data), const_cast<_Elem*>(data + size));
- }
-
- basic_sharedstrbuf(_In_ const basic_sharedstrbuf<_Elem, _Traits>& other)
- {
- setg(other.eback(), other.gptr(), other.egptr());
- }
-
- basic_sharedstrbuf<_Elem, _Traits>& operator =(_In_ const basic_sharedstrbuf<_Elem, _Traits>& other)
- {
- if (this != std::addressof(other))
- std::basic_streambuf<_Elem, _Traits>::operator =(other);
- return *this;
- }
-
- private:
- basic_sharedstrbuf(_Inout_ basic_sharedstrbuf<_Elem, _Traits>&& other) noexcept;
- basic_sharedstrbuf<_Elem, _Traits>& operator =(_Inout_ basic_sharedstrbuf<_Elem, _Traits>&& other) noexcept;
-
- protected:
- virtual pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
- {
- if (which & std::ios_base::in) {
- _Elem* target;
- switch (way) {
- case std::ios_base::beg: target = eback() + static_cast(off); break;
- case std::ios_base::cur: target = gptr() + static_cast(off); break;
- case std::ios_base::end: target = egptr() + static_cast(off); break;
- default: throw std::invalid_argument("invalid seek reference");
- }
- if (eback() <= target && target <= egptr()) {
- gbump(static_cast(target - gptr()));
- return pos_type{ off_type{ target - eback() } };
- }
- }
- return pos_type{ off_type{-1} };
- }
-
- virtual pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
- {
- if (which & std::ios_base::in) {
- _Elem* target = eback() + static_cast(pos);
- if (eback() <= target && target <= egptr()) {
- gbump(static_cast(target - gptr()));
- return pos_type{ off_type{ target - eback() } };
- }
- }
- return pos_type{ off_type{-1} };
- }
- };
-
- template
- class basic_isharedstrstream : public std::basic_istream<_Elem, _Traits>
- {
- public:
- basic_isharedstrstream(_In_reads_(size) const _Elem* data, _In_ size_t size) :
- m_buf(data, size),
- std::basic_istream<_Elem, _Traits>(&m_buf)
- {}
-
- protected:
- basic_sharedstrbuf<_Elem, _Traits> m_buf;
- };
-
- using isharedstrstream = basic_isharedstrstream>;
- using wisharedstrstream = basic_isharedstrstream>;
-
- ///
- /// Diagnostic input stream buffer
- ///
- /// Verifies multiple input streams read the same data.
- ///
- template
- class basic_diagstreambuf : public std::basic_streambuf<_Elem, _Traits>
- {
- public:
- using guest_stream = std::basic_iostream<_Elem, _Traits>;
-
- template
- basic_diagstreambuf(_In_ const _Iter first, _In_ const _Iter last) : m_streams(first, last) {}
- basic_diagstreambuf(_In_reads_(count) guest_stream* const* streams, _In_ size_t count) : basic_diagstreambuf(streams, streams + count) {}
-
- private:
- basic_diagstreambuf(_In_ const basic_diagstreambuf<_Elem, _Traits>& other);
- basic_diagstreambuf<_Elem, _Traits>& operator =(_In_ const basic_diagstreambuf<_Elem, _Traits>& other);
- basic_diagstreambuf(_Inout_ basic_diagstreambuf<_Elem, _Traits>&& other) noexcept;
- basic_diagstreambuf<_Elem, _Traits>& operator =(_Inout_ basic_diagstreambuf<_Elem, _Traits>&& other) noexcept;
-
- protected:
- virtual pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
- {
- if (m_streams.empty())
- return pos_type{ off_type{-1} };
- auto r = pos_type{ off_type{-1} };
- if ((which & std::ios_base::in)) {
- m_streams[0]->seekg(off, way);
- r = m_streams[0]->bad() ? pos_type{ off_type{-1} } : m_streams[0]->tellg();
- for (size_t i = 1, n = m_streams.size(); i < n; ++i) {
- m_streams[i]->seekg(off, way);
- if (m_streams[i]->bad())
- r = pos_type{ off_type{-1} };
- }
- }
- if ((which & std::ios_base::out)) {
- m_streams[0]->seekp(off, way);
- r = m_streams[0]->bad() ? pos_type{ off_type{-1} } : m_streams[0]->tellp();
- for (size_t i = 1, n = m_streams.size(); i < n; ++i) {
- m_streams[i]->seekp(off, way);
- if (m_streams[i]->bad())
- r = pos_type{ off_type{-1} };
- }
- }
- return r;
- }
-
- virtual pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::in | std::ios_base::out)
- {
- return seekoff(pos, std::ios_base::beg, which);
- }
-
- virtual int_type underflow()
- {
- int_type eof = _Traits::eof();
- if (m_streams.empty())
- eof;
- _Elem data;
- m_streams[0]->read(&data, 1);
- int_type r = m_streams[0]->gcount() == 1 ? _Traits::to_int_type(data) : eof;
- for (size_t i = 1, n = m_streams.size(); i < n; ++i) {
- m_streams[i]->read(&data, 1);
- int_type temp_r = m_streams[i]->gcount() == 1 ? _Traits::to_int_type(data) : eof;
- if (r != temp_r)
- r = eof;
- }
- return r;
- }
-
- virtual int_type overflow(int_type ch = _Traits::eof())
- {
- if (_Traits::not_eof(ch)) {
- _Elem data = _Traits::to_char_type(ch);
- bool good = true;
- for (size_t i = 0, n = m_streams.size(); i < n; ++i) {
- m_streams[i]->write(&data, 1);
- good &= m_streams[i]->good();
- }
- return good ? 0 : _Traits::eof();
- }
- return 0;
- }
-
- virtual int sync()
- {
- int r = 0;
- for (size_t i = 0, n = m_streams.size(); i < n; ++i)
- if (m_streams[i]->sync() < 0)
- r = -1;
- return r;
- }
-
- protected:
- std::vector m_streams;
- };
-
- ///
- /// Diagnostic input/output stream
- ///
- /// Verifies multiple input streams read the same data.
- /// Writes to multiple output streams the same data.
- ///
- template
- class basic_diagstream : public std::basic_iostream<_Elem, _Traits>
- {
- public:
- using guest_stream = std::basic_iostream<_Elem, _Traits>;
-
- template
- basic_diagstream(_In_ const _Iter first, _In_ const _Iter last) :
- m_buf(first, last),
- std::basic_iostream<_Elem, _Traits>(&m_buf)
- {}
-
- basic_diagstream(_In_reads_(count) guest_stream* const* streams, _In_ size_t count) :
- basic_diagstream(streams, streams + count)
- {}
-
- protected:
- basic_diagstreambuf<_Elem, _Traits> m_buf;
- };
-
- using diagstream = basic_diagstream>;
- using wdiagstream = basic_diagstream>;
-
-#ifdef _WIN32
- /// \cond internal
- template struct robber, &std::filebuf::_Myfile>;
- template struct robber, &std::wfilebuf::_Myfile>;
-
- inline FILE* filebuf_fhandle(_In_ std::filebuf* rb)
- {
- return (*rb).*get(getter());
- }
-
- inline FILE* filebuf_fhandle(_In_ std::wfilebuf* rb)
- {
- return (*rb).*get(getter());
- }
- /// \endcond
-#endif
-
- ///
- /// File stream with additional std::filesystem features
- ///
- template
- class basic_fstream : public std::basic_fstream<_Elem, _Traits>
- {
- public:
- using _Mybase = std::basic_fstream<_Elem, _Traits>;
-#if _HAS_CXX20
- using time_point = std::chrono::time_point;
-#else
- using time_point = std::chrono::time_point;
-#endif
-
- basic_fstream() {}
-
- explicit basic_fstream(
- _In_z_ const char* file_name,
- _In_ ios_base::openmode mode = ios_base::in | ios_base::out,
- _In_ int prot = ios_base::_Default_open_prot) : _Mybase(file_name, mode, prot) {}
-
- explicit basic_fstream(
- _In_z_ const wchar_t* file_name,
- _In_ ios_base::openmode mode = ios_base::in | ios_base::out,
- _In_ int prot = ios_base::_Default_open_prot) : _Mybase(file_name, mode, prot) {}
-
- template
- explicit basic_fstream(
- _In_ const std::basic_string<_Elem2, _Traits2, _Ax>& str,
- _In_ ios_base::openmode mode = ios_base::in | ios_base::out,
- _In_ int prot = ios_base::_Default_open_prot) : basic_fstream(str.c_str(), mode, prot) {}
-
- explicit basic_fstream(_In_ FILE* file) : _Mybase(file) {}
-
- basic_fstream(_Inout_ basic_fstream&& other) : _Mybase(std::move(other)) {}
-
- ///
- /// Sets end of file at current put position
- ///
- void truncate()
- {
- flush();
- auto h = os_fhandle();
-#ifdef _WIN32
- if (h == INVALID_HANDLE_VALUE)
- throw std::runtime_error("invalid handle");
- auto pos = tellp();
- LONG
- pos_lo = static_cast(pos & 0xffffffff),
- pos_hi = static_cast((pos >> 32) & 0xffffffff);
- if (SetFilePointer(h, pos_lo, &pos_hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
- throw std::runtime_error("failed to seek");
- if (!SetEndOfFile(h))
- throw std::runtime_error("failed to truncate");
-#else
-#error Implement!
-#endif
- }
-
- ///
- /// Returns file modification time
- ///
- /// \returns File modification time
- ///
- time_point mtime() const
- {
- auto h = os_fhandle();
-#ifdef _WIN32
- if (h == INVALID_HANDLE_VALUE)
- throw std::runtime_error("invalid handle");
- FILETIME ft;
- if (!GetFileTime(h, NULL, NULL, &ft))
- throw std::runtime_error("failed to get mtime");
-#if _HAS_CXX20
- return time_point(time_point::duration(((static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime)));
-#else
- // Adjust epoch to std::chrono::time_point/time_t.
- return time_point(time_point::duration(((static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ll));
-#endif
-#else
-#error Implement!
-#endif
- }
-
- protected:
-#ifdef _WIN32
- HANDLE os_fhandle() const
- {
- FILE* f = filebuf_fhandle(rdbuf());
- if (f == NULL)
- return INVALID_HANDLE_VALUE;
-
- int fd = _fileno(f);
- if (fd == -1)
- return INVALID_HANDLE_VALUE;
-
- return (HANDLE)_get_osfhandle(fd);
- }
-#else
-#error Implement!
-#endif
- };
-
- using fstream = basic_fstream>;
- using wfstream = basic_fstream>;
-
- ///
- /// String stream
- ///
- template
- class basic_stringstream : public std::basic_stringstream<_Elem, _Traits, _Alloc> {
- public:
- using _Mybase = std::basic_stringstream<_Elem, _Traits, _Alloc>;
- using _Mystr = std::basic_string<_Elem, _Traits, _Alloc>;
-
- basic_stringstream() {}
- explicit basic_stringstream(_In_ std::ios_base::openmode mode) : _Mybase(mode) {}
- explicit basic_stringstream(_In_ const _Mystr& str, _In_ std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : _Mybase(str, mode) {}
- basic_stringstream(_Inout_ basic_stringstream&& other) : _Mybase(std::move(other)) {}
-
- ///
- /// Initializes stream with content from file.
- ///
- /// \param[in] filename File name
- /// \param[in] mode Mode flags to open file. The std::stringstream returned is always opened as in|out.
- /// \param[in] prot Protection flags to open file
- ///
- template
- explicit basic_stringstream(_In_z_ const T* filename, _In_ std::ios_base::openmode mode = std::ios_base::in, _In_ int prot = std::ios_base::_Default_open_prot) :
- _Mybase(std::ios_base::in | std::ios_base::out | (mode & std::ios_base::binary | std::ios_base::app))
- {
- std::basic_ifstream<_Elem, _Traits> input(filename, mode & ~(std::ios_base::ate | std::ios_base::app), prot);
- input.seekg(0, input.end);
- auto size = input.tellg();
- if (size > SIZE_MAX)
- throw std::runtime_error("file too big to fit into memory");
- str().reserve(static_cast(size));
- input.seekg(0);
- do {
- _Elem buf[0x1000];
- input.read(buf, _countof(buf));
- write(buf, input.gcount());
- } while (!input.eof());
- if (!(mode & (std::ios_base::ate | std::ios_base::app)))
- seekp(0);
- }
-
- ///
- /// Initializes stream with content from file.
- ///
- /// \param[in] filename File name
- /// \param[in] mode Mode flags to open file. The std::stringstream returned is always opened as in|out.
- /// \param[in] prot Protection flags to open file
- ///
- template , class _Alloc2 = std::allocator<_Elem2>>
- explicit basic_stringstream(_In_ const std::basic_string<_Elem2, _Traits2, _Alloc2>& filename, _In_ std::ios_base::openmode mode = std::ios_base::in, _In_ int prot = std::ios_base::_Default_open_prot) :
- basic_stringstream(filename.c_str(), mode, prot)
- {}
-
- ///
- /// Saves stream content to a file.
- ///
- /// \param[in] filename File name
- /// \param[in] mode Mode flags to open file
- /// \param[in] prot Protection flags to open file
- ///
- template
- void save(_In_z_ const T* filename, _In_ std::ios_base::openmode mode = std::ios_base::out, _In_ int prot = std::ios_base::_Default_open_prot)
- {
- std::basic_ofstream<_Elem, _Traits> output(filename, mode, prot);
- auto origin = tellg();
- seekg(0, end);
- auto size = tellg();
- do {
- _Elem buf[0x1000];
- read(buf, _countof(buf));
- output.write(buf, gcount());
- } while (!eof());
- seekg(origin);
- }
-
- template , class _Alloc2 = std::allocator>
- void save(_In_ const std::basic_string<_Elem2, _Traits2, _Alloc2>& filename, _In_ std::ios_base::openmode mode = std::ios_base::out, _In_ int prot = std::ios_base::_Default_open_prot)
- {
- save(filename.data(), mode, prot);
- }
- };
-
- using stringstream = basic_stringstream, std::allocator>;
- using wstringstream = basic_stringstream, std::allocator>;
-}
diff --git a/include/stdex/sal.hpp b/include/stdex/sal.hpp
index c12645b7d..8101d2411 100644
--- a/include/stdex/sal.hpp
+++ b/include/stdex/sal.hpp
@@ -64,3 +64,25 @@
#ifndef _Success_
#define _Success_(p)
#endif
+#ifndef _Ret_notnull_
+#define _Ret_notnull_
+#endif
+#ifndef _Must_inspect_result_
+#define _Must_inspect_result_
+#endif
+
+#ifndef _Likely_
+#if _HAS_CXX20
+#define _Likely_ [[likely]]
+#else
+#define _Likely_
+#endif
+#endif
+
+#ifndef _Unlikely_
+#if _HAS_CXX20
+#define _Unlikely_ [[unlikely]]
+#else
+#define _Unlikely_
+#endif
+#endif
diff --git a/include/stdex/stream.hpp b/include/stdex/stream.hpp
new file mode 100644
index 000000000..7d23f0f47
--- /dev/null
+++ b/include/stdex/stream.hpp
@@ -0,0 +1,3664 @@
+/*
+ SPDX-License-Identifier: MIT
+ Copyright © 2023 Amebis
+*/
+
+#pragma once
+
+#include "endian.hpp"
+#include "interval.hpp"
+#include "math.hpp"
+#include "ring.hpp"
+#include "sal.hpp"
+#include "string.hpp"
+#include "system.hpp"
+#include "unicode.hpp"
+#include
+#include
+#include
+#if defined(_WIN32) && !defined(WIN32_LEAN_AND_MEAN)
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if !defined(SET_FILE_OP_TIMES) && defined(RDAT_BELEZI_CAS_DOSTOPA_VER)
+#define SET_FILE_OP_TIMES 1
+#pragma message("RDAT_BELEZI_CAS_DOSTOPA_VER is deprecated. Use SET_FILE_OP_TIMES instead.")
+#elif !defined(SET_FILE_OP_TIMES)
+#define SET_FILE_OP_TIMES 0
+#endif
+#if !defined(CHECK_STREAM_STATE) && defined(RDAT_NE_PREVERJAJ_STANJA_VER)
+#define CHECK_STREAM_STATE 0
+#pragma message("RDAT_NE_PREVERJAJ_EOF_VER is deprecated. Use CHECK_STREAM_STATE=0 instead.")
+#else
+#define CHECK_STREAM_STATE 1
+#endif
+
+namespace stdex
+{
+ namespace stream
+ {
+ ///
+ /// Stream internal state
+ ///
+ enum class state_t {
+ ok = 0,
+ eof,
+ fail,
+ };
+
+ ///
+ /// File size
+ ///
+ using fsize_t = uint64_t;
+ constexpr fsize_t fsize_max = UINT64_MAX;
+
+ constexpr size_t iterate_count = 0x10;
+ constexpr size_t default_block_size = 0x10000; ///< Amount of space used by copy or reallocation increments
+ constexpr wchar_t utf16_bom = L'\ufeff'; ///< Byte-order-mark written at each UTF-16 file start
+ constexpr const char utf8_bom[3] = { '\xef', '\xbb', '\xbf' }; ///> UTF-8 byte-order-mark
+
+ ///
+ /// Basic stream operations
+ ///
+ class basic
+ {
+ public:
+ basic(_In_ state_t state = state_t::ok) : m_state(state) {}
+
+ ///
+ /// Reads block of data from the stream
+ ///
+ /// \param[out] data Buffer to store read data
+ /// \param[in] length Byte limit of data to read
+ ///
+ /// \return Number of bytes succesfully read.
+ /// On EOF, 0 is returned and stream state is set to state_t::eof.
+ /// On error, 0 is returned and stream state is set to state_t::fail.
+ /// On null reads (length == 0), 0 is returned and stream state is set to state_t::ok.
+ ///
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ UNREFERENCED_PARAMETER(data);
+ UNREFERENCED_PARAMETER(length);
+ m_state = state_t::fail;
+ return 0;
+ }
+
+ ///
+ /// Writes block of data to the stream
+ ///
+ /// \param[in] data Buffer to write data from
+ /// \param[in] length Number of bytes to write
+ ///
+ /// \return Number of bytes succesfully written.
+ /// On error, stream state is set to state_t::fail.
+ ///
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ UNREFERENCED_PARAMETER(data);
+ UNREFERENCED_PARAMETER(length);
+ m_state = state_t::fail;
+ return 0;
+ }
+
+ ///
+ /// Persists volatile element data
+ ///
+ virtual void flush()
+ {
+ m_state = state_t::ok;
+ }
+
+ ///
+ /// Closes the stream
+ ///
+ virtual void close()
+ {
+ m_state = state_t::ok;
+ }
+
+ ///
+ /// Skips given amount of bytes of data on the stream
+ ///
+ virtual void skip(_In_ fsize_t amount)
+ {
+ if (amount == 1)
+ read_byte();
+ else if (amount < iterate_count) {
+ for (size_t i = 0; i < static_cast(amount); i++) {
+ read_byte();
+ if (!ok()) _Unlikely_
+ break;
+ }
+ }
+ else {
+ size_t block = static_cast(std::min(amount, default_block_size));
+ try {
+ std::unique_ptr dummy(new uint8_t[block]);
+ while (amount) {
+ amount -= read_array(dummy.get(), sizeof(uint8_t), static_cast(std::min(amount, block)));
+ if (!ok()) _Unlikely_
+ break;
+ }
+ }
+ catch (std::bad_alloc) { m_state = state_t::fail; }
+ }
+ }
+
+ ///
+ /// Returns stream state after last operation
+ ///
+ inline state_t state() const { return m_state; };
+
+ ///
+ /// Returns true if the stream state is clean i.e. previous operation was succesful
+ ///
+ inline bool ok() const { return m_state == state_t::ok; };
+
+ ///
+ /// Reads and returns remainder of the stream
+ ///
+ /// \param[in] max_length Byte limit of data to read
+ ///
+ /// \return Data read
+ ///
+ virtual std::vector read_remainder(_In_ size_t max_length = SIZE_MAX)
+ {
+ std::vector result;
+ size_t offset, length;
+ offset = 0;
+ length = default_block_size;
+ while (offset < max_length) {
+ length = std::min(length, max_length);
+ try { result.resize(length); }
+ catch (std::bad_alloc) {
+ m_state = state_t::fail;
+ return result;
+ }
+ auto num_read = read_array(result.data() + offset, sizeof(uint8_t), length - offset);
+ offset += num_read;
+ if (!ok()) _Unlikely_
+ break;
+ length += default_block_size;
+ }
+ result.resize(offset);
+ return result;
+ }
+
+ ///
+ /// Reads one byte of data
+ ///
+ inline uint8_t read_byte()
+ {
+ uint8_t byte;
+ if (read_array(&byte, sizeof(byte), 1) == 1)
+ return byte;
+ throw std::runtime_error("failed to read");
+ }
+
+ ///
+ /// Writes a byte of data
+ ///
+ void write_byte(_In_ uint8_t byte, _In_ fsize_t amount = 1)
+ {
+ if (amount == 1)
+ write(&byte, sizeof(uint8_t));
+ else if (amount < iterate_count) {
+ for (size_t i = 0; i < static_cast(amount); i++) {
+ write(&byte, sizeof(uint8_t));
+ if (!ok()) _Unlikely_
+ break;
+ }
+ }
+ else {
+ size_t block = static_cast(std::min(amount, default_block_size));
+ try {
+ std::unique_ptr dummy(new uint8_t[block]);
+ memset(dummy.get(), byte, block);
+ while (amount) {
+ amount -= write_array(dummy.get(), sizeof(uint8_t), static_cast(std::min(amount, block)));
+ if (!ok()) _Unlikely_
+ break;
+ }
+ }
+ catch (std::bad_alloc) { m_state = state_t::fail; }
+ }
+ }
+
+ ///
+ /// Reads one primitive data type
+ ///
+ /// This method is intended for chaining: e.g. stream.read_data(a).read_data(b).read_data(c)...
+ /// Since it would make it impossible to detect if any of the read_data(a) or read_data(b) failed should
+ /// read_data(c) succeed, the method skips reading if stream state is not ok.
+ ///
+ /// \param[in] data Where to store read data
+ ///
+ /// \returns This stream
+ ///
+ template
+ inline basic& read_data(_Out_ T& data)
+ {
+ if (!ok()) _Unlikely_ {
+ data = 0;
+ return *this;
+ }
+ if (read_array(&data, sizeof(T), 1) == 1)
+ LE2HE(&data);
+ else {
+ data = 0;
+ if (ok())
+ m_state = state_t::eof;
+ }
+ return *this;
+ }
+
+ ///
+ /// Writes one primitive data type
+ ///
+ /// This method is intended for chaining: e.g. stream.write_data(a).write_data(b).write_data(c)...
+ /// Since it would make it impossible to detect if any of the write_data(a) or write_data(b) failed should
+ /// write_data(c) succeed, the method skips writing if stream state is not ok.
+ ///
+ /// \param[in] data Data to write
+ ///
+ /// \return This stream
+ ///
+ template
+ inline basic& write_data(_In_ const T data)
+ {
+ if (!ok()) _Unlikely_
+ return *this;
+#ifdef BIG_ENDIAN
+ T data_le = HE2LE(data);
+ write(&data_le, sizeof(T));
+#else
+ write(&data, sizeof(T));
+#endif
+ return *this;
+ }
+
+ ///
+ /// Reads stream to the end-of-line or end-of-file.
+ ///
+ /// \return Number of read characters
+ ///
+ template, class _Ax = std::allocator>
+ inline size_t readln(_Inout_ std::basic_string& str)
+ {
+ str.clear();
+ return readln_and_attach(str);
+ }
+
+ ///
+ /// Reads stream to the end-of-line or end-of-file.
+ ///
+ /// \return Number of read characters
+ ///
+ template, class _Ax = std::allocator>
+ inline size_t readln(_Inout_ std::basic_string& wstr)
+ {
+ wstr.clear();
+ return readln_and_attach(wstr);
+ }
+
+ ///
+ /// Reads stream to the end-of-line or end-of-file.
+ ///
+ /// \return Number of read characters
+ ///
+ template, class _Ax = std::allocator>
+ size_t readln(_Inout_ std::basic_string& wstr, _In_ charset_id charset)
+ {
+ if (charset == charset_id::utf16)
+ return readln(wstr);
+ std::string str;
+ readln_and_attach(str);
+ wstr.clear();
+ str2wstr(wstr, str, charset);
+ return wstr.size();
+ }
+
+ ///
+ /// Reads stream to the end-of-line or end-of-file and append to str.
+ ///
+ /// \return Total number of chars in str
+ ///
+ template, class _Ax = std::allocator<_Elem>>
+ size_t readln_and_attach(_Inout_ std::basic_string<_Elem, _Traits, _Ax>& str)
+ {
+ bool initial = true;
+ _Elem chr, previous = (_Elem)0;
+ do {
+ read_array(&chr, sizeof(_Elem), 1);
+ if (!initial && !(previous == static_cast<_Elem>('\r') && chr == static_cast<_Elem>('\n')))
+ str += previous;
+ else
+ initial = false;
+ previous = chr;
+ } while (ok() && chr != static_cast<_Elem>('\n'));
+ return str.size();
+ }
+
+ ///
+ /// Reads stream to the end-of-line or end-of-file and append to str.
+ ///
+ /// \return Total number of chars in str
+ ///
+ template, class _Ax = std::allocator>
+ size_t readln_and_attach(_Inout_ std::basic_string& wstr, _In_ charset_id charset)
+ {
+ if (charset == charset_id::utf16)
+ return readln_and_attach(wstr);
+ std::string str;
+ readln_and_attach(str);
+ str2wstr(wstr, str, charset);
+ return wstr.size();
+ }
+
+ ///
+ /// Reads an array of data from the stream
+ ///
+ /// \return Number of read elements
+ ///
+ size_t read_array(_Out_writes_bytes_(size* count) void* array, _In_ size_t size, _In_ size_t count)
+ {
+ for (size_t to_read = mul(size, count);;) {
+ size_t num_read = read(array, to_read);
+ to_read -= num_read;
+ if (!to_read)
+ return count;
+ if (!ok()) _Unlikely_
+ return count - to_read / size;
+ reinterpret_cast(array) += num_read;
+ }
+ }
+
+ ///
+ /// Writes an array of data to the stream
+ ///
+ /// \return Number of elements written
+ ///
+ inline size_t write_array(_In_reads_bytes_opt_(size* count) const void* array, _In_ size_t size, _In_ size_t count)
+ {
+ return write(array, mul(size, count)) / size;
+ }
+
+ ///
+ /// Writes array of characters to the stream
+ ///
+ /// \param[in] wstr String to write
+ /// \param[in] num_chars String code unit count limit
+ /// \param[in] charset Charset to convert string to
+ ///
+ /// \return Number of code units written
+ ///
+ size_t write_array(_In_reads_or_z_opt_(num_chars) const wchar_t* wstr, _In_ size_t num_chars, _In_ charset_id charset)
+ {
+ if (!ok()) _Unlikely_
+ return 0;
+ num_chars = stdex::strnlen(wstr, num_chars);
+ if (charset != charset_id::utf16) {
+ std::string str(wstr2str(wstr, num_chars, charset));
+ return write_array(str.data(), sizeof(char), str.size());
+ }
+ return write_array(wstr, sizeof(wchar_t), num_chars);
+ }
+
+ ///
+ /// Reads length-prefixed string from the stream
+ ///
+ /// This method is intended for chaining: e.g. stream.read_str(a).read_str(b).read_str(c)...
+ /// Since it would make it impossible to detect if any of the read_str(a) or read_str(b) failed should
+ /// read_str(c) succeed, the method skips reading if stream state is not ok.
+ ///
+ /// \param[in] data String to read to
+ ///
+ /// \return This stream
+ ///
+ template, class _Ax = std::allocator<_Elem>>
+ inline basic& read_str(_Inout_ std::basic_string<_Elem, _Traits, _Ax>& data)
+ {
+ uint32_t num_chars;
+ read_data(num_chars);
+ if (!ok()) _Unlikely_ {
+ data.clear();
+ return *this;
+ }
+ data.resize(num_chars);
+ data.resize(read_array(data.data(), sizeof(_Elem), num_chars));
+ return *this;
+ }
+
+ ///
+ /// Writes string to the stream length-prefixed
+ ///
+ /// This method is intended for chaining: e.g. stream.write_str(a).write_str(b).write_str(c)...
+ /// Since it would make it impossible to detect if any of the write_str(a) or write_str(b) failed should
+ /// write_str(c) succeed, the method skips writing if stream state is not ok.
+ ///
+ /// \param[in] data String to write
+ ///
+ /// \return This stream
+ ///
+ template
+ inline basic& write_str(_In_z_ const T* data)
+ {
+ // Stream state will be checked in write_data.
+ size_t num_chars = stdex::strlen(data);
+ if (num_chars > UINT32_MAX)
+ throw std::invalid_argument("string too long");
+ write_data((uint32_t)num_chars);
+ if (!ok()) _Unlikely_
+ return *this;
+ write_array(data, sizeof(T), num_chars);
+ return *this;
+ }
+
+#ifdef _WIN32
+ ///
+ /// Writes SAFEARRAY data
+ ///
+ /// \return Number of bytes written
+ ///
+ size_t write_sa(_In_ LPSAFEARRAY sa)
+ {
+ safearray_accessor a(sa);
+ long ubound, lbound;
+ if (FAILED(SafeArrayGetUBound(sa, 1, &ubound)) ||
+ FAILED(SafeArrayGetLBound(sa, 1, &lbound)))
+ throw std::invalid_argument("SafeArrayGet[UL]Bound failed");
+ return write(a.data(), static_cast(ubound) - lbound + 1);
+ }
+#endif
+
+ ///
+ /// Writes content of another stream
+ ///
+ /// \return Number of bytes written
+ ///
+ fsize_t write_stream(_Inout_ basic& stream, _In_ fsize_t amount = fsize_max)
+ {
+ std::unique_ptr data(new uint8_t[static_cast(std::min(amount, default_block_size))]);
+ fsize_t num_copied = 0, to_write = amount;
+ m_state = state_t::ok;
+ while (to_write) {
+ size_t num_read = stream.read(data.get(), static_cast(std::min(default_block_size, to_write)));
+ size_t num_written = write(data.get(), num_read);
+ num_copied += num_written;
+ to_write -= num_written;
+ if (stream.m_state == state_t::eof) {
+ // EOF is not an error.
+ m_state = state_t::ok;
+ break;
+ }
+ m_state = stream.m_state;
+ if (!ok())
+ break;
+ }
+ return num_copied;
+ }
+
+ ///
+ /// Writes UTF8 or UTF-16 byte-order-mark
+ ///
+ void write_charset(_In_ charset_id charset)
+ {
+ if (charset == charset_id::utf16)
+ write_data(utf16_bom);
+ else if (charset == charset_id::utf8)
+ write_array(utf8_bom, sizeof(utf8_bom), 1);
+ }
+
+ ///
+ /// Writes formatted string to the stream
+ ///
+ /// \return Number of characters written
+ ///
+ size_t write_sprintf(_In_z_ _Printf_format_string_params_(2) const char* format, _In_opt_ locale_t locale, ...)
+ {
+ va_list params;
+ va_start(params, locale);
+ size_t num_chars = write_vsprintf(format, locale, params);
+ va_end(params);
+ return num_chars;
+ }
+
+ ///
+ /// Writes formatted string to the stream
+ ///
+ /// \return Number of characters written
+ ///
+ size_t write_sprintf(_In_z_ _Printf_format_string_params_(2) const wchar_t* format, _In_opt_ locale_t locale, ...)
+ {
+ va_list params;
+ va_start(params, locale);
+ size_t num_chars = write_vsprintf(format, locale, params);
+ va_end(params);
+ return num_chars;
+ }
+
+ ///
+ /// Writes formatted string to the stream
+ ///
+ /// \return Number of characters written
+ ///
+ size_t write_vsprintf(_In_z_ _Printf_format_string_params_(2) const char* format, _In_opt_ locale_t locale, _In_ va_list params)
+ {
+ std::string str;
+ str.reserve(default_block_size);
+ vappendf(str, format, locale, params);
+ return write_array(str.data(), sizeof(char), str.size());
+ }
+
+ ///
+ /// Writes formatted string to the stream
+ ///
+ /// \return Number of characters written
+ ///
+ size_t write_vsprintf(_In_z_ _Printf_format_string_params_(2) const wchar_t* format, _In_opt_ locale_t locale, _In_ va_list params)
+ {
+ std::wstring str;
+ str.reserve(default_block_size);
+ vappendf(str, format, locale, params);
+ return write_array(str.data(), sizeof(wchar_t), str.size());
+ }
+
+ inline basic& operator >>(_Out_ int8_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const int8_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ int16_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const int16_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ int32_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const int32_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ int64_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const int64_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ uint8_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const uint8_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ uint16_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const uint16_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ uint32_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const uint32_t data) { return write_data(data); }
+ inline basic& operator >>(_Out_ uint64_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const uint64_t data) { return write_data(data); }
+#ifdef _NATIVE_SIZE_T_DEFINED
+ inline basic& operator >>(_Out_ size_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const size_t data) { return write_data(data); }
+#endif
+ inline basic& operator >>(_Out_ float& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const float data) { return write_data(data); }
+ inline basic& operator >>(_Out_ double& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const double data) { return write_data(data); }
+ inline basic& operator >>(_Out_ char& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const char data) { return write_data(data); }
+#ifdef _NATIVE_WCHAR_T_DEFINED
+ inline basic& operator >>(_Out_ wchar_t& data) { return read_data(data); }
+ inline basic& operator <<(_In_ const wchar_t data) { return write_data(data); }
+#endif
+ template, class _Ax = std::allocator<_Elem>>
+ inline basic& operator >>(_Inout_ std::basic_string<_Elem, _Traits, _Ax>& data) { return read_str(data); }
+ template
+ inline basic& operator <<(_In_ const T* data) { return write_str(data); }
+
+ protected:
+ state_t m_state;
+ };
+
+ ///
+ /// Absolute file position
+ ///
+ using fpos_t = uint64_t;
+ constexpr fpos_t fpos_max = UINT64_MAX;
+ constexpr fpos_t fpos_min = 0;
+
+ ///
+ /// Relative file position
+ ///
+ using foff_t = int64_t;
+ constexpr foff_t foff_max = INT64_MAX;
+ constexpr foff_t foff_min = INT64_MIN;
+
+ ///
+ /// Seek anchor
+ ///
+ enum class seek_t {
+#ifdef _WIN32
+ beg = FILE_BEGIN,
+ cur = FILE_CURRENT,
+ end = FILE_END
+#else
+ beg = SEEK_SET,
+ cur = SEEK_CUR,
+ end = SEEK_END
+#endif
+ };
+
+#if _HAS_CXX20
+ using time_point = std::chrono::time_point;
+#else
+ using time_point = std::chrono::time_point;
+#endif
+
+ ///
+ /// Basic seekable stream operations
+ ///
+ class basic_file : virtual public basic
+ {
+ public:
+ virtual std::vector read_remainder(_In_ size_t max_length = SIZE_MAX)
+ {
+ size_t length = std::min(max_length, static_cast(size() - tell()));
+ std::vector result;
+ try { result.resize(length); }
+ catch (std::bad_alloc) {
+ m_state = state_t::fail;
+ return result;
+ }
+ result.resize(read_array(result.data(), sizeof(uint8_t), length));
+ return result;
+ }
+
+ ///
+ /// Seeks to specified relative file position
+ ///
+ /// \return Absolute file position after seek, or fpos_max if seek failed.
+ ///
+ virtual fpos_t seek(_In_ foff_t offset, _In_ seek_t how = seek_t::beg) = 0;
+
+ ///
+ /// Seeks to absolute file position
+ ///
+ /// \return Absolute file position after seek
+ ///
+ inline fpos_t seekbeg(_In_ fpos_t offset) { return seek(offset, seek_t::beg); }
+
+ ///
+ /// Seeks to relative from current file position
+ ///
+ /// \return Absolute file position after seek
+ ///
+ inline fpos_t seekcur(_In_ foff_t offset) { return seek(offset, seek_t::cur); }
+
+ ///
+ /// Seeks to relative from end file position
+ ///
+ /// \return Absolute file position after seek
+ ///
+ inline fpos_t seekend(_In_ foff_t offset) { return seek(offset, seek_t::end); }
+
+ virtual void skip(_In_ fsize_t amount)
+ {
+ seek(amount, seek_t::cur);
+ }
+
+ ///
+ /// Returns absolute file position in file or fpos_max if fails.
+ /// This method does not update stream state.
+ ///
+ /// \return Absolute file position or fpos_max if position cannot be determined.
+ ///
+ virtual fpos_t tell() const = 0;
+
+ ///
+ /// Locks file section for exclusive access
+ ///
+ virtual void lock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ UNREFERENCED_PARAMETER(offset);
+ UNREFERENCED_PARAMETER(length);
+ throw std::exception("not implemented");
+ }
+
+ ///
+ /// Unlocks file section for exclusive access
+ ///
+ virtual void unlock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ UNREFERENCED_PARAMETER(offset);
+ UNREFERENCED_PARAMETER(length);
+ throw std::exception("not implemented");
+ }
+
+ ///
+ /// Returns file size
+ /// Should the file size cannot be determined, the method returns fsize_max and it does not reset the state to failed.
+ ///
+ virtual fsize_t size() = 0;
+
+ ///
+ /// Sets file size - truncates the remainder of file content from the current file position to the end of file.
+ ///
+ virtual void truncate() = 0;
+
+ ///
+ /// Returns file creation time
+ ///
+ virtual time_point ctime() const
+ {
+ return time_point::min();
+ }
+
+ ///
+ /// Returns file access time
+ ///
+ virtual time_point atime() const
+ {
+ return time_point::min();
+ }
+
+ ///
+ /// Returns file modification time
+ ///
+ virtual time_point mtime() const
+ {
+ return time_point::min();
+ }
+
+ ///
+ /// Sets file create time
+ ///
+ virtual void set_ctime(time_point date)
+ {
+ UNREFERENCED_PARAMETER(date);
+ throw std::exception("not implemented");
+ }
+
+ ///
+ /// Sets file access time
+ ///
+ virtual void set_atime(time_point date)
+ {
+ UNREFERENCED_PARAMETER(date);
+ throw std::exception("not implemented");
+ }
+
+ ///
+ /// Sets file modification time
+ ///
+ virtual void set_mtime(time_point date)
+ {
+ UNREFERENCED_PARAMETER(date);
+ throw std::exception("not implemented");
+ }
+
+#ifdef _WIN32
+ ///
+ /// Reads to SAFEARRAY data
+ ///
+ LPSAFEARRAY read_sa()
+ {
+ assert(size() <= SIZE_MAX);
+ size_t length = static_cast(size());
+ std::unique_ptr sa(SafeArrayCreateVector(VT_UI1, 0, (ULONG)length));
+ if (!sa)
+ throw std::runtime_error("SafeArrayCreateVector failed");
+ safearray_accessor a(sa.get());
+ if (seek(0) != 0)
+ throw std::runtime_error("failed to seek");
+ if (read_array(a.data(), 1, length) != length)
+ throw std::runtime_error("failed to read");
+ return sa.release();
+ }
+#endif
+
+ ///
+ /// Attempts to detect textfile charset based on UTF16 or UTF8 BOM.
+ ///
+ /// \param[in] default_charset Fallback charset to return when no BOM detected.
+ ///
+ charset_id read_charset(_In_ charset_id default_charset = charset_id::default)
+ {
+ if (seek(0) != 0)
+ throw std::runtime_error("failed to seek");
+ wchar_t id_utf16;
+ read_array(&id_utf16, sizeof(wchar_t), 1);
+ if (!ok()) _Unlikely_
+ return default_charset;
+ if (id_utf16 == utf16_bom)
+ return charset_id::utf16;
+
+ if (seek(0) != 0)
+ throw std::runtime_error("failed to seek");
+ char id_utf8[3] = { 0 };
+ read_array(id_utf8, sizeof(id_utf8), 1);
+ if (!ok()) _Unlikely_
+ return default_charset;
+ if (strncmp(id_utf8, _countof(id_utf8), utf8_bom, _countof(utf8_bom)) == 0)
+ return charset_id::utf8;
+
+ if (seek(0) != 0)
+ throw std::runtime_error("failed to seek");
+ return default_charset;
+ }
+ };
+
+ ///
+ /// Modifies data on the fly when reading from/writing to a source stream
+ ///
+ class converter : public basic
+ {
+ protected:
+ explicit converter() :
+ basic(state_t::fail),
+ m_source(nullptr)
+ {}
+
+ void init(_Inout_ basic& source)
+ {
+ m_state = source.state();
+ m_source = &source;
+ }
+
+ public:
+ converter(_Inout_ basic& source) :
+ basic(source.state()),
+ m_source(&source)
+ {}
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ size_t num_read = m_source->read(data, length);
+ m_state = m_source->state();
+ return num_read;
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ size_t num_written = m_source->write(data, length);
+ m_state = m_source->state();
+ return num_written;
+ }
+
+ virtual void close()
+ {
+ m_source->close();
+ m_state = m_source->state();
+ }
+
+ virtual void flush()
+ {
+ m_source->flush();
+ m_state = m_source->state();
+ }
+
+ protected:
+ basic* m_source;
+ };
+
+ ///
+ /// Replicates writing of the same data to multiple streams
+ ///
+ class replicator : public basic
+ {
+ public:
+ virtual ~replicator()
+ {
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ {
+ const std::lock_guard lk(_w->mutex);
+ _w->op = worker::op_t::quit;
+ }
+ _w->cv.notify_one();
+ }
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w)
+ w->get()->thread.join();
+ }
+
+ ///
+ /// Adds stream on the list.
+ ///
+ void push_back(_In_ basic* source)
+ {
+ m_workers.push_back(std::unique_ptr(new worker(source)));
+ }
+
+ ///
+ /// Removes stream from the list.
+ ///
+ void remove(basic* source)
+ {
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ if (_w->source == source) {
+ {
+ const std::lock_guard lk(_w->mutex);
+ _w->op = worker::op_t::quit;
+ }
+ _w->cv.notify_one();
+ _w->thread.join();
+ m_workers.erase(w);
+ return;
+ }
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ {
+ const std::lock_guard lk(_w->mutex);
+ _w->op = worker::op_t::write;
+ _w->data = data;
+ _w->length = length;
+ }
+ _w->cv.notify_one();
+ }
+ size_t num_written = length;
+ m_state = state_t::ok;
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ std::unique_lock lk(_w->mutex);
+ _w->cv.wait(lk, [&] {return _w->op == worker::op_t::noop; });
+ if (_w->num_written < num_written)
+ num_written = _w->num_written;
+ if (ok() && !_w->source->ok())
+ m_state = _w->source->state();
+ }
+ return num_written;
+ }
+
+ virtual void close()
+ {
+ foreach_worker(worker::op_t::close);
+ }
+
+ virtual void flush()
+ {
+ foreach_worker(worker::op_t::flush);
+ }
+
+ protected:
+ class worker
+ {
+ public:
+ worker(_In_ basic* _source) :
+ source(_source),
+ op(op_t::noop),
+ data(nullptr),
+ length(0),
+ num_written(0),
+ thread(process_op, std::ref(*this))
+ {}
+
+ protected:
+ static void process_op(_Inout_ worker& w)
+ {
+ for (;;) {
+ std::unique_lock lk(w.mutex);
+ w.cv.wait(lk, [&] {return w.op != op_t::noop; });
+ switch (w.op) {
+ case op_t::quit:
+ return;
+ case op_t::write:
+ w.num_written = w.source->write(w.data, w.length);
+ break;
+ case op_t::close:
+ w.source->close();
+ break;
+ case op_t::flush:
+ w.source->flush();
+ break;
+ }
+ w.op = op_t::noop;
+ lk.unlock();
+ w.cv.notify_one();
+ }
+ }
+
+ public:
+ basic* source;
+ enum class op_t {
+ noop = 0,
+ quit,
+ write,
+ close,
+ flush,
+ } op; ///< Operation to perform
+ const void* data; ///< Data to write
+ size_t length; ///< Byte limit of data to write
+ size_t num_written; ///< Number of bytes written
+ std::mutex mutex;
+ std::condition_variable cv;
+ std::thread thread;
+ };
+
+ void foreach_worker(_In_ worker::op_t op)
+ {
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ {
+ const std::lock_guard lk(_w->mutex);
+ _w->op = op;
+ }
+ _w->cv.notify_one();
+ }
+ m_state = state_t::ok;
+ for (auto w = m_workers.begin(), w_end = m_workers.end(); w != w_end; ++w) {
+ auto _w = w->get();
+ std::unique_lock lk(_w->mutex);
+ _w->cv.wait(lk, [&] {return _w->op == worker::op_t::noop; });
+ if (ok())
+ m_state = _w->source->state();
+ }
+ }
+
+ std::list> m_workers;
+ };
+
+ constexpr size_t default_async_limit = 0x100000; ///< Default queue limit for readahead/writeback (in bytes)
+
+ ///
+ /// Provides read-ahead stream capability
+ ///
+ /// @tparam CAPACITY Read-ahead buffer size
+ ///
+ template
+ class async_reader : public converter
+ {
+ public:
+ async_reader(_Inout_ basic& source) :
+ converter(source),
+ m_worker(process, std::ref(*this))
+ {}
+
+ virtual ~async_reader()
+ {
+ m_ring.quit();
+ m_worker.join();
+ }
+
+#pragma warning(suppress: 6101) // See [1] below
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ for (size_t to_read = length;;) {
+ uint8_t* ptr; size_t num_read;
+ std::tie(ptr, num_read) = m_ring.front();
+ if (!ptr) _Unlikely_ {
+ // [1] Code analysis misses length - to_read bytes were written to data in previous loop iterations.
+ m_state = to_read < length || !length ? state_t::ok : m_source->state();
+ return length - to_read;
+ }
+ if (to_read < num_read)
+ num_read = to_read;
+ memcpy(data, ptr, num_read);
+ m_ring.pop(num_read);
+ to_read -= num_read;
+ if (!to_read) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_read;
+ }
+ }
+
+ protected:
+ static void process(_Inout_ async_reader& w)
+ {
+ for (;;) {
+ uint8_t* ptr; size_t num_write;
+ std::tie(ptr, num_write) = w.m_ring.back();
+ if (!ptr) _Unlikely_
+ break;
+ num_write = w.m_source->read(ptr, num_write);
+ w.m_ring.push(num_write);
+ if (!w.m_source->ok()) {
+ w.m_ring.quit();
+ break;
+ }
+ }
+ }
+
+ protected:
+ ring m_ring;
+ std::thread m_worker;
+ };
+
+ ///
+ /// Provides write-back stream capability
+ ///
+ /// @tparam CAPACITY Write-back buffer size
+ ///
+ template
+ class async_writer : public converter
+ {
+ public:
+ async_writer(_Inout_ basic& source) :
+ converter(source),
+ m_worker(process, std::ref(*this))
+ {}
+
+ virtual ~async_writer()
+ {
+ m_ring.quit();
+ m_worker.join();
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ for (size_t to_write = length;;) {
+ uint8_t* ptr; size_t num_write;
+ std::tie(ptr, num_write) = m_ring.back();
+ if (!ptr) _Unlikely_ {
+ m_state = state_t::fail;
+ return length - to_write;
+ }
+ if (to_write < num_write)
+ num_write = to_write;
+ memcpy(ptr, data, num_write);
+ m_ring.push(num_write);
+ to_write -= num_write;
+ if (!to_write) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_write;
+ }
+ }
+
+ virtual void flush()
+ {
+ m_ring.sync();
+ converter::flush();
+ }
+
+ protected:
+ static void process(_Inout_ async_writer& w)
+ {
+ for (;;) {
+ uint8_t* ptr; size_t num_read;
+ std::tie(ptr, num_read) = w.m_ring.front();
+ if (!ptr)
+ break;
+ num_read = w.m_source->write(ptr, num_read);
+ w.m_ring.pop(num_read);
+ if (!w.m_source->ok()) {
+ w.m_ring.quit();
+ break;
+ }
+ }
+ }
+
+ protected:
+ ring m_ring;
+ std::thread m_worker;
+ };
+
+ constexpr size_t default_buffer_size = 0x400; ///< default buffer size
+
+ ///
+ /// Buffered read/write stream
+ ///
+ class buffer : public converter
+ {
+ protected:
+ explicit buffer(_In_ size_t read_buffer_size = default_buffer_size, _In_ size_t write_buffer_size = default_buffer_size) :
+ converter(),
+ m_read_buffer(read_buffer_size),
+ m_write_buffer(write_buffer_size)
+ {}
+
+ public:
+ buffer(_Inout_ basic& source, _In_ size_t read_buffer_size = default_buffer_size, _In_ size_t write_buffer_size = default_buffer_size) :
+ converter(source),
+ m_read_buffer(read_buffer_size),
+ m_write_buffer(write_buffer_size)
+ {}
+
+ virtual ~buffer()
+ {
+ if (m_source)
+ flush_write();
+ }
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ for (size_t to_read = length;;) {
+ size_t buffer_size = m_read_buffer.tail - m_read_buffer.head;
+ if (to_read <= buffer_size) {
+ memcpy(data, m_read_buffer.data + m_read_buffer.head, to_read);
+ m_read_buffer.head += to_read;
+ m_state = state_t::ok;
+ return length;
+ }
+ if (buffer_size) {
+ memcpy(data, m_read_buffer.data + m_read_buffer.head, buffer_size);
+ reinterpret_cast(data) += buffer_size;
+ to_read -= buffer_size;
+ }
+ m_read_buffer.head = 0;
+ if (to_read > m_read_buffer.capacity) {
+ // When needing to read more data than buffer capacity, bypass the buffer.
+ m_read_buffer.tail = 0;
+ to_read -= m_source->read(data, to_read);
+ m_state = to_read < length ? state_t::ok : m_source->state();
+ return length - to_read;
+ }
+ m_read_buffer.tail = m_source->read(m_read_buffer.data, m_read_buffer.capacity);
+ if (m_read_buffer.tail < m_read_buffer.capacity && m_read_buffer.tail < to_read) _Unlikely_ {
+ memcpy(data, m_read_buffer.data, m_read_buffer.tail);
+ m_read_buffer.head = m_read_buffer.tail;
+ to_read -= m_read_buffer.tail;
+ m_state = to_read < length ? state_t::ok : m_source->state();
+ return length - to_read;
+ }
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ if (!length) _Unlikely_ {
+ // Pass null writes (zero-byte length). Null write operations have special meaning with with Windows pipes.
+ flush_write();
+ if (!ok()) _Unlikely_
+ return 0;
+ m_source->write(nullptr, 0);
+ m_state = m_source->state();
+ return 0;
+ }
+
+ for (size_t to_write = length;;) {
+ size_t available_buffer = m_write_buffer.capacity - m_write_buffer.tail;
+ if (to_write <= available_buffer) {
+ memcpy(m_write_buffer.data + m_write_buffer.tail, data, to_write);
+ m_write_buffer.tail += to_write;
+ m_state = state_t::ok;
+ return length;
+ }
+ if (available_buffer) {
+ memcpy(m_write_buffer.data + m_write_buffer.tail, data, available_buffer);
+ reinterpret_cast(data) += available_buffer;
+ to_write -= available_buffer;
+ m_write_buffer.tail += available_buffer;
+ }
+ size_t buffer_size = m_write_buffer.tail - m_write_buffer.head;
+ if (buffer_size) {
+ m_write_buffer.head += m_source->write(m_write_buffer.data + m_write_buffer.head, buffer_size);
+ m_state = m_source->state();
+ if (m_write_buffer.head == m_write_buffer.tail)
+ m_write_buffer.head = m_write_buffer.tail = 0;
+ else
+ return length - to_write;
+ }
+ if (to_write > m_write_buffer.capacity) {
+ // When needing to write more data than buffer capacity, bypass the buffer.
+ to_write -= m_source->write(data, to_write);
+ m_state = m_source->state();
+ return length - to_write;
+ }
+ }
+ }
+
+ virtual void flush()
+ {
+ flush_write();
+ if (ok())
+ converter::flush();
+ }
+
+ protected:
+ void flush_write()
+ {
+ size_t buffer_size = m_write_buffer.tail - m_write_buffer.head;
+ if (buffer_size) {
+ m_write_buffer.head += m_source->write(m_write_buffer.data + m_write_buffer.head, buffer_size);
+ if (m_write_buffer.head == m_write_buffer.tail) {
+ m_write_buffer.head = 0;
+ m_write_buffer.tail = 0;
+ }
+ else {
+ m_state = m_source->state();
+ return;
+ }
+ }
+ m_state = state_t::ok;
+ }
+
+ struct buffer_t {
+ uint8_t* data;
+ size_t head, tail, capacity;
+
+ buffer_t(_In_ size_t buffer_size) :
+ head(0),
+ tail(0),
+ capacity(buffer_size),
+ data(buffer_size ? new uint8_t[buffer_size] : nullptr)
+ {}
+
+ ~buffer_t()
+ {
+ if (data)
+ delete[] data;
+ }
+ } m_read_buffer, m_write_buffer;
+ };
+
+ ///
+ /// Limits reading from/writing to stream to a predefined number of bytes
+ ///
+ class limiter : public converter
+ {
+ public:
+ limiter(_Inout_ basic& source, _In_ fsize_t _read_limit = 0, _In_ fsize_t _write_limit = 0) :
+ converter(source),
+ read_limit(_read_limit),
+ write_limit(_write_limit)
+ {}
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ size_t num_read;
+ if (read_limit == fsize_max) {
+ num_read = m_source->read(data, length);
+ m_state = m_source->state();
+ }
+ else if (length <= read_limit) {
+ num_read = m_source->read(data, length);
+ m_state = m_source->state();
+ read_limit -= num_read;
+ }
+ else if (length && !read_limit) {
+ num_read = 0;
+ m_state = state_t::eof;
+ }
+ else {
+ num_read = m_source->read(data, static_cast(read_limit));
+ m_state = m_source->state();
+ read_limit -= num_read;
+ }
+ return num_read;
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ size_t num_written;
+ if (write_limit == fsize_max) {
+ num_written = m_source->write(data, length);
+ m_state = m_source->state();
+ }
+ else if (length <= write_limit) {
+ num_written = m_source->write(data, length);
+ m_state = m_source->state();
+ write_limit -= num_written;
+ }
+ else if (length && !write_limit) {
+ num_written = 0;
+ m_state = state_t::fail;
+ }
+ else {
+ num_written = m_source->write(data, static_cast(write_limit));
+ m_state = m_source->state();
+ write_limit -= num_written;
+ }
+ return num_written;
+ }
+
+ public:
+ fsize_t
+ read_limit, ///< Number of bytes left that may be read from the stream
+ write_limit; ///< Number of bytes left, that can be written to the stream
+ };
+
+ ///
+ /// Limits reading from/writing to stream to a predefined window
+ ///
+ class window : public limiter
+ {
+ public:
+ window(_Inout_ basic& source, _In_ fpos_t _read_offset = 0, _In_ fsize_t read_limit = fsize_max, _In_ fpos_t _write_offset = 0, _In_ fsize_t write_limit = fsize_max) :
+ limiter(source, read_limit, write_limit),
+ read_offset(_read_offset),
+ write_offset(_write_offset)
+ {}
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ if (read_offset) {
+ m_source->skip(read_offset);
+ m_state = m_source->state();
+ if (!ok()) _Unlikely_
+ return 0;
+ read_offset = 0;
+ }
+ size_t num_read;
+ if (read_limit == fsize_max) {
+ num_read = m_source->read(data, length);
+ m_state = m_source->state();
+ }
+ else if (length <= read_limit) {
+ num_read = m_source->read(data, length);
+ m_state = m_source->state();
+ read_limit -= num_read;
+ }
+ else if (length && !read_limit) {
+ num_read = 0;
+ m_source->skip(length);
+ m_state = state_t::eof;
+ }
+ else {
+ num_read = m_source->read(data, static_cast(read_limit));
+ m_state = m_source->state();
+ read_limit -= num_read;
+ }
+ return num_read;
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ size_t num_skipped, num_written;
+ if (length <= write_offset) {
+ write_offset -= length;
+ m_state = state_t::ok;
+ return length;
+ }
+ if (write_offset) {
+ reinterpret_cast(data) += static_cast(write_offset);
+ length -= static_cast(write_offset);
+ num_skipped = static_cast(write_offset);
+ write_offset = 0;
+ }
+ else
+ num_skipped = 0;
+ if (write_limit == fsize_max) {
+ num_written = m_source->write(data, length);
+ m_state = m_source->state();
+ }
+ else if (length <= write_limit) {
+ num_written = m_source->write(data, length);
+ m_state = m_source->state();
+ write_limit -= num_written;
+ }
+ else if (length && !write_limit) {
+ num_skipped += length;
+ num_written = 0;
+ m_state = state_t::ok;
+ }
+ else {
+ num_skipped += length - static_cast(write_limit);
+ num_written = m_source->write(data, static_cast(write_limit));
+ m_state = m_source->state();
+ write_limit -= num_written;
+ }
+ return num_skipped + num_written;
+ }
+
+ public:
+ fpos_t
+ read_offset, ///< Number of bytes to skip on read
+ write_offset; ///< Number of bytes to discard on write
+ };
+
+ ///
+ /// Limits file reading/writing to a predefined window
+ ///
+ class file_window : public basic_file
+ {
+ public:
+ file_window(_Inout_ basic_file& source, fpos_t offset = 0, fsize_t length = 0) :
+ basic(source.state()),
+ m_source(source),
+ m_offset(source.tell()),
+ m_region(offset, offset + length)
+ {}
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ if (m_region.contains(m_offset)) {
+ size_t num_read = m_source.read(data, static_cast(std::min(length, m_region.end - m_offset)));
+ m_state = m_source.state();
+ m_offset += num_read;
+ return num_read;
+ }
+ m_state = length ? state_t::eof : state_t::ok;
+ return 0;
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ if (m_region.contains(m_offset)) {
+ size_t num_written = m_source.write(data, static_cast(std::min(length, m_region.end - m_offset)));
+ m_state = m_source.state();
+ m_offset += num_written;
+ return num_written;
+ }
+ m_state = state_t::fail;
+ return 0;
+ }
+
+ virtual void close()
+ {
+ m_source.close();
+ m_state = m_source.state();
+ }
+
+ virtual void flush()
+ {
+ m_source.flush();
+ m_state = m_source.state();
+ }
+
+ virtual fpos_t seek(_In_ foff_t offset, _In_ seek_t how = seek_t::beg)
+ {
+ m_offset = m_source.seek(offset, how);
+ m_state = m_source.state();
+ return ok() ? m_offset - m_region.start : fpos_max;
+ }
+
+ virtual void skip(_In_ fsize_t amount)
+ {
+ m_source.skip(amount);
+ m_state = m_source.state();
+ }
+
+ virtual fpos_t tell() const
+ {
+ fpos_t offset = m_source.tell();
+ return m_region.contains(offset) ? offset - m_region.start : fpos_max;
+ }
+
+ virtual void lock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ if (m_region.contains(offset)) {
+ m_source.lock(m_region.start + offset, std::min(length, m_region.end - offset));
+ m_state = m_source.state();
+ }
+ else
+ m_state = state_t::fail;
+ }
+
+ virtual void unlock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ if (m_region.contains(offset)) {
+ m_source.unlock(m_region.start + offset, std::min(length, m_region.end - offset));
+ m_state = m_source.state();
+ }
+ else
+ m_state = state_t::fail;
+ }
+
+ virtual fsize_t size()
+ {
+ return m_region.size();
+ }
+
+ virtual void truncate()
+ {
+ m_state = state_t::fail;
+ }
+
+ protected:
+ basic_file& m_source;
+ fpos_t m_offset;
+ interval m_region;
+ };
+
+ constexpr size_t default_cache_size = 0x1000; ///< privzeta velikost medpomnilnika
+
+ ///
+ /// Cached file
+ ///
+ class cache : public basic_file
+ {
+ protected:
+ explicit cache(_In_ size_t cache_size = default_cache_size) :
+ basic(state_t::fail),
+ m_source(nullptr),
+ m_cache(cache_size),
+ m_offset(0)
+#if SET_FILE_OP_TIMES
+ , m_atime(time_point::min()),
+ m_mtime(time_point::min())
+#endif
+ {}
+
+ void init(_Inout_ basic_file& source)
+ {
+ m_state = source.state();
+ m_source = &source;
+ m_offset = source.tell();
+ }
+
+ public:
+ cache(_Inout_ basic_file& source, _In_ size_t cache_size = default_cache_size) :
+ basic(source.state()),
+ m_source(&source),
+ m_cache(cache_size),
+ m_offset(source.tell())
+#if SET_FILE_OP_TIMES
+ , m_atime(time_point::min()),
+ m_mtime(time_point::min())
+#endif
+ {}
+
+ virtual ~cache()
+ {
+ if (m_source) {
+ flush_cache();
+ m_source->seek(m_offset);
+ }
+ }
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+#if SET_FILE_OP_TIMES
+ m_atime = time_point::now();
+#endif
+ for (size_t to_read = length;;) {
+ if (m_cache.status != cache_t::cache_t::status_t::empty) {
+ if (m_cache.region.contains(m_offset)) {
+ size_t remaining_cache = static_cast(m_cache.region.end - m_offset);
+ if (to_read <= remaining_cache) {
+ memcpy(data, m_cache.data + static_cast(m_offset - m_cache.region.start), to_read);
+ m_offset += to_read;
+ m_state = state_t::ok;
+ return length;
+ }
+ memcpy(data, m_cache.data + static_cast(m_offset - m_cache.region.start), remaining_cache);
+ reinterpret_cast(data) += remaining_cache;
+ to_read -= remaining_cache;
+ m_offset += remaining_cache;
+ }
+ flush_cache();
+ if (!ok()) _Unlikely_ {
+ if (to_read < length)
+ m_state = state_t::ok;
+ return length - to_read;
+ }
+ }
+ {
+ fpos_t end_max = m_offset + to_read;
+ if (m_offset / m_cache.capacity < end_max / m_cache.capacity) {
+ // Read spans multiple cache blocks. Bypass cache to the last block.
+ m_source->seek(m_offset);
+ if (!m_source->ok()) _Unlikely_ {
+ m_state = to_read < length ? state_t::ok : state_t::fail;
+ return length - to_read;
+ }
+ size_t num_read = m_source->read(data, to_read - static_cast(end_max % m_cache.capacity));
+ m_offset += num_read;
+ to_read -= num_read;
+ if (!to_read) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_read;
+ m_state = m_source->state();
+ if (!ok()) {
+ if (to_read < length)
+ m_state = state_t::ok;
+ return length - to_read;
+ }
+ }
+ }
+ load_cache(m_offset);
+ if (!ok() || m_cache.region.end <= m_offset) _Unlikely_ {
+ m_state = to_read < length ? state_t::ok : state_t::fail;
+ return length - to_read;
+ }
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+#if SET_FILE_OP_TIMES
+ m_atime = m_mtime = time_point::now();
+#endif
+ for (size_t to_write = length;;) {
+ if (m_cache.status != cache_t::cache_t::status_t::empty) {
+ fpos_t end_max = m_cache.region.start + m_cache.capacity;
+ if (m_cache.region.start <= m_offset && m_offset < end_max) {
+ size_t remaining_cache = static_cast(end_max - m_offset);
+ if (to_write <= remaining_cache) {
+ memcpy(m_cache.data + static_cast(m_offset - m_cache.region.start), data, to_write);
+ m_offset += to_write;
+ m_cache.status = cache_t::cache_t::status_t::dirty;
+ m_cache.region.end = std::max(m_cache.region.end, m_offset);
+ m_state = state_t::ok;
+ return length;
+ }
+ memcpy(m_cache.data + static_cast(m_offset - m_cache.region.start), data, remaining_cache);
+ reinterpret_cast(data) += remaining_cache;
+ to_write -= remaining_cache;
+ m_offset += remaining_cache;
+ m_cache.status = cache_t::cache_t::status_t::dirty;
+ m_cache.region.end = end_max;
+ }
+ flush_cache();
+ if (!ok()) _Unlikely_
+ return length - to_write;
+ }
+ {
+ fpos_t end_max = m_offset + to_write;
+ if (m_offset / m_cache.capacity < end_max / m_cache.capacity) {
+ // Write spans multiple cache blocks. Bypass cache to the last block.
+ m_source->seek(m_offset);
+ if (!ok()) _Unlikely_
+ return length - to_write;
+ size_t num_written = m_source->write(data, to_write - static_cast(end_max % m_cache.capacity));
+ reinterpret_cast(data) += num_written;
+ to_write -= num_written;
+ m_offset += num_written;
+ m_state = m_source->state();
+ if (!to_write || !ok())
+ return length - to_write;
+ }
+ }
+ load_cache(m_offset);
+ if (!ok()) _Unlikely_
+ return length - to_write;
+ }
+ }
+
+ virtual void close()
+ {
+ flush_cache();
+ m_source->close();
+ m_state = m_source->state();
+ }
+
+ virtual void flush()
+ {
+#if SET_FILE_OP_TIMES
+ m_atime = m_mtime = time_point::min();
+#endif
+ flush_cache();
+ if (!ok()) _Unlikely_
+ return;
+ m_source->flush();
+ }
+
+ virtual fpos_t seek(_In_ foff_t offset, _In_ seek_t how = seek_t::beg)
+ {
+ m_state = state_t::ok;
+ switch (how) {
+ case seek_t::beg:
+ return m_offset = offset;
+ case seek_t::cur:
+ return m_offset += offset;
+ case seek_t::end:
+ return m_offset = size() + offset;
+ default:
+ throw std::invalid_argument("unknown seek origin");
+ }
+ }
+
+ virtual fpos_t tell() const
+ {
+ return m_offset;
+ }
+
+ virtual void lock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ m_source->lock(offset, length);
+ m_state = m_source->state();
+ }
+
+ virtual void unlock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+ m_source->unlock(offset, length);
+ m_state = m_source->state();
+ }
+
+ virtual fsize_t size()
+ {
+ return m_cache.data ? std::max(m_source->size(), m_cache.region.end) : m_source->size();
+ }
+
+ virtual void truncate()
+ {
+#if SET_FILE_OP_TIMES
+ m_atime = m_mtime = time_point::now();
+#endif
+ m_source->seek(m_offset);
+ if (m_cache.region.end <= m_offset) {
+ // Truncation does not affect cache.
+ }
+ else if (m_cache.region.start <= m_offset) {
+ // Truncation truncates cache.
+ m_cache.region.end = m_offset;
+ }
+ else {
+ // Truncation invalidates cache.
+ m_cache.region = 0;
+ m_cache.status = cache_t::cache_t::status_t::empty;
+ }
+ m_source->truncate();
+ m_state = m_source->state();
+ }
+
+ virtual time_point ctime() const
+ {
+ return m_source->ctime();
+ }
+
+ virtual time_point atime() const
+ {
+#if SET_FILE_OP_TIMES
+ return std::max(m_atime, m_source->atime());
+#else
+ return m_source->atime();
+#endif
+ }
+
+ virtual time_point mtime() const
+ {
+#if SET_FILE_OP_TIMES
+ return std::max(m_mtime, m_source->mtime());
+#else
+ return m_source->mtime();
+#endif
+ }
+
+ virtual void set_ctime(time_point date)
+ {
+ m_source->set_ctime(date);
+ }
+
+ virtual void set_atime(time_point date)
+ {
+#if SET_FILE_OP_TIMES
+ m_atime = date;
+#endif
+ m_source->set_atime(date);
+ }
+
+ virtual void set_mtime(time_point date)
+ {
+#if SET_FILE_OP_TIMES
+ m_mtime = date;
+#endif
+ m_source->set_mtime(date);
+ }
+
+ protected:
+ void flush_cache()
+ {
+ if (m_cache.status != cache_t::cache_t::status_t::dirty) {
+ m_state = state_t::ok;
+ }
+ else if (!m_cache.region.empty()) {
+ m_source->seek(m_cache.region.start);
+ m_source->write(m_cache.data, static_cast(m_cache.region.size()));
+ m_state = m_source->state();
+ if (ok())
+ m_cache.status = cache_t::cache_t::status_t::loaded;
+ }
+ else {
+ m_state = state_t::ok;
+ m_cache.status = cache_t::cache_t::status_t::loaded;
+ }
+ }
+
+ void invalidate_cache()
+ {
+ flush_cache();
+ if (ok()) {
+ m_cache.region = 0;
+ m_cache.status = cache_t::cache_t::status_t::empty;
+ }
+ }
+
+ void load_cache(_In_ fpos_t start)
+ {
+ assert(m_cache.status != cache_t::cache_t::status_t::dirty);
+ start -= start % m_cache.capacity; // Align to cache block size.
+ m_source->seek(m_cache.region.start = start);
+ if (m_source->ok()) {
+ m_cache.region.end = start + m_source->read(m_cache.data, m_cache.capacity);
+ m_cache.status = cache_t::cache_t::status_t::loaded;
+ m_state = state_t::ok; // Regardless the read failure, we still might have cached some data.
+ }
+ else
+ m_state = state_t::fail;
+ }
+
+ basic_file* m_source;
+ struct cache_t {
+ uint8_t* data;
+ size_t capacity;
+ enum class status_t {
+ empty = 0,
+ loaded,
+ dirty,
+ } status;
+ interval region; ///< valid data region
+
+ cache_t(_In_ size_t _capacity) :
+ data(new uint8_t[_capacity]),
+ capacity(_capacity),
+ status(status_t::empty),
+ region(0)
+ {}
+
+ ~cache_t()
+ {
+ delete[] data;
+ }
+ } m_cache;
+ fpos_t m_offset; ///< Logical absolute file position
+#if SET_FILE_OP_TIMES
+ time_point
+ m_atime,
+ m_mtime;
+#endif
+ };
+
+ ///
+ /// OS data stream (file, pipe, socket...)
+ ///
+ class basic_sys : virtual public basic, public sys_object
+ {
+ public:
+ basic_sys(_In_opt_ sys_handle h = invalid_handle, _In_ state_t state = state_t::ok) :
+ basic(state),
+ sys_object(h)
+ {}
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ // Windows Server 2003 and Windows XP: Pipe write operations across a network are limited in size per write.
+ // The amount varies per platform. For x86 platforms it's 63.97 MB. For x64 platforms it's 31.97 MB. For Itanium
+ // it's 63.95 MB. For more information regarding pipes, see the Remarks section.
+ size_t
+#if defined(_WIN64)
+ block_size = 0x1F80000;
+#elif defined(_WIN32)
+ block_size = 0x3f00000;
+#else
+ block_size = SSIZE_MAX;
+#endif
+ for (size_t to_read = length;;) {
+#ifdef _WIN32
+ // ReadFile() might raise exception (e.g. STATUS_FILE_BAD_FORMAT/0xE0000002).
+ BOOL succeeded;
+ DWORD num_read;
+ __try { succeeded = ReadFile(m_h, data, static_cast(std::min(to_read, block_size)), &num_read, nullptr); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { succeeded = FALSE; SetLastError(ERROR_UNHANDLED_EXCEPTION); num_read = 0; }
+ if (!succeeded && GetLastError() == ERROR_NO_SYSTEM_RESOURCES && block_size > default_block_size) _Unlikely_ {
+ // Error "Insufficient system resources exist to complete the requested service." occurs
+ // ocasionally, when attempting to read too much data at once (e.g. over \\TSClient).
+ block_size = default_block_size;
+ continue;
+ }
+ if (!succeeded) _Unlikely_
+#else
+ ssize_t num_read = static_cast(std::min(to_read, block_size));
+ num_read = read(m_h, data, num_read);
+ if (num_read < 0) _Unlikely_
+#endif
+ {
+ m_state = to_read < length ? state_t::ok : state_t::fail;
+ return length - to_read;
+ }
+ if (!num_read) _Unlikely_ {
+ m_state = to_read < length || !length ? state_t::ok : state_t::eof;
+ return length - to_read;
+ }
+ to_read -= num_read;
+ if (!to_read) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_read;
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ // Windows Server 2003 and Windows XP: Pipe write operations across a network are limited in size per write.
+ // The amount varies per platform. For x86 platforms it's 63.97 MB. For x64 platforms it's 31.97 MB. For Itanium
+ // it's 63.95 MB. For more information regarding pipes, see the Remarks section.
+ constexpr size_t
+#if defined(_WIN64)
+ block_size = 0x1F80000;
+#elif defined(_WIN32)
+ block_size = 0x3f00000;
+#else
+ block_size = SSIZE_MAX;
+#endif
+ for (size_t to_write = length;;) {
+#ifdef _WIN32
+ // ReadFile() might raise an exception. Be cautious with WriteFile() too.
+ BOOL succeeded;
+ DWORD num_written;
+ __try { succeeded = WriteFile(m_h, data, static_cast(std::min(to_write, block_size)), &num_written, nullptr); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { succeeded = FALSE; SetLastError(ERROR_UNHANDLED_EXCEPTION); num_written = 0; }
+ to_write -= num_written;
+ if (!to_write) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_written;
+ if (!succeeded) _Unlikely_ {
+ m_state = state_t::fail;
+ return length - to_write;
+ }
+#else
+ ssize_t num_written = write(m_h, data, static_cast(std::min(to_write, block_size)));
+ if (num_written < 0) _Unlikely_ {
+ m_state = state_t::fail;
+ return length - to_write;
+ }
+ to_write -= num_written;
+ if (!to_write) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_written;
+#endif
+ }
+ }
+
+ virtual void close()
+ {
+ try {
+ sys_object::close();
+ m_state = state_t::ok;
+ }
+ catch (std::exception) {
+ m_state = state_t::fail;
+ }
+ }
+
+ virtual void flush()
+ {
+#ifdef _WIN32
+ m_state = FlushFileBuffers(m_h) ? state_t::ok : state_t::fail;
+#else
+ m_state = fsync(m_h) >= 0 ? state_t::ok : state_t::fail;
+#endif
+ }
+ };
+
+ ///
+ /// Buffered OS data stream (file, pipe, socket...)
+ ///
+ class buffered_sys : public buffer
+ {
+ public:
+ buffered_sys(_In_opt_ sys_handle h = invalid_handle, size_t read_buffer_size = default_buffer_size, size_t write_buffer_size = default_buffer_size) :
+ buffer(read_buffer_size, write_buffer_size),
+ m_source(h)
+ {
+ init(m_source);
+ }
+
+ protected:
+ basic_sys m_source;
+ };
+
+#ifdef _WIN32
+ ///
+ /// Wrapper for ISequentialStream
+ ///
+ class ISequentialStream : public basic
+ {
+ public:
+ ISequentialStream(_In_::ISequentialStream* source) : m_source(source)
+ {
+ m_source->AddRef();
+ }
+
+ virtual ~ISequentialStream()
+ {
+ m_source->Release();
+ }
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ for (size_t to_read = length;;) {
+ HRESULT hr;
+ ULONG num_read = 0;
+ __try { hr = m_source->Read(data, (ULONG)std::min(to_read, ULONG_MAX), &num_read); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { hr = E_FAIL; }
+ if (FAILED(hr)) _Unlikely_ {
+ m_state = to_read < length ? state_t::ok : state_t::fail;
+ return length - to_read;
+ }
+ to_read -= num_read;
+ if (hr == S_FALSE) _Unlikely_ {
+ m_state = to_read < length || !length ? state_t::ok : state_t::eof;
+ return length - to_read;
+ }
+ if (!to_read) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_read;
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ for (size_t to_write = length;;) {
+ HRESULT hr;
+ ULONG num_written = 0;
+ __try { hr = m_source->Write(data, static_cast(std::min(to_write, ULONG_MAX)), &num_written); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { hr = E_FAIL; }
+ // In abscence of documentation whether num_written gets set when FAILED(hr) (i.e. partially succesful writes),
+ // assume write failed completely.
+ if (FAILED(hr)) _Unlikely_ {
+ m_state = state_t::fail;
+ return length - to_write;
+ }
+ to_write -= num_written;
+ if (!to_write) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_written;
+ }
+ }
+
+ protected:
+ ::ISequentialStream* m_source;
+ };
+
+#ifndef WIN32_LEAN_AND_MEAN
+ ///
+ /// Wrapper for IIS ASP IRequest and IResponse
+ ///
+ class asp : public basic
+ {
+ public:
+ asp(_In_opt_ IRequest* request, _In_opt_ IResponse* response) :
+ m_request(request),
+ m_response(response)
+ {
+ if (m_request)
+ m_request->AddRef();
+ if (m_response)
+ m_response->AddRef();
+ }
+
+ virtual ~asp()
+ {
+ if (m_request)
+ m_request->Release();
+ if (m_response)
+ m_response->Release();
+ }
+
+ virtual _Success_(return != 0 || length == 0) size_t read(
+ _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
+ {
+ assert(data || !length);
+ if (!m_request) _Unlikely_ {
+ m_state = state_t::fail;
+ return 0;
+ }
+ for (size_t to_read = length;;) {
+ VARIANT var_amount, var_data;
+ V_VT(&var_amount) = VT_I4;
+ V_I4(&var_amount) = (LONG)std::min(to_read, LONG_MAX);
+ V_VT(&var_data) = VT_EMPTY;
+ HRESULT hr = [&]() {
+ __try { return m_request->BinaryRead(&var_amount, &var_data); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { return E_FAIL; }
+ }();
+ if (FAILED(hr)) _Unlikely_ {
+ m_state = to_read < length ? state_t::ok : state_t::fail;
+ return length - to_read;
+ }
+ assert(V_VT(&var_amount) == VT_I4);
+ assert(V_VT(&var_data) == (VT_ARRAY | VT_UI1));
+ std::unique_ptr sa(V_ARRAY(&var_data));
+ if (!V_I4(&var_amount)) _Unlikely_ {
+ m_state = to_read < length || !length ? state_t::ok : state_t::eof;
+ return length - to_read;
+ }
+ safearray_accessor a(sa.get());
+ memcpy(data, a.data(), V_I4(&var_amount));
+ to_read -= V_I4(&var_amount);
+ if (!to_read) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += V_I4(&var_amount);
+ }
+ }
+
+ virtual _Success_(return != 0) size_t write(
+ _In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
+ {
+ if (!m_response) {
+ m_state = state_t::fail;
+ return 0;
+ }
+ for (size_t to_write = length;;) {
+ UINT num_written = static_cast(std::min(to_write, UINT_MAX));
+ std::unique_ptr bstr_data(SysAllocStringByteLen(reinterpret_cast(data), num_written));
+ VARIANT var_data;
+ V_VT(&var_data) = VT_BSTR;
+ V_BSTR(&var_data) = bstr_data.get();
+ HRESULT hr = [&]() {
+ __try { return m_response->BinaryWrite(var_data); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { return E_FAIL; }
+ }();
+ if (FAILED(hr)) _Unlikely_ {
+ m_state = state_t::fail;
+ return length - to_write;
+ }
+ to_write -= num_written;
+ if (!to_write) {
+ m_state = state_t::ok;
+ return length;
+ }
+ reinterpret_cast(data) += num_written;
+ }
+ }
+
+ virtual void close()
+ {
+ if (m_response) {
+ __try { m_response->End(); }
+ __except (EXCEPTION_EXECUTE_HANDLER) {}
+ }
+ m_state = state_t::ok;
+ }
+
+ virtual void flush()
+ {
+ if (m_response) {
+ HRESULT hr;
+ __try { hr = m_response->Flush(); }
+ __except (EXCEPTION_EXECUTE_HANDLER) { hr = E_FAIL; }
+ m_state = SUCCEEDED(hr) ? state_t::ok : state_t::fail;
+ }
+ }
+
+ protected:
+ IRequest* m_request;
+ IResponse* m_response;
+ };
+#endif
+#endif
+
+ ///
+ /// File open mode
+ ///
+ enum mode_t
+ {
+ mode_for_reading = 1 << 0, ///< Open for reading
+ mode_for_writing = 1 << 1, ///< Open for writing
+ mode_for_chmod = 1 << 2, ///< Open for changing file attributes
+ mode_create = 1 << 3, ///< Create file
+ mode_preserve_existing = mode_create | (1 << 4), ///< If file already exists, open existing; otherwise, create a new one.
+ mode_append = 1 << 5, ///< Seek to the end of file after opening
+ mode_text = 0, ///< Open as text file
+ mode_binary = 1 << 6, ///< Open as binary file
+
+ share_none = 0, ///< Open for exclusive access (default)
+ share_reading = 1 << 7, ///< Allow others to read our file
+ share_writing = 1 << 8, ///< Allow others to write to our file
+ share_deleting = 1 << 9, ///< Allow others to mark our file for deletion
+ share_all = share_reading | share_writing | share_deleting, // Allow others all operations on our file
+
+ inherit_handle = 1 << 10, ///< Inherit handle in child processes (Windows-specific)
+
+ hint_write_thru = 1 << 11, ///< Write operations will not go through any intermediate cache, they will go directly to disk. (Windows-specific)
+ hint_no_buffering = 1 << 12, ///< The file or device is being opened with no system caching for data reads and writes. (Windows-specific)
+ hint_random_access = 1 << 13, ///< Access is intended to be random. (Windows-specific)
+ hint_sequential_access = 1 << 14, ///< Access is intended to be sequential from beginning to end. (Windows-specific)
+ };
+
+#pragma warning(push)
+#pragma warning(disable: 4250)
+ ///
+ /// File-system file
+ ///
+ class file : virtual public basic_file, virtual public basic_sys
+ {
+ public:
+ file(_In_opt_ sys_handle h = invalid_handle, _In_ state_t state = state_t::ok) : basic_sys(h, state) {}
+
+ ///
+ /// Opens file
+ ///
+ /// \param[in] filename Filename
+ /// \param[in] mode Bitwise combination of mode_t flags
+ ///
+ file(_In_z_ const sys_char* filename, _In_ int mode)
+ {
+ open(filename, mode);
+ }
+
+ ///
+ /// Opens file
+ ///
+ /// \param[in] filename Filename
+ /// \param[in] mode Bitwise combination of mode_t flags
+ ///
+ void open(_In_z_ const sys_char* filename, _In_ int mode)
+ {
+ if (m_h != invalid_handle)
+ close();
+
+#ifdef _WIN32
+ DWORD dwDesiredAccess = 0;
+ if (mode & mode_for_reading) dwDesiredAccess |= GENERIC_READ;
+ if (mode & mode_for_writing) dwDesiredAccess |= GENERIC_WRITE;
+ if (mode & mode_for_chmod) dwDesiredAccess |= FILE_WRITE_ATTRIBUTES;
+
+ DWORD dwShareMode = 0;
+ if (mode & share_reading) dwShareMode |= FILE_SHARE_READ;
+ if (mode & share_writing) dwShareMode |= FILE_SHARE_WRITE;
+ if (mode & share_deleting) dwShareMode |= FILE_SHARE_DELETE;
+
+ SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
+ sa.bInheritHandle = mode & inherit_handle ? true : false;
+
+ DWORD dwCreationDisposition;
+ switch (mode & mode_preserve_existing) {
+ case mode_create: dwCreationDisposition = CREATE_ALWAYS; break;
+ case mode_preserve_existing: dwCreationDisposition = OPEN_ALWAYS; break;
+ case 0: dwCreationDisposition = OPEN_EXISTING; break;
+ default: throw std::invalid_argument("invalid mode");
+ }
+
+ DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+ if (mode & hint_write_thru) dwFlagsAndAttributes |= FILE_FLAG_WRITE_THROUGH;
+ if (mode & hint_no_buffering) dwFlagsAndAttributes |= FILE_FLAG_NO_BUFFERING;
+ if (mode & hint_random_access) dwFlagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS;
+ if (mode & hint_sequential_access) dwFlagsAndAttributes |= FILE_FLAG_SEQUENTIAL_SCAN;
+
+ m_h = CreateFile(filename, dwDesiredAccess, dwShareMode, &sa, dwCreationDisposition, dwFlagsAndAttributes, nullptr);
+#else
+ int flags = 0;
+ if (mode & mode_for_reading) flags |= O_RDONLY;
+ if (mode & mode_for_writing) flags |= O_WRONLY;
+ if (mode & mode_create) flags |= mode & mode_preserve_existing ? O_CREAT : (O_CREAT | O_EXCL);
+ if (mode & hint_write_thru) flags |= O_DSYNC;
+ if (mode & hint_no_buffering) flags |= O_RSYNC;
+
+ m_h = open(filename, flags, DEFFILEMODE);
+#endif
+ if (m_h != invalid_handle) {
+ m_state = state_t::ok;
+ if (mode & mode_append)
+ seek(0, seek_t::end);
+ }
+ else
+ m_state = state_t::fail;
+ }
+
+ virtual fpos_t seek(_In_ foff_t offset, _In_ seek_t how = seek_t::beg)
+ {
+#ifdef _WIN32
+ LARGE_INTEGER li;
+ li.QuadPart = offset;
+ li.LowPart = SetFilePointer(m_h, li.LowPart, &li.HighPart, static_cast(how));
+ if (li.LowPart != 0xFFFFFFFF || GetLastError() == NO_ERROR) {
+ m_state = state_t::ok;
+ return li.QuadPart;
+ }
+#else
+ off64_t result = lseek64(m_h, offset, how);
+ if (result >= 0) {
+ m_state = state_t::ok;
+ return result;
+ }
+#endif
+ m_state = state_t::fail;
+ return fpos_max;
+ }
+
+ virtual fpos_t tell() const
+ {
+ if (m_h != invalid_handle) {
+#ifdef _WIN32
+ LARGE_INTEGER li;
+ li.QuadPart = 0;
+ li.LowPart = SetFilePointer(m_h, 0, &li.HighPart, FILE_CURRENT);
+ if (li.LowPart != 0xFFFFFFFF || GetLastError() == NO_ERROR)
+ return li.QuadPart;
+#else
+ off64_t result = lseek64(m_h, 0, SEEK_CUR);
+ if (result >= 0)
+ return result;
+#endif
+ }
+ return fpos_max;
+ }
+
+ virtual void lock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+#ifdef _WIN32
+ LARGE_INTEGER liOffset;
+ LARGE_INTEGER liSize;
+ liOffset.QuadPart = offset;
+ liSize.QuadPart = length;
+ if (LockFile(m_h, liOffset.LowPart, liOffset.HighPart, liSize.LowPart, liSize.HighPart)) {
+ m_state = state_t::ok;
+ return;
+ }
+#else
+ off64_t orig = lseek64(m_h, 0, SEEK_CUR);
+ if (orig >= 0) {
+ m_state = lseek64(m_h, offset, SEEK_SET) >= 0 && lockf64(m_h, F_LOCK, length) >= 0 ? state_t::ok : state_t::fail;
+ lseek64(m_h, orig, SEEK_SET);
+ m_state = state_t::ok;
+ return;
+ }
+#endif
+ m_state = state_t::fail;
+ }
+
+ virtual void unlock(_In_ fpos_t offset, _In_ fsize_t length)
+ {
+#ifdef _WIN32
+ LARGE_INTEGER liOffset;
+ LARGE_INTEGER liSize;
+ liOffset.QuadPart = offset;
+ liSize.QuadPart = length;
+ if (UnlockFile(m_h, liOffset.LowPart, liOffset.HighPart, liSize.LowPart, liSize.HighPart)) {
+ m_state = state_t::ok;
+ return;
+ }
+#else
+ off64_t orig = lseek64(m_h, 0, SEEK_CUR);
+ if (orig >= 0) {
+ if (lseek64(m_h, offset, SEEK_SET) >= 0 && lockf64(m_h, F_ULOCK, length) >= 0) {
+ lseek64(m_h, orig, SEEK_SET);
+ m_state = state_t::ok;
+ return;
+ }
+ lseek64(m_h, orig, SEEK_SET);
+ }
+#endif
+ m_state = state_t::fail;
+ }
+
+ virtual fsize_t size()
+ {
+#ifdef _WIN32
+ LARGE_INTEGER li;
+ li.LowPart = GetFileSize(m_h, (LPDWORD)&li.HighPart);
+ if (li.LowPart == 0xFFFFFFFF && GetLastError() != NO_ERROR)
+ li.QuadPart = -1;
+ return li.QuadPart;
+#else
+ off64_t length = -1, orig = lseek64(m_h, 0, SEEK_CUR);
+ if (orig >= 0) {
+ length = lseek64(m_h, 0, SEEK_END);
+ lseek64(m_h, orig, SEEK_SET);
+ }
+ return length;
+#endif
+ }
+
+ virtual void truncate()
+ {
+#ifdef _WIN32
+ if (SetEndOfFile(m_h)) {
+ m_state = state_t::ok;
+ return;
+ }
+#else
+ off64_t length = lseek64(m_h, 0, SEEK_CUR);
+ if (length >= 0 && ftruncate64(m_h, length) >= 0) {
+ m_state = state_t::ok;
+ return;
+ }
+#endif
+ m_state = state_t::fail;
+ }
+
+#ifdef _WIN32
+ static inline time_point ft2tp(_In_ const FILETIME& ft)
+ {
+#if _HAS_CXX20
+ uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+#else
+ uint64_t t = ((static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ll;
+#endif
+ return time_point(time_point::duration(t));
+ }
+
+ static inline void tp2ft(_In_ time_point tp, _Out_ FILETIME& ft)
+ {
+#if _HAS_CXX20
+ uint64_t t = tp.time_since_epoch().count();
+#else
+ uint64_t t = tp.time_since_epoch().count() + 116444736000000000ll;
+#endif
+ ft.dwHighDateTime = static_cast((t >> 32) & 0xffffffff);
+ ft.dwLowDateTime = static_cast(t & 0xffffffff);
+ }
+#endif
+
+ virtual time_point ctime() const
+ {
+#ifdef _WIN32
+ FILETIME ft;
+ if (GetFileTime(m_h, &ft, nullptr, nullptr))
+ return ft2tp(ft);
+#endif
+ return time_point::min();
+ }
+
+ virtual time_point atime() const
+ {
+#ifdef _WIN32
+ FILETIME ft;
+ if (GetFileTime(m_h, nullptr, &ft, nullptr))
+ return ft2tp(ft);
+#else
+ struct stat buf;
+ if (fstat(m_h, &buf) >= 0);
+ return time_point::from_time_t(buf.st_atim);
+#endif
+ return time_point::min();
+ }
+
+ virtual time_point mtime() const
+ {
+#ifdef _WIN32
+ FILETIME ft;
+ if (GetFileTime(m_h, nullptr, nullptr, &ft))
+ return ft2tp(ft);
+#else
+ struct stat buf;
+ if (fstat(m_h, &buf) >= 0)
+ return time_point::from_time_t(buf.st_mtim);
+#endif
+ return time_point::min();
+ }
+
+ virtual void set_ctime(time_point date)
+ {
+ assert(m_h != invalid_handle);
+#ifdef _WIN32
+ FILETIME ft;
+ tp2ft(date, ft);
+ if (SetFileTime(m_h, &ft, nullptr, nullptr))
+ return;
+#endif
+ throw std::runtime_error("failed to set file ctime");
+ }
+
+ virtual void set_atime(time_point date)
+ {
+ assert(m_h != invalid_handle);
+#ifdef _WIN32
+ FILETIME ft;
+ tp2ft(date, ft);
+ if (SetFileTime(m_h, nullptr, &ft, nullptr))
+ return;
+#else
+ struct timespec ts[2];
+ ts[0].tv_sec = date;
+ ts[1].tv_nsec = UTIME_OMIT;
+ if (futimens(m_h, ts) >= 0)
+ return;
+#endif
+ throw std::runtime_error("failed to set file atime");
+ }
+
+ virtual void set_mtime(time_point date)
+ {
+#ifdef _WIN32
+ FILETIME ft;
+ tp2ft(date, ft);
+ if (SetFileTime(m_h, nullptr, nullptr, &ft))
+ return;
+#else
+ struct timespec ts[2];
+ ts[0].tv_nsec = UTIME_OMIT;
+ ts[1].tv_sec = date;
+ if (futimens(m_h, ts) >= 0)
+ return;
+#endif
+ throw std::runtime_error("failed to set file mtime");
+ }
+ };
+#pragma warning(pop)
+
+ ///
+ /// Cached file-system file
+ ///
+ class cached_file : public cache
+ {
+ public:
+ cached_file(_In_opt_ sys_handle h = invalid_handle, _In_ state_t state = state_t::ok, _In_ size_t cache_size = default_cache_size) :
+ cache(cache_size),
+ m_source(h, state)
+ {
+ init(m_source);
+ }
+
+ ///
+ /// Opens file
+ ///
+ /// \param[in] filename Filename
+ /// \param[in] mode Bitwise combination of mode_t flags
+ /// \param[in] cache_size Size of the cache block
+ ///
+ cached_file(_In_z_ const sys_char* filename, _In_ int mode, _In_ size_t cache_size = default_cache_size) :
+ cache(cache_size),
+ m_source(filename, mode& mode_for_writing ? mode | mode_for_reading : mode)
+ {
+ init(m_source);
+ }
+
+ ///
+ /// Opens file
+ ///
+ /// \param[in] filename Filename
+ /// \param[in] mode Bitwise combination of mode_t flags
+ /// \param[in] cache_size Size of the cache block
+ ///
+ void open(_In_z_ const sys_char* filename, _In_ int mode)
+ {
+ if (mode & mode_for_writing) mode |= mode_for_reading;
+ m_source.open(filename, mode);
+ if (m_source.ok()) {
+#if SET_FILE_OP_TIMES
+ m_atime = m_mtime = time_point::min();
+#endif
+ m_offset = m_source.tell();
+ m_state = state_t::ok;
+ return;
+ }
+ m_state = state_t::fail;
+ }
+
+ protected:
+ file m_source;
+ };
+
+ ///
+ /// In-memory file
+ ///
+ class memory_file : public basic_file
+ {
+ public:
+ memory_file(_In_ state_t state = state_t::ok) :
+ basic(state),
+ m_data(nullptr),
+ m_offset(0),
+ m_size(0),
+ m_reserved(0),
+ m_manage(true)
+ {
+#if SET_FILE_OP_TIMES
+ m_ctime = m_atime = m_mtime = time_point::now();
+#endif
+ }
+
+ ///
+ /// Creates an empty file of reserved size
+ ///
+ /// \param[in] size Reserved size
+ /// \param[in] state Initial stream state
+ ///
+ memory_file(_In_ size_t size, _In_ state_t state = state_t::ok) :
+ basic(state),
+ m_data(reinterpret_cast