fileAPI.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2020 to 2022 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. // Include fileAPI without falling back on local syntax implicitly.
  24. // This prevents any local syntax from being implied in functions that are supposed to use variable pathSyntax.
  25. #define NO_IMPLICIT_PATH_SYNTAX
  26. #include "fileAPI.h"
  27. #ifdef USE_MICROSOFT_WINDOWS
  28. #include <windows.h>
  29. #else
  30. #include <unistd.h>
  31. #include <sys/stat.h>
  32. #include <dirent.h>
  33. #endif
  34. #include <fstream>
  35. #include <cstdlib>
  36. #include "bufferAPI.h"
  37. namespace dsr {
  38. constexpr const char32_t* getPathSeparator(PathSyntax pathSyntax) {
  39. if (pathSyntax == PathSyntax::Windows) {
  40. return U"\\";
  41. } else if (pathSyntax == PathSyntax::Posix) {
  42. return U"/";
  43. } else {
  44. return U"?";
  45. }
  46. }
  47. #ifdef USE_MICROSOFT_WINDOWS
  48. using NativeChar = wchar_t; // UTF-16
  49. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF16LE;
  50. #define FILE_ACCESS_FUNCTION _wfopen
  51. #define FILE_ACCESS_SELECTION (write ? L"wb" : L"rb")
  52. List<String> file_impl_getInputArguments() {
  53. // Get a pointer to static memory with the command
  54. LPWSTR cmd = GetCommandLineW();
  55. // Split the arguments into an array of arguments
  56. int argc = 0;
  57. LPWSTR *argv = CommandLineToArgvW(cmd, &argc);
  58. // Convert the arguments into dsr::List<dsr::String>
  59. List<String> args = file_impl_convertInputArguments(argc, (void**)argv);
  60. // Free the native list of arguments
  61. LocalFree(argv);
  62. return args;
  63. }
  64. #else
  65. using NativeChar = char; // UTF-8
  66. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF8;
  67. #define FILE_ACCESS_FUNCTION fopen
  68. #define FILE_ACCESS_SELECTION (write ? "wb" : "rb")
  69. List<String> file_impl_getInputArguments() { return List<String>(); }
  70. #endif
  71. // Length of fixed size buffers.
  72. const int maxLength = 512;
  73. static const NativeChar* toNativeString(const ReadableString &filename, Buffer &buffer) {
  74. buffer = string_saveToMemory(filename, nativeEncoding, LineEncoding::CrLf, false, true);
  75. return (const NativeChar*)buffer_dangerous_getUnsafeData(buffer);
  76. }
  77. static String fromNativeString(const NativeChar *text) {
  78. return string_dangerous_decodeFromData(text, nativeEncoding);
  79. }
  80. List<String> file_impl_convertInputArguments(int argn, void **argv) {
  81. List<String> result;
  82. result.reserve(argn);
  83. for (int a = 0; a < argn; a++) {
  84. result.push(fromNativeString((NativeChar*)(argv[a])));
  85. }
  86. return result;
  87. }
  88. static FILE* accessFile(const ReadableString &filename, bool write) {
  89. Buffer buffer;
  90. return FILE_ACCESS_FUNCTION(toNativeString(filename, buffer), FILE_ACCESS_SELECTION);
  91. }
  92. Buffer file_loadBuffer(const ReadableString& filename, bool mustExist) {
  93. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  94. FILE *file = accessFile(modifiedFilename, false);
  95. if (file != nullptr) {
  96. // Get the file's size by going to the end, measuring, and going back
  97. fseek(file, 0L, SEEK_END);
  98. int64_t fileSize = ftell(file);
  99. rewind(file);
  100. // Allocate a buffer of the file's size
  101. Buffer buffer = buffer_create(fileSize);
  102. fread((void*)buffer_dangerous_getUnsafeData(buffer), fileSize, 1, file);
  103. fclose(file);
  104. return buffer;
  105. } else {
  106. if (mustExist) {
  107. throwError(U"Failed to load ", modifiedFilename, ".\n");
  108. }
  109. // If the file cound not be found and opened, an empty buffer is returned
  110. return Buffer();
  111. }
  112. }
  113. bool file_saveBuffer(const ReadableString& filename, Buffer buffer, bool mustWork) {
  114. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  115. if (!buffer_exists(buffer)) {
  116. if (mustWork) {
  117. throwError(U"buffer_save: Can't save a buffer that don't exist to a file.\n");
  118. }
  119. return false;
  120. } else {
  121. FILE *file = accessFile(modifiedFilename, true);
  122. if (file != nullptr) {
  123. fwrite((void*)buffer_dangerous_getUnsafeData(buffer), buffer_getSize(buffer), 1, file);
  124. fclose(file);
  125. } else {
  126. if (mustWork) {
  127. throwError("Failed to save ", modifiedFilename, ".\n");
  128. }
  129. return false;
  130. }
  131. }
  132. // Success if there are no errors.
  133. return true;
  134. }
  135. const char32_t* file_separator() {
  136. return getPathSeparator(LOCAL_PATH_SYNTAX);
  137. }
  138. bool file_isSeparator(DsrChar c) {
  139. return c == U'\\' || c == U'/';
  140. }
  141. // Returns the index of the first / or \ in path, or defaultIndex if none existed.
  142. int64_t file_findFirstSeparator(const ReadableString &path, int64_t defaultIndex, int64_t startIndex) {
  143. for (int64_t i = startIndex; i < string_length(path); i++) {
  144. DsrChar c = path[i];
  145. if (file_isSeparator(c)) {
  146. return i;
  147. }
  148. }
  149. return defaultIndex;
  150. }
  151. // Returns the index of the last / or \ in path, or defaultIndex if none existed.
  152. int64_t file_findLastSeparator(const ReadableString &path, int64_t defaultIndex) {
  153. for (int64_t i = string_length(path) - 1; i >= 0; i--) {
  154. DsrChar c = path[i];
  155. if (file_isSeparator(c)) {
  156. return i;
  157. }
  158. }
  159. return defaultIndex;
  160. }
  161. String file_optimizePath(const ReadableString &path, PathSyntax pathSyntax) {
  162. String result; // The final output being appended.
  163. String currentEntry; // The current entry.
  164. bool hadSeparator = false;
  165. bool hadContent = false;
  166. int64_t inputLength = string_length(path);
  167. string_reserve(result, inputLength);
  168. // Read null terminator from one element outside of the path to allow concluding an entry not followed by any separator.
  169. // The null terminator is not actually stored, but reading out of bound gives a null terminator.
  170. for (int64_t i = 0; i <= inputLength; i++) {
  171. DsrChar c = path[i];
  172. bool separator = file_isSeparator(c);
  173. if (separator || i == inputLength) {
  174. bool appendEntry = true;
  175. bool appendSeparator = separator;
  176. if (hadSeparator) {
  177. if (hadContent && string_length(currentEntry) == 0) {
  178. // Reduce non-leading // into / by skipping "" entries.
  179. // Any leading multiples of slashes have their count preserved, because some systems use them to indicate special use cases.
  180. appendEntry = false;
  181. appendSeparator = false;
  182. } else if (string_match(currentEntry, U".")) {
  183. // Reduce /./ into / by skipping "." entries.
  184. appendEntry = false;
  185. appendSeparator = false;
  186. } else if (string_match(currentEntry, U"..")) {
  187. // Reduce the parent directory against the reverse ".." entry.
  188. result = file_getRelativeParentFolder(result, pathSyntax);
  189. if (string_match(result, U"?")) {
  190. return U"?";
  191. }
  192. appendEntry = false;
  193. }
  194. }
  195. if (appendEntry) {
  196. string_append(result, string_removeOuterWhiteSpace(currentEntry));
  197. }
  198. if (appendSeparator) {
  199. string_append(result, getPathSeparator(pathSyntax));
  200. }
  201. currentEntry = U"";
  202. if (separator) {
  203. hadSeparator = true;
  204. }
  205. } else {
  206. string_appendChar(currentEntry, c);
  207. hadContent = true;
  208. }
  209. }
  210. // Remove trailing separators if we had content.
  211. if (hadSeparator && hadContent) {
  212. int64_t lastNonSeparator = -1;
  213. for (int64_t i = string_length(result) - 1; i >= 0; i--) {
  214. if (!file_isSeparator(result[i])) {
  215. lastNonSeparator = i;
  216. break;
  217. }
  218. }
  219. result = string_until(result, lastNonSeparator);
  220. }
  221. return result;
  222. }
  223. ReadableString file_getPathlessName(const ReadableString &path) {
  224. return string_after(path, file_findLastSeparator(path));
  225. }
  226. bool file_hasExtension(const String& path) {
  227. int64_t lastDotIndex = string_findLast(path, U'.');
  228. int64_t lastSeparatorIndex = file_findLastSeparator(path);
  229. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  230. return true;
  231. } else {
  232. return false;
  233. }
  234. }
  235. ReadableString file_getExtension(const String& filename) {
  236. int64_t lastDotIndex = string_findLast(filename, U'.');
  237. int64_t lastSeparatorIndex = file_findLastSeparator(filename);
  238. // Only use the last dot if there is no folder separator after it.
  239. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  240. return string_removeOuterWhiteSpace(string_after(filename, lastDotIndex));
  241. } else {
  242. return U"";
  243. }
  244. }
  245. ReadableString file_getExtensionless(const String& filename) {
  246. int64_t lastDotIndex = string_findLast(filename, U'.');
  247. int64_t lastSeparatorIndex = file_findLastSeparator(filename);
  248. // Only use the last dot if there is no folder separator after it.
  249. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  250. return string_removeOuterWhiteSpace(string_before(filename, lastDotIndex));
  251. } else {
  252. return string_removeOuterWhiteSpace(filename);
  253. }
  254. }
  255. String file_getRelativeParentFolder(const ReadableString &path, PathSyntax pathSyntax) {
  256. String optimizedPath = file_optimizePath(path, pathSyntax);
  257. if (string_length(optimizedPath) == 0) {
  258. // Use .. to go outside of the current directory.
  259. return U"..";
  260. } else if (string_match(file_getPathlessName(optimizedPath), U"?")) {
  261. // From unknown to unknown.
  262. return U"?";
  263. } else if (file_isRoot(optimizedPath, false, pathSyntax)) {
  264. // If it's the known true root, then we know that it does not have a parent and must fail.
  265. return U"?";
  266. } else if (file_isRoot(optimizedPath, true, pathSyntax)) {
  267. // If it's an alias for an arbitrary folder, use .. to leave it.
  268. return file_combinePaths(optimizedPath, U"..", pathSyntax);
  269. } else if (string_match(file_getPathlessName(optimizedPath), U"..")) {
  270. // Add more dots to the path.
  271. return file_combinePaths(optimizedPath, U"..", pathSyntax);
  272. } else {
  273. // Inside of something.
  274. int64_t lastSeparator = file_findLastSeparator(optimizedPath, 0);
  275. if (pathSyntax == PathSyntax::Windows) {
  276. // Return everything before the last separator.
  277. return string_before(optimizedPath, lastSeparator);
  278. } else { // PathSyntax::Posix
  279. if (file_hasRoot(path, false, pathSyntax) && lastSeparator == 0) {
  280. // Keep the absolute root.
  281. return U"/";
  282. } else {
  283. // Keep everything before the last separator.
  284. return string_before(optimizedPath, lastSeparator);
  285. }
  286. }
  287. }
  288. }
  289. String file_getTheoreticalAbsoluteParentFolder(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax) {
  290. if (file_hasRoot(path, true, LOCAL_PATH_SYNTAX)) {
  291. // Absolute paths should be treated the same as a theoretical path.
  292. return file_getRelativeParentFolder(path, pathSyntax);
  293. } else {
  294. // If the input is not absolute, convert it before taking the parent directory.
  295. return file_getRelativeParentFolder(file_getTheoreticalAbsolutePath(path, currentPath, pathSyntax), pathSyntax);
  296. }
  297. }
  298. String file_getAbsoluteParentFolder(const ReadableString &path) {
  299. return file_getTheoreticalAbsoluteParentFolder(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX);
  300. }
  301. bool file_isRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) {
  302. ReadableString cleanPath = string_removeOuterWhiteSpace(path);
  303. int64_t length = string_length(cleanPath);
  304. if (length == 0) {
  305. // Relative path is not a root.
  306. return false;
  307. } else if (length == 1) {
  308. DsrChar c = cleanPath[0];
  309. if (pathSyntax == PathSyntax::Windows) {
  310. return c == U'\\'; // Implicit drive root.
  311. } else { // PathSyntax::Posix
  312. return c == U'/' || (c == U'~' && treatHomeFolderAsRoot); // Root over all drives or home folder.
  313. }
  314. } else {
  315. if (pathSyntax == PathSyntax::Windows && cleanPath[length - 1] == U':') {
  316. // C:, D:, ...
  317. return true;
  318. } else {
  319. return false;
  320. }
  321. }
  322. }
  323. bool file_hasRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) {
  324. int64_t firstSeparator = file_findFirstSeparator(path);
  325. if (firstSeparator == -1) {
  326. // If there is no separator, path has a root if it is a root.
  327. return file_isRoot(path, treatHomeFolderAsRoot, pathSyntax);
  328. } else if (firstSeparator == 0) {
  329. // Starting with a separator. Either an implicit drive on Windows or the whole system's root on Posix.
  330. return true;
  331. } else {
  332. // Has a root if the first entry before the first slash is a root.
  333. return file_isRoot(string_before(path, firstSeparator), treatHomeFolderAsRoot, pathSyntax);
  334. }
  335. }
  336. bool file_setCurrentPath(const ReadableString &path) {
  337. Buffer buffer;
  338. const NativeChar *nativePath = toNativeString(file_optimizePath(path, LOCAL_PATH_SYNTAX), buffer);
  339. #ifdef USE_MICROSOFT_WINDOWS
  340. return SetCurrentDirectoryW(nativePath);
  341. #else
  342. return chdir(nativePath) == 0;
  343. #endif
  344. }
  345. String file_getCurrentPath() {
  346. #ifdef USE_MICROSOFT_WINDOWS
  347. NativeChar resultBuffer[maxLength + 1] = {0};
  348. GetCurrentDirectoryW(maxLength, resultBuffer);
  349. return fromNativeString(resultBuffer);
  350. #else
  351. NativeChar resultBuffer[maxLength + 1] = {0};
  352. getcwd(resultBuffer, maxLength);
  353. return fromNativeString(resultBuffer);
  354. #endif
  355. }
  356. String file_followSymbolicLink(const ReadableString &path, bool mustExist) {
  357. String modifiedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  358. Buffer buffer;
  359. const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
  360. #ifdef USE_MICROSOFT_WINDOWS
  361. // TODO: Is there anything that can be used as a symbolic link on Windows?
  362. #else
  363. NativeChar resultBuffer[maxLength + 1] = {0};
  364. if (readlink(nativePath, resultBuffer, maxLength) != -1) {
  365. return fromNativeString(resultBuffer);
  366. }
  367. #endif
  368. if (mustExist) { throwError(U"The symbolic link ", path, " could not be found!\n"); }
  369. return U"?";
  370. }
  371. String file_getApplicationFolder(bool allowFallback) {
  372. #ifdef USE_MICROSOFT_WINDOWS
  373. NativeChar resultBuffer[maxLength + 1] = {0};
  374. GetModuleFileNameW(nullptr, resultBuffer, maxLength);
  375. return file_getRelativeParentFolder(fromNativeString(resultBuffer), LOCAL_PATH_SYNTAX);
  376. #else
  377. NativeChar resultBuffer[maxLength + 1] = {0};
  378. if (readlink("/proc/self/exe", resultBuffer, maxLength) != -1) {
  379. // Linux detected
  380. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  381. } else if (readlink("/proc/curproc/file", resultBuffer, maxLength) != -1) {
  382. // BSD detected
  383. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  384. } else if (readlink("/proc/self/path/a.out", resultBuffer, maxLength) != -1) {
  385. // Solaris detected
  386. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  387. } else if (allowFallback) {
  388. return file_getCurrentPath();
  389. } else {
  390. throwError("file_getApplicationFolder is not implemented for the current system!\n");
  391. return U"";
  392. }
  393. #endif
  394. }
  395. String file_combinePaths(const ReadableString &a, const ReadableString &b, PathSyntax pathSyntax) {
  396. ReadableString cleanA = string_removeOuterWhiteSpace(a);
  397. ReadableString cleanB = string_removeOuterWhiteSpace(b);
  398. int64_t lengthA = string_length(cleanA);
  399. int64_t lengthB = string_length(cleanB);
  400. if (file_hasRoot(b, true, pathSyntax)) {
  401. // Restarting from root or home folder.
  402. return cleanB;
  403. } else if (lengthA == 0) {
  404. // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator.
  405. return cleanB;
  406. } else if (lengthB == 0) {
  407. // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator.
  408. return cleanA;
  409. } else {
  410. if (file_isSeparator(a[lengthA - 1])) {
  411. // Already ending with a separator.
  412. return string_combine(cleanA, cleanB);
  413. } else {
  414. // Combine using a separator.
  415. return string_combine(cleanA, getPathSeparator(pathSyntax), cleanB);
  416. }
  417. }
  418. }
  419. // Returns path with the drive letter applied from currentPath if missing in path.
  420. // Used for converting drive relative paths into true absolute paths on MS-Windows.
  421. static String applyDriveLetter(const ReadableString &path, const ReadableString &currentPath) {
  422. // Convert implicit drive into a named drive.
  423. if (path[0] == U'\\') {
  424. int64_t colonIndex = string_findFirst(currentPath, U':', -1);
  425. if (colonIndex == -1) {
  426. return U"?";
  427. } else {
  428. // Get the drive letter from the current path.
  429. String drive = string_until(currentPath, colonIndex);
  430. return string_combine(drive, path);
  431. }
  432. } else {
  433. // Already absolute.
  434. return path;
  435. }
  436. }
  437. String file_getTheoreticalAbsolutePath(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax) {
  438. // Home folders are absolute enough, because we don't want to lose the account ambiguity by mangling it into hardcoded usernames.
  439. if (file_hasRoot(path, true, pathSyntax)) {
  440. if (pathSyntax == PathSyntax::Windows) {
  441. // Make sure that no drive letter is missing.
  442. return applyDriveLetter(file_optimizePath(path, pathSyntax), currentPath);
  443. } else {
  444. // Already absolute.
  445. return file_optimizePath(path, pathSyntax);
  446. }
  447. } else {
  448. // Convert from relative path.
  449. return file_optimizePath(file_combinePaths(currentPath, path, pathSyntax), pathSyntax);
  450. }
  451. }
  452. String file_getAbsolutePath(const ReadableString &path) {
  453. return file_getTheoreticalAbsolutePath(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX);
  454. }
  455. int64_t file_getFileSize(const ReadableString& filename) {
  456. int64_t result = -1;
  457. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  458. Buffer buffer;
  459. const NativeChar *nativePath = toNativeString(modifiedFilename, buffer);
  460. #ifdef USE_MICROSOFT_WINDOWS
  461. LARGE_INTEGER fileSize;
  462. HANDLE fileHandle = CreateFileW(nativePath, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  463. if (fileHandle != INVALID_HANDLE_VALUE) {
  464. if (GetFileSizeEx(fileHandle, &fileSize)) {
  465. result = fileSize.QuadPart;
  466. }
  467. CloseHandle(fileHandle);
  468. }
  469. #else
  470. struct stat info;
  471. if (stat(nativePath, &info) == 0) {
  472. result = info.st_size;
  473. }
  474. #endif
  475. return result;
  476. }
  477. String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation) {
  478. string_append(target, indentation);
  479. if (source == EntryType::NotFound) {
  480. string_append(target, U"not found");
  481. } else if (source == EntryType::File) {
  482. string_append(target, U"a file");
  483. } else if (source == EntryType::Folder) {
  484. string_append(target, U"a folder");
  485. } else if (source == EntryType::SymbolicLink) {
  486. string_append(target, U"a symbolic link");
  487. } else {
  488. string_append(target, U"unhandled");
  489. }
  490. return target;
  491. }
  492. EntryType file_getEntryType(const ReadableString &path) {
  493. EntryType result = EntryType::NotFound;
  494. String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  495. Buffer buffer;
  496. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  497. #ifdef USE_MICROSOFT_WINDOWS
  498. DWORD dwAttrib = GetFileAttributesW(nativePath);
  499. if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
  500. if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
  501. result = EntryType::Folder;
  502. } else {
  503. result = EntryType::File;
  504. }
  505. }
  506. #else
  507. struct stat info;
  508. int errorCode = stat(nativePath, &info);
  509. if (errorCode == 0) {
  510. if (S_ISDIR(info.st_mode)) {
  511. result = EntryType::Folder;
  512. } else if (S_ISREG(info.st_mode)) {
  513. result = EntryType::File;
  514. } else if (S_ISLNK(info.st_mode)) {
  515. result = EntryType::SymbolicLink;
  516. } else {
  517. result = EntryType::UnhandledType;
  518. }
  519. }
  520. #endif
  521. return result;
  522. }
  523. bool file_getFolderContent(const ReadableString& folderPath, std::function<void(const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType)> action) {
  524. String optimizedPath = file_optimizePath(folderPath, LOCAL_PATH_SYNTAX);
  525. #ifdef USE_MICROSOFT_WINDOWS
  526. String pattern = file_combinePaths(optimizedPath, U"*.*", LOCAL_PATH_SYNTAX);
  527. Buffer buffer;
  528. const NativeChar *nativePattern = toNativeString(pattern, buffer);
  529. WIN32_FIND_DATAW findData;
  530. HANDLE findHandle = FindFirstFileW(nativePattern, &findData);
  531. if (findHandle == INVALID_HANDLE_VALUE) {
  532. return false;
  533. } else {
  534. while (true) {
  535. String entryName = fromNativeString(findData.cFileName);
  536. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  537. String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX);
  538. EntryType entryType = EntryType::UnhandledType;
  539. if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  540. entryType = EntryType::Folder;
  541. } else {
  542. entryType = EntryType::File;
  543. }
  544. action(entryPath, entryName, entryType);
  545. }
  546. if (!FindNextFileW(findHandle, &findData)) { break; }
  547. }
  548. FindClose(findHandle);
  549. }
  550. #else
  551. Buffer buffer;
  552. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  553. DIR *directory = opendir(nativePath);
  554. if (directory == nullptr) {
  555. return false;
  556. } else {
  557. while (true) {
  558. dirent *entry = readdir(directory);
  559. if (entry != nullptr) {
  560. String entryName = fromNativeString(entry->d_name);
  561. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  562. String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX);
  563. EntryType entryType = file_getEntryType(entryPath);
  564. action(entryPath, entryName, entryType);
  565. }
  566. } else {
  567. break;
  568. }
  569. }
  570. }
  571. closedir(directory);
  572. #endif
  573. return true;
  574. }
  575. void file_getPathEntries(const ReadableString& path, std::function<void(ReadableString, int64_t, int64_t)> action) {
  576. int64_t sectionStart = 0;
  577. int64_t length = string_length(path);
  578. for (int64_t i = 0; i < string_length(path); i++) {
  579. DsrChar c = path[i];
  580. if (file_isSeparator(c)) {
  581. int64_t sectionEnd = i - 1; // Inclusive end
  582. ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd);
  583. if (string_length(content)) { action(content, sectionStart, sectionEnd); }
  584. sectionStart = i + 1;
  585. }
  586. }
  587. if (length > sectionStart) {
  588. int64_t sectionEnd = length - 1; // Inclusive end
  589. ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd);
  590. if (string_length(content)) { action(content, sectionStart, sectionEnd); }
  591. }
  592. }
  593. bool file_createFolder(const ReadableString &path) {
  594. bool result = false;
  595. String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  596. Buffer buffer;
  597. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  598. #ifdef USE_MICROSOFT_WINDOWS
  599. // Create folder with permissions inherited.
  600. result = (CreateDirectoryW(nativePath, nullptr) != 0);
  601. #else
  602. // Create folder with default permissions. Read, write and search for owner and group. Read and search for others.
  603. result = (mkdir(nativePath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
  604. #endif
  605. return result;
  606. }
  607. }