openclonk/src/platform/StdFile.cpp

1109 lines
27 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Lots of file helpers */
#include "C4Include.h"
#include "platform/StdFile.h"
#ifdef HAVE_IO_H
#include <io.h>
#endif
#ifdef _WIN32
#include "platform/C4windowswrapper.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <zlib.h>
/* Path & Filename */
#ifdef _WIN32
static const char *DirectorySeparators = R"(/\)";
#else
static const char *DirectorySeparators = "/";
#endif
// Return pointer to position after last backslash.
char *GetFilename(char *szPath)
{
if (!szPath) return nullptr;
char *pPos,*pFilename=szPath;
for (pPos=szPath; *pPos; pPos++) if (*pPos==DirectorySeparator || *pPos=='/') pFilename = pPos+1;
return pFilename;
}
const char *GetFilename(const char *szPath)
{
if (!szPath) return nullptr;
const char *pPos,*pFilename=szPath;
for (pPos=szPath; *pPos; pPos++) if (*pPos==DirectorySeparator || *pPos=='/') pFilename = pPos+1;
return pFilename;
}
const char* GetFilenameOnly(const char *strFilename)
{
// Get filename to static buffer
static char strBuffer[_MAX_PATH + 1];
SCopy(GetFilename(strFilename), strBuffer);
// Truncate extension
RemoveExtension(strBuffer);
// Return buffer
return strBuffer;
}
const char *GetC4Filename(const char *szPath)
{
// returns path to file starting at first .c4*-directory.
if (!szPath) return nullptr;
const char *pPos,*pFilename=szPath;
for (pPos=szPath; *pPos; pPos++)
{
if (*pPos==DirectorySeparator || *pPos=='/')
{
if (pPos >= szPath+4 && SEqual2NoCase(pPos-4, ".oc")) return pFilename;
pFilename = pPos+1;
}
}
return pFilename;
}
int GetTrailingNumber(const char *strString)
{
// Default
int iNumber = 0;
// Start from end
const char *cpPos = strString + SLen(strString);
// Walk back while number
while ((cpPos > strString) && Inside(*(cpPos - 1), '0', '9')) cpPos--;
// Scan number
sscanf(cpPos, "%d", &iNumber);
// Return result
return iNumber;
}
// Like GetFilename, but searches for a slash instead of a backslash
// (unix-style paths)
char *GetFilenameWeb(char *szPath)
{
if (!szPath) return nullptr;
char *pPos, *pFilename=szPath;
for (pPos=szPath; *pPos; pPos++) if (*pPos == '/') pFilename = pPos+1;
return pFilename;
}
const char *GetFilenameWeb(const char *szPath)
{
if (!szPath) return nullptr;
const char *pPos, *pFilename=szPath;
for (pPos=szPath; *pPos; pPos++) if (*pPos == '/') pFilename = pPos+1;
return pFilename;
}
// Return pointer to last file extension.
char *GetExtension(char *szFilename)
{
int pos, end;
for (end=0; szFilename[end]; end++) {}
pos = end;
while ((pos > 0) && (szFilename[pos-1] != '.') && (szFilename[pos-1] != DirectorySeparator)) --pos;
if ((pos > 0) && szFilename[pos-1] == '.') return szFilename + pos;
return szFilename + end;
}
const char *GetExtension(const char *szFilename)
{
int pos, end;
for (end=0; szFilename[end]; end++) {}
pos = end;
while ((pos>0) && (szFilename[pos-1] != '.') && (szFilename[pos-1] != DirectorySeparator)) pos--;
if ((pos > 0) && szFilename[pos-1] == '.') return szFilename+pos;
return szFilename+end;
}
void RealPath(const char *szFilename, char *pFullFilename)
{
#ifdef _WIN32
wchar_t *wpath = _wfullpath(nullptr, GetWideChar(szFilename), 0);
StdStrBuf path(wpath);
// I'm pretty sure pFullFilename will always have at least a size of _MAX_PATH, but ughh
// This should return a StdStrBuf
SCopy(path.getData(), pFullFilename, _MAX_PATH);
free(wpath);
#else
char *pSuffix = nullptr;
char szCopy[_MAX_PATH + 1];
for (;;)
{
// Try to convert to full filename. Note this might fail if the given file doesn't exist
if (realpath(szFilename, pFullFilename))
break;
// ... which is undesired behaviour here. Try to reduce the filename until it works.
if (!pSuffix)
{
SCopy(szFilename, szCopy, _MAX_PATH);
szFilename = szCopy;
pSuffix = szCopy + SLen(szCopy);
}
else
*pSuffix = '/';
while (pSuffix > szCopy)
if (*--pSuffix == '/')
break;
if (pSuffix <= szCopy)
{
// Give up: Just copy whatever we got
SCopy(szFilename, pFullFilename, _MAX_PATH);
return;
}
*pSuffix = 0;
}
// Append suffix
if (pSuffix)
{
*pSuffix = '/';
SAppend(pSuffix, pFullFilename, _MAX_PATH);
}
#endif
}
// Copy (extended) parent path (without backslash) to target buffer.
bool GetParentPath(const char *szFilename, char *szBuffer)
{
// Prepare filename
SCopy(szFilename,szBuffer,_MAX_PATH);
// Extend relative single filenames
#ifdef _WIN32
if (!SCharCount(DirectorySeparator,szFilename)) _fullpath(szBuffer,szFilename,_MAX_PATH);
#else
if (!SCharCount(DirectorySeparator,szFilename)) RealPath(szFilename,szBuffer);
#endif
// Truncate path
return TruncatePath(szBuffer);
}
bool GetParentPath(const char *szFilename, StdStrBuf *outBuf)
{
char buf[_MAX_PATH+1]; *buf='\0';
if (!GetParentPath(szFilename, buf)) return false;
outBuf->Copy(buf);
return true;
}
const char *GetRelativePathS(const char *strPath, const char *strRelativeTo)
{
// Specified path is relative to base path
#ifdef _WIN32
if (SEqual2NoCase(strPath, strRelativeTo))
#else
if (SEqual2(strPath, strRelativeTo))
#endif
{
// return relative section
return strPath + SLen(strRelativeTo) + ((strPath[SLen(strRelativeTo)] == DirectorySeparator) ? +1 : 0);
}
// Not relative: return full path
return strPath;
}
bool IsGlobalPath(const char *szPath)
{
#ifdef _WIN32
// C:\...
if (*szPath && szPath[1] == ':') return true;
#endif
// /usr/bin, \Temp\, ...
if (*szPath == DirectorySeparator) return true;
return false;
}
// Truncate string before last backslash.
bool TruncatePath(char *szPath)
{
if (!szPath) return false;
int iBSPos;
iBSPos=SCharLastPos(DirectorySeparator,szPath);
#ifndef _WIN32
int iBSPos2;
iBSPos2=SCharLastPos('\\',szPath);
if (iBSPos2 > iBSPos) fprintf(stderr, "Warning: TruncatePath with a \\ (%s)\n", szPath);
#endif
if (iBSPos<0) return false;
szPath[iBSPos]=0;
return true;
}
// Append terminating backslash if not present.
void AppendBackslash(char *szFilename)
{
int i=SLen(szFilename);
if (i>0) if (szFilename[i-1]==DirectorySeparator) return;
SAppendChar(DirectorySeparator,szFilename);
}
// Remove terminating backslash if present.
void TruncateBackslash(char *szFilename)
{
int i=SLen(szFilename);
if (i>0) if (szFilename[i-1]==DirectorySeparator) szFilename[i-1]=0;
}
// Append extension if no extension.
void DefaultExtension(char *szFilename, const char *szExtension)
{
if (!(*GetExtension(szFilename)))
{ SAppend(".",szFilename); SAppend(szExtension,szFilename); }
}
void DefaultExtension(StdStrBuf *sFilename, const char *szExtension)
{
assert(sFilename);
if (!(*GetExtension(sFilename->getData())))
{ sFilename->AppendChar('.'); sFilename->Append(szExtension); }
}
// Append or overwrite extension.
void EnforceExtension(char *szFilename, const char *szExtension)
{
char *ext = GetExtension(szFilename);
if (ext[0]) { SCopy(szExtension,ext); }
else { SAppend(".",szFilename); SAppend(szExtension,szFilename); }
}
void EnforceExtension(StdStrBuf *sFilename, const char *szExtension)
{
assert(sFilename);
const char *ext = GetExtension(sFilename->getData());
if (ext[0]) { sFilename->ReplaceEnd(ext - sFilename->getData(), szExtension); }
else { sFilename->AppendChar('.'); sFilename->Append(szExtension); }
}
// remove extension
void RemoveExtension(char *szFilename)
{
char *ext = GetExtension(szFilename);
if (ext[0]) ext[-1]=0;
}
void RemoveExtension(StdStrBuf *psFileName)
{
if (psFileName && *psFileName)
{
RemoveExtension(psFileName->getMData());
psFileName->SetLength(strlen(psFileName->getData()));
}
}
// Enforce indexed extension until item does not exist.
void MakeTempFilename(char *szFilename)
{
DefaultExtension(szFilename,"tmp");
char *fn_ext=GetExtension(szFilename);
int cnum=-1;
do
{
cnum++;
osprintf(fn_ext,"%03d",cnum);
}
while (FileExists(szFilename) && (cnum<999));
}
void MakeTempFilename(StdStrBuf *sFilename)
{
assert(sFilename);
if (!sFilename->getLength()) sFilename->Copy("temp.tmp");
EnforceExtension(sFilename, "tmp");
char *fn_ext=GetExtension(sFilename->getMData());
int cnum=-1;
do
{
cnum++;
osprintf(fn_ext,"%03d",cnum);
}
while (FileExists(sFilename->getData()) && (cnum<999));
}
bool WildcardListMatch(const char *szWildcardList, const char *szString)
{
// safety
if (!szString || !szWildcardList) return false;
// match any item in list
StdStrBuf sWildcard, sWildcardList(szWildcardList);
int32_t i=0;
while (sWildcardList.GetSection(i++, &sWildcard, '|'))
{
if (WildcardMatch(sWildcard.getData(), szString)) return true;
}
// none matched
return false;
}
bool IsWildcardString(const char *szString)
{
// safety
if (!szString) return false;
// known wildcard characters: *?[
return (SCharCount('?', szString)>0) || (SCharCount('*', szString)>0) || (SCharCount('[', szString)>0);
}
// Parses a character class, skips over it and returns whether it matches the given character.
static bool WildcardMatchCharacterClass(const char **charclass, char matchchar)
{
const char *cc = *charclass;
if (*cc++ != '[') return false;
bool match = false;
// ] is allowed as first character, e.g. []]
do
{
// range, e.g. [a-z]
if (cc[1] == '-' && cc[2] != ']')
{
if (matchchar >= cc[0] && matchchar <= cc[2])
match = true;
cc += 2;
}
// single character
else if (*cc == matchchar)
match = true;
}
while (*++cc && *cc != ']');
*charclass = cc;
return match;
}
bool WildcardMatch(const char *szWildcard, const char *szString)
{
// safety
if (!szString || !szWildcard) return false;
// match char-wise
const char *pWild = szWildcard, *pPos = szString;
const char *pLWild = nullptr, *pLPos = nullptr; // backtracking
while (*pWild || pLWild)
// string wildcard?
if (*pWild == '*')
{ pLWild = ++pWild; pLPos = pPos; }
// nothing left to match?
else if (!*pPos)
break;
// character class, equal or one-character-wildcard? proceed
else if ((*pWild == '[' && WildcardMatchCharacterClass(&pWild, *pPos))
|| *pWild == '?'
|| tolower(*pWild) == tolower(*pPos))
{ pWild++; pPos++; }
// backtrack possible?
else if (pLPos)
{ pWild = pLWild; pPos = ++pLPos; }
// match failed
else
return false;
// match complete if both strings are fully matched
return !*pWild && !*pPos;
}
// create a valid file name from some title
void MakeFilenameFromTitle(char *szTitle)
{
// copy all chars but those to be stripped
char *szFilename=szTitle, *szTitle2=szTitle;
while (*szTitle2)
{
bool fStrip;
if (IsWhiteSpace(*szTitle2))
fStrip = (szFilename==szTitle);
else if (static_cast<unsigned int>(*szTitle2) > 127)
fStrip = true;
else
fStrip = (SCharPos(*szTitle2, R"(!"'%&/=?+*#:;<>\.)") >= 0);
if (!fStrip) *szFilename++ = *szTitle2;
++szTitle2;
}
// truncate spaces from end
while (IsWhiteSpace(*--szFilename)) if (szFilename==szTitle) { --szFilename; break; }
// terminate
*++szFilename=0;
// no name? (only invalid chars)
if (!*szTitle) SCopy("unnamed", szTitle, 50);
// done
}
/* Files */
bool FileExists(const char *szFilename)
{
#ifdef _WIN32
return GetFileAttributes(GetWideChar(szFilename)) != INVALID_FILE_ATTRIBUTES;
#else
return (!access(szFilename,F_OK));
#endif
}
size_t FileSize(const char *szFilename)
{
#if defined(_WIN32) || defined(_WIN64)
auto attributes = WIN32_FILE_ATTRIBUTE_DATA();
if (GetFileAttributesEx(GetWideChar(szFilename), GetFileExInfoStandard, &attributes) == 0)
return 0;
#ifdef _WIN64
return (static_cast<size_t>(attributes.nFileSizeHigh) << (sizeof(attributes.nFileSizeLow) * 8)) | attributes.nFileSizeLow;
#else
return attributes.nFileSizeLow;
#endif
#else
struct stat stStats;
if (stat(szFilename,&stStats)) return 0;
return stStats.st_size;
#endif
}
// operates on a filedescriptor from open or fileno
size_t FileSize(int fdes)
{
#ifdef _WIN32
return _filelength(fdes);
#else
struct stat stStats;
if (fstat(fdes,&stStats)) return 0;
return stStats.st_size;
#endif
}
int FileTime(const char *szFilename)
{
#ifdef _WIN32
auto attributes = WIN32_FILE_ATTRIBUTE_DATA();
if (GetFileAttributesEx(GetWideChar(szFilename), GetFileExInfoStandard, &attributes) == 0)
return 0;
int64_t ft = (static_cast<int64_t>(attributes.ftLastWriteTime.dwHighDateTime) << (sizeof(attributes.ftLastWriteTime.dwLowDateTime) * 8)) | attributes.ftLastWriteTime.dwLowDateTime;
ft -= 116444736000000000;
ft /= 10000000;
return ft;
#else
struct stat stStats;
if (stat(szFilename,&stStats)!=0) return 0;
return stStats.st_mtime;
#endif
}
bool EraseFile(const char *szFilename)
{
#ifdef _WIN32
SetFileAttributesW(GetWideChar(szFilename), FILE_ATTRIBUTE_NORMAL);
if (DeleteFileW(GetWideChar(szFilename)) == 0)
{
switch (GetLastError())
{
case ERROR_PATH_NOT_FOUND:
case ERROR_FILE_NOT_FOUND:
// While deleting it didn't work, the file doesn't exist (anymore).
// Pretend everything is fine.
return true;
default:
// Some other error left us unable to delete the file.
return false;
}
}
return true;
#else
// either unlink or remove could be used. Well, stick to ANSI C where possible.
if (remove(szFilename))
{
if (errno == ENOENT)
{
// Hah, here the wrapper actually makes sense:
// The engine only cares about the file not being there after this call.
return true;
}
return false;
}
return true;
#endif
}
#ifndef _WIN32
bool CopyFile(const char *szSource, const char *szTarget, bool FailIfExists)
{
int fds = open (szSource, O_RDONLY | O_CLOEXEC);
if (!fds) return false;
struct stat info; fstat(fds, &info);
int fdt = open (szTarget, O_CLOEXEC | O_WRONLY | O_CREAT | (FailIfExists? O_EXCL : O_TRUNC), info.st_mode);
if (!fdt)
{
close (fds);
return false;
}
char buffer[1024]; ssize_t l;
while ((l = read(fds, buffer, sizeof(buffer))) > 0)
if (write(fdt, buffer, l) < l)
{
l = -1;
break;
}
close (fds);
close (fdt);
// On error, return false
return l != -1;
}
bool RenameFile(const char *szFilename, const char *szNewFilename)
{
if (rename(szFilename,szNewFilename) < 0)
{
if (errno != EXDEV) return false;
if (CopyFile(szFilename, szNewFilename, false))
{
return EraseFile(szFilename);
}
return false;
}
return true;
}
#else
#undef CopyFile
bool CopyFile(const char *szSource, const char *szTarget, bool FailIfExists)
{
return !!CopyFileW(GetWideChar(szSource), GetWideChar(szTarget), FailIfExists);
}
bool RenameFile(const char *szFilename, const char *szNewFilename)
{
return !!MoveFileExW(GetWideChar(szFilename), GetWideChar(szNewFilename), MOVEFILE_COPY_ALLOWED);
}
#endif
bool MakeOriginalFilename(char *szFilename)
{
// safety
if (!szFilename) return false;
#ifdef _WIN32
// root-directory?
if (Inside(SLen(szFilename), 2u, 3u)) if (szFilename[1]==':')
{
szFilename[2]='\\'; szFilename[3]=0;
if (GetDriveTypeW(GetWideChar(szFilename)) == DRIVE_NO_ROOT_DIR) return false;
return true;
}
struct _wfinddata_t fdt; intptr_t shnd;
if ((shnd=_wfindfirst(GetWideChar(szFilename),&fdt))<0) return false;
_findclose(shnd);
StdStrBuf name(fdt.name);
SCopy(GetFilename(name.getData()),GetFilename(szFilename),_MAX_FNAME);
#else
if (SCharPos('*', szFilename) != -1)
{
fputs ("Warning: MakeOriginalFilename with \"", stderr);
fputs (szFilename, stderr);
fputs ("\"!\n", stderr);
}
#endif
return true;
}
/* Directories */
const char *GetWorkingDirectory()
{
#ifdef _WIN32
static StdStrBuf buffer;
wchar_t *widebuf = nullptr;
DWORD widebufsz = GetCurrentDirectoryW(0, nullptr);
widebuf = new wchar_t[widebufsz];
if (GetCurrentDirectoryW(widebufsz, widebuf) == 0) {
delete[] widebuf;
return nullptr;
}
buffer.Take(StdStrBuf(widebuf));
delete[] widebuf;
return buffer.getData();
#else
static char buf[_MAX_PATH+1];
return getcwd(buf,_MAX_PATH);
#endif
}
bool SetWorkingDirectory(const char *path)
{
#ifdef _WIN32
return SetCurrentDirectoryW(GetWideChar(path)) != 0;
#else
return (chdir(path)==0);
#endif
}
bool CreatePath(const std::string &path)
{
assert(!path.empty());
#ifdef _WIN32
if (CreateDirectoryW(GetWideChar(path.c_str()), nullptr))
{
return true;
}
else
{
DWORD err = GetLastError();
switch (err)
{
case ERROR_PATH_NOT_FOUND:
break;
case ERROR_ALREADY_EXISTS:
return true;
default:
// Something major has happened: Log
{
wchar_t * str;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, err, 0, (LPWSTR)&str, 0, nullptr))
{
LogF("CreateDirectory failed: %s", StdStrBuf(str).getData());
LocalFree(str);
}
return false;
}
}
}
#else
if (!mkdir(path.c_str(), S_IREAD | S_IWRITE | S_IEXEC))
return true;
switch (errno)
{
case ENOENT:
break;
case EEXIST:
// FIXME: Check whether the path is blocked by a non-directory
return true;
default:
return false;
}
#endif
// Recursively create parent path
std::string::size_type slash = path.find_last_of(DirectorySeparators);
if (slash == 0 || slash == std::string::npos)
return false;
return CreatePath(path.substr(0, slash)) && CreatePath(path);
}
bool DirectoryExists(const char *szFilename)
{
// Ignore trailing slash or backslash, except when we are probing the
// root directory '/'.
char bufFilename[_MAX_PATH + 1];
if (szFilename && szFilename[0])
{
unsigned int len = SLen(szFilename);
if (len > 1 && ((szFilename[len - 1] == '\\') || (szFilename[len - 1] == '/')))
{
SCopy(szFilename, bufFilename, _MAX_PATH);
bufFilename[SLen(bufFilename) - 1] = 0;
szFilename = bufFilename;
}
}
// Check file attributes
#ifdef _WIN32
struct _wfinddata_t fdt; intptr_t shnd;
if ((shnd=_wfindfirst(GetWideChar(szFilename),&fdt))<0) return false;
_findclose(shnd);
if (fdt.attrib & _A_SUBDIR) return true;
#else
struct stat stStats;
if (stat(szFilename,&stStats)!=0) return 0;
return (S_ISDIR(stStats.st_mode));
#endif
return false;
}
bool CopyDirectory(const char *szSource, const char *szTarget, bool fResetAttributes)
{
// Source check
if (!szSource || !szTarget) return false;
if (!DirectoryExists(szSource)) return false;
// Do not process system navigation directories
if (SEqual(GetFilename(szSource),".")
|| SEqual(GetFilename(szSource),".."))
return true;
// Overwrite target
if (!EraseItem(szTarget)) return false;
// Create target directory
bool status=true;
#ifdef _WIN32
if (_wmkdir(GetWideChar(szTarget))!=0) return false;
// Copy contents to target directory
char contents[_MAX_PATH+1];
SCopy(szSource,contents); AppendBackslash(contents);
SAppend("*",contents);
_wfinddata_t fdt; intptr_t hfdt;
if ( (hfdt=_wfindfirst(GetWideChar(contents),&fdt)) > -1 )
{
do
{
char itemsource[_MAX_PATH+1],itemtarget[_MAX_PATH+1];
SCopy(szSource,itemsource); AppendBackslash(itemsource); SAppend(StdStrBuf(fdt.name).getData(),itemsource);
SCopy(szTarget,itemtarget); AppendBackslash(itemtarget); SAppend(StdStrBuf(fdt.name).getData(),itemtarget);
if (!CopyItem(itemsource,itemtarget, fResetAttributes)) status=false;
}
while (_wfindnext(hfdt,&fdt)==0);
_findclose(hfdt);
}
#else
if (mkdir(szTarget,0777)!=0) return false;
DIR * d = opendir(szSource);
dirent * ent;
char itemsource[_MAX_PATH+1],itemtarget[_MAX_PATH+1];
while ((ent = readdir(d)))
{
SCopy(szSource,itemsource); AppendBackslash(itemsource); SAppend(ent->d_name,itemsource);
SCopy(szTarget,itemtarget); AppendBackslash(itemtarget); SAppend(ent->d_name,itemtarget);
if (!CopyItem(itemsource,itemtarget, fResetAttributes)) status=false;
}
closedir(d);
#endif
return status;
}
bool EraseDirectory(const char *szDirName)
{
// Do not process system navigation directories
if (SEqual(GetFilename(szDirName),".")
|| SEqual(GetFilename(szDirName),".."))
return true;
char path[_MAX_PATH+1];
#ifdef _WIN32
// Get path to directory contents
SCopy(szDirName,path); SAppend(R"(\*.*)",path);
// Erase subdirectories and files
ForEachFile(path,&EraseItem);
#else
DIR * d = opendir(szDirName);
dirent * ent;
while ((ent = readdir(d)))
{
SCopy(szDirName,path); AppendBackslash(path); SAppend(ent->d_name,path);
if (!EraseItem(path)) return false;
}
closedir(d);
#endif
// Check working directory
if (SEqual(szDirName,GetWorkingDirectory()))
{
// Will work only if szDirName is full path and correct case!
SCopy(GetWorkingDirectory(),path);
int lbacks = SCharLastPos(DirectorySeparator,path);
if (lbacks > -1)
{
path[lbacks]=0; SetWorkingDirectory(path);
}
}
// Remove directory
#ifdef _WIN32
return !!RemoveDirectoryW(GetWideChar(szDirName));
#else
return (rmdir(szDirName)==0 || errno == ENOENT);
#endif
}
/* Items */
bool RenameItem(const char *szItemName, const char *szNewItemName)
{
// FIXME: What if the directory would have to be copied?
return RenameFile(szItemName,szNewItemName);
}
bool EraseItem(const char *szItemName)
{
if (!EraseFile(szItemName)) return EraseDirectory(szItemName);
else return true;
}
bool CreateItem(const char *szItemname)
{
// Overwrite any old item
EraseItem(szItemname);
// Create dummy item
FILE *fhnd;
#ifdef _WIN32
if (!(fhnd=_wfopen(GetWideChar(szItemname), L"wb"))) return false;
#else
if (!(fhnd=fopen(szItemname,"wb"))) return false;
#endif
fclose(fhnd);
// Success
return true;
}
bool CopyItem(const char *szSource, const char *szTarget, bool fResetAttributes)
{
// Check for identical source and target
if (ItemIdentical(szSource,szTarget)) return true;
// Copy directory
if (DirectoryExists(szSource))
return CopyDirectory(szSource,szTarget,fResetAttributes);
// Copy file
if (!CopyFile(szSource,szTarget,false)) return false;
// Reset any attributes if desired
#ifdef _WIN32
if (fResetAttributes) if (!SetFileAttributesW(GetWideChar(szTarget), FILE_ATTRIBUTE_NORMAL)) return false;
#else
if (fResetAttributes) if (chmod(szTarget, S_IRWXU)) return false;
#endif
return true;
}
bool MoveItem(const char *szSource, const char *szTarget)
{
if (ItemIdentical(szSource,szTarget)) return true;
return RenameFile(szSource, szTarget);
}
bool ItemIdentical(const char *szFilename1, const char *szFilename2)
{
char szFullFile1[_MAX_PATH+1],szFullFile2[_MAX_PATH+1];
RealPath(szFilename1, szFullFile1); RealPath(szFilename2, szFullFile2);
#ifdef _WIN32
if (SEqualNoCase(szFullFile1,szFullFile2)) return true;
#else
if (SEqual(szFullFile1,szFullFile2)) return true;
#endif
return false;
}
//------------------------- Multi File Processing --------------------------------------------------------------------------------------------------------
struct DirectoryIteratorP
{
DirectoryIteratorP() = default;
DirectoryIterator::FileList files;
std::string directory;
int ref{1};
};
DirectoryIterator::DirectoryIterator()
: p(new DirectoryIteratorP), iter(p->files.end())
{}
DirectoryIterator::DirectoryIterator(const DirectoryIterator &other)
: p(other.p), iter(other.iter)
{
++p->ref;
}
DirectoryIterator & DirectoryIterator::operator = (const DirectoryIterator & other)
{
p = other.p; iter = other.iter;
++p->ref;
return *this;
}
DirectoryIterator::~DirectoryIterator()
{
if (--p->ref == 0)
delete p;
}
void DirectoryIterator::Clear()
{
// clear cache
if (p->ref > 1)
{
// Detach from shared memory
--p->ref;
p = new DirectoryIteratorP;
}
p->directory.clear();
p->files.clear();
iter = p->files.end();
}
void DirectoryIterator::Reset ()
{
iter = p->files.begin();
}
void DirectoryIterator::Reset (const char * dirname, bool force_reread)
{
if (p->directory == dirname && !force_reread)
{
// Skip reinitialisation and just reset the iterator
iter = p->files.begin();
return;
}
if (p->ref > 1)
{
// Detach from shared memory
--p->ref;
p = new DirectoryIteratorP;
}
p->files.clear();
iter = p->files.end();
Read(dirname);
}
DirectoryIterator::DirectoryIterator (const char * dirname)
: p(new DirectoryIteratorP), iter(p->files.end())
{
Read(dirname);
}
void DirectoryIterator::Read(const char *dirname)
{
assert(dirname && *dirname);
assert(p->files.empty());
std::string search_path(dirname);
if (!search_path.empty() && search_path.back() != DirectorySeparator)
search_path.push_back(DirectorySeparator);
#ifdef WIN32
auto file = WIN32_FIND_DATAW();
HANDLE fh = FindFirstFileW(GetWideChar((search_path + '*').c_str()), &file);
if (fh == INVALID_HANDLE_VALUE)
{
switch (GetLastError())
{
case ERROR_PATH_NOT_FOUND:
case ERROR_FILE_NOT_FOUND:
// This is okay, either the directory doesn't exist or there are no files
return;
default:
// Something else broke
Log("DirectoryIterator::Read(const char*): Unable to read file system");
return;
}
}
// Insert files into list
do
{
// ...unless they're . or ..
if (file.cFileName[0] == '.' && (file.cFileName[1] == '\0' || (file.cFileName[1] == '.' && file.cFileName[2] == '\0')))
continue;
size_t size = (file.nFileSizeHigh * (size_t(MAXDWORD) + 1)) + file.nFileSizeLow;
p->files.emplace_back(StdStrBuf(file.cFileName).getData(), size);
}
while (FindNextFileW(fh, &file));
FindClose(fh);
#else
DIR *fh = opendir(dirname);
if (fh == nullptr)
{
switch (errno)
{
case ENOENT:
case ENOTDIR:
// Okay, so there's no files here.
return;
default:
// Something else broke
LogF("DirectoryIterator::Read(\"%s\"): %s", dirname, strerror(errno));
return;
}
}
dirent *file;
// Insert files into list
while ((file = readdir(fh)) != nullptr)
{
// ...unless they're . or ..
if (file->d_name[0] == '.' && (file->d_name[1] == '\0' || (file->d_name[1] == '.' && file->d_name[2] == '\0')))
continue;
p->files.emplace_back(file->d_name, 0);
}
closedir(fh);
#endif
// Sort list
std::sort(p->files.begin(), p->files.end());
for (auto & file : p->files)
file.first.insert(0, search_path); // prepend path to all file entries
iter = p->files.begin();
p->directory = dirname;
}
DirectoryIterator& DirectoryIterator::operator++()
{
if (iter != p->files.end())
++iter;
return *this;
}
const char * DirectoryIterator::operator*() const
{
if (iter == p->files.end())
return nullptr;
return iter->first.c_str();
}
DirectoryIterator DirectoryIterator::operator++(int)
{
DirectoryIterator tmp(*this);
++*this;
return tmp;
}
size_t DirectoryIterator::GetFileSize() const
{
#ifdef _WIN32
return iter->second;
#else
return FileSize(iter->first.c_str());
#endif
}
int ForEachFile(const char *szDirName, bool (*fnCallback)(const char *))
{
if (!szDirName || !fnCallback)
return 0;
char szFilename[_MAX_PATH+1];
SCopy(szDirName,szFilename);
bool fHasWildcard = (SCharPos('*', szFilename)>=0);
if (!fHasWildcard) // parameter without wildcard: Append "/*.*" or "\*.*"
AppendBackslash(szFilename);
int iFileCount = 0;
#ifdef _WIN32
struct _wfinddata_t fdt; intptr_t fdthnd;
if (!fHasWildcard) // parameter without wildcard: Append "/*.*" or "\*.*"
SAppend("*",szFilename,_MAX_PATH);
if ((fdthnd = _wfindfirst (GetWideChar(szFilename), &fdt)) < 0)
return 0;
do
{
if (!wcscmp(fdt.name, L".") || !wcscmp(fdt.name, L"..")) continue;
StdStrBuf name(fdt.name);
SCopy(name.getData(),GetFilename(szFilename));
if ((*fnCallback)(szFilename))
iFileCount++;
}
while (_wfindnext(fdthnd,&fdt)==0);
_findclose(fdthnd);
#else
if (fHasWildcard) fprintf(stderr, "Warning: ForEachFile with * (%s)\n", szDirName);
DIR * d = opendir(szDirName);
if (!d) return 0;
dirent * ent;
while ((ent = readdir(d)))
{
SCopy(ent->d_name,GetFilename(szFilename));
if ((*fnCallback)(szFilename))
iFileCount++;
}
closedir(d);
#endif
return iFileCount;
}