fileAPI.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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.h"
  24. #ifdef USE_MICROSOFT_WINDOWS
  25. #include <windows.h>
  26. #else
  27. #include <unistd.h>
  28. #include <sys/stat.h>
  29. #include <dirent.h>
  30. #endif
  31. #include <fstream>
  32. #include <cstdlib>
  33. #include "bufferAPI.h"
  34. namespace dsr {
  35. #ifdef USE_MICROSOFT_WINDOWS
  36. using NativeChar = wchar_t; // UTF-16
  37. static const char32_t* pathSeparator = U"\\";
  38. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF16LE;
  39. #define FILE_ACCESS_FUNCTION _wfopen
  40. #define FILE_ACCESS_SELECTION (write ? L"wb" : L"rb")
  41. List<String> file_impl_getInputArguments() {
  42. // Get a pointer to static memory with the command
  43. LPWSTR cmd = GetCommandLineW();
  44. // Split the arguments into an array of arguments
  45. int argc = 0;
  46. LPWSTR *argv = CommandLineToArgvW(cmd, &argc);
  47. // Convert the arguments into dsr::List<dsr::String>
  48. List<String> args = file_impl_convertInputArguments(argc, (void**)argv);
  49. // Free the native list of arguments
  50. LocalFree(argv);
  51. return args;
  52. }
  53. #else
  54. using NativeChar = char; // UTF-8
  55. static const char32_t* pathSeparator = U"/";
  56. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF8;
  57. #define FILE_ACCESS_FUNCTION fopen
  58. #define FILE_ACCESS_SELECTION (write ? "wb" : "rb")
  59. List<String> file_impl_getInputArguments() { return List<String>(); }
  60. #endif
  61. // Length of fixed size buffers.
  62. const int maxLength = 512;
  63. static const NativeChar* toNativeString(const ReadableString &filename, Buffer &buffer) {
  64. buffer = string_saveToMemory(filename, nativeEncoding, LineEncoding::CrLf, false, true);
  65. return (const NativeChar*)buffer_dangerous_getUnsafeData(buffer);
  66. }
  67. static String fromNativeString(const NativeChar *text) {
  68. return string_dangerous_decodeFromData(text, nativeEncoding);
  69. }
  70. List<String> file_impl_convertInputArguments(int argn, void **argv) {
  71. List<String> result;
  72. result.reserve(argn);
  73. for (int a = 0; a < argn; a++) {
  74. result.push(fromNativeString((NativeChar*)(argv[a])));
  75. }
  76. return result;
  77. }
  78. static FILE* accessFile(const ReadableString &filename, bool write) {
  79. Buffer buffer;
  80. return FILE_ACCESS_FUNCTION(toNativeString(filename, buffer), FILE_ACCESS_SELECTION);
  81. }
  82. Buffer file_loadBuffer(const ReadableString& filename, bool mustExist) {
  83. String modifiedFilename = file_optimizePath(filename);
  84. FILE *file = accessFile(modifiedFilename, false);
  85. if (file != nullptr) {
  86. // Get the file's size by going to the end, measuring, and going back
  87. fseek(file, 0L, SEEK_END);
  88. int64_t fileSize = ftell(file);
  89. rewind(file);
  90. // Allocate a buffer of the file's size
  91. Buffer buffer = buffer_create(fileSize);
  92. fread((void*)buffer_dangerous_getUnsafeData(buffer), fileSize, 1, file);
  93. fclose(file);
  94. return buffer;
  95. } else {
  96. if (mustExist) {
  97. throwError(U"Failed to load ", filename, " which was optimized into ", modifiedFilename, ".\n");
  98. }
  99. // If the file cound not be found and opened, an empty buffer is returned
  100. return Buffer();
  101. }
  102. }
  103. void file_saveBuffer(const ReadableString& filename, Buffer buffer) {
  104. String modifiedFilename = file_optimizePath(filename);
  105. if (!buffer_exists(buffer)) {
  106. throwError(U"buffer_save: Cannot save a buffer that don't exist to a file.\n");
  107. } else {
  108. FILE *file = accessFile(modifiedFilename, true);
  109. if (file != nullptr) {
  110. fwrite((void*)buffer_dangerous_getUnsafeData(buffer), buffer_getSize(buffer), 1, file);
  111. fclose(file);
  112. } else {
  113. throwError("Failed to save ", filename, " which was optimized into ", modifiedFilename, ".\n");
  114. }
  115. }
  116. }
  117. const char32_t* file_separator() {
  118. return pathSeparator;
  119. }
  120. inline bool isSeparator(DsrChar c) {
  121. return c == U'\\' || c == U'/';
  122. }
  123. // Returns the index of the last / or \ in path, or defaultIndex if none existed.
  124. static int64_t getLastSeparator(const ReadableString &path, int defaultIndex) {
  125. for (int64_t i = string_length(path) - 1; i >= 0; i--) {
  126. DsrChar c = path[i];
  127. if (isSeparator(c)) {
  128. return i;
  129. }
  130. }
  131. return defaultIndex;
  132. }
  133. String file_optimizePath(const ReadableString &path) {
  134. String result;
  135. int inputLength = string_length(path);
  136. string_reserve(result, inputLength);
  137. for (int i = 0; i < inputLength; i++) {
  138. DsrChar c = path[i];
  139. if (isSeparator(c)) {
  140. string_append(result, pathSeparator);
  141. } else {
  142. string_appendChar(result, c);
  143. }
  144. }
  145. return result;
  146. }
  147. ReadableString file_getPathlessName(const ReadableString &path) {
  148. return string_after(path, getLastSeparator(path, -1));
  149. }
  150. ReadableString file_getParentFolder(const ReadableString &path) {
  151. return string_before(path, getLastSeparator(path, 0));
  152. }
  153. bool file_hasRoot(const ReadableString &path, bool treatHomeFolderAsRoot) {
  154. #ifdef USE_MICROSOFT_WINDOWS
  155. // If a colon is found, it is a root path.
  156. return string_findFirst(path, U':') > -1;
  157. #else
  158. // If the path begins with a separator, it is the root folder in Posix systems.
  159. // If the path begins with a tilde (~), it is a home folder.
  160. DsrChar firstC = path[0];
  161. return firstC == U'/' || (treatHomeFolderAsRoot && firstC == U'~');
  162. #endif
  163. }
  164. bool file_setCurrentPath(const ReadableString &path) {
  165. Buffer buffer;
  166. const NativeChar *nativePath = toNativeString(file_optimizePath(path), buffer);
  167. #ifdef USE_MICROSOFT_WINDOWS
  168. return SetCurrentDirectoryW(nativePath);
  169. #else
  170. return chdir(nativePath) == 0;
  171. #endif
  172. }
  173. String file_getCurrentPath() {
  174. #ifdef USE_MICROSOFT_WINDOWS
  175. NativeChar resultBuffer[maxLength + 1] = {0};
  176. GetCurrentDirectoryW(maxLength, resultBuffer);
  177. return fromNativeString(resultBuffer);
  178. #else
  179. NativeChar resultBuffer[maxLength + 1] = {0};
  180. getcwd(resultBuffer, maxLength);
  181. return fromNativeString(resultBuffer);
  182. #endif
  183. }
  184. String file_getApplicationFolder(bool allowFallback) {
  185. #ifdef USE_MICROSOFT_WINDOWS
  186. NativeChar resultBuffer[maxLength + 1] = {0};
  187. GetModuleFileNameW(nullptr, resultBuffer, maxLength);
  188. return file_getParentFolder(fromNativeString(resultBuffer));
  189. #else
  190. NativeChar resultBuffer[maxLength + 1] = {0};
  191. if (readlink("/proc/self/exe", resultBuffer, maxLength) != -1) {
  192. // Linux detected
  193. return file_getParentFolder(fromNativeString(resultBuffer));
  194. } else if (readlink("/proc/curproc/file", resultBuffer, maxLength) != -1) {
  195. // BSD detected
  196. return file_getParentFolder(fromNativeString(resultBuffer));
  197. } else if (readlink("/proc/self/path/a.out", resultBuffer, maxLength) != -1) {
  198. // Solaris detected
  199. return file_getParentFolder(fromNativeString(resultBuffer));
  200. } else if (allowFallback) {
  201. return file_getCurrentPath();
  202. } else {
  203. throwError("file_getApplicationFolder is not implemented for the current system!\n");
  204. return U"";
  205. }
  206. #endif
  207. }
  208. String file_combinePaths(const ReadableString &a, const ReadableString &b) {
  209. if (file_hasRoot(b)) {
  210. return b;
  211. } else {
  212. if (isSeparator(a[string_length(a) - 1])) {
  213. // Already ending with a separator.
  214. return string_combine(a, b);
  215. } else {
  216. // Combine using a separator.
  217. return string_combine(a, pathSeparator, b);
  218. }
  219. }
  220. }
  221. String file_getAbsolutePath(const ReadableString &path) {
  222. if (file_hasRoot(path)) {
  223. return path;
  224. } else {
  225. return file_combinePaths(file_getCurrentPath(), path);
  226. }
  227. }
  228. int64_t file_getFileSize(const ReadableString& filename) {
  229. int64_t result = -1;
  230. String modifiedFilename = file_optimizePath(filename);
  231. Buffer buffer;
  232. const NativeChar *nativePath = toNativeString(modifiedFilename, buffer);
  233. #ifdef USE_MICROSOFT_WINDOWS
  234. LARGE_INTEGER fileSize;
  235. HANDLE fileHandle = CreateFileW(nativePath, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  236. if (fileHandle != INVALID_HANDLE_VALUE) {
  237. if (GetFileSizeEx(fileHandle, &fileSize)) {
  238. result = fileSize.QuadPart;
  239. }
  240. CloseHandle(fileHandle);
  241. }
  242. #else
  243. struct stat info;
  244. if (stat(nativePath, &info) == 0) {
  245. result = info.st_size;
  246. }
  247. #endif
  248. return result;
  249. }
  250. String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation) {
  251. string_append(target, indentation);
  252. if (source == EntryType::NotFound) {
  253. string_append(target, U"not found");
  254. } else if (source == EntryType::File) {
  255. string_append(target, U"a file");
  256. } else if (source == EntryType::Folder) {
  257. string_append(target, U"a folder");
  258. } else if (source == EntryType::SymbolicLink) {
  259. string_append(target, U"a symbolic link");
  260. } else {
  261. string_append(target, U"unhandled");
  262. }
  263. return target;
  264. }
  265. EntryType file_getEntryType(const ReadableString &path) {
  266. EntryType result = EntryType::NotFound;
  267. String modifiedPath = file_optimizePath(path);
  268. Buffer buffer;
  269. const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
  270. #ifdef USE_MICROSOFT_WINDOWS
  271. DWORD dwAttrib = GetFileAttributesW(nativePath);
  272. if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
  273. if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
  274. result = EntryType::Folder;
  275. } else {
  276. result = EntryType::File;
  277. }
  278. }
  279. #else
  280. struct stat info;
  281. int errorCode = stat(nativePath, &info);
  282. if (errorCode == 0) {
  283. if (S_ISDIR(info.st_mode)) {
  284. result = EntryType::Folder;
  285. } else if (S_ISREG(info.st_mode)) {
  286. result = EntryType::File;
  287. } else if (S_ISLNK(info.st_mode)) {
  288. result = EntryType::SymbolicLink;
  289. } else {
  290. result = EntryType::UnhandledType;
  291. }
  292. }
  293. #endif
  294. return result;
  295. }
  296. bool file_getFolderContent(const ReadableString& folderPath, std::function<void(const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType)> action) {
  297. String modifiedPath = file_optimizePath(folderPath);
  298. #ifdef USE_MICROSOFT_WINDOWS
  299. String pattern = file_combinePaths(modifiedPath, U"*.*");
  300. Buffer buffer;
  301. const NativeChar *nativePattern = toNativeString(pattern, buffer);
  302. WIN32_FIND_DATAW findData;
  303. HANDLE findHandle = FindFirstFileW(nativePattern, &findData);
  304. if (findHandle == INVALID_HANDLE_VALUE) {
  305. return false;
  306. } else {
  307. while (true) {
  308. String entryName = fromNativeString(findData.cFileName);
  309. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  310. String entryPath = file_combinePaths(modifiedPath, entryName);
  311. EntryType entryType = EntryType::UnhandledType;
  312. if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  313. entryType = EntryType::Folder;
  314. } else {
  315. entryType = EntryType::File;
  316. }
  317. action(entryPath, entryName, entryType);
  318. }
  319. if (!FindNextFileW(findHandle, &findData)) { break; }
  320. }
  321. FindClose(findHandle);
  322. }
  323. #else
  324. Buffer buffer;
  325. const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
  326. DIR *directory = opendir(nativePath);
  327. if (directory == nullptr) {
  328. return false;
  329. } else {
  330. while (true) {
  331. dirent *entry = readdir(directory);
  332. if (entry != nullptr) {
  333. String entryName = fromNativeString(entry->d_name);
  334. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  335. String entryPath = file_combinePaths(modifiedPath, entryName);
  336. EntryType entryType = file_getEntryType(entryPath);
  337. action(entryPath, entryName, entryType);
  338. }
  339. } else {
  340. break;
  341. }
  342. }
  343. }
  344. closedir(directory);
  345. #endif
  346. return true;
  347. }
  348. }