PrimeWorldEditor/src/Common/FileUtil.cpp

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"";
}
}