浏览代码

Made a smaller filesystem wrapper over the system APIs to be more reliable.

David Piuva 3 年之前
父节点
当前提交
b3b80ea8de
共有 2 个文件被更改,包括 196 次插入105 次删除
  1. 110 62
      Source/DFPSR/api/fileAPI.cpp
  2. 86 43
      Source/DFPSR/api/fileAPI.h

+ 110 - 62
Source/DFPSR/api/fileAPI.cpp

@@ -21,13 +21,15 @@
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 
+#include "fileAPI.h"
+#ifdef USE_MICROSOFT_WINDOWS
+	#include <windows.h>
+#else
+	#include <unistd.h>
+#endif
 #include <fstream>
 #include <cstdlib>
-#include "fileAPI.h"
 #include "bufferAPI.h"
-#ifdef __cpp_lib_filesystem
-	#include <filesystem>
-#endif
 
 namespace dsr {
 
@@ -35,28 +37,54 @@ namespace dsr {
 // * pathSeparator is the token used to separate folders in the system, expressed as a UTF-32 string literal.
 // * accessFile is the function for opening a file using the UTF-32 filename, for reading or writing.
 //   The C API is used for access, because some C++ standard library implementations don't support wide strings for MS-Windows.
-#if defined(WIN32) || defined(_WIN32)
-	#include <windows.h>
+#ifdef USE_MICROSOFT_WINDOWS
+	using NativeChar = wchar_t; // UTF-16
 	static const char32_t* pathSeparator = U"\\";
 	static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF16LE;
 	#define FILE_ACCESS_FUNCTION _wfopen
 	#define FILE_ACCESS_SELECTION (write ? L"wb" : L"rb")
+	List<String> file_impl_getInputArguments() {
+		// Get a pointer to static memory with the command
+		LPWSTR cmd = GetCommandLineW();
+		// Split the arguments into an array of arguments
+		int argc = 0;
+		LPWSTR *argv = CommandLineToArgvW(cmd, &argc);
+		// Convert the arguments into dsr::List<dsr::String>
+		List<String> args = file_impl_convertInputArguments(argc, (void**)argv);
+		// Free the native list of arguments
+		LocalFree(argv);
+		return args;
+	}
 #else
+	using NativeChar = char; // UTF-8
 	static const char32_t* pathSeparator = U"/";
 	static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF8;
 	#define FILE_ACCESS_FUNCTION fopen
 	#define FILE_ACCESS_SELECTION (write ? "wb" : "rb")
+	List<String> file_impl_getInputArguments() { return List<String>(); }
 #endif
 
+// Length of fixed size buffers.
+const int maxLength = 512;
+
 static const NativeChar* toNativeString(const ReadableString &filename, Buffer &buffer) {
 	buffer = string_saveToMemory(filename, nativeEncoding, LineEncoding::CrLf, false, true);
 	return (const NativeChar*)buffer_dangerous_getUnsafeData(buffer);
 }
 
-static String fromNativeString(NativeChar *text) {
+static String fromNativeString(const NativeChar *text) {
 	return string_dangerous_decodeFromData(text, nativeEncoding);
 }
 
+List<String> file_impl_convertInputArguments(int argn, void **argv) {
+	List<String> result;
+	result.reserve(argn);
+	for (int a = 0; a < argn; a++) {
+		result.push(fromNativeString((NativeChar*)(argv[a])));
+	}
+	return result;
+}
+
 static FILE* accessFile(const ReadableString &filename, bool write) {
 	Buffer buffer;
 	return FILE_ACCESS_FUNCTION(toNativeString(filename, buffer), FILE_ACCESS_SELECTION);
@@ -101,77 +129,97 @@ const char32_t* file_separator() {
 	return pathSeparator;
 }
 
-ReadableString file_getPathlessName(const ReadableString &path) {
-	int lastSeparator = -1;
+inline bool isSeparator(DsrChar c) {
+	return c == U'\\' || c == U'/';
+}
+
+// Returns the index of the last / or \ in path, or defaultIndex if none existed.
+static int64_t getLastSeparator(const ReadableString &path, int defaultIndex) {
 	for (int64_t i = string_length(path) - 1; i >= 0; i--) {
 		DsrChar c = path[i];
-		if (c == U'\\' || c == U'/') {
-			lastSeparator = i;
-			break;
+		if (isSeparator(c)) {
+			return i;
 		}
 	}
-	return string_after(path, lastSeparator);
+	return defaultIndex;
 }
 
-List<String> file_convertInputArguments(int argn, NativeChar **argv) {
-	List<String> result;
-	result.reserve(argn);
-	for (int a = 0; a < argn; a++) {
-		result.push(fromNativeString(argv[a]));
-	}
-	return result;
+ReadableString file_getPathlessName(const ReadableString &path) {
+	return string_after(path, getLastSeparator(path, -1));
 }
 
-#ifdef __cpp_lib_filesystem
+ReadableString file_getFolderPath(const ReadableString &path) {
+	return string_before(path, getLastSeparator(path, string_length(path)));
+}
 
-static String fromU32String(std::u32string text) {
-	dsr::String result;
-	string_reserve(result, text.length());
-	int i = 0;
-	while (true) {
-		DsrChar c = text[i];
-		if (c == '\0') {
-			break;
-		} else {
-			string_appendChar(result, c);
-		}
-		i++;
-	}
-	return result;
+bool file_hasRoot(const ReadableString &path) {
+	#ifdef USE_MICROSOFT_WINDOWS
+		// If a colon is found, it is a root path.
+		return string_findFirst(path, U':') > -1;
+	#else
+		// If the path begins with a separator, it is the root folder in Posix systems.
+		return path[0] == U'/';
+	#endif
 }
 
-#define FROM_PATH(CONTENT) fromU32String((CONTENT).generic_u32string())
+String file_getCurrentPath() {
+	#ifdef USE_MICROSOFT_WINDOWS
+		NativeChar resultBuffer[maxLength + 1] = {0};
+		GetCurrentDirectoryW(maxLength, resultBuffer);
+		return fromNativeString(resultBuffer);
+	#else
+		NativeChar resultBuffer[maxLength + 1] = {0};
+		getcwd(resultBuffer, maxLength);
+		return fromNativeString(resultBuffer);
+	#endif
+}
 
-// Macros for creating wrapper functions
-#define DEF_FUNC_VOID_TO_STRING(FUNC_NAME, CALL_NAME) String FUNC_NAME() { return FROM_PATH(CALL_NAME()); }
-#define DEF_FUNC_STRING_TO_STRING(FUNC_NAME, ARG_NAME, CALL_NAME) String FUNC_NAME(const ReadableString& ARG_NAME) { Buffer buffer; return FROM_PATH(CALL_NAME(toNativeString(ARG_NAME, buffer))); }
-#define DEF_FUNC_STRING_TO_OTHER(FUNC_NAME, RETURN_TYPE, ARG_NAME, CALL_NAME) RETURN_TYPE FUNC_NAME(const ReadableString& ARG_NAME) { Buffer buffer; return CALL_NAME(toNativeString(ARG_NAME, buffer)); }
+#ifdef USE_MICROSOFT_WINDOWS
+static String file_getApplicationFilePath() {
+	NativeChar resultBuffer[maxLength + 1] = {0};
+	GetModuleFileNameW(nullptr, resultBuffer, maxLength);
+	return fromNativeString(resultBuffer);
+}
+#endif
 
-// Wrapper function implementations
-DEF_FUNC_VOID_TO_STRING(file_getCurrentPath, std::filesystem::current_path)
-DEF_FUNC_STRING_TO_STRING(file_getAbsolutePath, path, std::filesystem::absolute)
-DEF_FUNC_STRING_TO_STRING(file_getCanonicalPath, path, std::filesystem::canonical)
-DEF_FUNC_STRING_TO_OTHER(file_exists, bool, path, std::filesystem::exists)
-DEF_FUNC_STRING_TO_OTHER(file_getSize, int64_t, path, std::filesystem::file_size)
-DEF_FUNC_STRING_TO_OTHER(file_remove, bool, path, std::filesystem::remove)
-DEF_FUNC_STRING_TO_OTHER(file_removeRecursively, bool, path, std::filesystem::remove_all)
-DEF_FUNC_STRING_TO_OTHER(file_createFolder, bool, path, std::filesystem::create_directory)
+String file_getApplicationFolder(bool allowFallback) {
+	#ifdef USE_MICROSOFT_WINDOWS
+		return file_getFolderPath(file_getApplicationFilePath());
+	#else
+		#ifdef USE_LINUX
+			// TODO: Implement using "/proc/self/exe" on Linux
+			//       https://www.wikitechy.com/tutorials/linux/how-to-find-the-location-of-the-executable-in-c
+			NativeChar resultBuffer[maxLength + 1] = {0};
+			//"/proc/curproc/file" on FreeBSD, which is not yet supported
+    		//"/proc/self/path/a.out" on Solaris, which is not yet supported
+			readlink("/proc/self/exe", resultBuffer, maxLength);
+			return file_getFolderPath(fromNativeString(resultBuffer));
+		#else
+			if (allowFallback) {
+				return file_getCurrentPath();
+			} else {
+				throwError("file_getApplicationFolder is not implemented!\n");
+			}
+		#endif
+	#endif
+}
 
-void file_getFolderContent(const ReadableString& folderPath, std::function<void(ReadableString, EntryType)> action) {
-	Buffer buffer;
-	for (auto const& entry : std::filesystem::directory_iterator{toNativeString(folderPath, buffer)}) {
-		EntryType entryType = EntryType::Unknown;
-		if (entry.is_directory()) {
-			entryType = EntryType::Folder;
-		} else if (entry.is_regular_file()) {
-			entryType = EntryType::File;
-		} else if (entry.is_symlink()) {
-			entryType = EntryType::SymbolicLink;
-		}
-		action(FROM_PATH(entry.path()), entryType);
+String file_combinePaths(const ReadableString &a, const ReadableString &b) {
+	if (isSeparator(a[string_length(a) - 1])) {
+		// Already ending with a separator.
+		return string_combine(a, b);
+	} else {
+		// Combine using a separator.
+		return string_combine(a, pathSeparator, b);
 	}
 }
 
-#endif
+String file_getAbsolutePath(const ReadableString &path) {
+	if (file_hasRoot(path)) {
+		return path;
+	} else {
+		return file_combinePaths(file_getCurrentPath(), path);
+	}
+}
 
 }

+ 86 - 43
Source/DFPSR/api/fileAPI.h

@@ -21,24 +21,39 @@
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 
+/*
+TODO:
+* bool file_setCurrentPath(const ReadableString& path);
+	WINBASEAPI WINBOOL WINAPI SetCurrentDirectoryW(LPCWSTR lpPathName);
+	chdir on Posix
+* bool file_createFolder(const ReadableString& path);
+	WINBASEAPI WINBOOL WINAPI CreateDirectoryW (LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes);
+	mkdir on Posix
+* int64_t file_getSize(const ReadableString& path);
+	WINBASEAPI WINBOOL WINAPI GetFileSizeEx (HANDLE hFile, PLARGE_INTEGER lpFileSize);
+* bool file_remove(const ReadableString& path);
+	WINBASEAPI WINBOOL WINAPI DeleteFileW (LPCWSTR lpFileName);
+* bool file_exists(const ReadableString& path);
+	Can open a file without permissions and see if it works.
+* void file_getFolderContent(const ReadableString& folderPath, std::function<void(ReadableString, EntryType)> action)
+	How to do this the safest way with Unicode as a minimum requirement?
+*/
+
 #ifndef DFPSR_API_FILE
 #define DFPSR_API_FILE
 
 #include "../api/stringAPI.h"
 #include "bufferAPI.h"
-#include <version>
+#if defined(WIN32) || defined(_WIN32)
+	#define USE_MICROSOFT_WINDOWS
+#endif
+#if defined(__linux__)
+	#define USE_LINUX
+#endif
 
 // A module for file access that exists to prevent cyclic dependencies between strings and buffers.
 //   Buffers need a filename to be saved or loaded while strings use buffers to store their characters.
 namespace dsr {
-	// NativeChar is defined as whatever type the native system uses for expressing Unicode
-	// Use for input arguments in the main function to allow using file_convertInputArguments.
-	#if defined(WIN32) || defined(_WIN32)
-		using NativeChar = wchar_t; // UTF-16
-	#else
-		using NativeChar = char; // UTF-8
-	#endif
-
 	// Post-condition:
 	//   Returns the content of the file referred to be filename.
 	//   If mustExist is true, then failure to load will throw an exception.
@@ -53,44 +68,72 @@ namespace dsr {
 	//   Can be used to construct a file path that works for both forward and backward slash separators.
 	const char32_t* file_separator();
 
-	// Returns the local name after the last path separator, or the whole path if no separator was found.
+	// TODO: Create regression tests for the file system.
+
+	// Returns the local name of the file or folder after the last path separator, or the whole path if no separator was found.
+	// Examples with / as the path separator:
+	//   file_getFolderPath(U"MyFolder/Cars.txt") == U"Cars.txt"
+	//   file_getFolderPath(U"MyFolder/")         == U""
+	//   file_getFolderPath(U"MyFolder")          == U"MyFolder"
+	//   file_getFolderPath(U"MyFolder/Folder2")  == U"Folder2"
 	ReadableString file_getPathlessName(const ReadableString &path);
 
-	// Convert input arguments from main into a list of dsr::String.
-	// argv must be declared as wchar_t** on MS-Windows to allow using Unicode, which can be done by using the dsr::NativeChar alias:
-	//     int main(int argn, NativeChar **argv) {
-	List<String> file_convertInputArguments(int argn, NativeChar **argv);
+	// Returns the file's folder path with the file removed, or path unchanged if it was already a path.
+	// Does not include the last slash.
+	// Examples with / as the path separator:
+	//   file_getFolderPath(U"MyFolder/Documents/Cars.txt") == U"MyFolder/Documents"
+	//   file_getFolderPath(U"MyFolder/Documents/")         == U"MyFolder/Documents"
+	//   file_getFolderPath(U"MyFolder/Documents")          == U"MyFolder/Documents"
+	ReadableString file_getFolderPath(const ReadableString &path);
+
+	// Combines two parts into a path and automatically adding a local separator when needed.
+	// Can be used to get the full path of a file in a folder or add another folder to the path.
+	// b may not begin with a separator, because only a is allowed to contain the root.
+	// Examples with / as the path separator:
+	//   file_combinePaths(U"Folder", U"Document.txt") == U"Folder/Document.txt"
+	//   file_combinePaths(U"Folder/", U"Document.txt") == U"Folder/Document.txt"
+	String file_combinePaths(const ReadableString &a, const ReadableString &b);
+
+	// Returns true iff path contains a root, according to the local path syntax.
+	bool file_hasRoot(const ReadableString &path);
 
-	// Portable wrapper over the STD filesystem library, so that you get Unicode support with one type of string on all platforms.
-	// To enable this wrapper, just compile everything with C++17 or a newer version that still has the feature.
-	// Using more STD functionality can make it harder to port your project when C++ compilers are no longer maintained,
-	//   but exploring the filesystem is necessary for selecting files to load or save in editors.
-	#ifdef __cpp_lib_filesystem
-		enum class EntryType {
-			Unknown, Folder, File, SymbolicLink
-		};
-		// Get the current path, from where the application was called and relative paths start.
-		String file_getCurrentPath();
-		// Get the path's absolute form to know the exact location.
-		String file_getAbsolutePath(const ReadableString& path);
-		// Get the path's absolute form without redundancy.
-		String file_getCanonicalPath(const ReadableString& path);
-		// Gets a callback for each file and folder directly inside of folderPath.
-		// Calls back with the entry's path and an integer representing the entry type.
-		void file_getFolderContent(const ReadableString& folderPath, std::function<void(ReadableString, EntryType)> action);
-		// Returns true iff path refers to a valid file or folder.
-		bool file_exists(const ReadableString& path);
-		// Returns the file's size.
-		int64_t file_getSize(const ReadableString& path);
-		// Removes the file or empty folder.
-		// Returns true iff path existed.
-		bool file_remove(const ReadableString& path);
-		// Removes the file or folder including any content.
-		// Returns true iff path existed.
-		bool file_removeRecursively(const ReadableString& path);
-		// Returns true on success.
-		bool file_createFolder(const ReadableString& path);
+	// A convenient wrapper for getting input arguments as a list of portable Unicode strings.
+	//   The actual main function gets placed in DSR_MAIN_CALLER, which calls the given function.
+	// Example:
+	//   DSR_MAIN_CALLER(dsrMain)
+	//   int dsrMain(List<String> args) {
+	//       printText("Input arguments:\n");
+	//       for (int a = 0; a < args.length(); a++) {
+	//           printText("  args[", a, "] = ", args[a], "\n");
+	//       }
+	//       return 0;
+	//   }
+	#ifdef USE_MICROSOFT_WINDOWS
+		#define DSR_MAIN_CALLER(MAIN_NAME) \
+			int MAIN_NAME(List<String> args); \
+			int main() { \
+				return MAIN_NAME(file_impl_getInputArguments()); \
+			}
+	#else
+		#define DSR_MAIN_CALLER(MAIN_NAME) \
+			int MAIN_NAME(List<String> args); \
+			int main(int argc, char **argv) { return MAIN_NAME(file_impl_convertInputArguments(argc, (void**)argv)); }
 	#endif
+	// Helper functions have to be exposed for the macro handle your input arguments.
+	//   Do not call these yourself.
+	List<String> file_impl_convertInputArguments(int argn, void **argv);
+	List<String> file_impl_getInputArguments();
+
+	// Get the current path, from where the application was called and relative paths start.
+	String file_getCurrentPath();
+	// Get the application's folder path, from where the application is stored.
+	// If not implemented and allowFallback is true,
+	//   the current path is returned instead as a qualified guess instead of raising an exception.
+	String file_getApplicationFolder(bool allowFallback = true);
+	// Gets an absolute version of the path, quickly without removing redundancy.
+	String file_getAbsolutePath(const ReadableString &path);
+	// Returns true iff path refers to a valid file or folder.
+	bool file_exists(const ReadableString& path);
 }
 
 #endif