From 38e1c3f8b760d3e22cc2e8f8fdfb5a8816308740 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Wed, 22 Jan 2025 21:44:40 +0800 Subject: [PATCH] feat(miscellaneous): add byte and text file access functions --- .../Private/Miscellaneous/FileSystem.cpp | 295 ++++++++++++++++++ .../Source/Public/Miscellaneous/FileSystem.h | 99 ++++++ 2 files changed, 394 insertions(+) create mode 100644 Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp create mode 100644 Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h diff --git a/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp b/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp new file mode 100644 index 0000000..4b1e184 --- /dev/null +++ b/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp @@ -0,0 +1,295 @@ +#include + +#include "Numerics/Bit.h" +#include "Templates/ScopeHelper.h" +#include "Containers/StaticArray.h" + +#include + +#pragma warning(push) +#pragma warning(disable: 4996) + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_BEGIN(FileSystem) + +bool LoadFileToArray(TArray& Result, FStringView Path) +{ + FILE* File = std::fopen(*Path, "rb"); + + if (File == nullptr) return false; + + auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); + + if (std::fseek(File, 0, SEEK_END) != 0) return false; + + const long Length = std::ftell(File); + + if (Length == -1) return false; + + if (std::fseek(File, 0, SEEK_SET) != 0) return false; + + Result.SetNum(Length); + + if (std::fread(Result.GetData(), sizeof(uint8), Length, File) != static_cast(Length)) return false; + + FileGuard.Release(); + + if (std::fclose(File) != 0) return false; + + return true; +} + +bool SaveArrayToFile(TArrayView Data, FStringView Path) +{ + FILE* File = std::fopen(*Path, "wb"); + + if (File == nullptr) return false; + + auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); + + if (std::fwrite(Data.GetData(), sizeof(uint8), Data.Num(), File) != Data.Num()) return false; + + FileGuard.Release(); + + if (std::fclose(File) != 0) return false; + + return true; +} + +template +bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bVerify /* = false */) +{ + FILE* File = std::fopen(*Path, "rb"); + + if (File == nullptr) return false; + + auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); + + if (std::fseek(File, 0, SEEK_END) != 0) return false; + + long Length = std::ftell(File); + + if (Length == -1) return false; + + if (std::fseek(File, 0, SEEK_SET) != 0) return false; + + TStaticArray Buffer = { 0xAA, 0xAA, 0xAA, 0xAA }; + + Ignore = std::fread(Buffer.GetData(), sizeof(uint8), Buffer.Num(), File); + + // Auto-detect the encoding if it is not specified. + if (Encoding == FileSystem::EEncoding::Default) + { + // Check if the file is a UTF-32 encoded file. + if (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) Encoding = FileSystem::EEncoding::UTF32BE; + else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::UTF32LE; + + // Check if the file is a UTF-16 encoded file. + else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE) Encoding = FileSystem::EEncoding::UTF16LE; + else if (Buffer[0] == 0xFE && Buffer[1] == 0xFF) Encoding = FileSystem::EEncoding::UTF16BE; + + // Check if the file is a UTF-8 encoded file. + else if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) Encoding = FileSystem::EEncoding::UTF8; + + // Check if the file is a wide character encoded file. + else if (Buffer[0] == 0x00 || Buffer[1] == 0x00 || Buffer[2] == 0x00 || Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::Wide; + + // Check if the file is a narrow character encoded file. + else Encoding = FileSystem::EEncoding::Narrow; + } + + // Jump to the BOM character if the file is a UTF-8, UTF-16 or UTF-32 encoded file. + switch (Encoding) + { + case FileSystem::EEncoding::Narrow: + case FileSystem::EEncoding::Wide: { Length -= 0; if (std::fseek(File, 0, SEEK_SET) != 0) return false; } break; + case FileSystem::EEncoding::UTF8: if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) { Length -= 3; if (std::fseek(File, 3, SEEK_SET) != 0) return false; } break; + case FileSystem::EEncoding::UTF16BE: if (Buffer[0] == 0xFE && Buffer[1] == 0xFF) { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; + case FileSystem::EEncoding::UTF16LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE) { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; + case FileSystem::EEncoding::UTF32BE: if (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; + case FileSystem::EEncoding::UTF32LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; + default: check_no_entry(); + } + + check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); + + const bool bByteSwap = + Math::EEndian::Native == Math::EEndian::Big ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : + Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; + + const auto LoadImpl = [File, Length, bByteSwap](TString& String) -> bool + { + if (Length % sizeof(U) != 0) return false; + + String.Reserve(Length / sizeof(U)); + + while (true) + { + U Char; + + const size_t ReadNum = std::fread(&Char, 1, sizeof(U), File); + + if (ReadNum == 0) break; + + if (ReadNum != sizeof(U)) return false; + + if constexpr (sizeof(U) > 1) if (bByteSwap) Char = Math::ByteSwap(Char); + +# if PLATFORM_WINDOWS + { + if (!String.IsEmpty() && String.Back() == LITERAL(U, '\r') && Char == LITERAL(U, '\n')) + { + String.PopBack(); + } + } +# endif + + String.PushBack(Char); + } + + return true; + }; + + bool bCompatible = false; + + if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Narrow; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Wide; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF8; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; + + else static_assert(sizeof(T) == -1, "Unsupported character type"); + + if (!bCompatible || bVerify) + { + switch (Encoding) + { + case FileSystem::EEncoding::Narrow: { FString Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } + case FileSystem::EEncoding::Wide: { FWString Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } + case FileSystem::EEncoding::UTF8: { FU8String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } + case FileSystem::EEncoding::UTF16BE: + case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } + case FileSystem::EEncoding::UTF32BE: + case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } + default: check_no_entry(); + } + } + + else if (!LoadImpl(Result)) return false; + + FileGuard.Release(); + + if (std::fclose(File) != 0) return false; + + return true; +} + +template +bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bWithBOM /* = true */) +{ + bool bCompatible = Encoding == FileSystem::EEncoding::Default; + + if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Narrow; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::Wide; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF8; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; + else if constexpr (CSameAs) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; + + else static_assert(sizeof(T) == -1, "Unsupported character type"); + + if (bCompatible) + { + FILE* File = std::fopen(*Path, "wb"); + + if (File == nullptr) return false; + + auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); + + if (bWithBOM) + { + if constexpr (CSameAs) + { + if (std::fwrite(U8TEXT("\uFEFF"), 1, 3, File) != 3) return false; + } + + else if constexpr (CSameAs) + { + constexpr TStaticArray BufferBE = { 0xFE, 0xFF }; + constexpr TStaticArray BufferLE = { 0xFF, 0xFE }; + + if (Encoding == FileSystem::EEncoding::UTF16BE) { if (std::fwrite(BufferBE.GetData(), 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } + else if (Encoding == FileSystem::EEncoding::UTF16LE) { if (std::fwrite(BufferLE.GetData(), 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } + + else if (std::fwrite(U16TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; + } + + else if constexpr (CSameAs) + { + constexpr TStaticArray BufferBE = { 0x00, 0x00, 0xFE, 0xFF }; + constexpr TStaticArray BufferLE = { 0xFF, 0xFE, 0x00, 0x00 }; + + if (Encoding == FileSystem::EEncoding::UTF32BE) { if (std::fwrite(BufferBE.GetData() , 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } + else if (Encoding == FileSystem::EEncoding::UTF32LE) { if (std::fwrite(BufferLE.GetData() , 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } + + else if (std::fwrite(U32TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; + } + } + + check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); + + const bool bByteSwap = + Math::EEndian::Native == Math::EEndian::Big ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : + Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; + + for (T Char : String) + { +# if PLATFORM_WINDOWS + { + if (Char == LITERAL(T, '\n')) + { + T Return = LITERAL(T, '\r'); + + if constexpr (sizeof(T) > 1) if (bByteSwap) Return = Math::ByteSwap(Return); + + if (std::fwrite(&Return, 1, sizeof(T), File) != sizeof(T)) return false; + } + } +# endif + + if constexpr (sizeof(T) > 1) if (bByteSwap) Char = Math::ByteSwap(Char); + + if (std::fwrite(&Char, 1, sizeof(T), File) != sizeof(T)) return false; + } + + FileGuard.Release(); + + if (std::fclose(File) != 0) return false; + + return true; + } + + switch (Encoding) + { + case FileSystem::EEncoding::Narrow: { FString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::Narrow, bWithBOM)) return false; break; } + case FileSystem::EEncoding::Wide: { FWString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::Wide, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF8: { FU8String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::UTF8, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF16BE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::UTF16BE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::UTF16LE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF32BE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::UTF32BE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, Path, FileSystem::EEncoding::UTF32LE, bWithBOM)) return false; break; } + default: check_no_entry(); return false; + } + + return true; +} + +NAMESPACE_END(FileSystem) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END + +#pragma warning(pop) diff --git a/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h b/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h new file mode 100644 index 0000000..0aee2ee --- /dev/null +++ b/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h @@ -0,0 +1,99 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" +#include "Containers/Array.h" +#include "Strings/StringView.h" +#include "Strings/String.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_BEGIN(FileSystem) + +/** The encoding of the text file. */ +enum class EEncoding : uint8 +{ + Default, + Narrow, + Wide, + UTF8, + UTF16BE, + UTF16LE, + UTF32BE, + UTF32LE, +}; + +/** Loads the file at the specified path into the byte array. */ +REDCRAFTUTILITY_API bool LoadFileToArray(TArray& Result, FStringView Path); + +/** Saves the byte array to the file at the specified path. */ +REDCRAFTUTILITY_API bool SaveArrayToFile(TArrayView Data, FStringView Path); + +/** + * Loads the file at the specified path into the string. + * + * @param Result - The string to load the file into. + * @param Path - The path to the file to load. + * @param Encoding - The encoding of the file. The default value indicates automatic detection. + * @param bVerify - Whether to verify the character validity of the file. + * + * @return true if the file was successfully loaded, false otherwise. + */ +template +bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bVerify = false); + +template REDCRAFTUTILITY_API bool LoadFileToString (FString&, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool LoadFileToString (FWString&, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool LoadFileToString (FU8String&, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool LoadFileToString(FU16String&, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool LoadFileToString(FU32String&, FStringView, FileSystem::EEncoding, bool); + +/** + * Saves the string to the file at the specified path. + * + * @param String - The string to save to the file. + * @param Path - The path to the file to save. + * @param Encoding - The encoding of the file. The default value indicates the same as the string. + * @param bWithBOM - Whether to write the BOM character at the beginning of the file. Not valid for narrow and wide encoding. + * + * @return true if the file was successfully saved, false otherwise. + */ +template +bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bWithBOM = true); + +template REDCRAFTUTILITY_API bool SaveStringToFile (FStringView, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool SaveStringToFile (FWStringView, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool SaveStringToFile (FU8StringView, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool SaveStringToFile(FU16StringView, FStringView, FileSystem::EEncoding, bool); +template REDCRAFTUTILITY_API bool SaveStringToFile(FU32StringView, FStringView, FileSystem::EEncoding, bool); + +/** + * Saves the string to the file at the specified path. + * + * @param String - The string to save to the file. + * @param Path - The path to the file to save. + * @param Encoding - The encoding of the file. The default value indicates the same as the string. + * @param bWithBOM - Whether to write the BOM character at the beginning of the file. Not valid for narrow and wide encoding. + * + * @return true if the file was successfully saved, false otherwise. + */ +template requires (CConvertibleTo || CConvertibleTo + || CConvertibleTo || CConvertibleTo || CConvertibleTo) +bool SaveStringToFile(T&& String, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bWithBOM = true) +{ + if constexpr (CConvertibleTo) return SaveStringToFile(FStringView (Forward(String)), Path, Encoding, bWithBOM); + else if constexpr (CConvertibleTo) return SaveStringToFile(FWStringView (Forward(String)), Path, Encoding, bWithBOM); + else if constexpr (CConvertibleTo) return SaveStringToFile(FU8StringView (Forward(String)), Path, Encoding, bWithBOM); + else if constexpr (CConvertibleTo) return SaveStringToFile(FU16StringView(Forward(String)), Path, Encoding, bWithBOM); + else if constexpr (CConvertibleTo) return SaveStringToFile(FU32StringView(Forward(String)), Path, Encoding, bWithBOM); + + return false; +} + +NAMESPACE_END(FileSystem) + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END