mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-05-29 10:41:22 +00:00
499 lines
13 KiB
C++
499 lines
13 KiB
C++
#include "FileUtil.h"
|
|
#include "AssertMacro.h"
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
|
|
// These are mostly just wrappers around boost::filesystem functions.
|
|
using namespace boost::filesystem;
|
|
|
|
namespace FileUtil
|
|
{
|
|
|
|
bool Exists(const TWideString &rkFilePath)
|
|
{
|
|
return exists(*rkFilePath);
|
|
}
|
|
|
|
bool IsRoot(const TWideString& rkPath)
|
|
{
|
|
// todo: is this actually a good/multiplatform way of checking for root?
|
|
TWideString AbsPath = MakeAbsolute(rkPath);
|
|
TWideStringList Split = AbsPath.Split(L"\\/");
|
|
return (Split.size() <= 1);
|
|
}
|
|
|
|
bool IsFile(const TWideString& rkFilePath)
|
|
{
|
|
return is_regular_file(*rkFilePath);
|
|
}
|
|
|
|
bool IsDirectory(const TWideString& rkDirPath)
|
|
{
|
|
return is_directory(*rkDirPath);
|
|
}
|
|
|
|
bool IsAbsolute(const TWideString& rkDirPath)
|
|
{
|
|
return boost::filesystem::path(*rkDirPath).is_absolute();
|
|
}
|
|
|
|
bool IsRelative(const TWideString& rkDirPath)
|
|
{
|
|
return boost::filesystem::path(*rkDirPath).is_relative();
|
|
}
|
|
|
|
bool IsEmpty(const TWideString& rkDirPath)
|
|
{
|
|
if (!IsDirectory(rkDirPath))
|
|
{
|
|
Log::Error("Non-directory path passed to IsEmpty(): " + rkDirPath.ToUTF8());
|
|
DEBUG_BREAK;
|
|
return false;
|
|
}
|
|
|
|
return is_empty(*rkDirPath);
|
|
}
|
|
|
|
bool MakeDirectory(const TWideString& rkNewDir)
|
|
{
|
|
if (!IsValidPath(rkNewDir, true))
|
|
{
|
|
Log::Error("Unable to create directory because name contains illegal characters: " + rkNewDir.ToUTF8());
|
|
return false;
|
|
}
|
|
|
|
return create_directories(*rkNewDir);
|
|
}
|
|
|
|
bool CopyFile(const TWideString& rkOrigPath, const TWideString& rkNewPath)
|
|
{
|
|
if (!IsValidPath(rkNewPath, false))
|
|
{
|
|
Log::Error("Unable to copy file because destination name contains illegal characters: " + rkNewPath.ToUTF8());
|
|
return false;
|
|
}
|
|
|
|
MakeDirectory(rkNewPath.GetFileDirectory());
|
|
boost::system::error_code Error;
|
|
copy(*rkOrigPath, *rkNewPath, Error);
|
|
return (Error == boost::system::errc::success);
|
|
}
|
|
|
|
bool CopyDirectory(const TWideString& rkOrigPath, const TWideString& rkNewPath)
|
|
{
|
|
if (!IsValidPath(rkNewPath, true))
|
|
{
|
|
Log::Error("Unable to copy directory because destination name contains illegal characters: " + rkNewPath.ToUTF8());
|
|
return false;
|
|
}
|
|
|
|
MakeDirectory(rkNewPath.GetFileDirectory());
|
|
boost::system::error_code Error;
|
|
copy_directory(*rkOrigPath, *rkNewPath, Error);
|
|
return (Error == boost::system::errc::success);
|
|
}
|
|
|
|
bool MoveFile(const TWideString& rkOldPath, const TWideString& rkNewPath)
|
|
{
|
|
if (!IsValidPath(rkNewPath, false))
|
|
{
|
|
Log::Error("Unable to move file because destination name contains illegal characters: " + rkNewPath.ToUTF8());
|
|
return false;
|
|
}
|
|
|
|
if (CopyFile(rkOldPath, rkNewPath))
|
|
return DeleteFile(rkOldPath);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool MoveDirectory(const TWideString& rkOldPath, const TWideString& rkNewPath)
|
|
{
|
|
if (!IsValidPath(rkNewPath, true))
|
|
{
|
|
Log::Error("Unable to move directory because destination name contains illegal characters: " + rkNewPath.ToUTF8());
|
|
return false;
|
|
}
|
|
|
|
if (CopyDirectory(rkOldPath, rkNewPath))
|
|
return DeleteDirectory(rkOldPath, false);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool DeleteFile(const TWideString& rkFilePath)
|
|
{
|
|
if (!IsFile(rkFilePath)) return false;
|
|
return remove(*rkFilePath) == 1;
|
|
}
|
|
|
|
bool DeleteDirectory(const TWideString& rkDirPath, bool FailIfNotEmpty)
|
|
{
|
|
// This is an extremely destructive function, be careful using it!
|
|
if (!IsDirectory(rkDirPath)) return false;
|
|
|
|
// Sanity check - don't delete root
|
|
bool Root = IsRoot(rkDirPath);
|
|
|
|
if (Root)
|
|
{
|
|
ASSERT(false);
|
|
Log::Error("Attempted to delete root directory!");
|
|
return false;
|
|
}
|
|
|
|
// Check if directory is empty
|
|
if (FailIfNotEmpty && !IsEmpty(rkDirPath))
|
|
return false;
|
|
|
|
// Delete directory
|
|
boost::system::error_code Error;
|
|
remove_all(*rkDirPath, Error);
|
|
return (Error == boost::system::errc::success);
|
|
}
|
|
|
|
bool ClearDirectory(const TWideString& rkDirPath)
|
|
{
|
|
// This is an extremely destructive function, be careful using it!
|
|
if (!IsDirectory(rkDirPath)) return false;
|
|
|
|
// Sanity check - don't clear root
|
|
bool Root = IsRoot(rkDirPath);
|
|
|
|
if (Root)
|
|
{
|
|
ASSERT(false);
|
|
Log::Error("Attempted to clear root directory!");
|
|
return false;
|
|
}
|
|
|
|
// Delete directory contents
|
|
TWideStringList DirContents;
|
|
GetDirectoryContents(rkDirPath, DirContents, false);
|
|
|
|
for (auto It = DirContents.begin(); It != DirContents.end(); It++)
|
|
{
|
|
bool Success = false;
|
|
|
|
if (IsFile(*It))
|
|
Success = DeleteFile(*It);
|
|
else if (IsDirectory(*It))
|
|
Success = DeleteDirectory(*It, false);
|
|
|
|
if (!Success)
|
|
Log::Error("Failed to delete filesystem object: " + TWideString(*It).ToUTF8());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u64 FileSize(const TWideString &rkFilePath)
|
|
{
|
|
return (u64) (Exists(*rkFilePath) ? file_size(*rkFilePath) : -1);
|
|
}
|
|
|
|
u64 LastModifiedTime(const TWideString& rkFilePath)
|
|
{
|
|
return (u64) last_write_time(*rkFilePath);
|
|
}
|
|
|
|
TWideString WorkingDirectory()
|
|
{
|
|
return boost::filesystem::current_path().string();
|
|
}
|
|
|
|
TWideString MakeAbsolute(TWideString Path)
|
|
{
|
|
if (!boost::filesystem::path(*Path).has_root_name())
|
|
Path = WorkingDirectory() + L"\\" + Path;
|
|
|
|
TWideStringList Components = Path.Split(L"/\\");
|
|
TWideStringList::iterator Prev;
|
|
|
|
for (TWideStringList::iterator Iter = Components.begin(); Iter != Components.end(); Iter++)
|
|
{
|
|
if (*Iter == L".")
|
|
Iter = Components.erase(Iter);
|
|
else if (*Iter == L"..")
|
|
Iter = std::prev(Components.erase(Prev, std::next(Iter)));
|
|
|
|
Prev = Iter;
|
|
}
|
|
|
|
TWideString Out;
|
|
for (auto it = Components.begin(); it != Components.end(); it++)
|
|
Out += *it + L"\\";
|
|
|
|
return Out;
|
|
}
|
|
|
|
TWideString MakeRelative(const TWideString& rkPath, const TWideString& rkRelativeTo /*= WorkingDirectory()*/)
|
|
{
|
|
TWideString AbsPath = MakeAbsolute(rkPath);
|
|
TWideString AbsRelTo = MakeAbsolute(rkRelativeTo);
|
|
TWideStringList PathComponents = AbsPath.Split(L"/\\");
|
|
TWideStringList RelToComponents = AbsRelTo.Split(L"/\\");
|
|
|
|
// Find furthest common parent
|
|
TWideStringList::iterator PathIter = PathComponents.begin();
|
|
TWideStringList::iterator RelToIter = RelToComponents.begin();
|
|
|
|
for (; PathIter != PathComponents.end() && RelToIter != RelToComponents.end(); PathIter++, RelToIter++)
|
|
{
|
|
if (*PathIter != *RelToIter)
|
|
break;
|
|
}
|
|
|
|
// If there's no common components, then we can't construct a relative path...
|
|
if (PathIter == PathComponents.begin())
|
|
return AbsPath;
|
|
|
|
// Construct output path
|
|
TWideString Out;
|
|
|
|
for (; RelToIter != RelToComponents.end(); RelToIter++)
|
|
Out += L"..\\";
|
|
|
|
for (; PathIter != PathComponents.end(); PathIter++)
|
|
Out += *PathIter + L"\\";
|
|
|
|
// Attempt to detect if this path is a file as opposed to a directory; if so, remove trailing backslash
|
|
if (PathComponents.back().Contains(L'.') && !rkPath.EndsWith(L'/') && !rkPath.EndsWith(L'\\'))
|
|
Out = Out.ChopBack(1);
|
|
|
|
return Out;
|
|
}
|
|
|
|
TWideString SimplifyRelativePath(const TWideString& rkPath)
|
|
{
|
|
TWideStringList PathComponents = rkPath.Split(L"/\\");
|
|
|
|
TWideStringList::iterator Iter = PathComponents.begin();
|
|
TWideStringList::iterator PrevIter = Iter;
|
|
|
|
for (auto Iter = PathComponents.begin(); Iter != PathComponents.end(); PrevIter = Iter, Iter++)
|
|
{
|
|
if (*Iter == L".." && *PrevIter != L"..")
|
|
{
|
|
PrevIter = PathComponents.erase(PrevIter);
|
|
PrevIter = PathComponents.erase(PrevIter);
|
|
Iter = PrevIter;
|
|
Iter--;
|
|
}
|
|
}
|
|
|
|
TWideString Out;
|
|
|
|
for (auto Iter = PathComponents.begin(); Iter != PathComponents.end(); Iter++)
|
|
Out += *Iter + L'\\';
|
|
|
|
return Out;
|
|
}
|
|
|
|
static const wchar_t gskIllegalNameChars[] = {
|
|
L'<', L'>', L'\"', L'/', L'\\', L'|', L'?', L'*'
|
|
};
|
|
|
|
TWideString SanitizeName(TWideString Name, bool Directory, bool RootDir /*= false*/)
|
|
{
|
|
// Windows only atm
|
|
if (Directory && (Name == L"." || Name == L".."))
|
|
return Name;
|
|
|
|
// Remove illegal characters from path
|
|
u32 NumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(wchar_t);
|
|
|
|
for (u32 iChr = 0; iChr < Name.Size(); iChr++)
|
|
{
|
|
wchar_t Chr = Name[iChr];
|
|
bool Remove = false;
|
|
|
|
if (Chr >= 0 && Chr <= 31)
|
|
Remove = true;
|
|
|
|
// For root, allow colon only as the last character of the name
|
|
else if (Chr == L':' && (!RootDir || iChr != Name.Size() - 1))
|
|
Remove = true;
|
|
|
|
else
|
|
{
|
|
for (u32 iBan = 0; iBan < NumIllegalChars; iBan++)
|
|
{
|
|
if (Chr == gskIllegalNameChars[iBan])
|
|
{
|
|
Remove = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Remove)
|
|
{
|
|
Name.Remove(iChr, 1);
|
|
iChr--;
|
|
}
|
|
}
|
|
|
|
// For directories, space and dot are not allowed at the end of the path
|
|
if (Directory)
|
|
{
|
|
u32 ChopNum = 0;
|
|
|
|
for (int iChr = (int) Name.Size() - 1; iChr >= 0; iChr--)
|
|
{
|
|
wchar_t Chr = Name[iChr];
|
|
|
|
if (Chr == L' ' || Chr == L'.')
|
|
ChopNum++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (ChopNum > 0) Name = Name.ChopBack(ChopNum);
|
|
}
|
|
|
|
// Remove spaces from beginning of path
|
|
u32 NumLeadingSpaces = 0;
|
|
while (NumLeadingSpaces < Name.Size() && Name[NumLeadingSpaces] == L' ')
|
|
NumLeadingSpaces++;
|
|
|
|
if (NumLeadingSpaces > 0)
|
|
Name = Name.ChopFront(NumLeadingSpaces);
|
|
|
|
return Name;
|
|
}
|
|
|
|
TWideString SanitizePath(TWideString Path, bool Directory)
|
|
{
|
|
TWideStringList Components = Path.Split(L"\\/");
|
|
u32 CompIdx = 0;
|
|
Path = L"";
|
|
|
|
for (auto It = Components.begin(); It != Components.end(); It++)
|
|
{
|
|
TWideString Comp = *It;
|
|
bool IsDir = Directory || CompIdx < Components.size() - 1;
|
|
bool IsRoot = CompIdx == 0;
|
|
Comp = SanitizeName(Comp, IsDir, IsRoot);
|
|
|
|
Path += Comp;
|
|
if (IsDir) Path += L'\\';
|
|
CompIdx++;
|
|
}
|
|
|
|
return Path;
|
|
}
|
|
|
|
bool IsValidName(const TWideString& rkName, bool Directory, bool RootDir /*= false*/)
|
|
{
|
|
// Only accounting for Windows limitations right now. However, this function should
|
|
// ideally return the same output on all platforms to ensure projects are cross compatible.
|
|
if (rkName.IsEmpty())
|
|
return false;
|
|
|
|
u32 NumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(wchar_t);
|
|
|
|
if (Directory && (rkName == L"." || rkName == L".."))
|
|
return true;
|
|
|
|
// Check for banned characters
|
|
for (u32 iChr = 0; iChr < rkName.Size(); iChr++)
|
|
{
|
|
wchar_t Chr = rkName[iChr];
|
|
|
|
if (Chr >= 0 && Chr <= 31)
|
|
return false;
|
|
|
|
// Allow colon only on last character of root
|
|
if (Chr == L':' && (!RootDir || iChr != rkName.Size() - 1))
|
|
return false;
|
|
|
|
for (u32 iBan = 0; iBan < NumIllegalChars; iBan++)
|
|
{
|
|
if (Chr == gskIllegalNameChars[iBan])
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (Directory && (rkName.Back() == L' ' || rkName.Back() == L'.'))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsValidPath(const TWideString& rkPath, bool Directory)
|
|
{
|
|
// Only accounting for Windows limitations right now. However, this function should
|
|
// ideally return the same output on all platforms to ensure projects are cross compatible.
|
|
TWideStringList Components = rkPath.Split(L"\\/");
|
|
|
|
// Validate other components
|
|
u32 CompIdx = 0;
|
|
|
|
for (auto It = Components.begin(); It != Components.end(); It++)
|
|
{
|
|
bool IsRoot = CompIdx == 0;
|
|
bool IsDir = Directory || CompIdx < (Components.size() - 1);
|
|
|
|
if (!IsValidName(*It, IsDir, IsRoot))
|
|
return false;
|
|
|
|
CompIdx++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GetDirectoryContents(TWideString DirPath, TWideStringList& rOut, bool Recursive /*= true*/, bool IncludeFiles /*= true*/, bool IncludeDirs /*= true*/)
|
|
{
|
|
if (IsDirectory(DirPath))
|
|
{
|
|
DirPath.Replace(L"/", L"\\");
|
|
bool IncludeAll = IncludeFiles && IncludeDirs;
|
|
|
|
auto AddFileLambda = [IncludeFiles, IncludeDirs, IncludeAll, &rOut](std::wstring Path) -> void {
|
|
bool ShouldAddFile = IncludeAll || (IncludeFiles && IsFile(Path)) || (IncludeDirs && IsDirectory(Path));
|
|
|
|
if (ShouldAddFile)
|
|
rOut.push_back(Path);
|
|
};
|
|
|
|
if (Recursive)
|
|
{
|
|
for (recursive_directory_iterator It(*DirPath); It != recursive_directory_iterator(); ++It)
|
|
{
|
|
#ifdef _WIN32
|
|
AddFileLambda( It->path().native() );
|
|
#else
|
|
AddFileLambda( TString(It->path().native()).ToUTF16().ToStdString() );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
for (directory_iterator It(*DirPath); It != directory_iterator(); ++It)
|
|
{
|
|
#ifdef _WIN32
|
|
AddFileLambda( It->path().native() );
|
|
#else
|
|
AddFileLambda( TString(It->path().native()).ToUTF16().ToStdString() );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TWideString FindFileExtension(const TWideString& rkDir, const TWideString& rkName)
|
|
{
|
|
for (directory_iterator It(*rkDir); It != directory_iterator(); ++It)
|
|
{
|
|
TWideString Name = It->path().filename().native();
|
|
if (Name.GetFileName(false) == rkName) return Name.GetFileExtension();
|
|
}
|
|
|
|
return L"";
|
|
}
|
|
|
|
}
|