FileSystem.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Container/ArrayPtr.h"
  5. #include "../Core/Context.h"
  6. #include "../Core/CoreEvents.h"
  7. #include "../Core/Thread.h"
  8. #include "../Core/Profiler.h"
  9. #include "../Engine/EngineEvents.h"
  10. #include "../IO/File.h"
  11. #include "../IO/FileSystem.h"
  12. #include "../IO/IOEvents.h"
  13. #include "../IO/Log.h"
  14. #ifdef __ANDROID__
  15. #include <SDL/SDL_rwops.h>
  16. #endif
  17. #ifndef MINI_URHO
  18. #include <SDL/SDL_filesystem.h>
  19. #endif
  20. #include <sys/stat.h>
  21. #include <cstdio>
  22. #ifdef _WIN32
  23. #ifndef _MSC_VER
  24. #define _WIN32_IE 0x501
  25. #endif
  26. #include "../Engine/WinWrapped.h"
  27. #include <shellapi.h>
  28. #include <direct.h>
  29. #include <shlobj.h>
  30. #include <sys/types.h>
  31. #include <sys/utime.h>
  32. #else
  33. #include <dirent.h>
  34. #include <cerrno>
  35. #include <unistd.h>
  36. #include <utime.h>
  37. #include <sys/wait.h>
  38. #define MAX_PATH 256
  39. #endif
  40. #if defined(__APPLE__)
  41. #include <mach-o/dyld.h>
  42. #endif
  43. extern "C"
  44. {
  45. #ifdef __ANDROID__
  46. const char* SDL_Android_GetFilesDir();
  47. char** SDL_Android_GetFileList(const char* path, int* count);
  48. void SDL_Android_FreeFileList(char*** array, int* count);
  49. #elif defined(IOS) || defined(TVOS)
  50. const char* SDL_IOS_GetResourceDir();
  51. const char* SDL_IOS_GetDocumentsDir();
  52. #endif
  53. }
  54. #include "../DebugNew.h"
  55. namespace Urho3D
  56. {
  57. int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
  58. {
  59. #if defined(TVOS) || defined(IOS)
  60. return -1;
  61. #else
  62. #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
  63. if (!redirectToLog)
  64. #endif
  65. return system(commandLine.CString());
  66. #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
  67. // Get a platform-agnostic temporary file name for stderr redirection
  68. String stderrFilename;
  69. String adjustedCommandLine(commandLine);
  70. char* prefPath = SDL_GetPrefPath("urho3d", "temp");
  71. if (prefPath)
  72. {
  73. stderrFilename = String(prefPath) + "command-stderr";
  74. adjustedCommandLine += " 2>" + stderrFilename;
  75. SDL_free(prefPath);
  76. }
  77. #ifdef _MSC_VER
  78. #define popen _popen
  79. #define pclose _pclose
  80. #endif
  81. // Use popen/pclose to capture the stdout and stderr of the command
  82. FILE* file = popen(adjustedCommandLine.CString(), "r");
  83. if (!file)
  84. return -1;
  85. // Capture the standard output stream
  86. char buffer[128];
  87. while (!feof(file))
  88. {
  89. if (fgets(buffer, sizeof(buffer), file))
  90. URHO3D_LOGRAW(String(buffer));
  91. }
  92. int exitCode = pclose(file);
  93. // Capture the standard error stream
  94. if (!stderrFilename.Empty())
  95. {
  96. SharedPtr<File> errFile(new File(context, stderrFilename, FILE_READ));
  97. while (!errFile->IsEof())
  98. {
  99. unsigned numRead = errFile->Read(buffer, sizeof(buffer));
  100. if (numRead)
  101. Log::WriteRaw(String(buffer, numRead), true);
  102. }
  103. }
  104. return exitCode;
  105. #endif
  106. #endif
  107. }
  108. int DoSystemRun(const String& fileName, const Vector<String>& arguments)
  109. {
  110. #ifdef TVOS
  111. return -1;
  112. #else
  113. String fixedFileName = GetNativePath(fileName);
  114. #ifdef _WIN32
  115. // Add .exe extension if no extension defined
  116. if (GetExtension(fixedFileName).Empty())
  117. fixedFileName += ".exe";
  118. String commandLine = "\"" + fixedFileName + "\"";
  119. for (unsigned i = 0; i < arguments.Size(); ++i)
  120. commandLine += " " + arguments[i];
  121. STARTUPINFOW startupInfo;
  122. PROCESS_INFORMATION processInfo;
  123. memset(&startupInfo, 0, sizeof startupInfo);
  124. memset(&processInfo, 0, sizeof processInfo);
  125. WString commandLineW(commandLine);
  126. if (!CreateProcessW(nullptr, (wchar_t*)commandLineW.CString(), nullptr, nullptr, 0, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &processInfo))
  127. return -1;
  128. WaitForSingleObject(processInfo.hProcess, INFINITE);
  129. DWORD exitCode;
  130. GetExitCodeProcess(processInfo.hProcess, &exitCode);
  131. CloseHandle(processInfo.hProcess);
  132. CloseHandle(processInfo.hThread);
  133. return exitCode;
  134. #else
  135. pid_t pid = fork();
  136. if (!pid)
  137. {
  138. Vector<const char*> argPtrs;
  139. argPtrs.Push(fixedFileName.CString());
  140. for (unsigned i = 0; i < arguments.Size(); ++i)
  141. argPtrs.Push(arguments[i].CString());
  142. argPtrs.Push(nullptr);
  143. execvp(argPtrs[0], (char**)&argPtrs[0]);
  144. return -1; // Return -1 if we could not spawn the process
  145. }
  146. else if (pid > 0)
  147. {
  148. int exitCode;
  149. wait(&exitCode);
  150. return exitCode;
  151. }
  152. else
  153. return -1;
  154. #endif
  155. #endif
  156. }
  157. /// Base class for async execution requests.
  158. class AsyncExecRequest : public Thread
  159. {
  160. public:
  161. /// Construct.
  162. explicit AsyncExecRequest(unsigned& requestID) :
  163. requestID_(requestID)
  164. {
  165. // Increment ID for next request
  166. ++requestID;
  167. if (requestID == M_MAX_UNSIGNED)
  168. requestID = 1;
  169. }
  170. /// Return request ID.
  171. unsigned GetRequestID() const { return requestID_; }
  172. /// Return exit code. Valid when IsCompleted() is true.
  173. int GetExitCode() const { return exitCode_; }
  174. /// Return completion status.
  175. bool IsCompleted() const { return completed_; }
  176. protected:
  177. /// Request ID.
  178. unsigned requestID_{};
  179. /// Exit code.
  180. int exitCode_{};
  181. /// Completed flag.
  182. volatile bool completed_{};
  183. };
  184. /// Async system command operation.
  185. class AsyncSystemCommand : public AsyncExecRequest
  186. {
  187. public:
  188. /// Construct and run.
  189. AsyncSystemCommand(unsigned requestID, const String& commandLine) :
  190. AsyncExecRequest(requestID),
  191. commandLine_(commandLine)
  192. {
  193. Run();
  194. }
  195. /// The function to run in the thread.
  196. void ThreadFunction() override
  197. {
  198. URHO3D_PROFILE_THREAD("AsyncSystemCommand Thread");
  199. exitCode_ = DoSystemCommand(commandLine_, false, nullptr);
  200. completed_ = true;
  201. }
  202. private:
  203. /// Command line.
  204. String commandLine_;
  205. };
  206. /// Async system run operation.
  207. class AsyncSystemRun : public AsyncExecRequest
  208. {
  209. public:
  210. /// Construct and run.
  211. AsyncSystemRun(unsigned requestID, const String& fileName, const Vector<String>& arguments) :
  212. AsyncExecRequest(requestID),
  213. fileName_(fileName),
  214. arguments_(arguments)
  215. {
  216. Run();
  217. }
  218. /// The function to run in the thread.
  219. void ThreadFunction() override
  220. {
  221. URHO3D_PROFILE_THREAD("AsyncSystemRun Thread");
  222. exitCode_ = DoSystemRun(fileName_, arguments_);
  223. completed_ = true;
  224. }
  225. private:
  226. /// File to run.
  227. String fileName_;
  228. /// Command line split in arguments.
  229. const Vector<String>& arguments_;
  230. };
  231. FileSystem::FileSystem(Context* context) :
  232. Object(context)
  233. {
  234. SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(FileSystem, HandleBeginFrame));
  235. // Subscribe to console commands
  236. SetExecuteConsoleCommands(true);
  237. }
  238. FileSystem::~FileSystem()
  239. {
  240. // If any async exec items pending, delete them
  241. if (asyncExecQueue_.Size())
  242. {
  243. for (List<AsyncExecRequest*>::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End(); ++i)
  244. delete(*i);
  245. asyncExecQueue_.Clear();
  246. }
  247. }
  248. bool FileSystem::SetCurrentDir(const String& pathName)
  249. {
  250. if (!CheckAccess(pathName))
  251. {
  252. URHO3D_LOGERROR("Access denied to " + pathName);
  253. return false;
  254. }
  255. #ifdef _WIN32
  256. if (SetCurrentDirectoryW(GetWideNativePath(pathName).CString()) == FALSE)
  257. {
  258. URHO3D_LOGERROR("Failed to change directory to " + pathName);
  259. return false;
  260. }
  261. #else
  262. if (chdir(GetNativePath(pathName).CString()) != 0)
  263. {
  264. URHO3D_LOGERROR("Failed to change directory to " + pathName);
  265. return false;
  266. }
  267. #endif
  268. return true;
  269. }
  270. bool FileSystem::CreateDir(const String& pathName)
  271. {
  272. if (!CheckAccess(pathName))
  273. {
  274. URHO3D_LOGERROR("Access denied to " + pathName);
  275. return false;
  276. }
  277. // Create each of the parents if necessary
  278. String parentPath = GetParentPath(pathName);
  279. if (parentPath.Length() > 1 && !DirExists(parentPath))
  280. {
  281. if (!CreateDir(parentPath))
  282. return false;
  283. }
  284. #ifdef _WIN32
  285. bool success = (CreateDirectoryW(GetWideNativePath(RemoveTrailingSlash(pathName)).CString(), nullptr) == TRUE) ||
  286. (GetLastError() == ERROR_ALREADY_EXISTS);
  287. #else
  288. bool success = mkdir(GetNativePath(RemoveTrailingSlash(pathName)).CString(), S_IRWXU) == 0 || errno == EEXIST;
  289. #endif
  290. if (success)
  291. URHO3D_LOGDEBUG("Created directory " + pathName);
  292. else
  293. URHO3D_LOGERROR("Failed to create directory " + pathName);
  294. return success;
  295. }
  296. void FileSystem::SetExecuteConsoleCommands(bool enable)
  297. {
  298. if (enable == executeConsoleCommands_)
  299. return;
  300. executeConsoleCommands_ = enable;
  301. if (enable)
  302. SubscribeToEvent(E_CONSOLECOMMAND, URHO3D_HANDLER(FileSystem, HandleConsoleCommand));
  303. else
  304. UnsubscribeFromEvent(E_CONSOLECOMMAND);
  305. }
  306. int FileSystem::SystemCommand(const String& commandLine, bool redirectStdOutToLog)
  307. {
  308. if (allowedPaths_.Empty())
  309. return DoSystemCommand(commandLine, redirectStdOutToLog, context_);
  310. else
  311. {
  312. URHO3D_LOGERROR("Executing an external command is not allowed");
  313. return -1;
  314. }
  315. }
  316. int FileSystem::SystemRun(const String& fileName, const Vector<String>& arguments)
  317. {
  318. if (allowedPaths_.Empty())
  319. return DoSystemRun(fileName, arguments);
  320. else
  321. {
  322. URHO3D_LOGERROR("Executing an external command is not allowed");
  323. return -1;
  324. }
  325. }
  326. unsigned FileSystem::SystemCommandAsync(const String& commandLine)
  327. {
  328. #ifdef URHO3D_THREADING
  329. if (allowedPaths_.Empty())
  330. {
  331. unsigned requestID = nextAsyncExecID_;
  332. auto* cmd = new AsyncSystemCommand(nextAsyncExecID_, commandLine);
  333. asyncExecQueue_.Push(cmd);
  334. return requestID;
  335. }
  336. else
  337. {
  338. URHO3D_LOGERROR("Executing an external command is not allowed");
  339. return M_MAX_UNSIGNED;
  340. }
  341. #else
  342. URHO3D_LOGERROR("Can not execute an asynchronous command as threading is disabled");
  343. return M_MAX_UNSIGNED;
  344. #endif
  345. }
  346. unsigned FileSystem::SystemRunAsync(const String& fileName, const Vector<String>& arguments)
  347. {
  348. #ifdef URHO3D_THREADING
  349. if (allowedPaths_.Empty())
  350. {
  351. unsigned requestID = nextAsyncExecID_;
  352. auto* cmd = new AsyncSystemRun(nextAsyncExecID_, fileName, arguments);
  353. asyncExecQueue_.Push(cmd);
  354. return requestID;
  355. }
  356. else
  357. {
  358. URHO3D_LOGERROR("Executing an external command is not allowed");
  359. return M_MAX_UNSIGNED;
  360. }
  361. #else
  362. URHO3D_LOGERROR("Can not run asynchronously as threading is disabled");
  363. return M_MAX_UNSIGNED;
  364. #endif
  365. }
  366. bool FileSystem::SystemOpen(const String& fileName, const String& mode)
  367. {
  368. if (allowedPaths_.Empty())
  369. {
  370. if (!FileExists(fileName) && !DirExists(fileName))
  371. {
  372. URHO3D_LOGERROR("File or directory " + fileName + " not found");
  373. return false;
  374. }
  375. #ifdef _WIN32
  376. bool success = (size_t)ShellExecuteW(nullptr, !mode.Empty() ? WString(mode).CString() : nullptr,
  377. GetWideNativePath(fileName).CString(), nullptr, nullptr, SW_SHOW) > 32;
  378. #else
  379. Vector<String> arguments;
  380. arguments.Push(fileName);
  381. bool success = SystemRun(
  382. #if defined(__APPLE__)
  383. "/usr/bin/open",
  384. #else
  385. "/usr/bin/xdg-open",
  386. #endif
  387. arguments) == 0;
  388. #endif
  389. if (!success)
  390. URHO3D_LOGERROR("Failed to open " + fileName + " externally");
  391. return success;
  392. }
  393. else
  394. {
  395. URHO3D_LOGERROR("Opening a file externally is not allowed");
  396. return false;
  397. }
  398. }
  399. bool FileSystem::Copy(const String& srcFileName, const String& destFileName)
  400. {
  401. if (!CheckAccess(GetPath(srcFileName)))
  402. {
  403. URHO3D_LOGERROR("Access denied to " + srcFileName);
  404. return false;
  405. }
  406. if (!CheckAccess(GetPath(destFileName)))
  407. {
  408. URHO3D_LOGERROR("Access denied to " + destFileName);
  409. return false;
  410. }
  411. SharedPtr<File> srcFile(new File(context_, srcFileName, FILE_READ));
  412. if (!srcFile->IsOpen())
  413. return false;
  414. SharedPtr<File> destFile(new File(context_, destFileName, FILE_WRITE));
  415. if (!destFile->IsOpen())
  416. return false;
  417. unsigned fileSize = srcFile->GetSize();
  418. SharedArrayPtr<unsigned char> buffer(new unsigned char[fileSize]);
  419. unsigned bytesRead = srcFile->Read(buffer.Get(), fileSize);
  420. unsigned bytesWritten = destFile->Write(buffer.Get(), fileSize);
  421. return bytesRead == fileSize && bytesWritten == fileSize;
  422. }
  423. bool FileSystem::Rename(const String& srcFileName, const String& destFileName)
  424. {
  425. if (!CheckAccess(GetPath(srcFileName)))
  426. {
  427. URHO3D_LOGERROR("Access denied to " + srcFileName);
  428. return false;
  429. }
  430. if (!CheckAccess(GetPath(destFileName)))
  431. {
  432. URHO3D_LOGERROR("Access denied to " + destFileName);
  433. return false;
  434. }
  435. #ifdef _WIN32
  436. return MoveFileW(GetWideNativePath(srcFileName).CString(), GetWideNativePath(destFileName).CString()) != 0;
  437. #else
  438. return rename(GetNativePath(srcFileName).CString(), GetNativePath(destFileName).CString()) == 0;
  439. #endif
  440. }
  441. bool FileSystem::Delete(const String& fileName)
  442. {
  443. if (!CheckAccess(GetPath(fileName)))
  444. {
  445. URHO3D_LOGERROR("Access denied to " + fileName);
  446. return false;
  447. }
  448. #ifdef _WIN32
  449. return DeleteFileW(GetWideNativePath(fileName).CString()) != 0;
  450. #else
  451. return remove(GetNativePath(fileName).CString()) == 0;
  452. #endif
  453. }
  454. String FileSystem::GetCurrentDir() const
  455. {
  456. #ifdef _WIN32
  457. wchar_t path[MAX_PATH];
  458. path[0] = 0;
  459. GetCurrentDirectoryW(MAX_PATH, path);
  460. return AddTrailingSlash(String(path));
  461. #else
  462. char path[MAX_PATH];
  463. path[0] = 0;
  464. getcwd(path, MAX_PATH);
  465. return AddTrailingSlash(String(path));
  466. #endif
  467. }
  468. bool FileSystem::CheckAccess(const String& pathName) const
  469. {
  470. String fixedPath = AddTrailingSlash(pathName);
  471. // If no allowed directories defined, succeed always
  472. if (allowedPaths_.Empty())
  473. return true;
  474. // If there is any attempt to go to a parent directory, disallow
  475. if (fixedPath.Contains(".."))
  476. return false;
  477. // Check if the path is a partial match of any of the allowed directories
  478. for (HashSet<String>::ConstIterator i = allowedPaths_.Begin(); i != allowedPaths_.End(); ++i)
  479. {
  480. if (fixedPath.Find(*i) == 0)
  481. return true;
  482. }
  483. // Not found, so disallow
  484. return false;
  485. }
  486. unsigned FileSystem::GetLastModifiedTime(const String& fileName) const
  487. {
  488. if (fileName.Empty() || !CheckAccess(fileName))
  489. return 0;
  490. #ifdef _WIN32
  491. struct _stat st;
  492. if (!_stat(fileName.CString(), &st))
  493. return (unsigned)st.st_mtime;
  494. else
  495. return 0;
  496. #else
  497. struct stat st{};
  498. if (!stat(fileName.CString(), &st))
  499. return (unsigned)st.st_mtime;
  500. else
  501. return 0;
  502. #endif
  503. }
  504. bool FileSystem::FileExists(const String& fileName) const
  505. {
  506. if (!CheckAccess(GetPath(fileName)))
  507. return false;
  508. #ifdef __ANDROID__
  509. if (URHO3D_IS_ASSET(fileName))
  510. {
  511. SDL_RWops* rwOps = SDL_RWFromFile(URHO3D_ASSET(fileName), "rb");
  512. if (rwOps)
  513. {
  514. SDL_RWclose(rwOps);
  515. return true;
  516. }
  517. else
  518. return false;
  519. }
  520. #endif
  521. String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
  522. #ifdef _WIN32
  523. DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
  524. if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY)
  525. return false;
  526. #else
  527. struct stat st{};
  528. if (stat(fixedName.CString(), &st) || st.st_mode & S_IFDIR)
  529. return false;
  530. #endif
  531. return true;
  532. }
  533. bool FileSystem::DirExists(const String& pathName) const
  534. {
  535. if (!CheckAccess(pathName))
  536. return false;
  537. #ifndef _WIN32
  538. // Always return true for the root directory
  539. if (pathName == "/")
  540. return true;
  541. #endif
  542. String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
  543. #ifdef __ANDROID__
  544. if (URHO3D_IS_ASSET(fixedName))
  545. {
  546. // Split the pathname into two components: the longest parent directory path and the last name component
  547. String assetPath(URHO3D_ASSET((fixedName + "/")));
  548. String parentPath;
  549. i32 pos = assetPath.FindLast('/', assetPath.Length() - 2);
  550. if (pos != String::NPOS)
  551. {
  552. parentPath = assetPath.Substring(0, pos);
  553. assetPath = assetPath.Substring(pos + 1);
  554. }
  555. assetPath.Resize(assetPath.Length() - 1);
  556. bool exist = false;
  557. int count;
  558. char** list = SDL_Android_GetFileList(parentPath.CString(), &count);
  559. for (int i = 0; i < count; ++i)
  560. {
  561. exist = assetPath == list[i];
  562. if (exist)
  563. break;
  564. }
  565. SDL_Android_FreeFileList(&list, &count);
  566. return exist;
  567. }
  568. #endif
  569. #ifdef _WIN32
  570. DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
  571. if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY))
  572. return false;
  573. #else
  574. struct stat st{};
  575. if (stat(fixedName.CString(), &st) || !(st.st_mode & S_IFDIR))
  576. return false;
  577. #endif
  578. return true;
  579. }
  580. void FileSystem::ScanDir(Vector<String>& result, const String& pathName, const String& filter, unsigned flags, bool recursive) const
  581. {
  582. result.Clear();
  583. if (CheckAccess(pathName))
  584. {
  585. String initialPath = AddTrailingSlash(pathName);
  586. ScanDirInternal(result, initialPath, initialPath, filter, flags, recursive);
  587. }
  588. }
  589. String FileSystem::GetProgramDir() const
  590. {
  591. #if defined(__ANDROID__)
  592. // This is an internal directory specifier pointing to the assets in the .apk
  593. // Files from this directory will be opened using special handling
  594. return APK;
  595. #elif defined(IOS) || defined(TVOS)
  596. return AddTrailingSlash(SDL_IOS_GetResourceDir());
  597. #elif defined(_WIN32)
  598. wchar_t exeName[MAX_PATH];
  599. exeName[0] = 0;
  600. GetModuleFileNameW(nullptr, exeName, MAX_PATH);
  601. return GetPath(String(exeName));
  602. #elif defined(__APPLE__)
  603. char exeName[MAX_PATH];
  604. memset(exeName, 0, MAX_PATH);
  605. unsigned size = MAX_PATH;
  606. _NSGetExecutablePath(exeName, &size);
  607. return GetPath(String(exeName));
  608. #elif defined(__linux__)
  609. char exeName[MAX_PATH];
  610. memset(exeName, 0, MAX_PATH);
  611. pid_t pid = getpid();
  612. String link = "/proc/" + String(pid) + "/exe";
  613. readlink(link.CString(), exeName, MAX_PATH);
  614. return GetPath(String(exeName));
  615. #else
  616. return GetCurrentDir();
  617. #endif
  618. }
  619. String FileSystem::GetUserDocumentsDir() const
  620. {
  621. #if defined(__ANDROID__)
  622. return AddTrailingSlash(SDL_Android_GetFilesDir());
  623. #elif defined(IOS) || defined(TVOS)
  624. return AddTrailingSlash(SDL_IOS_GetDocumentsDir());
  625. #elif defined(_WIN32)
  626. wchar_t pathName[MAX_PATH];
  627. pathName[0] = 0;
  628. SHGetSpecialFolderPathW(nullptr, pathName, CSIDL_PERSONAL, 0);
  629. return AddTrailingSlash(String(pathName));
  630. #else
  631. char pathName[MAX_PATH];
  632. pathName[0] = 0;
  633. strcpy(pathName, getenv("HOME"));
  634. return AddTrailingSlash(String(pathName));
  635. #endif
  636. }
  637. String FileSystem::GetAppPreferencesDir(const String& org, const String& app) const
  638. {
  639. String dir;
  640. #ifndef MINI_URHO
  641. char* prefPath = SDL_GetPrefPath(org.CString(), app.CString());
  642. if (prefPath)
  643. {
  644. dir = GetInternalPath(String(prefPath));
  645. SDL_free(prefPath);
  646. }
  647. else
  648. #endif
  649. URHO3D_LOGWARNING("Could not get application preferences directory");
  650. return dir;
  651. }
  652. void FileSystem::RegisterPath(const String& pathName)
  653. {
  654. if (pathName.Empty())
  655. return;
  656. allowedPaths_.Insert(AddTrailingSlash(pathName));
  657. }
  658. bool FileSystem::SetLastModifiedTime(const String& fileName, unsigned newTime)
  659. {
  660. if (fileName.Empty() || !CheckAccess(fileName))
  661. return false;
  662. #ifdef _WIN32
  663. struct _stat oldTime;
  664. struct _utimbuf newTimes;
  665. if (_stat(fileName.CString(), &oldTime) != 0)
  666. return false;
  667. newTimes.actime = oldTime.st_atime;
  668. newTimes.modtime = newTime;
  669. return _utime(fileName.CString(), &newTimes) == 0;
  670. #else
  671. struct stat oldTime{};
  672. struct utimbuf newTimes{};
  673. if (stat(fileName.CString(), &oldTime) != 0)
  674. return false;
  675. newTimes.actime = oldTime.st_atime;
  676. newTimes.modtime = newTime;
  677. return utime(fileName.CString(), &newTimes) == 0;
  678. #endif
  679. }
  680. void FileSystem::ScanDirInternal(Vector<String>& result, String path, const String& startPath,
  681. const String& filter, unsigned flags, bool recursive) const
  682. {
  683. path = AddTrailingSlash(path);
  684. String deltaPath;
  685. if (path.Length() > startPath.Length())
  686. deltaPath = path.Substring(startPath.Length());
  687. String filterExtension = filter.Substring(filter.FindLast('.'));
  688. if (filterExtension.Contains('*'))
  689. filterExtension.Clear();
  690. #ifdef __ANDROID__
  691. if (URHO3D_IS_ASSET(path))
  692. {
  693. String assetPath(URHO3D_ASSET(path));
  694. assetPath.Resize(assetPath.Length() - 1); // AssetManager.list() does not like trailing slash
  695. int count;
  696. char** list = SDL_Android_GetFileList(assetPath.CString(), &count);
  697. for (int i = 0; i < count; ++i)
  698. {
  699. String fileName(list[i]);
  700. if (!(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
  701. continue;
  702. #ifdef ASSET_DIR_INDICATOR
  703. // Patch the directory name back after retrieving the directory flag
  704. bool isDirectory = fileName.EndsWith(ASSET_DIR_INDICATOR);
  705. if (isDirectory)
  706. {
  707. fileName.Resize(fileName.Length() - sizeof(ASSET_DIR_INDICATOR) / sizeof(char) + 1);
  708. if (flags & SCAN_DIRS)
  709. result.Push(deltaPath + fileName);
  710. if (recursive)
  711. ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
  712. }
  713. else if (flags & SCAN_FILES)
  714. #endif
  715. {
  716. if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
  717. result.Push(deltaPath + fileName);
  718. }
  719. }
  720. SDL_Android_FreeFileList(&list, &count);
  721. return;
  722. }
  723. #endif
  724. #ifdef _WIN32
  725. WIN32_FIND_DATAW info;
  726. HANDLE handle = FindFirstFileW(WString(path + "*").CString(), &info);
  727. if (handle != INVALID_HANDLE_VALUE)
  728. {
  729. do
  730. {
  731. String fileName(info.cFileName);
  732. if (!fileName.Empty())
  733. {
  734. if (info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & SCAN_HIDDEN))
  735. continue;
  736. if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  737. {
  738. if (flags & SCAN_DIRS)
  739. result.Push(deltaPath + fileName);
  740. if (recursive && fileName != "." && fileName != "..")
  741. ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
  742. }
  743. else if (flags & SCAN_FILES)
  744. {
  745. if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
  746. result.Push(deltaPath + fileName);
  747. }
  748. }
  749. }
  750. while (FindNextFileW(handle, &info));
  751. FindClose(handle);
  752. }
  753. #else
  754. DIR* dir;
  755. struct dirent* de;
  756. struct stat st{};
  757. dir = opendir(GetNativePath(path).CString());
  758. if (dir)
  759. {
  760. while ((de = readdir(dir)))
  761. {
  762. /// \todo Filename may be unnormalized Unicode on Mac OS X. Re-normalize as necessary
  763. String fileName(de->d_name);
  764. bool normalEntry = fileName != "." && fileName != "..";
  765. if (normalEntry && !(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
  766. continue;
  767. String pathAndName = path + fileName;
  768. if (!stat(pathAndName.CString(), &st))
  769. {
  770. if (st.st_mode & S_IFDIR)
  771. {
  772. if (flags & SCAN_DIRS)
  773. result.Push(deltaPath + fileName);
  774. if (recursive && normalEntry)
  775. ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
  776. }
  777. else if (flags & SCAN_FILES)
  778. {
  779. if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
  780. result.Push(deltaPath + fileName);
  781. }
  782. }
  783. }
  784. closedir(dir);
  785. }
  786. #endif
  787. }
  788. void FileSystem::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
  789. {
  790. // Go through the execution queue and post + remove completed requests
  791. for (List<AsyncExecRequest*>::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End();)
  792. {
  793. AsyncExecRequest* request = *i;
  794. if (request->IsCompleted())
  795. {
  796. using namespace AsyncExecFinished;
  797. VariantMap& newEventData = GetEventDataMap();
  798. newEventData[P_REQUESTID] = request->GetRequestID();
  799. newEventData[P_EXITCODE] = request->GetExitCode();
  800. SendEvent(E_ASYNCEXECFINISHED, newEventData);
  801. delete request;
  802. i = asyncExecQueue_.Erase(i);
  803. }
  804. else
  805. ++i;
  806. }
  807. }
  808. void FileSystem::HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
  809. {
  810. using namespace ConsoleCommand;
  811. if (eventData[P_ID].GetString() == GetTypeName())
  812. SystemCommand(eventData[P_COMMAND].GetString(), true);
  813. }
  814. void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension, bool lowercaseExtension)
  815. {
  816. String fullPathCopy = GetInternalPath(fullPath);
  817. i32 extPos = fullPathCopy.FindLast('.');
  818. i32 pathPos = fullPathCopy.FindLast('/');
  819. if (extPos != String::NPOS && (pathPos == String::NPOS || extPos > pathPos))
  820. {
  821. extension = fullPathCopy.Substring(extPos);
  822. if (lowercaseExtension)
  823. extension = extension.ToLower();
  824. fullPathCopy = fullPathCopy.Substring(0, extPos);
  825. }
  826. else
  827. extension.Clear();
  828. pathPos = fullPathCopy.FindLast('/');
  829. if (pathPos != String::NPOS)
  830. {
  831. fileName = fullPathCopy.Substring(pathPos + 1);
  832. pathName = fullPathCopy.Substring(0, pathPos + 1);
  833. }
  834. else
  835. {
  836. fileName = fullPathCopy;
  837. pathName.Clear();
  838. }
  839. }
  840. String GetPath(const String& fullPath)
  841. {
  842. String path, file, extension;
  843. SplitPath(fullPath, path, file, extension);
  844. return path;
  845. }
  846. String GetFileName(const String& fullPath)
  847. {
  848. String path, file, extension;
  849. SplitPath(fullPath, path, file, extension);
  850. return file;
  851. }
  852. String GetExtension(const String& fullPath, bool lowercaseExtension)
  853. {
  854. String path, file, extension;
  855. SplitPath(fullPath, path, file, extension, lowercaseExtension);
  856. return extension;
  857. }
  858. String GetFileNameAndExtension(const String& fileName, bool lowercaseExtension)
  859. {
  860. String path, file, extension;
  861. SplitPath(fileName, path, file, extension, lowercaseExtension);
  862. return file + extension;
  863. }
  864. String ReplaceExtension(const String& fullPath, const String& newExtension)
  865. {
  866. String path, file, extension;
  867. SplitPath(fullPath, path, file, extension);
  868. return path + file + newExtension;
  869. }
  870. String AddTrailingSlash(const String& pathName)
  871. {
  872. String ret = pathName.Trimmed();
  873. ret.Replace('\\', '/');
  874. if (!ret.Empty() && ret.Back() != '/')
  875. ret += '/';
  876. return ret;
  877. }
  878. String RemoveTrailingSlash(const String& pathName)
  879. {
  880. String ret = pathName.Trimmed();
  881. ret.Replace('\\', '/');
  882. if (!ret.Empty() && ret.Back() == '/')
  883. ret.Resize(ret.Length() - 1);
  884. return ret;
  885. }
  886. String GetParentPath(const String& path)
  887. {
  888. i32 pos = RemoveTrailingSlash(path).FindLast('/');
  889. if (pos != String::NPOS)
  890. return path.Substring(0, pos + 1);
  891. else
  892. return String();
  893. }
  894. String GetInternalPath(const String& pathName)
  895. {
  896. return pathName.Replaced('\\', '/');
  897. }
  898. String GetNativePath(const String& pathName)
  899. {
  900. #ifdef _WIN32
  901. return pathName.Replaced('/', '\\');
  902. #else
  903. return pathName;
  904. #endif
  905. }
  906. WString GetWideNativePath(const String& pathName)
  907. {
  908. #ifdef _WIN32
  909. return WString(pathName.Replaced('/', '\\'));
  910. #else
  911. return WString(pathName);
  912. #endif
  913. }
  914. bool IsAbsolutePath(const String& pathName)
  915. {
  916. if (pathName.Empty())
  917. return false;
  918. String path = GetInternalPath(pathName);
  919. if (path[0] == '/')
  920. return true;
  921. #ifdef _WIN32
  922. if (path.Length() > 1 && IsAlpha(path[0]) && path[1] == ':')
  923. return true;
  924. #endif
  925. return false;
  926. }
  927. String FileSystem::GetTemporaryDir() const
  928. {
  929. #if defined(_WIN32)
  930. #if defined(MINI_URHO)
  931. return getenv("TMP");
  932. #else
  933. wchar_t pathName[MAX_PATH];
  934. pathName[0] = 0;
  935. GetTempPathW(SDL_arraysize(pathName), pathName);
  936. return AddTrailingSlash(String(pathName));
  937. #endif
  938. #else
  939. if (char* pathName = getenv("TMPDIR"))
  940. return AddTrailingSlash(pathName);
  941. #ifdef P_tmpdir
  942. return AddTrailingSlash(P_tmpdir);
  943. #else
  944. return "/tmp/";
  945. #endif
  946. #endif
  947. }
  948. }