/* * 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 #endif #ifdef _WIN32 #include "platform/C4windowswrapper.h" #endif #include #include #include /* 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(*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(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(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; }