// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/File.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Math/Math.h" #include "Engine/Engine/Globals.h" bool FileSystemBase::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) { // No supported return true; } bool FileSystemBase::ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) { // No supported return true; } bool FileSystemBase::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path) { // No supported return true; } bool FileSystemBase::ShowFileExplorer(const StringView& path) { // No supported return true; } void FileSystemBase::SaveBitmapToFile(byte* data, uint32 width, uint32 height, uint32 bitsPerPixel, const uint32 padding, const String& path) { // Try to open file auto file = File::Open(path, FileMode::CreateAlways, FileAccess::Write, FileShare::None); if (file == nullptr) return; const unsigned long headers_size = 54; const unsigned long pixel_data_size = height * ((width * bitsPerPixel / 8) + padding); const unsigned int filesize = headers_size + pixel_data_size; uint8 bmpfileheader[14] = { 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 }; uint8 bmpinfoheader[40] = { 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0 }; uint8 bmppad[3] = { 0, 0, 0 }; // Init the file header bmpfileheader[2] = (uint8)(filesize); bmpfileheader[3] = (uint8)(filesize >> 8); bmpfileheader[4] = (uint8)(filesize >> 16); bmpfileheader[5] = (uint8)(filesize >> 24); // Init the bitmap info header bmpinfoheader[4] = (uint8)(width); bmpinfoheader[5] = (uint8)(width >> 8); bmpinfoheader[6] = (uint8)(width >> 16); bmpinfoheader[7] = (uint8)(width >> 24); bmpinfoheader[8] = (uint8)(height); bmpinfoheader[9] = (uint8)(height >> 8); bmpinfoheader[10] = (uint8)(height >> 16); bmpinfoheader[11] = (uint8)(height >> 24); // Write the file header file->Write(bmpfileheader, sizeof(bmpfileheader)); // Write the bitmap info header file->Write(bmpinfoheader, sizeof(bmpinfoheader)); // Write the RGB Data file->Write(data, pixel_data_size); // Close the file Delete(file); } bool FileSystemBase::AreFilePathsEqual(const StringView& path1, const StringView& path2) { if (path1.Compare(path2, StringSearchCase::CaseSensitive) == 0) return true; // Normalize file paths String filename1(path1); String filename2(path2); NormalizePath(filename1); NormalizePath(filename2); return filename1.Compare(filename2, StringSearchCase::IgnoreCase) == 0; } void FileSystemBase::NormalizePath(String& path) { path.Replace('\\', '/'); if (path.Length() > 2 && StringUtils::IsAlpha(path[0]) && path[1] == ':') { path[2] = '\\'; } } bool FileSystemBase::IsRelative(const StringView& path) { const bool isRooted = (path.Length() >= 2 && StringUtils::IsAlpha(path[0]) && path[1] == ':') || path.StartsWith(StringView(TEXT("\\\\"), 2), StringSearchCase::CaseSensitive) || path.StartsWith('\\') || path.StartsWith('/'); return !isRooted; } String FileSystemBase::GetExtension(const StringView& path) { Char chr; int32 length = path.Length(); int32 num = length; do { num--; if (num < 0) { break; } chr = path[num]; if (chr != '.') { continue; } if (num == length - 1) { return String::Empty; } num++; return path.Substring(num, length - num); } while (chr != TEXT('\\') && chr != TEXT('/') && chr != TEXT(':')); return String::Empty; } void FileSystemBase::GetTempFilePath(String& tmpPath) { tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::N); } static void SplitPath(const String& path, Array& splitPath) { int32 start = 0; int32 separatorPos; do { separatorPos = path.FindFirstOf(TEXT("\\/"), start); if (separatorPos == -1) splitPath.Add(path.Substring(start)); else splitPath.Add(path.Substring(start, separatorPos - start)); start = separatorPos + 1; } while (separatorPos != -1); } String FileSystemBase::ConvertAbsolutePathToRelative(const String& basePath, const String& path) { Array toDirs; Array fromDirs; SplitPath(path, toDirs); SplitPath(basePath, fromDirs); String output; Array::Iterator toIt = toDirs.Begin(), fromIt = fromDirs.Begin(); const Array::Iterator toEnd = toDirs.End(), fromEnd = fromDirs.End(); while (toIt != toEnd && fromIt != fromEnd && *toIt == *fromIt) { ++toIt; ++fromIt; } while (fromIt != fromEnd) { output += TEXT("../"); ++fromIt; } while (toIt != toEnd) { output += *toIt; ++toIt; if (toIt != toEnd) output += '/'; } return output; } bool FileSystemBase::CopyFile(const String& dst, const String& src) { // Open and create files const auto srcFile = File::Open(src, FileMode::OpenExisting, FileAccess::Read); if (!srcFile) { return true; } const auto dstFile = File::Open(dst, FileMode::CreateAlways, FileAccess::Write); if (!dstFile) { Delete(srcFile); return true; } // Skip for empty file uint32 size = srcFile->GetSize(); if (size == 0) { Delete(srcFile); Delete(dstFile); return false; } // Copy data const uint32 bufferSize = Math::Min(1024 * 1024, size); byte* buffer = (byte*)Allocator::Allocate(bufferSize); while (size) { const uint32 readSize = Math::Min(bufferSize, size); srcFile->Read(buffer, readSize); dstFile->Write(buffer, readSize); size -= readSize; } Allocator::Free(buffer); // Cleanup Delete(srcFile); Delete(dstFile); return false; } bool FileSystemBase::CopyDirectory(const String& dst, const String& src, bool withSubDirectories) { // Check if source exists if (!FileSystem::DirectoryExists(*src)) return false; // Copy return FileSystemBase::DirectoryCopyHelper(dst, src, withSubDirectories); } uint64 FileSystemBase::GetDirectorySize(const StringView& path) { uint64 result = 0; Array files; FileSystem::DirectoryGetFiles(files, path, TEXT("*"), DirectorySearchOption::AllDirectories); for (const String& file : files) result += FileSystem::GetFileSize(file); return result; } String FileSystemBase::ConvertRelativePathToAbsolute(const String& path) { return ConvertRelativePathToAbsolute(Globals::StartupFolder, path); } String FileSystemBase::ConvertRelativePathToAbsolute(const String& basePath, const String& path) { String fullyPathed; if (IsRelative(path)) { fullyPathed = basePath; } fullyPathed /= path; NormalizePath(fullyPathed); return fullyPathed; } String FileSystemBase::ConvertAbsolutePathToRelative(const String& path) { return ConvertAbsolutePathToRelative(Globals::StartupFolder, path); } bool FileSystemBase::DirectoryCopyHelper(const String& dst, const String& src, bool withSubDirectories) { // Create dst directory if (!FileSystem::DirectoryExists(dst)) { if (FileSystem::CreateDirectory(dst)) return true; } // Copy all files Array cache(32); if (FileSystem::DirectoryGetFiles(cache, *src, TEXT("*"), DirectorySearchOption::TopDirectoryOnly)) return true; for (int32 i = 0; i < cache.Count(); i++) { String dstFile = dst / StringUtils::GetFileName(cache[i]); if (FileSystem::CopyFile(*dstFile, *cache[i])) return true; } // Copy all subdirectories (if need to) if (withSubDirectories) { cache.Clear(); if (FileSystem::GetChildDirectories(cache, src)) return true; for (int32 i = 0; i < cache.Count(); i++) { String dstDir = dst / StringUtils::GetFileName(cache[i]); if (DirectoryCopyHelper(dstDir, cache[i], true)) return true; } } return false; }