diff options
Diffstat (limited to 'src/zencore/basicfile.cpp')
| -rw-r--r-- | src/zencore/basicfile.cpp | 1033 |
1 files changed, 1033 insertions, 0 deletions
diff --git a/src/zencore/basicfile.cpp b/src/zencore/basicfile.cpp new file mode 100644 index 000000000..c2a21ae90 --- /dev/null +++ b/src/zencore/basicfile.cpp @@ -0,0 +1,1033 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zencore/basicfile.h> + +#include <zencore/compactbinary.h> +#include <zencore/except.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/memory/memory.h> +#include <zencore/testing.h> +#include <zencore/testutils.h> + +#if ZEN_PLATFORM_WINDOWS +# include <zencore/windows.h> +#else +# include <fcntl.h> +# include <sys/file.h> +# include <sys/stat.h> +# include <unistd.h> +#endif + +#include <fmt/format.h> +#include <gsl/gsl-lite.hpp> + +namespace zen { + +BasicFile::~BasicFile() +{ + Close(); +} + +void +BasicFile::Open(const std::filesystem::path& FileName, Mode Mode) +{ + std::error_code Ec; + Open(FileName, Mode, Ec); + + if (Ec) + { + throw std::system_error(Ec, fmt::format("failed to open file '{}', mode: {:x}", FileName, uint32_t(Mode))); + } +} + +void +BasicFile::Open(const std::filesystem::path& FileName, Mode InMode, std::error_code& Ec) +{ + Ec.clear(); + + Mode Mode = InMode & Mode::kModeMask; + +#if ZEN_PLATFORM_WINDOWS + DWORD dwCreationDisposition = 0; + DWORD dwDesiredAccess = 0; + switch (Mode) + { + case Mode::kRead: + dwCreationDisposition |= OPEN_EXISTING; + dwDesiredAccess |= GENERIC_READ; + break; + case Mode::kWrite: + dwCreationDisposition |= OPEN_ALWAYS; + dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE); + break; + case Mode::kDelete: + dwCreationDisposition |= OPEN_ALWAYS; + dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE | DELETE); + break; + case Mode::kTruncate: + dwCreationDisposition |= CREATE_ALWAYS; + dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE); + break; + case Mode::kTruncateDelete: + dwCreationDisposition |= CREATE_ALWAYS; + dwDesiredAccess |= (GENERIC_READ | GENERIC_WRITE | DELETE); + break; + } + + const DWORD dwShareMode = FILE_SHARE_READ | (EnumHasAllFlags(InMode, Mode::kPreventWrite) ? 0 : FILE_SHARE_WRITE) | + (EnumHasAllFlags(InMode, Mode::kPreventDelete) ? 0 : FILE_SHARE_DELETE); + const DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; + const HANDLE hTemplateFile = nullptr; + const HANDLE FileHandle = CreateFile(FileName.c_str(), + dwDesiredAccess, + dwShareMode, + /* lpSecurityAttributes */ nullptr, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + if (FileHandle == INVALID_HANDLE_VALUE) + { + Ec = MakeErrorCodeFromLastError(); + + return; + } +#else + int OpenFlags = O_CLOEXEC; + switch (Mode) + { + case Mode::kRead: + OpenFlags |= O_RDONLY; + break; + case Mode::kWrite: + case Mode::kDelete: + OpenFlags |= (O_RDWR | O_CREAT); + break; + case Mode::kTruncate: + case Mode::kTruncateDelete: + OpenFlags |= (O_RDWR | O_CREAT | O_TRUNC); + break; + } + + int Fd = open(FileName.c_str(), OpenFlags, 0666); + if (Fd < 0) + { + Ec = MakeErrorCodeFromLastError(); + return; + } + if (Mode != Mode::kRead) + { + fchmod(Fd, 0666); + } + + void* FileHandle = (void*)(uintptr_t(Fd)); +#endif + + m_FileHandle = FileHandle; +} + +void +BasicFile::Open(const std::filesystem::path& FileName, Mode Mode, std::function<bool(std::error_code& Ec)>&& RetryCallback) +{ + std::error_code Ec; + Open(FileName, Mode, Ec); + while (Ec && RetryCallback(Ec)) + { + Ec.clear(); + Open(FileName, Mode, Ec); + } + if (Ec) + { + throw std::system_error(Ec, fmt::format("failed to open file '{}', mode: {:x}", FileName, uint32_t(Mode))); + } +} + +void +BasicFile::Close() +{ + if (m_FileHandle) + { +#if ZEN_PLATFORM_WINDOWS + ::CloseHandle(m_FileHandle); +#else + int Fd = int(uintptr_t(m_FileHandle)); + close(Fd); +#endif + m_FileHandle = nullptr; + } +} + +IoBuffer +BasicFile::ReadRange(uint64_t FileOffset, uint64_t ByteCount) +{ + return IoBufferBuilder::MakeFromFileHandle(m_FileHandle, FileOffset, ByteCount); +} + +void +BasicFile::Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset) +{ + const uint64_t MaxChunkSize = 2u * 1024 * 1024 * 1024; + + while (BytesToRead) + { + const uint64_t NumberOfBytesToRead = Min(BytesToRead, MaxChunkSize); + int32_t Error = 0; + size_t BytesRead = 0; + +#if ZEN_PLATFORM_WINDOWS + OVERLAPPED Ovl{}; + + Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu); + Ovl.OffsetHigh = DWORD(FileOffset >> 32); + + DWORD dwNumberOfBytesRead = 0; + BOOL Success = ::ReadFile(m_FileHandle, Data, DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl); + if (Success) + { + BytesRead = size_t(dwNumberOfBytesRead); + } + else + { + Error = zen::GetLastError(); + } +#else + static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files"); + int Fd = int(uintptr_t(m_FileHandle)); + ssize_t ReadResult = pread(Fd, Data, NumberOfBytesToRead, FileOffset); + if (ReadResult != -1) + { + BytesRead = size_t(ReadResult); + } + else + { + Error = zen::GetLastError(); + } +#endif + + if (Error || (BytesRead != NumberOfBytesToRead)) + { + std::error_code DummyEc; + throw std::system_error(std::error_code(Error, std::system_category()), + fmt::format("ReadFile/pread failed (offset {:#x}, size {:#x}) file: '{}' (size {:#x})", + FileOffset, + NumberOfBytesToRead, + PathFromHandle(m_FileHandle, DummyEc), + FileSizeFromHandle(m_FileHandle))); + } + + BytesToRead -= NumberOfBytesToRead; + FileOffset += NumberOfBytesToRead; + Data = reinterpret_cast<uint8_t*>(Data) + NumberOfBytesToRead; + } +} + +IoBuffer +BasicFile::ReadAll() +{ + if (const uint64_t Size = FileSize()) + { + IoBuffer Buffer(Size); + Read(Buffer.MutableData(), Size, 0); + return Buffer; + } + else + { + return {}; + } +} + +void +BasicFile::StreamFile(std::function<void(const void* Data, uint64_t Size)>&& ChunkFun) +{ + StreamByteRange(0, FileSize(), std::move(ChunkFun)); +} + +void +BasicFile::StreamByteRange(uint64_t FileOffset, uint64_t Size, std::function<void(const void* Data, uint64_t Size)>&& ChunkFun) +{ + const uint64_t ChunkSize = 128 * 1024; + IoBuffer ReadBuffer{ChunkSize}; + void* BufferPtr = ReadBuffer.MutableData(); + + uint64_t RemainBytes = Size; + uint64_t CurrentOffset = FileOffset; + + while (RemainBytes) + { + const uint64_t ThisChunkBytes = zen::Min(ChunkSize, RemainBytes); + + Read(BufferPtr, ThisChunkBytes, CurrentOffset); + + ChunkFun(BufferPtr, ThisChunkBytes); + + CurrentOffset += ThisChunkBytes; + RemainBytes -= ThisChunkBytes; + } +} + +uint64_t +BasicFile::Write(CompositeBuffer Data, uint64_t FileOffset, std::error_code& Ec) +{ + uint64_t WrittenBytes = 0; + for (const SharedBuffer& Buffer : Data.GetSegments()) + { + MemoryView BlockView = Buffer.GetView(); + Write(BlockView, FileOffset + WrittenBytes, Ec); + + if (Ec) + { + return WrittenBytes; + } + + WrittenBytes += BlockView.GetSize(); + } + + return WrittenBytes; +} + +void +BasicFile::Write(MemoryView Data, uint64_t FileOffset, std::error_code& Ec) +{ + Write(Data.GetData(), Data.GetSize(), FileOffset, Ec); +} + +void +BasicFile::Write(const void* Data, uint64_t Size, uint64_t FileOffset, std::error_code& Ec) +{ + Ec.clear(); + + const uint64_t MaxChunkSize = 2u * 1024 * 1024 * 1024; + + while (Size) + { + const uint64_t NumberOfBytesToWrite = Min(Size, MaxChunkSize); + +#if ZEN_PLATFORM_WINDOWS + OVERLAPPED Ovl{}; + + Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu); + Ovl.OffsetHigh = DWORD(FileOffset >> 32); + + DWORD dwNumberOfBytesWritten = 0; + + BOOL Success = ::WriteFile(m_FileHandle, Data, DWORD(NumberOfBytesToWrite), &dwNumberOfBytesWritten, &Ovl); +#else + static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files"); + int Fd = int(uintptr_t(m_FileHandle)); + int BytesWritten = pwrite(Fd, Data, NumberOfBytesToWrite, FileOffset); + bool Success = (BytesWritten > 0); +#endif + + if (!Success) + { + Ec = MakeErrorCodeFromLastError(); + + return; + } + + Size -= NumberOfBytesToWrite; + FileOffset += NumberOfBytesToWrite; + Data = reinterpret_cast<const uint8_t*>(Data) + NumberOfBytesToWrite; + } +} + +void +BasicFile::Write(MemoryView Data, uint64_t FileOffset) +{ + Write(Data.GetData(), Data.GetSize(), FileOffset); +} + +void +BasicFile::Write(const void* Data, uint64_t Size, uint64_t Offset) +{ + std::error_code Ec; + Write(Data, Size, Offset, Ec); + + if (Ec) + { + std::error_code Dummy; + throw std::system_error(Ec, fmt::format("Failed to write to file '{}'", zen::PathFromHandle(m_FileHandle, Dummy))); + } +} + +void +BasicFile::WriteAll(IoBuffer Data, std::error_code& Ec) +{ + Write(Data.Data(), Data.Size(), 0, Ec); +} + +void +BasicFile::Flush() +{ + if (m_FileHandle == nullptr) + { + return; + } +#if ZEN_PLATFORM_WINDOWS + FlushFileBuffers(m_FileHandle); +#else + int Fd = int(uintptr_t(m_FileHandle)); + fsync(Fd); +#endif +} + +uint64_t +BasicFile::FileSize() const +{ +#if ZEN_PLATFORM_WINDOWS + ULARGE_INTEGER liFileSize; + liFileSize.LowPart = ::GetFileSize(m_FileHandle, &liFileSize.HighPart); + if (liFileSize.LowPart == INVALID_FILE_SIZE) + { + int Error = zen::GetLastError(); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, fmt::format("Failed to get file size from file '{}'", PathFromHandle(m_FileHandle, Dummy))); + } + } + return uint64_t(liFileSize.QuadPart); +#else + int Fd = int(uintptr_t(m_FileHandle)); + static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files"); + struct stat Stat; + if (fstat(Fd, &Stat) == -1) + { + std::error_code Dummy; + ThrowSystemError(GetLastError(), fmt::format("Failed to get file size from file '{}'", PathFromHandle(m_FileHandle, Dummy))); + } + return uint64_t(Stat.st_size); +#endif +} + +uint64_t +BasicFile::FileSize(std::error_code& Ec) const +{ +#if ZEN_PLATFORM_WINDOWS + ULARGE_INTEGER liFileSize; + liFileSize.LowPart = ::GetFileSize(m_FileHandle, &liFileSize.HighPart); + if (liFileSize.LowPart == INVALID_FILE_SIZE) + { + int Error = zen::GetLastError(); + if (Error) + { + Ec = MakeErrorCode(Error); + return 0; + } + } + return uint64_t(liFileSize.QuadPart); +#else + int Fd = int(uintptr_t(m_FileHandle)); + static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files"); + struct stat Stat; + if (fstat(Fd, &Stat) == -1) + { + Ec = MakeErrorCodeFromLastError(); + return 0; + } + return uint64_t(Stat.st_size); +#endif +} + +void +BasicFile::SetFileSize(uint64_t FileSize) +{ +#if ZEN_PLATFORM_WINDOWS + LARGE_INTEGER liFileSize; + liFileSize.QuadPart = FileSize; + BOOL OK = ::SetFilePointerEx(m_FileHandle, liFileSize, 0, FILE_BEGIN); + if (OK == FALSE) + { + int Error = zen::GetLastError(); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, + fmt::format("Failed to set file pointer to {} for file {}", FileSize, PathFromHandle(m_FileHandle, Dummy))); + } + } + OK = ::SetEndOfFile(m_FileHandle); + if (OK == FALSE) + { + int Error = zen::GetLastError(); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, + fmt::format("Failed to set end of file to {} for file {}", FileSize, PathFromHandle(m_FileHandle, Dummy))); + } + } +#elif ZEN_PLATFORM_MAC + int Fd = int(intptr_t(m_FileHandle)); + if (ftruncate(Fd, (off_t)FileSize) < 0) + { + int Error = zen::GetLastError(); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, + fmt::format("Failed to set truncate file to {} for file {}", FileSize, PathFromHandle(m_FileHandle, Dummy))); + } + } +#else + int Fd = int(intptr_t(m_FileHandle)); + if (ftruncate64(Fd, (off64_t)FileSize) < 0) + { + int Error = zen::GetLastError(); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, + fmt::format("Failed to set truncate file to {} for file {}", FileSize, PathFromHandle(m_FileHandle, Dummy))); + } + } + if (FileSize > 0) + { + int Error = posix_fallocate64(Fd, 0, (off64_t)FileSize); + if (Error) + { + std::error_code Dummy; + ThrowSystemError(Error, + fmt::format("Failed to allocate space of {} for file {}", FileSize, PathFromHandle(m_FileHandle, Dummy))); + } + } +#endif +} + +void +BasicFile::Attach(void* Handle) +{ + ZEN_ASSERT(Handle != nullptr); + ZEN_ASSERT(m_FileHandle == nullptr); + m_FileHandle = Handle; +} + +void* +BasicFile::Detach() +{ + void* FileHandle = m_FileHandle; + m_FileHandle = 0; + return FileHandle; +} + +////////////////////////////////////////////////////////////////////////// + +TemporaryFile::~TemporaryFile() +{ + Close(); +} + +void +TemporaryFile::Close() +{ + if (m_FileHandle) + { +#if ZEN_PLATFORM_WINDOWS + // Mark file for deletion when final handle is closed + + FILE_DISPOSITION_INFO Fdi{.DeleteFile = TRUE}; + + SetFileInformationByHandle(m_FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi); +#else + std::error_code Ec; + std::filesystem::path FilePath = zen::PathFromHandle(m_FileHandle, Ec); + if (!Ec) + { + unlink(FilePath.c_str()); + } +#endif + + BasicFile::Close(); + } +} + +void +TemporaryFile::CreateTemporary(std::filesystem::path TempDirName, std::error_code& Ec) +{ + StringBuilder<64> TempName; + Oid::NewOid().ToString(TempName); + + m_TempPath = TempDirName / TempName.c_str(); + + Open(m_TempPath, BasicFile::Mode::kTruncateDelete, Ec); +} + +void +TemporaryFile::MoveTemporaryIntoPlace(std::filesystem::path FinalFileName, std::error_code& Ec) +{ + // We intentionally call the base class Close() since otherwise we'll end up + // deleting the temporary file + BasicFile::Close(); + + std::filesystem::rename(m_TempPath, FinalFileName, Ec); + + if (Ec) + { + // Try to re-open the temp file so we clean up after us when TemporaryFile is destructed + std::error_code DummyEc; + Open(m_TempPath, BasicFile::Mode::kWrite, DummyEc); + } +} + +////////////////////////////////////////////////////////////////////////// + +void +TemporaryFile::SafeWriteFile(const std::filesystem::path& Path, MemoryView Data) +{ + TemporaryFile TempFile; + std::error_code Ec; + SafeWriteFile(Path, Data, Ec); + if (Ec) + { + throw std::system_error(Ec, fmt::format("Failed to safely write file '{}'", Path)); + } +} + +void +TemporaryFile::SafeWriteFile(const std::filesystem::path& Path, MemoryView Data, std::error_code& OutEc) +{ + TemporaryFile TempFile; + if (TempFile.CreateTemporary(Path.parent_path(), OutEc); !OutEc) + { + if (TempFile.Write(Data, 0, OutEc); !OutEc) + { + TempFile.MoveTemporaryIntoPlace(Path, OutEc); + } + } +} + +////////////////////////////////////////////////////////////////////////// + +LockFile::LockFile() +{ +} + +LockFile::~LockFile() +{ +#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + int Fd = int(intptr_t(m_FileHandle)); + flock(Fd, LOCK_UN | LOCK_NB); +#endif +} + +void +LockFile::Create(std::filesystem::path FileName, CbObject Payload, std::error_code& Ec) +{ +#if ZEN_PLATFORM_WINDOWS + Ec.clear(); + + const DWORD dwCreationDisposition = CREATE_ALWAYS; + DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE | DELETE; + const DWORD dwShareMode = FILE_SHARE_READ; + const DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE; + HANDLE hTemplateFile = nullptr; + + HANDLE FileHandle = CreateFile(FileName.c_str(), + dwDesiredAccess, + dwShareMode, + /* lpSecurityAttributes */ nullptr, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + if (FileHandle == INVALID_HANDLE_VALUE) + { + Ec = zen::MakeErrorCodeFromLastError(); + + return; + } +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + int Fd = open(FileName.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); + if (Fd < 0) + { + Ec = zen::MakeErrorCodeFromLastError(); + return; + } + fchmod(Fd, 0666); + + int LockRet = flock(Fd, LOCK_EX | LOCK_NB); + if (LockRet < 0) + { + Ec = zen::MakeErrorCodeFromLastError(); + close(Fd); + return; + } + + void* FileHandle = (void*)uintptr_t(Fd); +#endif + + m_FileHandle = FileHandle; + + BasicFile::Write(Payload.GetBuffer(), 0, Ec); +} + +void +LockFile::Update(CbObject Payload, std::error_code& Ec) +{ + BasicFile::Write(Payload.GetBuffer(), 0, Ec); +} + +////////////////////////////////////////////////////////////////////////// + +BasicFileBuffer::BasicFileBuffer(BasicFile& Base, uint64_t BufferSize) +: m_Base(Base) +, m_Buffer(nullptr) +, m_BufferSize(BufferSize) +, m_Size(Base.FileSize()) +, m_BufferStart(0) +, m_BufferEnd(0) +{ + m_Buffer = (uint8_t*)Memory::Alloc(m_BufferSize); +} + +BasicFileBuffer::~BasicFileBuffer() +{ + Memory::Free(m_Buffer); +} + +void +BasicFileBuffer::Read(void* Data, uint64_t Size, uint64_t FileOffset) +{ + if (m_Buffer == nullptr || (Size > m_BufferSize) || (FileOffset + Size > m_Size)) + { + m_Base.Read(Data, Size, FileOffset); + return; + } + uint8_t* WritePtr = ((uint8_t*)Data); + uint64_t Begin = FileOffset; + uint64_t End = FileOffset + Size; + if (FileOffset <= m_BufferStart) + { + if (End > m_BufferStart) + { + uint64_t Count = Min(m_BufferEnd, End) - m_BufferStart; + memcpy(WritePtr + End - Count - FileOffset, m_Buffer, Count); + End -= Count; + if (Begin == End) + { + return; + } + } + } + else if (FileOffset < m_BufferEnd) + { + uint64_t Count = Min(m_BufferEnd, End) - FileOffset; + memcpy(WritePtr + Begin - FileOffset, m_Buffer + Begin - m_BufferStart, Count); + Begin += Count; + if (Begin == End) + { + return; + } + } + m_BufferStart = Begin; + m_BufferEnd = Min(Begin + m_BufferSize, m_Size); + m_Base.Read(m_Buffer, m_BufferEnd - m_BufferStart, m_BufferStart); + uint64_t Count = Min(m_BufferEnd, End) - m_BufferStart; + memcpy(WritePtr + Begin - FileOffset, m_Buffer, Count); + ZEN_ASSERT(Begin + Count == End); +} + +MemoryView +BasicFileBuffer::MakeView(uint64_t Size, uint64_t FileOffset) +{ + if (FileOffset < m_BufferStart || (FileOffset + Size) > m_BufferEnd) + { + if (m_Buffer == nullptr || (Size > m_BufferSize) || (FileOffset + Size > m_Size)) + { + return {}; + } + m_BufferStart = FileOffset; + m_BufferEnd = Min(m_BufferStart + m_BufferSize, m_Size); + m_Base.Read(m_Buffer, m_BufferEnd - m_BufferStart, m_BufferStart); + } + return MemoryView(m_Buffer + (FileOffset - m_BufferStart), Size); +} + +////////////////////////////////////////////////////////////////////////// + +BasicFileWriter::BasicFileWriter(BasicFile& Base, uint64_t BufferSize) +: m_Base(Base) +, m_Buffer(nullptr) +, m_BufferSize(BufferSize) +, m_BufferStart(0) +, m_BufferEnd(0) +{ + m_Buffer = (uint8_t*)Memory::Alloc(m_BufferSize); +} + +BasicFileWriter::~BasicFileWriter() +{ + Flush(); + Memory::Free(m_Buffer); +} + +void +BasicFileWriter::Write(const void* Data, uint64_t Size, uint64_t FileOffset) +{ + if (m_Buffer == nullptr || (Size >= m_BufferSize)) + { + m_Base.Write(Data, Size, FileOffset); + return; + } + + // Note that this only supports buffering of sequential writes! + + if (FileOffset != m_BufferEnd) + { + Flush(); + m_BufferStart = m_BufferEnd = FileOffset; + } + + const uint8_t* DataPtr = (const uint8_t*)Data; + while (Size) + { + const uint64_t RemainingBufferCapacity = m_BufferStart + m_BufferSize - m_BufferEnd; + const uint64_t BlockWriteBytes = Min(RemainingBufferCapacity, Size); + const uint64_t BufferWriteOffset = FileOffset - m_BufferStart; + + ZEN_ASSERT_SLOW(BufferWriteOffset < m_BufferSize); + ZEN_ASSERT_SLOW((BufferWriteOffset + BlockWriteBytes) <= m_BufferSize); + + memcpy(m_Buffer + BufferWriteOffset, DataPtr, BlockWriteBytes); + + Size -= BlockWriteBytes; + m_BufferEnd += BlockWriteBytes; + FileOffset += BlockWriteBytes; + DataPtr += BlockWriteBytes; + + if ((m_BufferEnd - m_BufferStart) == m_BufferSize) + { + Flush(); + } + } +} + +void +BasicFileWriter::Flush() +{ + const uint64_t BufferedBytes = m_BufferEnd - m_BufferStart; + + if (BufferedBytes == 0) + return; + + const uint64_t WriteOffset = m_BufferStart; + m_BufferStart = m_BufferEnd; + + m_Base.Write(m_Buffer, BufferedBytes, WriteOffset); +} + +////////////////////////////////////////////////////////////////////////// + +/* + ___________ __ + \__ ___/___ _______/ |_ ______ + | |_/ __ \ / ___/\ __\/ ___/ + | |\ ___/ \___ \ | | \___ \ + |____| \___ >____ > |__| /____ > + \/ \/ \/ +*/ + +#if ZEN_WITH_TESTS + +TEST_CASE("BasicFile") +{ + ScopedCurrentDirectoryChange _; + + BasicFile File1; + CHECK_THROWS(File1.Open("zonk", BasicFile::Mode::kRead)); + CHECK_NOTHROW(File1.Open("zonk", BasicFile::Mode::kTruncate)); + CHECK_NOTHROW(File1.Write("abcd", 4, 0)); + CHECK(File1.FileSize() == 4); + { + IoBuffer Data = File1.ReadAll(); + CHECK(Data.Size() == 4); + CHECK_EQ(memcmp(Data.Data(), "abcd", 4), 0); + } + CHECK_NOTHROW(File1.Write("efgh", 4, 2)); + CHECK(File1.FileSize() == 6); + { + IoBuffer Data = File1.ReadAll(); + CHECK(Data.Size() == 6); + CHECK_EQ(memcmp(Data.Data(), "abefgh", 6), 0); + } +} + +TEST_CASE("TemporaryFile") +{ + ScopedCurrentDirectoryChange _; + + SUBCASE("DeleteOnClose") + { + std::filesystem::path Path; + { + TemporaryFile TmpFile; + std::error_code Ec; + TmpFile.CreateTemporary(std::filesystem::current_path(), Ec); + CHECK(!Ec); + Path = TmpFile.GetPath(); + CHECK(std::filesystem::exists(Path)); + } + CHECK(std::filesystem::exists(Path) == false); + } + + SUBCASE("MoveIntoPlace") + { + TemporaryFile TmpFile; + std::error_code Ec; + TmpFile.CreateTemporary(std::filesystem::current_path(), Ec); + CHECK(!Ec); + std::filesystem::path TempPath = TmpFile.GetPath(); + std::filesystem::path FinalPath = std::filesystem::current_path() / "final"; + CHECK(std::filesystem::exists(TempPath)); + TmpFile.MoveTemporaryIntoPlace(FinalPath, Ec); + CHECK(!Ec); + CHECK(std::filesystem::exists(TempPath) == false); + CHECK(std::filesystem::exists(FinalPath)); + } +} + +TEST_CASE("BasicFileBuffer") +{ + ScopedCurrentDirectoryChange _; + { + BasicFile File1; + const std::string_view Data = "0123456789abcdef"; + File1.Open("buffered", BasicFile::Mode::kTruncate); + for (uint32_t I = 0; I < 16; ++I) + { + File1.Write(Data.data(), Data.size(), I * Data.size()); + } + } + SUBCASE("EvenBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + // Non-primed + { + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 1 * 16); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "0123456789abcdef"); + } + // Primed + { + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 1 * 16); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "0123456789abcdef"); + } + } + SUBCASE("UnevenBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + // Non-primed + { + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 7); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "789abcdef0123456"); + } + // Primed + { + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 7); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "789abcdef0123456"); + } + } + SUBCASE("BiggerThanBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[17] = {0}; + File1Buffer.Read(Buffer, 17, 0 * 16); + std::string_view Verify(Buffer, 17); + CHECK(Verify == "0123456789abcdef0"); + } + SUBCASE("InsideBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 0 * 16); + + File1Buffer.Read(Buffer, 8, 2); + std::string_view Verify(Buffer, 8); + CHECK(Verify == "23456789"); + } + SUBCASE("BeginningOfBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 8); + + File1Buffer.Read(Buffer, 8, 8); + std::string_view Verify(Buffer, 8); + CHECK(Verify == "89abcdef"); + } + SUBCASE("EndOfBuffer") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 0 * 16); + + File1Buffer.Read(Buffer, 8, 8); + std::string_view Verify(Buffer, 8); + CHECK(Verify == "89abcdef"); + } + SUBCASE("OverEnd") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 0 * 16); + + File1Buffer.Read(Buffer, 16, 8); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "89abcdef01234567"); + } + SUBCASE("OverBegin") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 1 * 16); + + File1Buffer.Read(Buffer, 16, 8); + std::string_view Verify(Buffer, 16); + CHECK(Verify == "89abcdef01234567"); + } + SUBCASE("EndOfFile") + { + BasicFile File1; + File1.Open("buffered", BasicFile::Mode::kRead); + BasicFileBuffer File1Buffer(File1, 16); + char Buffer[16] = {0}; + File1Buffer.Read(Buffer, 16, 0 * 16); + + File1Buffer.Read(Buffer, 8, 256 - 8); + std::string_view Verify(Buffer, 8); + CHECK(Verify == "89abcdef"); + } +} + +void +basicfile_forcelink() +{ +} + +#endif + +} // namespace zen |