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