From 0264e2bbfd7bca9c76516a304650cc38e9d9cb86 Mon Sep 17 00:00:00 2001 From: Arseny Smirnov Date: Wed, 1 May 2019 12:23:19 +0300 Subject: [PATCH] WalkPath: allow to skip dirs and to abort a walk GitOrigin-RevId: 6be49d1c098b0d17b63ae1f26dbefb6904320d6b --- tdutils/td/utils/port/path.cpp | 128 +++++++++++++++++++++++---------- tdutils/td/utils/port/path.h | 28 +++++++- tdutils/test/port.cpp | 44 +++++++++--- 3 files changed, 151 insertions(+), 49 deletions(-) diff --git a/tdutils/td/utils/port/path.cpp b/tdutils/td/utils/port/path.cpp index bb472fb2..3c4e2c54 100644 --- a/tdutils/td/utils/port/path.cpp +++ b/tdutils/td/utils/port/path.cpp @@ -79,11 +79,16 @@ Status mkpath(CSlice path, int32 mode) { } Status rmrf(CSlice path) { - return walk_path(path, [](CSlice path, bool is_dir) { - if (is_dir) { - rmdir(path).ignore(); - } else { - unlink(path).ignore(); + return walk_path(path, [](CSlice path, WalkPath::Type type) { + switch (type) { + case WalkPath::Type::EnterDir: + break; + case WalkPath::Type::ExitDir: + rmdir(path).ignore(); + break; + case WalkPath::Type::NotDir: + unlink(path).ignore(); + break; } }); } @@ -246,18 +251,16 @@ Result mkdtemp(CSlice dir, Slice prefix) { } namespace detail { -Status walk_path_dir(string &path, FileFd fd, - const std::function &func) TD_WARN_UNUSED_RESULT; +using WalkFunction = std::function; +Result walk_path_dir(string &path, FileFd fd, const WalkFunction &func) TD_WARN_UNUSED_RESULT; -Status walk_path_dir(string &path, - const std::function &func) TD_WARN_UNUSED_RESULT; +Result walk_path_dir(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; -Status walk_path_file(string &path, - const std::function &func) TD_WARN_UNUSED_RESULT; +Result walk_path_file(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; -Status walk_path(string &path, const std::function &func) TD_WARN_UNUSED_RESULT; +Result walk_path(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; -Status walk_path_subdir(string &path, DIR *dir, const std::function &func) { +Result walk_path_subdir(string &path, DIR *dir, const WalkFunction &func) { while (true) { errno = 0; auto *entry = readdir(dir); @@ -266,7 +269,7 @@ Status walk_path_subdir(string &path, DIR *dir, const std::function(entry->d_name)); if (name == "." || name == "..") { @@ -280,7 +283,7 @@ Status walk_path_subdir(string &path, DIR *dir, const std::function status; #ifdef DT_DIR if (entry->d_type == DT_UNKNOWN) { status = walk_path(path, func); @@ -293,22 +296,40 @@ Status walk_path_subdir(string &path, DIR *dir, const std::function &func) { +Result walk_path_dir(string &path, DIR *subdir, const WalkFunction &func) { SCOPE_EXIT { closedir(subdir); }; - TRY_STATUS(walk_path_subdir(path, subdir, func)); - func(path, true); - return Status::OK(); + switch (func(path, WalkPath::Type::EnterDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + return true; + case WalkPath::Action::Continue: + break; + } + TRY_RESULT(is_ok, walk_path_subdir(path, subdir, func)); + if (!is_ok) { + return false; + } + switch (func(path, WalkPath::Type::ExitDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; } -Status walk_path_dir(string &path, FileFd fd, const std::function &func) { +Result walk_path_dir(string &path, FileFd fd, const WalkFunction &func) { auto native_fd = fd.move_as_native_fd(); auto *subdir = fdopendir(native_fd.fd()); if (subdir == nullptr) { @@ -318,7 +339,7 @@ Status walk_path_dir(string &path, FileFd fd, const std::function &func) { +Result walk_path_dir(string &path, const WalkFunction &func) { auto *subdir = opendir(path.c_str()); if (subdir == nullptr) { return OS_ERROR(PSLICE() << tag("opendir", path)); @@ -326,12 +347,18 @@ Status walk_path_dir(string &path, const std::function &func) { - func(path, false); - return Status::OK(); +Result walk_path_file(string &path, const WalkFunction &func) { + switch (func(path, WalkPath::Type::NotDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; } -Status walk_path(string &path, const std::function &func) { +Result walk_path(string &path, const WalkFunction &func) { TRY_RESULT(fd, FileFd::open(path, FileFd::Read)); auto stat = fd.stat(); bool is_dir = stat.is_dir_; @@ -345,15 +372,16 @@ Status walk_path(string &path, const std::function &func) { +Status WalkPath::do_run(CSlice path, const detail::WalkFunction &func) { string curr_path; curr_path.reserve(PATH_MAX + 10); curr_path = path.c_str(); - return detail::walk_path(curr_path, func); + TRY_STATUS(detail::walk_path(curr_path, func)); + return Status::OK(); } #endif @@ -518,9 +546,17 @@ Result> mkstemp(CSlice dir) { return Status::Error(PSLICE() << "Can't create temporary file \"" << file_pattern << '"'); } -static Status walk_path_dir(const std::wstring &dir_name, - const std::function &func) { +static Result walk_path_dir(const std::wstring &dir_name, + const std::function &func) { std::wstring name = dir_name + L"\\*"; + switch (func(entry_name, WalkPath::Type::EnterDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + return true; + case WalkPath::Action::Continue: + break; + } WIN32_FIND_DATA file_data; auto handle = FindFirstFileExW(name.c_str(), FindExInfoStandard, &file_data, FindExSearchNameMatch, nullptr, 0); @@ -536,9 +572,18 @@ static Status walk_path_dir(const std::wstring &dir_name, TRY_RESULT(entry_name, from_wstring(full_name)); if (file_data.cFileName[0] != '.') { if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - TRY_STATUS(walk_path_dir(full_name, func)); + TRY_RESULT(is_ok, walk_path_dir(full_name, func)); + if (!is_ok) { + return false; + } } else { - func(entry_name, false); + switch (func(entry_name, WalkPath::Type::NotDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } } } auto status = FindNextFileW(handle, &file_data); @@ -551,18 +596,25 @@ static Status walk_path_dir(const std::wstring &dir_name, } } TRY_RESULT(entry_name, from_wstring(dir_name)); - func(entry_name, true); - return Status::OK(); + switch (func(entry_name, WalkPath::Type::ExitDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; } -Status walk_path(CSlice path, const std::function &func) { +Status WalkPath::do_run(CSlice path, const std::function &func) { TRY_RESULT(wpath, to_wstring(path)); Slice path_slice = path; while (!path_slice.empty() && (path_slice.back() == '/' || path_slice.back() == '\\')) { path_slice.remove_suffix(1); wpath.pop_back(); } - return walk_path_dir(wpath, func); + TRY_STATUS(walk_path_dir(wpath, func)); + return Status::OK(); } #endif diff --git a/tdutils/td/utils/port/path.h b/tdutils/td/utils/port/path.h index 4439d030..f7364894 100644 --- a/tdutils/td/utils/port/path.h +++ b/tdutils/td/utils/port/path.h @@ -40,6 +40,32 @@ Result> mkstemp(CSlice dir) TD_WARN_UNUSED_RESULT; Result mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT; -Status walk_path(CSlice path, const std::function &func) TD_WARN_UNUSED_RESULT; +class WalkPath { + public: + enum class Action { Continue, Abort, SkipDir }; + enum class Type { EnterDir, ExitDir, NotDir }; + + template ()("", Type::ExitDir))> + static std::enable_if_t::value, Status> run(CSlice path, F &&func) TD_WARN_UNUSED_RESULT { + return do_run(path, func); + } + template ()("", Type::ExitDir))> + static std::enable_if_t::value, Status> run(CSlice path, F &&func) TD_WARN_UNUSED_RESULT { + return do_run(path, [&](CSlice name, Type type) { + func(name, type); + return Action::Continue; + }); + } + + private: + static TD_WARN_UNUSED_RESULT Status do_run(CSlice path, + const std::function &func); +}; + +// deprecated interface +template +TD_WARN_UNUSED_RESULT Status walk_path(CSlice path, F &&func) { + return WalkPath::run(path, func); +} } // namespace td diff --git a/tdutils/test/port.cpp b/tdutils/test/port.cpp index 0c4bd991..9a2550d0 100644 --- a/tdutils/test/port.cpp +++ b/tdutils/test/port.cpp @@ -10,6 +10,7 @@ #include "td/utils/port/path.h" #include "td/utils/Slice.h" #include "td/utils/tests.h" +#include "td/utils/misc.h" using namespace td; @@ -17,7 +18,7 @@ TEST(Port, files) { CSlice main_dir = "test_dir"; rmrf(main_dir).ignore(); ASSERT_TRUE(FileFd::open(main_dir, FileFd::Write).is_error()); - ASSERT_TRUE(walk_path(main_dir, [](CSlice name, bool is_directory) { UNREACHABLE(); }).is_error()); + ASSERT_TRUE(walk_path(main_dir, [](CSlice name, WalkPath::Type type) { UNREACHABLE(); }).is_error()); mkdir(main_dir).ensure(); mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "A").ensure(); mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "B").ensure(); @@ -34,16 +35,39 @@ TEST(Port, files) { int cnt = 0; const int ITER_COUNT = 1000; for (int i = 0; i < ITER_COUNT; i++) { - walk_path(main_dir, - [&](CSlice name, bool is_directory) { - if (!is_directory) { - ASSERT_TRUE(name == fd_path || name == fd2_path); - } - cnt++; - }) - .ensure(); + walk_path(main_dir, [&](CSlice name, WalkPath::Type type) { + if (type == WalkPath::Type::NotDir) { + ASSERT_TRUE(name == fd_path || name == fd2_path); + } + cnt++; + }).ensure(); } - ASSERT_EQ(7 * ITER_COUNT, cnt); + ASSERT_EQ((5 * 2 + 2) * ITER_COUNT, cnt); + bool was_abort = false; + walk_path(main_dir, [&](CSlice name, WalkPath::Type type) { + CHECK(!was_abort); + if (type == WalkPath::Type::EnterDir && ends_with(name, PSLICE() << "/B")) { + was_abort = true; + return WalkPath::Action::Abort; + } + return WalkPath::Action::Continue; + }).ensure(); + CHECK(was_abort); + + cnt = 0; + bool is_first_dir = true; + walk_path(main_dir, [&](CSlice name, WalkPath::Type type) { + cnt++; + if (type == WalkPath::Type::EnterDir) { + if (is_first_dir) { + is_first_dir = false; + } else { + return WalkPath::Action::SkipDir; + } + } + return WalkPath::Action::Continue; + }).ensure(); + ASSERT_EQ(6, cnt); ASSERT_EQ(0u, fd.get_size()); ASSERT_EQ(12u, fd.write("Hello world!").move_as_ok());