diff --git a/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp b/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp index 4b1e184..3b79faa 100644 --- a/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp +++ b/Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp @@ -1,11 +1,22 @@ #include #include "Numerics/Bit.h" +#include "Numerics/Math.h" #include "Templates/ScopeHelper.h" #include "Containers/StaticArray.h" #include +#if PLATFORM_WINDOWS +# undef TEXT +# include +# undef CreateDirectory +#elif PLATFORM_LINUX +# include +# include +# include +#endif + #pragma warning(push) #pragma warning(disable: 4996) @@ -17,6 +28,8 @@ NAMESPACE_BEGIN(FileSystem) bool LoadFileToArray(TArray& Result, FStringView Path) { + if (!FileSystem::Exists(Path)) return false; + FILE* File = std::fopen(*Path, "rb"); if (File == nullptr) return false; @@ -27,7 +40,7 @@ bool LoadFileToArray(TArray& Result, FStringView Path) const long Length = std::ftell(File); - if (Length == -1) return false; + if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return false; if (std::fseek(File, 0, SEEK_SET) != 0) return false; @@ -62,6 +75,8 @@ bool SaveArrayToFile(TArrayView Data, FStringView Path) template bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bVerify /* = false */) { + if (!FileSystem::Exists(Path)) return false; + FILE* File = std::fopen(*Path, "rb"); if (File == nullptr) return false; @@ -72,7 +87,7 @@ bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncodin long Length = std::ftell(File); - if (Length == -1) return false; + if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return false; if (std::fseek(File, 0, SEEK_SET) != 0) return false; @@ -136,7 +151,7 @@ bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncodin if (ReadNum != sizeof(U)) return false; - if constexpr (sizeof(U) > 1) if (bByteSwap) Char = Math::ByteSwap(Char); + if (bByteSwap) Char = Math::ByteSwap(static_cast>(Char)); # if PLATFORM_WINDOWS { @@ -187,6 +202,12 @@ bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncodin return true; } +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); + template bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bWithBOM /* = true */) { @@ -252,14 +273,14 @@ bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEnco { T Return = LITERAL(T, '\r'); - if constexpr (sizeof(T) > 1) if (bByteSwap) Return = Math::ByteSwap(Return); + if (bByteSwap) Return = Math::ByteSwap(static_cast>(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 (bByteSwap) Char = Math::ByteSwap(static_cast>(Char)); if (std::fwrite(&Char, 1, sizeof(T), File) != sizeof(T)) return false; } @@ -271,21 +292,301 @@ bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEnco return true; } + FString PathWithNull; + + PathWithNull.Reserve(Path.Num() + 1); + + PathWithNull += Path; + PathWithNull += '\0'; + 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; } + case FileSystem::EEncoding::Narrow: { FString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Narrow, bWithBOM)) return false; break; } + case FileSystem::EEncoding::Wide: { FWString Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Wide, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF8: { FU8String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF8, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF16BE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16BE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16LE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF32BE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32BE, bWithBOM)) return false; break; } + case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32LE, bWithBOM)) return false; break; } default: check_no_entry(); return false; } return 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); + +size_t FileSize(FStringView Path) +{ + if (!FileSystem::Exists(Path)) return static_cast(-1); + + FILE* File = std::fopen(*Path, "rb"); + + if (File == nullptr) return static_cast(-1); + + auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); + + if (std::fseek(File, 0, SEEK_END) != 0) return static_cast(-1); + + const long Length = std::ftell(File); + + if (!Math::IsWithin(Length, 0, TNumericLimits::Max())) return static_cast(-1); + + FileGuard.Release(); + + if (std::fclose(File) != 0) return static_cast(-1); + + return Length; +} + +bool Delete(FStringView Path) +{ + return std::remove(*Path) == 0; +} + +bool Exists(FStringView Path) +{ +# if PLATFORM_WINDOWS + { + DWORD Attributes = GetFileAttributesA(*Path); + + if (Attributes == INVALID_FILE_ATTRIBUTES) return false; + + return !(Attributes & FILE_ATTRIBUTE_DIRECTORY); + } +# elif PLATFORM_LINUX + { + struct stat FileInfo; + + FileInfo.st_size = -1; + + if (stat(*Path, &FileInfo) != 0) return false; + + if (!S_ISREG(FileInfo.st_mode)) return false; + + return true; + } +# endif + + return false; +} + +bool Copy(FStringView Destination, FStringView Source) +{ + if (!FileSystem::Exists(Source)) return false; + + FILE* FileA = std::fopen(*Source, "rb"); + + if (FileA == nullptr) return false; + + auto FileGuardA = TScopeCallback([=] { Ignore = std::fclose(FileA); }); + + FILE* FileB = std::fopen(*Destination, "wb"); + + if (FileB == nullptr) return false; + + auto FileGuardB = TScopeCallback([=] { Ignore = std::fclose(FileB); }); + + size_t ReadSize; + + constexpr size_t BufferSize = 4096; + + TStaticArray Buffer; + + do + { + ReadSize = std::fread(Buffer.GetData(), 1, BufferSize, FileA); + + if (std::fwrite(Buffer.GetData(), 1, ReadSize, FileB) != ReadSize) return false; + } + while (ReadSize == BufferSize); + + FileGuardA.Release(); + + if (std::fclose(FileA) != 0) return false; + + FileGuardB.Release(); + + if (std::fclose(FileB) != 0) return false; + + return true; +} + +bool Rename(FStringView Destination, FStringView Source) +{ + return std::rename(*Source, *Destination) == 0; +} + +bool CreateDirectory(FStringView Path, bool bRecursive /* = false */) +{ + if (Path.Num() == 0) return false; + + if (bRecursive) + { + if (Path.Back() == '/' || Path.Back() == '\\') Path = Path.First(Path.Num() - 1); + + FStringView Parent = Path.First(Path.FindLastOf("/\\")); + + if (!FileSystem::ExistsDirectory(Parent) && !FileSystem::CreateDirectory(Parent, true)) return false; + } + +# if PLATFORM_WINDOWS + { + return CreateDirectoryA(*Path, nullptr) != 0; + } +# elif PLATFORM_LINUX + { + return mkdir(*Path, 0755) == 0; + } +# endif + + return false; +} + +bool DeleteDirectory(FStringView Path, bool bRecursive /* = false */) +{ + if (bRecursive) + { + FString Temp; + + bool bSuccessfully = FileSystem::IterateDirectory(Path, [&](FStringView File, bool bIsDirectory) -> bool + { + Temp.Reset(false); + + Temp += Path; + Temp += '/'; + Temp += File; + Temp += '\0'; + + if (bIsDirectory) + { + if (!FileSystem::DeleteDirectory(Temp, true)) return false; + } + + else + { + if (!FileSystem::Delete(Temp)) return false; + } + + return true; + }); + + if (!bSuccessfully) return false; + } + +# if PLATFORM_WINDOWS + { + return RemoveDirectoryA(*Path) != 0; + } +# elif PLATFORM_LINUX + { + return rmdir(*Path) == 0; + } +# endif + + return false; +} + +bool ExistsDirectory(FStringView Path) +{ +# if PLATFORM_WINDOWS + { + DWORD Attributes = GetFileAttributesA(*Path); + + if (Attributes == INVALID_FILE_ATTRIBUTES) return false; + + return Attributes & FILE_ATTRIBUTE_DIRECTORY; + } +# elif PLATFORM_LINUX + { + DIR* Directory = opendir(*Path); + + if (Directory == nullptr) return false; + + Ignore = closedir(Directory); + + return true; + } +# endif + + return false; +} + +bool IterateDirectory(FStringView Path, TFunctionRef Visitor) +{ +# if PLATFORM_WINDOWS + { + FString FindPath; + + FindPath.Reserve(Path.Num() + 3); + + FindPath += Path; + FindPath += '\\'; + FindPath += '*'; + FindPath += '\0'; + + WIN32_FIND_DATA FindData; + + HANDLE FindHandle = FindFirstFileA(*FindPath, &FindData); + + auto FindGuard = TScopeCallback([=] { Ignore = FindClose(FindHandle); }); + + if (FindHandle == INVALID_HANDLE_VALUE) return false; + + do + { + const FStringView FilePath = FindData.cFileName; + + if (FilePath == "." || FilePath == "..") continue; + + const bool bIsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + + if (!Visitor(FilePath, bIsDirectory)) return false; + } + while (FindNextFileA(FindHandle, &FindData) != 0); + + FindGuard.Release(); + + if (!FindClose(FindHandle)) return false; + + return true; + } +# elif PLATFORM_LINUX + { + DIR* Directory = opendir(*Path); + + if (Directory == nullptr) return false; + + auto DirectoryGuard = TScopeCallback([=] { Ignore = closedir(Directory); }); + + dirent* Entry; + + while ((Entry = readdir(Directory)) != nullptr) + { + const FStringView FilePath = Entry->d_name; + + if (FilePath == "." || FilePath == "..") continue; + + const bool bIsDirectory = Entry->d_type == DT_DIR; + + if (!Visitor(FilePath, bIsDirectory)) return false; + } + + DirectoryGuard.Release(); + + if (closedir(Directory) != 0) return false; + + return true; + } +# endif + + return false; +} + NAMESPACE_END(FileSystem) NAMESPACE_MODULE_END(Utility) diff --git a/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h b/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h index 0aee2ee..369b092 100644 --- a/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h +++ b/Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h @@ -2,12 +2,14 @@ #include "CoreTypes.h" #include "TypeTraits/TypeTraits.h" +#include "Templates/Utility.h" +#include "Templates/Function.h" #include "Containers/Array.h" #include "Strings/StringView.h" #include "Strings/String.h" NAMESPACE_REDCRAFT_BEGIN -NAMESPACE_MODULE_BEGIN(Redcraft) + NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) NAMESPACE_BEGIN(FileSystem) @@ -42,13 +44,7 @@ REDCRAFTUTILITY_API bool SaveArrayToFile(TArrayView Data, FStringVi * @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); +REDCRAFTUTILITY_API bool LoadFileToString(TString& Result, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bVerify = false); /** * Saves the string to the file at the specified path. @@ -61,13 +57,7 @@ template REDCRAFTUTILITY_API bool LoadFileToString(FU32String&, FString * @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); +REDCRAFTUTILITY_API bool SaveStringToFile(TStringView String, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bWithBOM = true); /** * Saves the string to the file at the specified path. @@ -92,6 +82,24 @@ bool SaveStringToFile(T&& String, FStringView Path, FileSystem::EEncoding Encodi return false; } +REDCRAFTUTILITY_API size_t FileSize(FStringView Path); + +REDCRAFTUTILITY_API bool Delete(FStringView Path); + +REDCRAFTUTILITY_API bool Exists(FStringView Path); + +REDCRAFTUTILITY_API bool Copy(FStringView Destination, FStringView Source); + +REDCRAFTUTILITY_API bool Rename(FStringView Destination, FStringView Source); + +REDCRAFTUTILITY_API bool CreateDirectory(FStringView Path, bool bRecursive = false); + +REDCRAFTUTILITY_API bool DeleteDirectory(FStringView Path, bool bRecursive = false); + +REDCRAFTUTILITY_API bool ExistsDirectory(FStringView Path); + +REDCRAFTUTILITY_API bool IterateDirectory(FStringView Path, TFunctionRef Visitor); + NAMESPACE_END(FileSystem) NAMESPACE_MODULE_END(Utility)