From 088d2cab7bd4da280d0bbfdf5ab84e30afd7ea52 Mon Sep 17 00:00:00 2001 From: Christian Kaiser Date: Thu, 5 Sep 2024 19:01:50 -0300 Subject: [PATCH] Add list_files filtering --- base/fs.h | 15 +++++++++++- base/fs_tests.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++++ base/fs_unix.h | 29 ++++++++++++++++------- base/fs_win32.h | 41 ++++++++++++++++++++++++-------- 4 files changed, 125 insertions(+), 19 deletions(-) diff --git a/base/fs.h b/base/fs.h index 908a804d9..fc27f8509 100644 --- a/base/fs.h +++ b/base/fs.h @@ -65,7 +65,20 @@ namespace base { // elements (is a normalized path). std::string get_absolute_path(const std::string& path); - paths list_files(const std::string& path); + // Item types that list_files() can be filtered by + enum class ItemType { + All, + Directories, + Files + }; + + // List all the items in a given path by default, two other parameters second parameters: + // filter can be use to distinguish between All items, directories and files. + // The name search can be used to match files by extension with something like "*.png" or by exact + // match without wildcards. + const paths list_files(const std::string& path, + ItemType filter = ItemType::All, + const std::string& = "*"); // Returns true if the given character is a valud path separator // (any of '\' or '/' characters). diff --git a/base/fs_tests.cpp b/base/fs_tests.cpp index b48f1e91c..b26668a39 100644 --- a/base/fs_tests.cpp +++ b/base/fs_tests.cpp @@ -411,6 +411,65 @@ TEST(FS, CopyFiles) EXPECT_EQ(data, read_file_content(dst)); } +TEST(FS, ListFiles) +{ + // Prepare files + make_directory("a"); + EXPECT_TRUE(is_directory("a")); + + make_directory("a/b"); + EXPECT_TRUE(is_directory("a/b")); + make_directory("a/c"); + EXPECT_TRUE(is_directory("a/b")); + + write_file_content("a/d", (uint8_t*)"123", 3); + EXPECT_TRUE(is_file("a/d")); + + write_file_content("a/e", (uint8_t*)"321", 3); + EXPECT_TRUE(is_file("a/e")); + + // Test normal find with asterisk match + EXPECT_TRUE(list_files("non-existent-folder").empty()); + EXPECT_EQ(list_files("a").size(), 4); + + auto dirs = list_files("a", FileTypes::DirectoriesOnly); + EXPECT_EQ(dirs.size(), 2); + EXPECT_TRUE(std::find(dirs.begin(), dirs.end(), "b") != dirs.end()); + EXPECT_TRUE(std::find(dirs.begin(), dirs.end(), "c") != dirs.end()); + + auto files = list_files("a", FileTypes::FilesOnly); + EXPECT_EQ(files.size(), 2); + EXPECT_TRUE(std::find(files.begin(), files.end(), "d") != files.end()); + EXPECT_TRUE(std::find(files.begin(), files.end(), "e") != files.end()); + + // Test pattern matching + make_directory("a/c-match-me"); + EXPECT_TRUE(is_directory("a/c-match-me")); + + EXPECT_EQ(list_files("a", FileTypes::DirectoriesOnly, "c-*-me").size(), 1); + EXPECT_EQ(list_files("a", FileTypes::DirectoriesOnly, "c-match-me").size(), 1); + EXPECT_EQ(list_files("a", FileTypes::FilesOnly, "c-*-me").size(), 0); + + write_file_content("a/c-alsomatch-me", (uint8_t*)"321", 3); + EXPECT_TRUE(is_file("a/c-alsomatch-me")); + + EXPECT_EQ(list_files("a", FileTypes::FilesOnly, "c-*-me").size(), 1); + EXPECT_EQ(list_files("a", FileTypes::FilesOnly, "c-alsomatch-me").size(), 1); + EXPECT_EQ(list_files("a", FileTypes::All, "c-*").size(), 2); + EXPECT_EQ(list_files("a", FileTypes::All, "*-me").size(), 2); + EXPECT_EQ(list_files("a", FileTypes::All, "*match*").size(), 2); + EXPECT_EQ(list_files("a", FileTypes::All).size(), 6); + + // Remove files + delete_file("a/e"); + delete_file("a/d"); + delete_file("a/c-alsomatch-me"); + remove_directory("a/c-match-me"); + remove_directory("a/c"); + remove_directory("a/b"); + remove_directory("a"); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/base/fs_unix.h b/base/fs_unix.h index 3d8c2e846..915ea1cba 100644 --- a/base/fs_unix.h +++ b/base/fs_unix.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -229,20 +230,32 @@ std::string get_absolute_path(const std::string& path) return full; } -paths list_files(const std::string& path) +const paths list_files(const std::string& path, ItemType filter, const std::string& match) { paths files; DIR* handle = opendir(path.c_str()); - if (handle) { - dirent* item; - while ((item = readdir(handle)) != nullptr) { - std::string filename = item->d_name; - if (filename != "." && filename != "..") - files.push_back(filename); + if (!handle) + return files; + + dirent* item; + while ((item = readdir(handle)) != nullptr) { + if (item->d_type == DT_DIR) { + if (filter == ItemType::Files) + continue; + + if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0) + continue; } + else if (filter == ItemType::Directories) + continue; - closedir(handle); + if (fnmatch(match.c_str(), item->d_name, FNM_CASEFOLD) == FNM_NOMATCH) + continue; + + files.push_back(item->d_name); } + + closedir(handle); return files; } diff --git a/base/fs_win32.h b/base/fs_win32.h index 6366ff1a6..7464f109c 100644 --- a/base/fs_win32.h +++ b/base/fs_win32.h @@ -180,19 +180,40 @@ std::string get_absolute_path(const std::string& path) return to_utf8(buffer); } -paths list_files(const std::string& path) +const paths list_files(const std::string& path, + ItemType filter, + const std::string& match) { WIN32_FIND_DATA fd; paths files; - HANDLE handle = FindFirstFile(base::from_utf8(base::join_path(path, "*")).c_str(), &fd); - if (handle) { - do { - std::string filename = base::to_utf8(fd.cFileName); - if (filename != "." && filename != "..") - files.push_back(filename); - } while (FindNextFile(handle, &fd)); - FindClose(handle); - } + HANDLE handle = FindFirstFileEx( + base::from_utf8(base::join_path(path, match)).c_str(), + FindExInfoBasic, + &fd, + (filter == ItemType::Directories) ? FindExSearchLimitToDirectories : + FindExSearchNameMatch, + NULL, + 0); + + if (handle == INVALID_HANDLE_VALUE) + return files; + + do { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (filter == ItemType::Files) + continue; + + if (lstrcmpW(fd.cFileName, L".") == 0 || + lstrcmpW(fd.cFileName, L"..") == 0) + continue; + } + else if (filter == ItemType::Directories) + continue; + + files.push_back(base::to_utf8(fd.cFileName)); + } while (FindNextFile(handle, &fd)); + + FindClose(handle); return files; }