// // Copyright (c) 2008-2014 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "Precompiled.h" #include "../Container/ArrayPtr.h" #include "../Core/Context.h" #include "../Core/CoreEvents.h" #include "../Engine/EngineEvents.h" #include "../IO/File.h" #include "../IO/FileSystem.h" #include "../IO/IOEvents.h" #include "../IO/Log.h" #include "../Core/Thread.h" #include #include #include #include #ifdef WIN32 #ifndef _MSC_VER #define _WIN32_IE 0x501 #endif #include #include #include #include #include #include #else #include #include #include #include #include #define MAX_PATH 256 #endif #if defined(__APPLE__) #include #endif #ifdef ANDROID extern "C" const char* SDL_Android_GetFilesDir(); #endif #ifdef IOS extern "C" const char* SDL_IOS_GetResourceDir(); extern "C" const char* SDL_IOS_GetDocumentsDir(); #endif #include "../DebugNew.h" namespace Atomic { int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context) { if (!redirectToLog) return system(commandLine.CString()); // Get a platform-agnostic temporary file name for stderr redirection String stderrFilename; String adjustedCommandLine(commandLine); char* prefPath = SDL_GetPrefPath("urho3d", "temp"); if (prefPath) { stderrFilename = String(prefPath) + "command-stderr"; adjustedCommandLine += " 2>" + stderrFilename; SDL_free(prefPath); } #ifdef _MSC_VER #define popen _popen #define pclose _pclose #endif // Use popen/pclose to capture the stdout and stderr of the command FILE *file = popen(adjustedCommandLine.CString(), "r"); if (!file) return -1; // Capture the standard output stream char buffer[128]; while (!feof(file)) { if (fgets(buffer, sizeof(buffer), file)) LOGRAW(String(buffer)); } int exitCode = pclose(file); // Capture the standard error stream if (!stderrFilename.Empty()) { SharedPtr errFile(new File(context, stderrFilename, FILE_READ)); while (!errFile->IsEof()) { unsigned numRead = errFile->Read(buffer, sizeof(buffer)); if (numRead) Log::WriteRaw(String(buffer, numRead), true); } } return exitCode; } int DoSystemRun(const String& fileName, const Vector& arguments) { String fixedFileName = GetNativePath(fileName); #ifdef WIN32 // Add .exe extension if no extension defined if (GetExtension(fixedFileName).Empty()) fixedFileName += ".exe"; String commandLine = "\"" + fixedFileName + "\""; for (unsigned i = 0; i < arguments.Size(); ++i) commandLine += " " + arguments[i]; STARTUPINFOW startupInfo; PROCESS_INFORMATION processInfo; memset(&startupInfo, 0, sizeof startupInfo); memset(&processInfo, 0, sizeof processInfo); WString commandLineW(commandLine); if (!CreateProcessW(NULL, (wchar_t*)commandLineW.CString(), 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &startupInfo, &processInfo)) return -1; WaitForSingleObject(processInfo.hProcess, INFINITE); DWORD exitCode; GetExitCodeProcess(processInfo.hProcess, &exitCode); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); return exitCode; #else pid_t pid = fork(); if (!pid) { PODVector argPtrs; argPtrs.Push(fixedFileName.CString()); for (unsigned i = 0; i < arguments.Size(); ++i) argPtrs.Push(arguments[i].CString()); argPtrs.Push(0); execvp(argPtrs[0], (char**)&argPtrs[0]); return -1; // Return -1 if we could not spawn the process } else if (pid > 0) { int exitCode; wait(&exitCode); return exitCode; } else return -1; #endif } /// Base class for async execution requests. class AsyncExecRequest : public Thread { public: /// Construct. AsyncExecRequest(unsigned& requestID) : requestID_(requestID), completed_(false) { // Increment ID for next request ++requestID; if (requestID == M_MAX_UNSIGNED) requestID = 1; } /// Return request ID. unsigned GetRequestID() const { return requestID_; } /// Return exit code. Valid when IsCompleted() is true. int GetExitCode() const { return exitCode_; } /// Return completion status. bool IsCompleted() const { return completed_; } protected: /// Request ID. unsigned requestID_; /// Exit code. int exitCode_; /// Completed flag. volatile bool completed_; }; /// Async system command operation. class AsyncSystemCommand : public AsyncExecRequest { public: /// Construct and run. AsyncSystemCommand(unsigned requestID, const String& commandLine) : AsyncExecRequest(requestID), commandLine_(commandLine) { Run(); } /// The function to run in the thread. virtual void ThreadFunction() { exitCode_ = DoSystemCommand(commandLine_, false, 0); completed_ = true; } private: /// Command line. String commandLine_; }; /// Async system run operation. class AsyncSystemRun : public AsyncExecRequest { public: /// Construct and run. AsyncSystemRun(unsigned requestID, const String& fileName, const Vector& arguments) : AsyncExecRequest(requestID), fileName_(fileName), arguments_(arguments) { Run(); } /// The function to run in the thread. virtual void ThreadFunction() { exitCode_ = DoSystemRun(fileName_, arguments_); completed_ = true; } private: /// File to run. String fileName_; /// Command line split in arguments. const Vector& arguments_; }; FileSystem::FileSystem(Context* context) : Object(context), nextAsyncExecID_(1), executeConsoleCommands_(false) { SubscribeToEvent(E_BEGINFRAME, HANDLER(FileSystem, HandleBeginFrame)); // Subscribe to console commands SetExecuteConsoleCommands(true); } FileSystem::~FileSystem() { // If any async exec items pending, delete them if (asyncExecQueue_.Size()) { for (List::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End(); ++i) delete(*i); asyncExecQueue_.Clear(); } } bool FileSystem::SetCurrentDir(const String& pathName) { if (!CheckAccess(pathName)) { LOGERROR("Access denied to " + pathName); return false; } #ifdef WIN32 if (SetCurrentDirectoryW(GetWideNativePath(pathName).CString()) == FALSE) { LOGERROR("Failed to change directory to " + pathName); return false; } #else if (chdir(GetNativePath(pathName).CString()) != 0) { LOGERROR("Failed to change directory to " + pathName); return false; } #endif return true; } bool FileSystem::CreateDir(const String& pathName) { if (!CheckAccess(pathName)) { LOGERROR("Access denied to " + pathName); return false; } #ifdef WIN32 bool success = (CreateDirectoryW(GetWideNativePath(RemoveTrailingSlash(pathName)).CString(), 0) == TRUE) || (GetLastError() == ERROR_ALREADY_EXISTS); #else bool success = mkdir(GetNativePath(RemoveTrailingSlash(pathName)).CString(), S_IRWXU) == 0 || errno == EEXIST; #endif if (success) LOGDEBUG("Created directory " + pathName); else LOGERROR("Failed to create directory " + pathName); return success; } void FileSystem::SetExecuteConsoleCommands(bool enable) { if (enable == executeConsoleCommands_) return; executeConsoleCommands_ = enable; if (enable) SubscribeToEvent(E_CONSOLECOMMAND, HANDLER(FileSystem, HandleConsoleCommand)); else UnsubscribeFromEvent(E_CONSOLECOMMAND); } int FileSystem::SystemCommand(const String& commandLine, bool redirectStdOutToLog) { if (allowedPaths_.Empty()) return DoSystemCommand(commandLine, redirectStdOutToLog, context_); else { LOGERROR("Executing an external command is not allowed"); return -1; } } int FileSystem::SystemRun(const String& fileName, const Vector& arguments) { if (allowedPaths_.Empty()) return DoSystemRun(fileName, arguments); else { LOGERROR("Executing an external command is not allowed"); return -1; } } unsigned FileSystem::SystemCommandAsync(const String& commandLine) { if (allowedPaths_.Empty()) { unsigned requestID = nextAsyncExecID_; AsyncSystemCommand* cmd = new AsyncSystemCommand(nextAsyncExecID_, commandLine); asyncExecQueue_.Push(cmd); return requestID; } else { LOGERROR("Executing an external command is not allowed"); return M_MAX_UNSIGNED; } } unsigned FileSystem::SystemRunAsync(const String& fileName, const Vector& arguments) { if (allowedPaths_.Empty()) { unsigned requestID = nextAsyncExecID_; AsyncSystemRun* cmd = new AsyncSystemRun(nextAsyncExecID_, fileName, arguments); asyncExecQueue_.Push(cmd); return requestID; } else { LOGERROR("Executing an external command is not allowed"); return M_MAX_UNSIGNED; } } bool FileSystem::SystemOpen(const String& fileName, const String& mode) { if (allowedPaths_.Empty()) { if (!FileExists(fileName) && !DirExists(fileName)) { LOGERROR("File or directory " + fileName + " not found"); return false; } #ifdef WIN32 bool success = (size_t)ShellExecuteW(0, !mode.Empty() ? WString(mode).CString() : 0, GetWideNativePath(fileName).CString(), 0, 0, SW_SHOW) > 32; #else Vector arguments; arguments.Push(fileName); bool success = SystemRun( #if defined(__APPLE__) "/usr/bin/open", #else "/usr/bin/xdg-open", #endif arguments) == 0; #endif if (!success) LOGERROR("Failed to open " + fileName + " externally"); return success; } else { LOGERROR("Opening a file externally is not allowed"); return false; } } bool FileSystem::Copy(const String& srcFileName, const String& destFileName) { if (!CheckAccess(GetPath(srcFileName))) { LOGERROR("Access denied to " + srcFileName); return false; } if (!CheckAccess(GetPath(destFileName))) { LOGERROR("Access denied to " + destFileName); return false; } SharedPtr srcFile(new File(context_, srcFileName, FILE_READ)); if (!srcFile->IsOpen()) return false; SharedPtr destFile(new File(context_, destFileName, FILE_WRITE)); if (!destFile->IsOpen()) return false; unsigned fileSize = srcFile->GetSize(); SharedArrayPtr buffer(new unsigned char[fileSize]); unsigned bytesRead = srcFile->Read(buffer.Get(), fileSize); unsigned bytesWritten = destFile->Write(buffer.Get(), fileSize); return bytesRead == fileSize && bytesWritten == fileSize; } bool FileSystem::Rename(const String& srcFileName, const String& destFileName) { if (!CheckAccess(GetPath(srcFileName))) { LOGERROR("Access denied to " + srcFileName); return false; } if (!CheckAccess(GetPath(destFileName))) { LOGERROR("Access denied to " + destFileName); return false; } #ifdef WIN32 return MoveFileW(GetWideNativePath(srcFileName).CString(), GetWideNativePath(destFileName).CString()) != 0; #else return rename(GetNativePath(srcFileName).CString(), GetNativePath(destFileName).CString()) == 0; #endif } bool FileSystem::Delete(const String& fileName) { if (!CheckAccess(GetPath(fileName))) { LOGERROR("Access denied to " + fileName); return false; } #ifdef WIN32 return DeleteFileW(GetWideNativePath(fileName).CString()) != 0; #else return remove(GetNativePath(fileName).CString()) == 0; #endif } String FileSystem::GetCurrentDir() const { #ifdef WIN32 wchar_t path[MAX_PATH]; path[0] = 0; GetCurrentDirectoryW(MAX_PATH, path); return AddTrailingSlash(String(path)); #else char path[MAX_PATH]; path[0] = 0; getcwd(path, MAX_PATH); return AddTrailingSlash(String(path)); #endif } bool FileSystem::CheckAccess(const String& pathName) const { String fixedPath = AddTrailingSlash(pathName); // If no allowed directories defined, succeed always if (allowedPaths_.Empty()) return true; // If there is any attempt to go to a parent directory, disallow if (fixedPath.Contains("..")) return false; // Check if the path is a partial match of any of the allowed directories for (HashSet::ConstIterator i = allowedPaths_.Begin(); i != allowedPaths_.End(); ++i) { if (fixedPath.Find(*i) == 0) return true; } // Not found, so disallow return false; } unsigned FileSystem::GetLastModifiedTime(const String& fileName) const { if (fileName.Empty() || !CheckAccess(fileName)) return 0; #ifdef WIN32 struct _stat st; if (!_stat(fileName.CString(), &st)) return (unsigned)st.st_mtime; else return 0; #else struct stat st; if (!stat(fileName.CString(), &st)) return (unsigned)st.st_mtime; else return 0; #endif } bool FileSystem::FileExists(const String& fileName) const { if (!CheckAccess(GetPath(fileName))) return false; String fixedName = GetNativePath(RemoveTrailingSlash(fileName)); #ifdef ANDROID if (fixedName.StartsWith("/apk/")) { SDL_RWops* rwOps = SDL_RWFromFile(fileName.Substring(5).CString(), "rb"); if (rwOps) { SDL_RWclose(rwOps); return true; } else return false; } #endif #ifdef WIN32 DWORD attributes = GetFileAttributesW(WString(fixedName).CString()); if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY) return false; #else struct stat st; if (stat(fixedName.CString(), &st) || st.st_mode & S_IFDIR) return false; #endif return true; } bool FileSystem::DirExists(const String& pathName) const { if (!CheckAccess(pathName)) return false; #ifndef WIN32 // Always return true for the root directory if (pathName == "/") return true; #endif String fixedName = GetNativePath(RemoveTrailingSlash(pathName)); #ifdef ANDROID /// \todo Actually check for existence, now true is always returned for directories within the APK if (fixedName.StartsWith("/apk/")) return true; #endif #ifdef WIN32 DWORD attributes = GetFileAttributesW(WString(fixedName).CString()); if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) return false; #else struct stat st; if (stat(fixedName.CString(), &st) || !(st.st_mode & S_IFDIR)) return false; #endif return true; } void FileSystem::ScanDir(Vector& result, const String& pathName, const String& filter, unsigned flags, bool recursive) const { result.Clear(); if (CheckAccess(pathName)) { String initialPath = AddTrailingSlash(pathName); ScanDirInternal(result, initialPath, initialPath, filter, flags, recursive); } } String FileSystem::GetProgramDir() const { // Return cached value if possible if (!programDir_.Empty()) return programDir_; #if defined(ANDROID) // This is an internal directory specifier pointing to the assets in the .apk // Files from this directory will be opened using special handling programDir_ = "/apk/"; return programDir_; #elif defined(IOS) programDir_ = AddTrailingSlash(SDL_IOS_GetResourceDir()); return programDir_; #elif defined(WIN32) wchar_t exeName[MAX_PATH]; exeName[0] = 0; GetModuleFileNameW(0, exeName, MAX_PATH); programDir_ = GetPath(String(exeName)); #elif defined(__APPLE__) char exeName[MAX_PATH]; memset(exeName, 0, MAX_PATH); unsigned size = MAX_PATH; _NSGetExecutablePath(exeName, &size); programDir_ = GetPath(String(exeName)); #elif defined(__linux__) char exeName[MAX_PATH]; memset(exeName, 0, MAX_PATH); pid_t pid = getpid(); String link = "/proc/" + String(pid) + "/exe"; readlink(link.CString(), exeName, MAX_PATH); programDir_ = GetPath(String(exeName)); #endif // If the executable directory does not contain CoreData & Data directories, but the current working directory does, use the // current working directory instead /// \todo Should not rely on such fixed convention String currentDir = GetCurrentDir(); if (!DirExists(programDir_ + "CoreData") && !DirExists(programDir_ + "Data") && (DirExists(currentDir + "CoreData") || DirExists(currentDir + "Data"))) programDir_ = currentDir; // Sanitate /./ construct away programDir_.Replace("/./", "/"); return programDir_; } String FileSystem::GetUserDocumentsDir() const { #if defined(ANDROID) return AddTrailingSlash(SDL_Android_GetFilesDir()); #elif defined(IOS) return AddTrailingSlash(SDL_IOS_GetDocumentsDir()); #elif defined(WIN32) wchar_t pathName[MAX_PATH]; pathName[0] = 0; SHGetSpecialFolderPathW(0, pathName, CSIDL_PERSONAL, 0); return AddTrailingSlash(String(pathName)); #else char pathName[MAX_PATH]; pathName[0] = 0; strcpy(pathName, getenv("HOME")); return AddTrailingSlash(String(pathName)); #endif } String FileSystem::GetAppPreferencesDir(const String& org, const String& app) const { String dir; char* prefPath = SDL_GetPrefPath(org.CString(), app.CString()); if (prefPath) { dir = GetInternalPath(String(prefPath)); SDL_free(prefPath); } else LOGWARNING("Could not get application preferences directory"); return dir; } void FileSystem::RegisterPath(const String& pathName) { if (pathName.Empty()) return; allowedPaths_.Insert(AddTrailingSlash(pathName)); } bool FileSystem::SetLastModifiedTime(const String& fileName, unsigned newTime) { if (fileName.Empty() || !CheckAccess(fileName)) return false; #ifdef WIN32 struct _stat oldTime; struct _utimbuf newTimes; if (_stat(fileName.CString(), &oldTime) != 0) return false; newTimes.actime = oldTime.st_atime; newTimes.modtime = newTime; return _utime(fileName.CString(), &newTimes) == 0; #else struct stat oldTime; struct utimbuf newTimes; if (stat(fileName.CString(), &oldTime) != 0) return false; newTimes.actime = oldTime.st_atime; newTimes.modtime = newTime; return utime(fileName.CString(), &newTimes) == 0; #endif } #if defined(ANDROID) extern "C" { #include #include static AAssetManager *gAssetManager = NULL; void Java_com_github_urho3d_Urho3D_nativeSetAssetManager(JNIEnv *env, jobject thiz, jobject assetManageObject) { gAssetManager = AAssetManager_fromJava(env, assetManageObject); } } // TODO: AAssetManager_openDir doesn't support subdirectories :/ void FileSystem::ScanDirInternal(Vector& result, String path, const String& startPath, const String& filter, unsigned flags, bool recursive) const { if (!gAssetManager) { LOGERROR("FileSystem::ScanDirInternal - AssetManager Not Set"); return; } if (path.StartsWith("/apk/")) path = path.Substring(5); // Asset Manager is very sensitive on paths path.Replace(String("//"), String("/")); path = RemoveTrailingSlash(path); //LOGINFOF("Scanning: %s", path.CString()); String filterExtension = filter.Substring(filter.Find('.')); if (filterExtension.Contains('*')) filterExtension.Clear(); String deltaPath; if (path.Length() > startPath.Length()) deltaPath = path.Substring(startPath.Length()); AAssetDir* assetDir = AAssetManager_openDir(gAssetManager, path.CString()); const char* _filename = (const char*)NULL; while ((_filename = AAssetDir_getNextFileName(assetDir)) != NULL) { String filename = _filename; if (flags & SCAN_FILES) { if (filterExtension.Empty() || filename.EndsWith(filterExtension)) result.Push(deltaPath + filename); } //LOGINFOF("filename: %s", filename.CString()); } AAssetDir_close(assetDir); return; } #else void FileSystem::ScanDirInternal(Vector& result, String path, const String& startPath, const String& filter, unsigned flags, bool recursive) const { path = AddTrailingSlash(path); String deltaPath; if (path.Length() > startPath.Length()) deltaPath = path.Substring(startPath.Length()); String filterExtension = filter.Substring(filter.Find('.')); if (filterExtension.Contains('*')) filterExtension.Clear(); #ifdef WIN32 WIN32_FIND_DATAW info; HANDLE handle = FindFirstFileW(WString(path + "*").CString(), &info); if (handle != INVALID_HANDLE_VALUE) { do { String fileName(info.cFileName); if (!fileName.Empty()) { if (info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & SCAN_HIDDEN)) continue; if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (flags & SCAN_DIRS) result.Push(deltaPath + fileName); if (recursive && fileName != "." && fileName != "..") ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive); } else if (flags & SCAN_FILES) { if (filterExtension.Empty() || fileName.EndsWith(filterExtension)) result.Push(deltaPath + fileName); } } } while (FindNextFileW(handle, &info)); FindClose(handle); } #else DIR *dir; struct dirent *de; struct stat st; dir = opendir(GetNativePath(path).CString()); if (dir) { while ((de = readdir(dir))) { /// \todo Filename may be unnormalized Unicode on Mac OS X. Re-normalize as necessary String fileName(de->d_name); bool normalEntry = fileName != "." && fileName != ".."; if (normalEntry && !(flags & SCAN_HIDDEN) && fileName.StartsWith(".")) continue; String pathAndName = path + fileName; if (!stat(pathAndName.CString(), &st)) { if (st.st_mode & S_IFDIR) { if (flags & SCAN_DIRS) result.Push(deltaPath + fileName); if (recursive && normalEntry) ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive); } else if (flags & SCAN_FILES) { if (filterExtension.Empty() || fileName.EndsWith(filterExtension)) result.Push(deltaPath + fileName); } } } closedir(dir); } #endif } #endif bool FileSystem::RemoveDir(const String& directoryIn, bool recursive) { String directory = AddTrailingSlash(directoryIn); if (!DirExists(directory)) return false; Vector results; // ensure empty if not recursive if (!recursive) { ScanDir(results, directory, "*", SCAN_DIRS | SCAN_FILES | SCAN_HIDDEN, true ); while (results.Remove(".")) {} while (results.Remove("..")) {} if (results.Size()) return false; #ifdef WIN32 return RemoveDirectoryW(GetWideNativePath(directory).CString()) != 0; #endif #ifdef __APPLE__ return remove(GetNativePath(directory).CString()) == 0; #endif } // delete all files at this level ScanDir(results, directory, "*", SCAN_FILES | SCAN_HIDDEN, false ); for (unsigned i = 0; i < results.Size(); i++) { if (!Delete(directory + results[i])) return false; } results.Clear(); // recurse into subfolders ScanDir(results, directory, "*", SCAN_DIRS, false ); for (unsigned i = 0; i < results.Size(); i++) { if (results[i] == "." || results[i] == "..") continue; if (!RemoveDir(directory + results[i], true)) return false; } return RemoveDir(directory, false); } void FileSystem::HandleBeginFrame(StringHash eventType, VariantMap& eventData) { /// Go through the execution queue and post + remove completed requests for (List::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End();) { AsyncExecRequest* request = *i; if (request->IsCompleted()) { using namespace AsyncExecFinished; VariantMap& eventData = GetEventDataMap(); eventData[P_REQUESTID] = request->GetRequestID(); eventData[P_EXITCODE] = request->GetExitCode(); SendEvent(E_ASYNCEXECFINISHED, eventData); delete request; i = asyncExecQueue_.Erase(i); } else ++i; } } void FileSystem::HandleConsoleCommand(StringHash eventType, VariantMap& eventData) { using namespace ConsoleCommand; if (eventData[P_ID].GetString() == GetTypeName()) SystemCommand(eventData[P_COMMAND].GetString(), true); } String FileSystem::GetAppBundleResourceFolder() { #if __APPLE__ && !defined(IOS) // Fix up to use the bundle layout String programDir = GetProgramDir(); unsigned p = programDir.FindLast("/MacOS/"); if (p != String::NPOS) { programDir.Erase(p, programDir.Length() - p); programDir += "/Resources/"; return programDir; } #endif return ""; } void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension, bool lowercaseExtension) { String fullPathCopy = GetInternalPath(fullPath); unsigned extPos = fullPathCopy.FindLast('.'); unsigned pathPos = fullPathCopy.FindLast('/'); if (extPos != String::NPOS && (pathPos == String::NPOS || extPos > pathPos)) { extension = fullPathCopy.Substring(extPos); if (lowercaseExtension) extension = extension.ToLower(); fullPathCopy = fullPathCopy.Substring(0, extPos); } else extension.Clear(); pathPos = fullPathCopy.FindLast('/'); if (pathPos != String::NPOS) { fileName = fullPathCopy.Substring(pathPos + 1); pathName = fullPathCopy.Substring(0, pathPos + 1); } else { fileName = fullPathCopy; pathName.Clear(); } } String GetPath(const String& fullPath) { String path, file, extension; SplitPath(fullPath, path, file, extension); return path; } String GetFileName(const String& fullPath) { String path, file, extension; SplitPath(fullPath, path, file, extension); return file; } String GetExtension(const String& fullPath, bool lowercaseExtension) { String path, file, extension; SplitPath(fullPath, path, file, extension, lowercaseExtension); return extension; } String GetFileNameAndExtension(const String& fileName, bool lowercaseExtension) { String path, file, extension; SplitPath(fileName, path, file, extension, lowercaseExtension); return file + extension; } String ReplaceExtension(const String& fullPath, const String& newExtension) { String path, file, extension; SplitPath(fullPath, path, file, extension); return path + file + newExtension; } String AddTrailingSlash(const String& pathName) { String ret = pathName.Trimmed(); ret.Replace('\\', '/'); if (!ret.Empty() && ret.Back() != '/') ret += '/'; return ret; } String RemoveTrailingSlash(const String& pathName) { String ret = pathName.Trimmed(); ret.Replace('\\', '/'); if (!ret.Empty() && ret.Back() == '/') ret.Resize(ret.Length() - 1); return ret; } String GetParentPath(const String& path) { unsigned pos = RemoveTrailingSlash(path).FindLast('/'); if (pos != String::NPOS) return path.Substring(0, pos + 1); else return String(); } String GetInternalPath(const String& pathName) { return pathName.Replaced('\\', '/'); } String GetNativePath(const String& pathName) { #ifdef WIN32 return pathName.Replaced('/', '\\'); #else return pathName; #endif } WString GetWideNativePath(const String& pathName) { #ifdef WIN32 return WString(pathName.Replaced('/', '\\')); #else return WString(pathName); #endif } bool IsAbsolutePath(const String& pathName) { if (pathName.Empty()) return false; String path = GetInternalPath(pathName); if (path[0] == '/') return true; #ifdef WIN32 if (path.Length() > 1 && IsAlpha(path[0]) && path[1] == ':') return true; #endif return false; } bool IsAbsoluteParentPath(const String& absParentPath, const String& fullPath) { if (!IsAbsolutePath(absParentPath) || !IsAbsolutePath(fullPath)) return false; String path1 = AddTrailingSlash(absParentPath); String path2 = AddTrailingSlash(GetPath(fullPath)); if (path2.StartsWith(path1)) return true; return false; } }