Browse Source

Implemented file_getFileSize, file_getEntryType and file_getFolderContent.

David Piuva 3 years ago
parent
commit
90ad46829d
3 changed files with 171 additions and 35 deletions
  1. 135 9
      Source/DFPSR/api/fileAPI.cpp
  2. 28 15
      Source/DFPSR/api/fileAPI.h
  3. 8 11
      Source/SDK/fileFinder/main.cpp

+ 135 - 9
Source/DFPSR/api/fileAPI.cpp

@@ -26,6 +26,8 @@
 	#include <windows.h>
 #else
 	#include <unistd.h>
+	#include <sys/stat.h>
+	#include <dirent.h>
 #endif
 #include <fstream>
 #include <cstdlib>
@@ -33,10 +35,6 @@
 
 namespace dsr {
 
-// If porting to a new operating system that is not following Posix standard, list how the file system works here.
-// * 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.
 #ifdef USE_MICROSOFT_WINDOWS
 	using NativeChar = wchar_t; // UTF-16
 	static const char32_t* pathSeparator = U"\\";
@@ -229,12 +227,16 @@ String file_getApplicationFolder(bool allowFallback) {
 }
 
 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);
+	if (file_hasRoot(b)) {
+		return b;
 	} else {
-		// Combine using a separator.
-		return string_combine(a, pathSeparator, 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);
+		}
 	}
 }
 
@@ -246,4 +248,128 @@ String file_getAbsolutePath(const ReadableString &path) {
 	}
 }
 
+int64_t file_getFileSize(const ReadableString& filename) {
+	int64_t result = -1;
+	String modifiedFilename = file_optimizePath(filename);
+	Buffer buffer;
+	const NativeChar *nativePath = toNativeString(modifiedFilename, buffer);
+	#ifdef USE_MICROSOFT_WINDOWS
+		LARGE_INTEGER fileSize;
+		HANDLE fileHandle = CreateFileW(nativePath, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+		if (fileHandle != INVALID_HANDLE_VALUE) {
+			if (GetFileSizeEx(fileHandle, &fileSize)) {
+				result = fileSize.QuadPart;
+			}
+			CloseHandle(fileHandle);
+		}
+	#else
+		struct stat info;
+		if (stat(nativePath, &info) == 0) {
+			result = info.st_size;
+		}
+	#endif
+	return result;
+}
+
+String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation) {
+	string_append(target, indentation);
+	if (source == EntryType::NotFound) {
+		string_append(target, U"not found");
+	} else if (source == EntryType::File) {
+		string_append(target, U"a file");
+	} else if (source == EntryType::Folder) {
+		string_append(target, U"a folder");
+	} else if (source == EntryType::SymbolicLink) {
+		string_append(target, U"a symbolic link");
+	} else {
+		string_append(target, U"unhandled");
+	}
+	return target;
+}
+
+EntryType file_getEntryType(const ReadableString &path) {
+	EntryType result = EntryType::NotFound;
+	String modifiedPath = file_optimizePath(path);
+	Buffer buffer;
+	const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
+	#ifdef USE_MICROSOFT_WINDOWS
+		DWORD dwAttrib = GetFileAttributesW(nativePath);
+		if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
+			if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
+				result = EntryType::Folder;
+			} else {
+				result = EntryType::File;
+			}
+		}
+	#else
+		struct stat info;
+		int errorCode = stat(nativePath, &info);
+		if (errorCode == 0) {
+			if (S_ISDIR(info.st_mode)) {
+				result = EntryType::Folder;
+			} else if (S_ISREG(info.st_mode)) {
+				result = EntryType::File;
+			} else if (S_ISLNK(info.st_mode)) {
+				result = EntryType::SymbolicLink;
+			} else {
+				result = EntryType::UnhandledType;
+			}
+		}
+	#endif
+	return result;
+}
+
+bool file_getFolderContent(const ReadableString& folderPath, std::function<void(const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType)> action) {
+	String modifiedPath = file_optimizePath(folderPath);
+	#ifdef USE_MICROSOFT_WINDOWS
+		String pattern = file_combinePaths(modifiedPath, U"*.*");
+		Buffer buffer;
+		const NativeChar *nativePattern = toNativeString(pattern, buffer);
+		WIN32_FIND_DATAW findData;
+		HANDLE findHandle = FindFirstFileW(nativePattern, &findData);
+		if (findHandle == INVALID_HANDLE_VALUE) {
+			return false;
+		} else {
+			while (true) {
+				String entryName = fromNativeString(findData.cFileName);
+				if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
+					String entryPath = file_combinePaths(modifiedPath, entryName);
+					EntryType entryType = EntryType::UnhandledType;
+					if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+						entryType = EntryType::Folder;
+					} else {
+						entryType = EntryType::File;
+					}
+					action(entryPath, entryName, entryType);
+				}
+				if (!FindNextFileW(findHandle, &findData)) { break; }
+			}
+			FindClose(findHandle);
+		}
+	#else
+		Buffer buffer;
+		const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
+		DIR *directory = opendir(nativePath);
+		if (directory == nullptr) {
+			return false;
+		} else {
+			while (true) {
+				dirent *entry = readdir(directory);
+				if (entry != nullptr) {
+					String entryName = fromNativeString(entry->d_name);
+					if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
+						String entryPath = file_combinePaths(modifiedPath, entryName);
+						EntryType entryType = file_getEntryType(entryPath);
+						action(entryPath, entryName, entryType);
+					}
+				} else {
+					break;
+				}
+			}
+		}
+		closedir(directory);
+	#endif
+	return true;
+}
+
 }

+ 28 - 15
Source/DFPSR/api/fileAPI.h

@@ -23,20 +23,12 @@
 
 /*
 TODO:
-* bool file_setCurrentPath(const ReadableString& path);
-	WINBASEAPI WINBOOL WINAPI SetCurrentDirectoryW(LPCWSTR lpPathName);
-	chdir on Posix
+* Test that overwriting a large file with a smaller file does not leave anything from the overwritten file on any system.
 * 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
@@ -48,11 +40,13 @@ TODO:
 	#define USE_MICROSOFT_WINDOWS
 #endif
 
+// TODO: Create regression tests for the file system.
+
 // 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 {
 	// Post-condition:
-	//   Returns the content of the file referred to by file_optimizePath(filename).
+	//   Returns the content of the readable file referred to by file_optimizePath(filename).
 	//   If mustExist is true, then failure to load will throw an exception.
 	//   If mustExist is false, then failure to load will return an empty handle (returning false for buffer_exists).
 	Buffer file_loadBuffer(const ReadableString& filename, bool mustExist = true);
@@ -69,8 +63,6 @@ namespace dsr {
 	// TODO: Remove redundant . and .. to reduce the risk of running out of buffer space.
 	String file_optimizePath(const ReadableString &path);
 
-	// 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"
@@ -125,15 +117,36 @@ namespace dsr {
 
 	// Get the current path, from where the application was called and relative paths start.
 	String file_getCurrentPath();
-	// Sets the current path to file_optimizePath(path).
-	// Returns true on success and false on failure.
+	// Side-effects: Sets the current path to file_optimizePath(path).
+	// Post-condition: Returns Returns true on success and false on failure.
 	bool file_setCurrentPath(const ReadableString &path);
-	// Get the application's folder path, from where the application is stored.
+	// Post-condition: Returns  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);
+	// Pre-condition: filename must refer to a file so that file_getEntryType(filename) == EntryType::File.
+	// Post-condition: Returns a structure with information about the file at file_optimizePath(filename), or -1 if no such file exists.
+	int64_t file_getFileSize(const ReadableString& filename);
+
+	// Entry types distinguish between files folders and other things in the file system.
+	enum class EntryType { NotFound, UnhandledType, File, Folder, SymbolicLink };
+	String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation);
+
+	// Post-condition: Returns what the file_optimizePath(path) points to in the filesystem.
+	// Different comparisons on the result can be used to check if something exists.
+	//   Use file_getEntryType(filename) == EntryType::File to check if a file exists.
+	//   Use file_getEntryType(folderPath) == EntryType::Folder to check if a folder exists.
+	//   Use file_getEntryType(path) != EntryType::NotFound to check if the path leads to anything.
+	EntryType file_getEntryType(const ReadableString &path);
+
+	// Side-effects: Calls action with the entry's path, name and type for everything detected in folderPath.
+	//               entryPath equals file_combinePaths(folderPath, entryName), and is used for recursive calls when entryType == EntryType::Folder.
+	//               entryName equals file_getPathlessName(entryPath).
+	//               entryType equals file_getEntryType(entryPath).
+	// Post-condition: Returns true iff the folder could be found.
+	bool file_getFolderContent(const ReadableString& folderPath, std::function<void(const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType)> action);
 }
 
 #endif

+ 8 - 11
Source/SDK/fileFinder/main.cpp

@@ -16,15 +16,14 @@ TODO:
 using namespace dsr;
 
 void exploreFolder(const ReadableString& folderPath, const ReadableString& indentation) {
-	/*file_getFolderContent(folderPath, [indentation](ReadableString entryPath, EntryType entryType) {
-		ReadableString shortName = file_getPathlessName(entryPath);
+	if (!file_getFolderContent(folderPath, [indentation](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
+		printText(indentation, "* Entry: ", entryName, " as ", entryType, "\n");
 		if (entryType == EntryType::Folder) {
-			printText(indentation, " Folder(", shortName, ")\n");
 			exploreFolder(entryPath, indentation + "  ");
-		} else if (entryType == EntryType::File) {
-			printText(indentation, " File(", shortName, ") of ", file_getSize(entryPath), " bytes\n");
 		}
-	});*/
+	})) {
+		printText("Failed to explore ", folderPath, "\n");
+	}
 }
 
 DSR_MAIN_CALLER(dsrMain)
@@ -33,20 +32,18 @@ void dsrMain(List<String> args) {
 	for (int a = 0; a < args.length(); a++) {
 		printText("  args[", a, "] = ", args[a], "\n");
 	}
-	/*
-	String absolutePath = file_getCanonicalPath(args[0]);
+	String absolutePath = file_getAbsolutePath(args[0]);
 	printText("Absolute path = ", absolutePath, "\n");
 	if (args.length() > 1) {
 		// Explore each listed folder from input arguments.
 		for (int a = 1; a < args.length(); a++) {
 			printText("Exploring ", args[a], "\n");
-			exploreFolder(args[a], U"");
+			exploreFolder(args[a], U"  ");
 		}
 	} else {
 		// Test program on the current path when nothing was entered.
 		String currentPath = file_getCurrentPath();
 		printText("Exploring ", currentPath, " because no folders were given.\n");
-		exploreFolder(currentPath, U"");
+		exploreFolder(currentPath, U"  ");
 	}
-	*/
 }